diff --git a/crates/tools/src/guides.rs b/crates/tools/src/guides.rs index 746554585..0b8e756ab 100644 --- a/crates/tools/src/guides.rs +++ b/crates/tools/src/guides.rs @@ -1260,8 +1260,9 @@ impl Leave { } } - /// A box dragged from its corner or centre `at` (Rectangle, Ellipse, Arc, the grids, - /// Artboard): Shift keeps the dragged corner on a diagonal, the box a square. + /// A box dragged from its corner or centre `at` (Rectangle, Ellipse, Arc, the grids, and + /// Artboard, which passes no Shift): Shift keeps the dragged corner on a diagonal, the box a + /// square. pub fn diagonal(at: Point, shift: bool) -> Self { Self { at, shift: shift.then_some(Keep::Square), angles: &[] } } diff --git a/crates/tools/src/xform/artboard.rs b/crates/tools/src/xform/artboard.rs index 104ccc814..1e027a201 100644 --- a/crates/tools/src/xform/artboard.rs +++ b/crates/tools/src/xform/artboard.rs @@ -6,13 +6,14 @@ //! Double-click an empty part of an artboard to open Artboard Options. Click an empty //! part of an artboard to make it active (dashed bounds, 8 handles and its name); Shift-click adds //! artboards to the selection or takes them out (dashed bounds), and a drag or Delete then acts on -//! all of them (#1139). Drag inside moves -//! it (with its artwork when the `moveArt` option is on, Shift constrains; Alt moves a copy and +//! all of them (#1139). Shift-drag inside an artboard draws a new artboard there, and Shift-drag on +//! the pasteboard is a marquee: the artboards it touches leave the selection or join it. Drag inside +//! moves it (with its artwork when the `moveArt` option is on; Alt moves a copy and //! leaves the artboard where it was), drag a handle resizes it //! (Shift proportional, Alt from centre; with the `scaleArt` option on, Scale Artwork with Artboard, //! always proportional, and the art fully inside scales with it), drag on the pasteboard draws a new artboard, Delete removes //! the active one (Copy, Cut and Paste take it with its art: `artboard.copy`) and Escape returns to -//! the Selection tool. Moving and resizing snap like drawing +//! the Selection tool (during a drag it only cancels the drag). Moving and resizing snap like drawing //! does: to whole pixels, to the grid, or with Smart Guides to other artboards, their bleed and //! objects (never to the dragged artboard or the art moving or scaling with it); the artboard's own bleed edges //! snap too. A new artboard's corners snap as drawn points do ([`DrawSnap`]). @@ -21,7 +22,7 @@ use serde_json::{Value, json}; use vectorcraft_geom::{Affine, Point, Rect, Vec2}; use super::{BLUE, polygon, rect_corners}; -use crate::bbox::{Handle, hit_handle, move_delta, scale_for_drag}; +use crate::bbox::{Handle, hit_handle, scale_for_drag}; use crate::guides::{DrawSnap, Leave, Targets}; use crate::{Action, Cursor, Mods, Overlay, PointerEvent, PointerKind, Tool, ToolContext, ToolKey}; @@ -50,6 +51,13 @@ enum Drag { Create { start: Point, cur: Point, + /// A Shift-press inside an artboard (its index and the pressed point): released without + /// being dragged, it toggles that artboard. + toggle: Option<(usize, Point)>, + }, + Marquee { + start: Point, + cur: Point, }, } @@ -133,6 +141,30 @@ impl ArtboardTool { self.others = sel.into_iter().filter(|j| *j != self.active).collect(); } + /// A Shift-drag marquee `r`: the artboards it touches leave the selection when they are in + /// it and join it otherwise, the last one joining becoming active. One artboard always stays + /// selected. + fn toggle_touched(&mut self, cx: &ToolContext, r: Rect) { + let touched: Vec = cx + .doc + .artboards + .iter() + .enumerate() + .filter(|(_, a)| r.x0 <= a.rect.x1 && a.rect.x0 <= r.x1 && r.y0 <= a.rect.y1 && a.rect.y0 <= r.y1) + .map(|(i, _)| i) + .collect(); + let sel = self.selected(cx); + let added: Vec = touched.iter().copied().filter(|i| !sel.contains(i)).collect(); + let kept: Vec = sel.into_iter().filter(|i| !touched.contains(i)).chain(added.iter().copied()).collect(); + let Some(&first) = kept.first() else { return }; + self.active = match added.last() { + Some(i) => *i, + None if kept.contains(&self.active) => self.active, + None => first, + }; + self.others = kept.into_iter().filter(|j| *j != self.active).collect(); + } + /// Gather the Smart Guide targets for dragging artboard `index` (call before the first preview, /// while the document still shows where everything started). fn begin_snapping(&mut self, cx: &ToolContext, index: usize, art_moves: bool) { @@ -268,12 +300,23 @@ impl Tool for ArtboardTool { // artboard command's minimum side or pass non-finite bounds to it. self.click_art = fittable_bounds(cx, p).map(|r| (p, r)); } + if m.shift { + // Shift-drag inside an artboard draws a new one there, and a Shift-click + // toggles the artboard on release. Shift-drag outside every artboard is a + // marquee: the artboards it touches toggle on release. + match cx.doc.artboard_at(p) { + Some(i) => { + let start = self.draw.press(cx, p, &[], None); + self.drag = Some(Drag::Create { start, cur: start, toggle: Some((i, p)) }); + } + None => self.drag = Some(Drag::Marquee { start: p, cur: p }), + } + return vec![]; + } if let Some(i) = cx.doc.artboard_at(p) && let Some(a) = cx.doc.artboards.get(i) { - if m.shift { - self.toggle(cx, i); - } else if !self.selected(cx).contains(&i) { + if !self.selected(cx).contains(&i) { // A plain click on an artboard that isn't selected selects just it; on a // selected one, the selection stays, so the drag moves them all. self.others.clear(); @@ -284,7 +327,7 @@ impl Tool for ArtboardTool { } else { self.others.clear(); let p = self.draw.press(cx, p, &[], None); - self.drag = Some(Drag::Create { start: p, cur: p }); + self.drag = Some(Drag::Create { start: p, cur: p, toggle: None }); } vec![] } @@ -303,7 +346,7 @@ impl Tool for ArtboardTool { out.push(Action::Begin(label(m.alt).into())); } self.drag = Some(Drag::Move { index, many, start, rect, began: true, copy: m.alt }); - let d = self.snap_move(cx, rect, move_delta(start, p, m.shift), m.alt); + let d = self.snap_move(cx, rect, p - start, m.alt); self.preview = Some(rect + d); let mut v = json!({ "index": index, "dx": d.x, "dy": d.y, "moveArt": self.move_art, "copy": m.alt }); if many { @@ -333,9 +376,13 @@ impl Tool for ArtboardTool { out.push(Action::Preview("artboard.setProps".into(), v)); out } - (PointerKind::Drag, Some(Drag::Create { start, .. })) => { - let cur = self.draw.drag(cx, p, Some(&Leave::diagonal(start, m.shift))); - self.drag = Some(Drag::Create { start, cur }); + (PointerKind::Drag, Some(Drag::Create { start, toggle, .. })) => { + let cur = self.draw.drag(cx, p, Some(&Leave::diagonal(start, false))); + self.drag = Some(Drag::Create { start, cur, toggle }); + vec![] + } + (PointerKind::Drag, Some(Drag::Marquee { start, .. })) => { + self.drag = Some(Drag::Marquee { start, cur: p }); vec![] } (PointerKind::Up, Some(d)) => { @@ -361,7 +408,18 @@ impl Tool for ArtboardTool { vec![Action::Commit] } Drag::Resize { began: true, .. } => vec![Action::Commit], - Drag::Create { start, cur } => { + Drag::Create { toggle: Some((i, at)), .. } if p.distance(at) < cx.tol(3.0) => { + self.toggle(cx, i); + vec![] + } + Drag::Marquee { start, .. } => { + let r = Rect::from_points(start, p); + if r.width() >= cx.tol(3.0) || r.height() >= cx.tol(3.0) { + self.toggle_touched(cx, r); + } + vec![] + } + Drag::Create { start, cur, .. } => { let r = Rect::from_points(start, cur); if r.width() < cx.tol(3.0) || r.height() < cx.tol(3.0) { return vec![]; @@ -420,9 +478,18 @@ impl Tool for ArtboardTool { vec![Action::Exec("artboard.delete".into(), params)] } ToolKey::Escape => { - let busy = matches!(self.drag, Some(Drag::Move { began: true, .. } | Drag::Resize { began: true, .. })); + // During any drag (a move, a resize, a new artboard or a marquee) Escape only + // cancels the gesture; the next one leaves the tool. + let dragging = self.drag.is_some(); + let previewed = matches!(self.drag, Some(Drag::Move { began: true, .. } | Drag::Resize { began: true, .. })); self.end_drag(); - if busy { vec![Action::Cancel] } else { vec![Action::SwitchTool("selection".into())] } + if previewed { + vec![Action::Cancel] + } else if dragging { + vec![] + } else { + vec![Action::SwitchTool("selection".into())] + } } _ => vec![], } @@ -437,7 +504,7 @@ impl Tool for ArtboardTool { fn overlays(&self, cx: &ToolContext) -> Vec { let mut o = vec![]; - if let Some(Drag::Create { start, cur }) = self.drag { + if let Some(Drag::Create { start, cur, .. }) = self.drag { o.extend_from_slice(self.draw.guides()); let r = Rect::from_points(start, cur); o.push(Overlay::Marquee(r)); @@ -446,6 +513,9 @@ impl Tool for ArtboardTool { } return o; } + if let Some(Drag::Marquee { start, cur }) = self.drag { + o.push(Overlay::Marquee(Rect::from_points(start, cur))); + } // The other selected artboards: dashed bounds (in a drag the document shows them moved). for r in self.others.iter().filter_map(|i| cx.doc.artboards.get(*i)).map(|a| a.rect) { o.push(Overlay::Path { path: polygon(&rect_corners(r), true), color: BLUE, width: 1.0, dashed: true }); @@ -472,7 +542,7 @@ impl Tool for ArtboardTool { o } - fn cursor(&self, cx: &ToolContext, p: Point, _m: Mods) -> Cursor { + fn cursor(&self, cx: &ToolContext, p: Point, m: Mods) -> Cursor { if let Some(r) = self.active_rect(cx) && let Some(h) = hit_handle(r, p, cx.tol(5.0)) { @@ -484,7 +554,8 @@ impl Tool for ArtboardTool { } .turned(cx.view_angle()); } - if cx.doc.artboard_at(p).is_some() { Cursor::Move } else { Cursor::Crosshair } + // With Shift a drag draws or selects, never moves. + if !m.shift && cx.doc.artboard_at(p).is_some() { Cursor::Move } else { Cursor::Crosshair } } fn options(&self) -> Value { @@ -703,6 +774,119 @@ mod tests { assert_eq!(t.options()["selected"], json!([2])); } + /// A Shift-click toggles its artboard when the button comes up, so the press alone leaves the + /// selection as it was. + #[test] + fn a_shift_click_toggles_its_artboard_on_release() { + let d = two_boards(); + let (s, p) = (Selection::default(), paint()); + let c = cx(&d, &s, &p); + let shift = Mods { shift: true, ..Default::default() }; + let mut t = ArtboardTool::default(); + assert_eq!(t.pointer(&c, &ev(PointerKind::Down, 700.0, 100.0).with_mods(shift)), vec![]); + assert_eq!(t.options()["selected"], json!([0]), "the press alone selects nothing"); + assert_eq!(t.pointer(&c, &ev(PointerKind::Up, 701.0, 100.0).with_mods(shift)), vec![]); + assert_eq!(t.options()["selected"], json!([1, 0])); + for kind in [PointerKind::Down, PointerKind::Up] { + t.pointer(&c, &ev(kind, 700.0, 100.0).with_mods(shift)); + } + assert_eq!(t.options()["selected"], json!([0]), "a second Shift-click takes it out"); + } + + /// Shift-drag inside an artboard draws a new artboard there, neither square nor moving the + /// artboard it starts in, and the cursor shows a crosshair while Shift is held. + #[test] + fn a_shift_drag_inside_an_artboard_draws_a_new_one() { + let d = two_boards(); + let (s, p) = (Selection::default(), paint()); + let mut c = cx(&d, &s, &p); + c.smart_guides = false; + let shift = Mods { shift: true, ..Default::default() }; + let mut t = ArtboardTool::default(); + assert_eq!(t.cursor(&c, Point::new(300.0, 300.0), Mods::default()), Cursor::Move); + assert_eq!(t.cursor(&c, Point::new(300.0, 300.0), shift), Cursor::Crosshair); + assert_eq!(t.pointer(&c, &ev(PointerKind::Down, 300.0, 300.0).with_mods(shift)), vec![]); + assert_eq!(t.pointer(&c, &ev(PointerKind::Drag, 420.0, 380.0).with_mods(shift)), vec![], "no move preview"); + assert!(matches!(t.overlays(&c)[0], Overlay::Marquee(_))); + let a = t.pointer(&c, &ev(PointerKind::Up, 420.0, 380.0).with_mods(shift)); + assert_eq!(a, create_artboard(Rect::new(300.0, 300.0, 420.0, 380.0))); + assert_eq!(t.options()["selected"], json!([0]), "the artboard drawn in is not toggled"); + } + + /// Shift-drag on the pasteboard is a marquee: the artboards it touches leave the selection + /// or join it, the last one joining becoming active, and no artboard is drawn. + #[test] + fn a_shift_drag_on_the_pasteboard_selects_the_artboards_it_touches() { + let mut d = two_boards(); + let mut third = d.artboards[0].clone(); + third.rect = Rect::new(0.0, 600.0, 200.0, 800.0); + d.artboards.push(third); + let (s, p) = (Selection::default(), paint()); + let c = cx(&d, &s, &p); + let shift = Mods { shift: true, ..Default::default() }; + let mut t = ArtboardTool::default(); + let marquee = |t: &mut ArtboardTool, from: (f64, f64), to: (f64, f64)| { + assert_eq!(t.pointer(&c, &ev(PointerKind::Down, from.0, from.1).with_mods(shift)), vec![]); + assert_eq!(t.pointer(&c, &ev(PointerKind::Drag, to.0, to.1).with_mods(shift)), vec![]); + assert!(t.overlays(&c).iter().any(|o| matches!(o, Overlay::Marquee(_)))); + t.pointer(&c, &ev(PointerKind::Up, to.0, to.1).with_mods(shift)) + }; + // From between the artboards across Artboard 1 (selected) and Artboard 3: 1 leaves, 3 joins. + assert_eq!(marquee(&mut t, (550.0, 150.0), (150.0, 650.0)), vec![]); + assert_eq!(t.options()["selected"], json!([2])); + // Right of Artboard 2 across its corner: it joins and becomes active. + assert_eq!(marquee(&mut t, (850.0, -50.0), (700.0, 100.0)), vec![]); + assert_eq!(t.options()["selected"], json!([1, 2])); + // A Shift-click on the pasteboard changes nothing. + assert_eq!(marquee(&mut t, (550.0, 150.0), (551.0, 150.0)), vec![]); + assert_eq!(t.options()["selected"], json!([1, 2])); + } + + /// A drag begun without Shift still moves the artboard, and on the pasteboard still draws one; + /// Shift pressed during either no longer keeps the move at 45° or the new artboard square. + #[test] + fn a_plain_drag_still_moves_and_shift_no_longer_constrains_it() { + let d = two_boards(); + let (s, p) = (Selection::default(), paint()); + let mut c = cx(&d, &s, &p); + c.smart_guides = false; + let shift = Mods { shift: true, ..Default::default() }; + let mut t = ArtboardTool::default(); + t.pointer(&c, &ev(PointerKind::Down, 700.0, 100.0)); + let v = preview_params(&t.pointer(&c, &ev(PointerKind::Drag, 730.0, 110.0).with_mods(shift))); + assert_eq!((v["index"].as_u64(), v["dx"].as_f64(), v["dy"].as_f64()), (Some(1), Some(30.0), Some(10.0))); + assert_eq!(t.pointer(&c, &ev(PointerKind::Up, 730.0, 110.0).with_mods(shift)), vec![Action::Commit]); + t.pointer(&c, &ev(PointerKind::Down, 1000.0, 1000.0)); + t.pointer(&c, &ev(PointerKind::Drag, 1100.0, 1050.0).with_mods(shift)); + let a = t.pointer(&c, &ev(PointerKind::Up, 1100.0, 1050.0).with_mods(shift)); + assert_eq!(a, create_artboard(Rect::new(1000.0, 1000.0, 1100.0, 1050.0))); + } + + /// Escape during a marquee or while a new artboard is being drawn ends the gesture without + /// selecting or drawing anything and keeps the tool; the next Escape leaves it. + #[test] + fn escape_cancels_a_marquee_or_a_new_artboard_and_keeps_the_tool() { + let d = two_boards(); + let (s, p) = (Selection::default(), paint()); + let mut c = cx(&d, &s, &p); + c.smart_guides = false; + let shift = Mods { shift: true, ..Default::default() }; + let mut t = ArtboardTool::default(); + // A Shift-drag marquee on the pasteboard, a Shift-drag inside Artboard 1 and a plain drag on + // the pasteboard. + let drags = + [((850.0, -50.0), (700.0, 100.0), shift), ((300.0, 300.0), (420.0, 380.0), shift), ((1000.0, 1000.0), (1100.0, 1050.0), Mods::default())]; + for (from, to, mods) in drags { + t.pointer(&c, &ev(PointerKind::Down, from.0, from.1).with_mods(mods)); + t.pointer(&c, &ev(PointerKind::Drag, to.0, to.1).with_mods(mods)); + assert_eq!(t.key(&c, ToolKey::Escape, Mods::default()), vec![], "Escape from {from:?} only ends the gesture"); + assert!(!t.busy()); + assert_eq!(t.pointer(&c, &ev(PointerKind::Up, to.0, to.1).with_mods(mods)), vec![], "the release draws nothing"); + assert_eq!(t.options()["selected"], json!([0]), "the release selects nothing"); + } + assert_eq!(t.key(&c, ToolKey::Escape, Mods::default()), vec![Action::SwitchTool("selection".into())]); + } + /// Scale Artwork with Artboard (#602): the resize asks for the art to scale, keeps the artboard's /// proportions, and never snaps to the art scaling with it. #[test] diff --git a/crates/ui-egui/src/chrome.rs b/crates/ui-egui/src/chrome.rs index 4b882c511..b744ed420 100644 --- a/crates/ui-egui/src/chrome.rs +++ b/crates/ui-egui/src/chrome.rs @@ -972,7 +972,14 @@ fn hint_for(tool: &str) -> Option<&'static [(&'static str, bool)]> { (" to sample a color", false), ], "gradient" => &[("Drag", true), (" across a selected object to set the gradient direction", false)], - "artboard" => &[("Click", true), (" to select an artboard | ", false), ("Drag", true), (" on the canvas to create one", false)], + "artboard" => &[ + ("Click", true), + (" to select an artboard | ", false), + ("Drag", true), + (" on the canvas to create one (", false), + ("Shift", true), + (" selects several, or draws one inside an artboard)", false), + ], "width" => &[ ("Drag", true), (" a stroke to add a width point | ", false), diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 348fe653e..6cd23e5eb 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -3555,7 +3555,8 @@ imageTrace Outline Контур to sample a color за вземане на проба от цвят across a selected object to set the gradient direction през избран обект за задаване посоката на градиента to select an artboard | за избор на работна област | - on the canvas to create one върху платното за създаване на такава + on the canvas to create one ( върху платното за създаване на такава ( + selects several, or draws one inside an artboard) избира няколко или създава нова в работна област) a stroke to add a width point | щрих за добавяне на точка за дебелина | to change one side | за промяна на една страна | to select more points | за избор на още точки | diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 03b915463..b65019498 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1136,7 +1136,8 @@ lighting Light Světlo to sample a color nabere barvu across a selected object to set the gradient direction přes vybraný objekt nastaví směr přechodu to select an artboard | vybere pracovní plochu | - on the canvas to create one na plátně vytvoří novou + on the canvas to create one ( na plátně vytvoří novou ( + selects several, or draws one inside an artboard) vybere více nebo vytvoří novou uvnitř pracovní plochy) a stroke to add a width point | tahu přidá bod šířky | to change one side | změní jednu stranu | to select more points | vybere další body | diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 0ec4cb936..32cbe07e4 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1057,7 +1057,8 @@ lighting Light Lys to sample a color for at tage en farveprøve across a selected object to set the gradient direction hen over et markeret objekt for at angive gradientretningen to select an artboard | for at markere et tegnebræt | - on the canvas to create one på canvaset for at oprette et + on the canvas to create one ( på canvaset for at oprette et ( + selects several, or draws one inside an artboard) markerer flere eller opretter et inde i et tegnebræt) a stroke to add a width point | på en kontur for at tilføje et breddepunkt | to change one side | for at ændre den ene side | to select more points | for at markere flere punkter | diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index f27da2e10..e38aebd3b 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -3546,7 +3546,8 @@ imageTrace Outline Kontur to sample a color nimmt eine Farbe auf across a selected object to set the gradient direction über ein ausgewähltes Objekt legt die Verlaufsrichtung fest to select an artboard | wählt eine Zeichenfläche aus | - on the canvas to create one auf der Arbeitsfläche erstellt eine neue + on the canvas to create one ( auf der Arbeitsfläche erstellt eine neue ( + selects several, or draws one inside an artboard) wählt mehrere aus oder erstellt eine neue in einer Zeichenfläche) a stroke to add a width point | an einer Kontur fügt einen Breitenpunkt hinzu | to change one side | ändert nur eine Seite | to select more points | wählt weitere Punkte aus | diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index d463fa4af..ea97191a3 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -4821,7 +4821,8 @@ theme Appearance Εμφάνιση to sample a color για δειγματοληψία χρώματος across a selected object to set the gradient direction κατά μήκος επιλεγμένου αντικειμένου για ορισμό κατεύθυνσης ντεγκραντέ to select an artboard | για επιλογή πίνακα σχεδίασης | - on the canvas to create one στον καμβά για δημιουργία ενός + on the canvas to create one ( στον καμβά για δημιουργία ενός ( + selects several, or draws one inside an artboard) επιλέγει πολλούς ή δημιουργεί νέο μέσα σε πίνακα σχεδίασης) a stroke to add a width point | περίγραμμα για προσθήκη σημείου πλάτους | to change one side | για αλλαγή μίας πλευράς | to select more points | για επιλογή περισσότερων σημείων | diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 0fec9d8dd..702ed2f78 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -3614,7 +3614,8 @@ imageTrace Outline Contorno to sample a color para muestrear un color across a selected object to set the gradient direction sobre un objeto seleccionado para definir la dirección del degradado to select an artboard | para seleccionar una mesa de trabajo | - on the canvas to create one en el lienzo para crear una + on the canvas to create one ( en el lienzo para crear una ( + selects several, or draws one inside an artboard) selecciona varias o crea una dentro de una mesa de trabajo) a stroke to add a width point | un trazo para agregar un punto de anchura | to change one side | para cambiar un solo lado | to select more points | para seleccionar más puntos | diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 6e65c347e..b71537934 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1114,7 +1114,8 @@ lighting Light Valo to sample a color ottaaksesi värinäytteen across a selected object to set the gradient direction valitun objektin poikki asettaaksesi liukuvärin suunnan to select an artboard | valitaksesi piirtoalustan | - on the canvas to create one kankaalla luodaksesi uuden + on the canvas to create one ( kankaalla luodaksesi uuden ( + selects several, or draws one inside an artboard) valitsee useita tai luo uuden piirtoalustan sisään) a stroke to add a width point | viivaa lisätäksesi leveyspisteen | to change one side | muuttaaksesi yhtä puolta | to select more points | valitaksesi lisää pisteitä | diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index d78900e8a..0ea4ab92d 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -3611,7 +3611,8 @@ imageTrace Outline Contour to sample a color pour prélever une couleur across a selected object to set the gradient direction sur un objet sélectionné pour définir la direction du dégradé to select an artboard | pour sélectionner un plan de travail | - on the canvas to create one sur la zone de travail pour en créer un + on the canvas to create one ( sur la zone de travail pour en créer un ( + selects several, or draws one inside an artboard) en sélectionne plusieurs ou en crée un dans un plan de travail) a stroke to add a width point | sur un contour pour ajouter un point de largeur | to change one side | pour modifier un seul côté | to select more points | pour sélectionner d’autres points | diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index d1188b811..72df268ef 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -38,7 +38,8 @@ from center | – rajzolás a középpontból | keeps its proportions) – arányok megtartása) near a selected pin to rotate | egy kijelölt tű közelében – forgatás | - on the canvas to create one a vásznon – új rajztábla + on the canvas to create one ( a vásznon – új rajztábla ( + selects several, or draws one inside an artboard) – több kijelölése, vagy új rajztábla egy rajztáblán belül) over paths to roughen their outlines | útvonalakon – a körvonaluk eldurvítása | over paths, and hold still to keep going | útvonalakon – nyomva tartva folytatódik | right or left to zoom in or out | jobbra vagy balra – nagyítás vagy kicsinyítés | diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index b12036dae..96322e600 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -1057,7 +1057,8 @@ lighting Light Cahaya to sample a color untuk mengambil sampel warna across a selected object to set the gradient direction melintasi objek terpilih untuk mengatur arah gradien to select an artboard | untuk memilih artboard | - on the canvas to create one di kanvas untuk membuatnya + on the canvas to create one ( di kanvas untuk membuatnya ( + selects several, or draws one inside an artboard) memilih beberapa, atau membuat satu di dalam artboard) a stroke to add a width point | stroke untuk menambah titik lebar | to change one side | untuk mengubah satu sisi | to select more points | untuk memilih lebih banyak titik | diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 4cd46733c..501c1e8b5 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -3649,7 +3649,8 @@ imageTrace Outline Contorno to sample a color per campionare un colore across a selected object to set the gradient direction su un oggetto selezionato per impostare la direzione della sfumatura to select an artboard | per selezionare una tavola da disegno | - on the canvas to create one sull'area di disegno per crearne una + on the canvas to create one ( sull'area di disegno per crearne una ( + selects several, or draws one inside an artboard) ne seleziona più d'una o ne crea una dentro una tavola da disegno) a stroke to add a width point | su una traccia per aggiungere un punto di larghezza | to change one side | per modificare un solo lato | to select more points | per selezionare altri punti | diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 0289c34b0..08e644550 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -747,7 +747,8 @@ from center | :中心から描画 | keeps its proportions) :縦横比を固定) near a selected pin to rotate | :選択したピンの近くで回転 | - on the canvas to create one :アートボードを作成 + on the canvas to create one ( :アートボードを作成( + selects several, or draws one inside an artboard) :複数を選択、またはアートボード内に作成) over paths to roughen their outlines | :パスの輪郭を粗くする | over paths, and hold still to keep going | :パス上で押し続けると効果が続く | the art to add a pin | :アートにピンを追加 | diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 4334f70e7..f5da9519a 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -5213,3 +5213,7 @@ imageTrace Outline კონტური Note Paper ჩანაწერების ქაღალდი Water Paper… სველი ქაღალდი… Water Paper სველი ქაღალდი + +# Artboard tool hints (#2497) + on the canvas to create one ( ტილოზე — ახლის შესაქმნელად ( + selects several, or draws one inside an artboard) რამდენიმეს მონიშნავს ან ახალს სამუშაო დაფის შიგნით ხატავს) diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 3fd6f8c34..7289fe56a 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -742,7 +742,8 @@ from center | 중심에서 | keeps its proportions) 비율 유지) near a selected pin to rotate | 선택한 핀 근처에서 회전 | - on the canvas to create one 캔버스에서 만들기 + on the canvas to create one ( 캔버스에서 만들기 ( + selects several, or draws one inside an artboard) 여러 개 선택 또는 아트보드 안에 만들기) over paths to roughen their outlines | 패스 위에서 윤곽선 거칠게 만들기 | over paths, and hold still to keep going | 패스 위에서 멈춰 있으면 계속 진행 | the art to add a pin | 아트워크를 클릭하여 핀 추가 | diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index d766b8d49..348efa61d 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1071,7 +1071,8 @@ lighting Light Licht to sample a color om een kleur over te nemen across a selected object to set the gradient direction over een geselecteerd object om de richting van het verloop in te stellen to select an artboard | om een tekengebied te selecteren | - on the canvas to create one op het canvas om er een te maken + on the canvas to create one ( op het canvas om er een te maken ( + selects several, or draws one inside an artboard) selecteert er meerdere of maakt er een binnen een tekengebied) a stroke to add a width point | op een lijn om een breedtepunt toe te voegen | to change one side | om één kant te wijzigen | to select more points | om meer punten te selecteren | diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 03138b413..df480785b 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -1067,7 +1067,8 @@ lighting Light Światło to sample a color aby pobrać próbkę koloru across a selected object to set the gradient direction przez zaznaczony obiekt, aby ustawić kierunek gradientu to select an artboard | aby zaznaczyć obszar kompozycji | - on the canvas to create one na płótnie, aby go utworzyć + on the canvas to create one ( na płótnie, aby go utworzyć ( + selects several, or draws one inside an artboard) zaznacza kilka lub tworzy nowy wewnątrz obszaru kompozycji) a stroke to add a width point | obrys, aby dodać punkt szerokości | to change one side | aby zmienić jedną stronę | to select more points | aby zaznaczyć więcej punktów | diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index c60cc9ea5..41b10b294 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -40,7 +40,8 @@ from center | do centro | keeps its proportions) mantém suas proporções) near a selected pin to rotate | perto de um pino selecionado para girar | - on the canvas to create one na tela para criar um + on the canvas to create one ( na tela para criar um ( + selects several, or draws one inside an artboard) seleciona várias ou cria uma dentro de uma mesa de trabalho) over paths to roughen their outlines | sobre caminhos para enrugar seus contornos | over paths, and hold still to keep going | sobre caminhos, e mantenha parado para continuar | the art to add a pin | a arte para adicionar um pino | diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 438c78bf1..e4b5e9368 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -3645,7 +3645,8 @@ imageTrace Outline Контур to sample a color для взятия образца цвета across a selected object to set the gradient direction по выделенному объекту для задания направления градиента to select an artboard | для выбора монтажной области | - on the canvas to create one на холсте для создания одной + on the canvas to create one ( на холсте для создания одной ( + selects several, or draws one inside an artboard) выбирает несколько или создаёт новую внутри монтажной области) a stroke to add a width point | обводку для добавления точки ширины | to change one side | для изменения одной стороны | to select more points | для выделения большего числа точек | diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 7b985dd6d..0a98233a9 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -3555,7 +3555,8 @@ imageTrace Outline Disposition to sample a color för att hämta en färg across a selected object to set the gradient direction över ett markerat objekt för att ange gradientens riktning to select an artboard | för att markera en rityta | - on the canvas to create one på arbetsytan för att skapa en + on the canvas to create one ( på arbetsytan för att skapa en ( + selects several, or draws one inside an artboard) markerar flera eller skapar en ny i en rityta) a stroke to add a width point | en kontur för att lägga till en breddpunkt | to change one side | för att ändra en sida | to select more points | för att markera fler punkter | diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 5c3c2e926..20c7c57e7 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -3642,7 +3642,8 @@ imageTrace Outline Контур to sample a color для взяття зразка кольору across a selected object to set the gradient direction по виділеному об’єкту, щоб задати напрям градієнта to select an artboard | для вибору монтажної області | - on the canvas to create one на полотні для створення однієї + on the canvas to create one ( на полотні для створення однієї ( + selects several, or draws one inside an artboard) вибирає кілька або створює нову всередині монтажної області) a stroke to add a width point | обведення для додавання точки ширини | to change one side | для зміни однієї сторони | to select more points | для виділення інших точок | diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 270d012bf..4c1be7012 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -24,7 +24,8 @@ from center | từ tâm | keeps its proportions) giữ nguyên tỷ lệ) near a selected pin to rotate | gần ghim đã chọn để xoay | - on the canvas to create one trên vùng vẽ để tạo một cái + on the canvas to create one ( trên vùng vẽ để tạo một cái ( + selects several, or draws one inside an artboard) chọn nhiều cái hoặc tạo cái mới bên trong bảng vẽ) over paths to roughen their outlines | qua đường dẫn để làm thô đường bao | over paths, and hold still to keep going | qua đường dẫn và giữ yên để tiếp tục | right or left to zoom in or out | sang phải hoặc trái để phóng to hoặc thu nhỏ | diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index a35275297..fc9dbf18c 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -3503,7 +3503,8 @@ imageTrace Outline 轮廓 to sample a color 以取样颜色 across a selected object to set the gradient direction 横跨所选对象以设置渐变方向 to select an artboard | 以选择画板 | - on the canvas to create one 在画布上以创建画板 + on the canvas to create one ( 在画布上以创建画板 ( + selects several, or draws one inside an artboard) 可选择多个,或在画板内创建画板) a stroke to add a width point | 描边可添加宽度点 | to change one side | 可只更改单侧 | to select more points | 可选择更多点 | diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 4998ee62e..6b248416c 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -4401,7 +4401,8 @@ imageTrace Outline 輪廓 to sample a color 可取樣顏色 across a selected object to set the gradient direction 橫越所選物件可設定漸層方向 to select an artboard | 可選取工作區域 | - on the canvas to create one 畫布可建立工作區域 + on the canvas to create one ( 畫布可建立工作區域 ( + selects several, or draws one inside an artboard) 可選取多個,或在工作區域內建立工作區域) a stroke to add a width point | 筆畫可新增寬度點 | to change one side | 可只變更單側 | to select more points | 可選取更多點 | diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 76e7a1764..e9c7b1714 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -437,6 +437,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - **Edit Artboards in Properties:** the active artboard's name is a field. Typing a new name and pressing Enter or clicking elsewhere renames that artboard in one undo step, as the Artboards panel's rename does (`artboard.setProps {index, name}`); pressing Escape or leaving the field empty keeps the old name. A click on the Artboard Options icon beside the field opens Artboard Options for the active artboard, as double-clicking the Artboard tool's button does (`tool.options {tool: "artboard"}`). Pointer and key tests in `panels/properties.rs` cover both. Public references: [Resize artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/resize-artboards.html) and [Create and add new artboards](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/add-new-artboards.html). - **Resizing several artboards:** with several artboards selected with the Artboard tool, a new W or H in Properties, the Transform panel or the Control bar, a preset or an orientation in Properties or the Control bar, and a new Width or Height in Artboard Options resize every selected artboard in one undo step (`artboard.setProps {artboards}`); with Scale Artwork with Artboard on, Properties, the Transform panel and the Control bar scale each object once, with the artboard it was inside before the change. The Transform panel, the Control bar and Artboard Options resize each one about its own reference point, a preset sizes each one in its own orientation, and X and Y still move the active artboard alone. Pointer and key tests in `panels/properties.rs`, dialog tests in `dialogs/tests.rs` and an engine test in `tests_xform.rs` cover them. Public references: [Artboard Enhancements In Illustrator CC](https://blog.adobe.com/en/publish/2017/11/10/artboard-enhancements-in-illustrator-cc) ("modify them all in bulk by defining the height and width values") and [Using multiple artboards](https://helpx.adobe.com/illustrator/using/using-multiple-artboards.html) (2019). - **Artboard Escape routing:** Escape leaves Artboard editing for Selection; during a drag it first cancels the gesture. Headless egui key events cover idle Escape, active-drag cancellation and a subsequent Escape in `tests_distortkeys.rs`. +- **Shift with the Artboard tool:** a Shift-click adds an artboard to the selection or takes it out when the button comes up. A Shift-drag inside an artboard draws a new artboard there, and a Shift-drag on the pasteboard is a marquee that toggles the artboards it touches, as the [default keyboard shortcuts](https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html) ("Create artboard inside another artboard") and [Using multiple artboards](https://web.archive.org/web/20210925094818/https://helpx.adobe.com/illustrator/using/using-multiple-artboards.html) (archived copy) describe. Shift no longer keeps an artboard move at 45° steps or a drawn artboard square. Escape during a marquee or while a new artboard is being drawn cancels it and keeps the Artboard tool. Pointer and key tests in `crates/tools/src/xform/artboard.rs` cover the four gestures and Escape. - **Artboard deletion keys:** Delete/Backspace delete selected boards in one Undo step, retaining the original first board in the current order when all are selected, as in the public [Delete artboards guide](https://helpx.adobe.com/illustrator/desktop/create-manage-artboards/add-edit-artboards/delete-artboards.html). The last board and active drags stay protected; neither key clears selected artwork. Headless shortcut regressions in `tests_distortkeys.rs` cover reordered boards, artwork, saved state and Undo/Redo. - **Linux RPM packaging:** 64-bit runtime capabilities replace Fedora-specific required package names (#1275); glibc 2.35, libm and libgcc_s are declared alongside dynamically loaded xkbcommon and Vulkan/EGL. Generated x86_64/aarch64 metadata and isolated synthetic-provider resolution checked; real-distro install/start-up QA remains pending. - **Linux file associations:** the desktop entry lists the MIME type of every format File › Open reads but PSD, PSB, `.ps` PostScript and Targa, `.ai`, `.ait` and EPS included, and the MIME package declares the types shared-mime-info lacks (VectorCraft documents and templates, `.ait`, Affinity), so file managers list VectorCraft under Open With for them, as Finder does on macOS. `the_linux_desktop_entry_opens_every_readable_format` in `apps/vectorcraft/src/main.rs` checks both files against `OPEN_EXTS`. On Debian 13, Ubuntu 24.04 and Fedora 42, `desktop-file-validate` accepts the desktop entry, `update-mime-database` accepts the MIME package, and `gio` registers the app for every one of these types.