diff --git a/ROADMAP.md b/ROADMAP.md index 0b2611fe..fa806707 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -116,7 +116,7 @@ grade by behaviour against `plan/illustrator/`, not by whether a menu item exist - **Temporary tools:** Cmd with any other tool drags with the selection tool used last (Selection, Direct Selection or Group Selection). Held Space pans and Cmd+Space zooms (Cmd+Alt+Space out) whatever the tool. - **Panel drag and drop:** art dragged off the canvas onto the Symbols panel becomes a symbol, and a symbol dragged onto the canvas places an instance where it is dropped. - **Tool double-clicks:** double-clicking a tool's button opens its options: Hand fits the artboard in the window, Zoom shows 100 %, Rotate, Scale, Reflect and Shear open their Object › Transform dialogs, Gradient, Eyedropper, Blend and the Liquify tools their panels or options. -- **Layers panel:** every layer, sublayer, group and object is a clickable row at any depth: a click highlights it and makes its layer current (new art goes there), Shift-click a range, Ctrl/Cmd-click toggles. Sublayers are no longer treated as objects: clicks, marquees, Select All and the Select menu take the art inside them, and each sublayer selects in its own colour. Selection squares on rows with selected art (click selects a row's art, Shift adds or removes; dragging the square moves the selection to another row, Alt copies), the eye and lock on every row with drag-down-the-column (one undo step), Ctrl/Cmd-click an eye for per-layer Outline, triangles with Alt to open or close everything inside, drag and drop rows above, below or into layers and groups with a drop line (Alt copies, the trash deletes), inline rename, Delete key on highlighted rows. Buttons and the full panel menu: New Layer/Sublayer (Alt: options, Ctrl: on top), Duplicate, Delete, Options for Selection, clipping mask, isolation, Locate Object, Merge Selected, Flatten Artwork, Collect in New Layer, Release to Layers (Sequence and Build), Reverse Order, Template, Hide/Outline/Lock Others and Show/Preview/Unlock All, Paste Remembers Layers, Panel Options (row size, thumbnails per kind, Show Layers Only). Layer Options: name, colour (presets or custom), Template, Lock, Show, Print, Preview (outline drawing and clicking) and Dim Images to %. Every operation is a command (`layer.move`, `layer.highlight`, `layer.merge`…) with validation against cycles and invalid nesting. +- **Layers panel:** every layer, sublayer, group and object is a clickable row at any depth: a click highlights it and makes its layer current (new art goes there), Shift-click a range, Ctrl/Cmd-click toggles. Sublayers are no longer treated as objects: clicks, marquees, Select All and the Select menu take the art inside them, and each sublayer selects in its own colour. Selection squares on rows with selected art (click selects a row's art, Shift adds or removes; dragging the square moves the selection to another row, Alt copies), the eye and lock on every row with drag-down-the-column (one undo step), Ctrl/Cmd-click an eye for per-layer Outline, Alt-click an eye or lock to hide or lock the other layers (again: show or unlock them all), triangles with Alt to open or close everything inside, drag and drop rows above, below or into layers and groups with a drop line (Alt copies, the trash deletes), inline rename, Delete key on highlighted rows. Buttons and the full panel menu: New Layer/Sublayer (Alt: options, Ctrl: on top), Duplicate, Delete, Options for Selection, clipping mask, isolation, Locate Object, Merge Selected, Flatten Artwork, Collect in New Layer, Release to Layers (Sequence and Build), Reverse Order, Template, Hide/Outline/Lock Others and Show/Preview/Unlock All, Paste Remembers Layers, Panel Options (row size, thumbnails per kind, Show Layers Only). Layer Options: name, colour (presets or custom), Template, Lock, Show, Print, Preview (outline drawing and clicking) and Dim Images to %. Every operation is a command (`layer.move`, `layer.highlight`, `layer.merge`…) with validation against cycles and invalid nesting. - **Drawing aids:** Smart Guides and snapping, guides dragged out of the rulers, and Draw Normal / Behind / Inside modes. - **Views:** the status bar's artboard navigator (first, previous, next, last; `view.goToArtboard`) goes to an artboard and fits it, and Fit Artboard in Window and Actual Size show the navigator's artboard. - **Geometry and effects:** diff --git a/crates/ui-egui/src/panels/layers.rs b/crates/ui-egui/src/panels/layers.rs index caa715f8..65099b47 100644 --- a/crates/ui-egui/src/panels/layers.rs +++ b/crates/ui-egui/src/panels/layers.rs @@ -11,7 +11,9 @@ //! another row moves the selected art there (Alt copies it). //! - The eye and lock columns toggle a row (`layer.setProps`); dragging down a column sets every //! row it passes to the same state, in one undo step. Ctrl/Cmd-click an eye switches the layer -//! between Preview and Outline. +//! between Preview and Outline. Alt-click an eye hides the other layers (`layer.hideOthers`), or +//! shows every layer when they are hidden (`layer.showAll`); Alt-click a lock does the same with +//! `layer.lockOthers` and `layer.unlockAll`. //! - Dragging rows moves them above, below or into a layer or group (`layer.move`, a drop line or //! a box shows where); Alt copies them; dropped on the trash they are deleted. //! - The triangle opens or closes a row; Alt-click does the same to everything inside it. @@ -543,19 +545,19 @@ fn row(ui: &mut Ui, view: &View, n: &Node, depth: usize, clip_path: bool, expand } } -/// Alt-clicking row `n`'s eye or lock (`prop`: `visible` or `locked`) toggles that column for the -/// other items beside it (the other layers for a layer), in one step: hides or locks them while -/// any of them is shown or unlocked, else shows or unlocks them all. → the action, if there are -/// others. -fn others_action(doc: &Document, n: &Node, prop: &str) -> Option<(String, Value)> { - let siblings = doc.children(doc.parent_of(n.id))?; - let others: Vec<&Node> = siblings.iter().map(|c| &**c).filter(|c| c.id != n.id).collect(); - if others.is_empty() { - return None; - } - let value = if prop == "visible" { !others.iter().any(|o| o.visible) } else { others.iter().any(|o| !o.locked) }; - let ids: Vec = others.iter().map(|o| o.id.0).collect(); - Some(("layer.setProps".into(), json!({ "ids": ids, prop: value }))) +/// Alt-clicking row `n`'s eye (`eye`) or lock: Hide Others or Lock Others, every top-level layer +/// but the one holding the row; when they all are hidden (locked) already, Show All Layers (Unlock +/// All Layers). Each is one undo step. +fn others_action(doc: &Document, n: &Node, eye: bool) -> (String, Value) { + let own = doc.layer_of(n.id); + let mut others = doc.layers.iter().filter(|l| Some(l.id) != own); + let cmd = match (eye, if eye { others.any(|l| l.visible) } else { others.any(|l| !l.locked) }) { + (true, true) => "layer.hideOthers", + (true, false) => "layer.showAll", + (false, true) => "layer.lockOthers", + (false, false) => "layer.unlockAll", + }; + (cmd.into(), json!({ "ids": [n.id.0] })) } /// The eye and lock columns of row `n` at `r`. @@ -590,7 +592,7 @@ fn eye_and_lock(ui: &mut Ui, view: &View, n: &Node, r: egui::Rect, out: &mut Out let m = ui.input(|i| i.modifiers); if er.clicked() { if m.alt { - out.actions.extend(others_action(view.doc, n, "visible")); + out.actions.push(others_action(view.doc, n, true)); } else if m.command && n.is_layer() { // Ctrl/Cmd-click: Preview ↔ Outline for this layer. out.actions.push(("layer.setProps".into(), json!({"ids": [n.id.0], "preview": !preview}))); @@ -599,7 +601,7 @@ fn eye_and_lock(ui: &mut Ui, view: &View, n: &Node, r: egui::Rect, out: &mut Out } } if lr.clicked() && m.alt { - out.actions.extend(others_action(view.doc, n, "locked")); + out.actions.push(others_action(view.doc, n, false)); } else if lr.clicked() { out.actions.push(("layer.setProps".into(), json!({"ids": [n.id.0], "locked": !n.locked}))); } @@ -1039,12 +1041,26 @@ mod tests { assert_eq!(undo(&app), before + 1); click(&mut app, eye, true); assert_eq!(state(&app), [(true, false); 3]); - // Alt-click its lock: the others lock. + // Alt-click its lock: the others lock; again, every layer unlocks. click(&mut app, lock, true); assert_eq!(state(&app), [(true, true), (true, false), (true, true)]); + assert_eq!(app.session.active().unwrap().history.undo.last().map(|u| u.label.as_str()), Some("Lock Others")); // A plain click still toggles only that layer. click(&mut app, eye, false); assert_eq!(state(&app), [(true, true), (false, false), (true, true)]); + click(&mut app, lock, true); + assert_eq!(state(&app), [(true, false), (false, false), (true, false)]); + // A sublayer's row stands for its top-level layer: the other layers hide. + click(&mut app, eye, false); + let layer2 = app.session.active().unwrap().doc.layers[1].id.0; + let sub = run(&mut app, "layer.newSublayer", json!({"parent": layer2})); + // Rows: Layer 3, Layer 2, its sublayer, Layer 1. + let (circles, _) = frame(&mut app, &ctx, vec![], false); + assert_eq!(circles.len(), 4); + click(&mut app, egui::pos2(12.0, circles[2].y), true); + assert_eq!(state(&app), [(false, false), (true, false), (false, false)]); + let doc = &app.session.active().unwrap().doc; + assert!(doc.node(NodeId(sub["id"].as_u64().unwrap())).unwrap().visible); } /// A document with two rectangles on one layer → (app, layer, [bottom, top]). diff --git a/docs/mcp.md b/docs/mcp.md index f1fe2f96..59efb4c1 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -752,7 +752,8 @@ and `layerRows`, the rows highlighted in the panel. templates stay), `layer.collectInNew {ids?}`, `layer.releaseToLayers {id?, build?}` and `layer.releaseToLayersBuild` (each object of a layer or group in a sublayer of its own; Build adds up copies), `layer.reverse {ids?}`, `layer.template {ids?, on?}`, `layer.hideOthers` / `layer.showAll`, - `layer.outlineOthers` / `layer.previewAll`, `layer.lockOthers` / `layer.unlockAll`, `layer.pasteRemembersLayers`, + `layer.outlineOthers` / `layer.previewAll`, `layer.lockOthers` / `layer.unlockAll` (Alt-clicking an eye or a lock + runs Hide or Lock Others for the row's layer, or Show or Unlock All when the others already are), `layer.pasteRemembersLayers`, `object.isolate {id}` / `object.exitIsolation`. - **Dialogs and panel state** (control channel and the app's MCP): `ui.layerOptions {ids?}` and `ui.newLayer {sublayer?}` open Layer Options (dialog `layerOptions`: name, color, template, locked, visible, printable, preview,