From 336d860ed3ee5292c0b6ed2a9af45cd216a268eb Mon Sep 17 00:00:00 2001 From: MarekSpodBiedry <73122968+MarekSpodBiedry@users.noreply.github.com> Date: Sat, 17 Oct 2026 07:46:28 +0200 Subject: [PATCH] Layers panel: drop a layer under the last row to move it to the bottom (#2260) --- crates/ui-egui/src/panels/layers.rs | 43 +++++++++++++++++++++++++++-- docs/target-app-parity.md | 2 ++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/crates/ui-egui/src/panels/layers.rs b/crates/ui-egui/src/panels/layers.rs index 8966ef3dd..df9d00d76 100644 --- a/crates/ui-egui/src/panels/layers.rs +++ b/crates/ui-egui/src/panels/layers.rs @@ -17,8 +17,9 @@ //! to Outline and the row's layer to Preview (`layer.outlineOthers`), or switches every layer to //! Preview when the others are in Outline (`layer.previewAll`). //! - 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. A dimmed copy of the row follows the pointer. Dropped on -//! the trash they are deleted, on New Layer duplicated. +//! a box shows where); Alt copies them. A dimmed copy of the row follows the pointer. Layers +//! dropped in the empty space under the last row go below the bottom layer. Dropped on the trash +//! they are deleted, on New Layer duplicated. //! - The triangle opens or closes a row; Alt-click does the same to everything inside it. //! - Double-clicking a name renames it; double-clicking elsewhere on a row opens its options. //! - A target circle targets its layer, group or object (`layer.target`); dragging it onto another @@ -619,6 +620,29 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { } // The column drag: live while it lasts, one undo step when the button is released. let released = ui.input(|i| i.pointer.any_released()); + // Layers dragged into the empty space under the last row go below the bottom layer (there is + // no row there to take the drop). + let below = list_rect.top() - scrolled.inner.state.offset.y + list.len() as f32 * view.h; + if query.is_empty() + && !app.ui.layers_filter.is_on() + && let Some(drag) = egui::DragAndDrop::payload::(ui.ctx()) + && let LayersDrag::Rows(ids) = &*drag + && let Some(bottom) = doc.layers.first() + && !ids.is_empty() + && !ids.contains(&bottom.id) + && ids.iter().all(|i| doc.node(*i).is_some_and(Node::is_layer)) + && let Some(p) = ui.input(|i| i.pointer.hover_pos()) + && list_rect.contains(p) + && p.y > below + { + let stroke = Stroke::new(2.0, view.t.accent); + ui.painter().line_segment([egui::pos2(list_rect.left(), below), egui::pos2(list_rect.right(), below)], stroke); + if released { + let copy = ui.input(|i| i.modifiers.alt); + let ids: Vec = ids.iter().map(|i| i.0).collect(); + out.actions.push(("layer.move".into(), json!({"ids": ids, "target": bottom.id.0, "place": "below", "copy": copy}))); + } + } let changed = out.column.is_some(); let column = out.column.take().unwrap_or_else(|| view.column.clone()); if let Some(c) = column.as_ref().filter(|_| changed || released) { @@ -2442,6 +2466,21 @@ mod tests { assert_eq!(app.run("ui.layersFilter", json!({})).unwrap(), json!({"objects": ["paths", "guides"], "layers": []}), "unchanged"); } + #[test] + fn dragging_a_layer_into_the_empty_space_under_the_rows_moves_it_to_the_bottom() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 200, "height": 200})).unwrap(); + app.session.execute("layer.new", &json!({"top": true})).unwrap(); + let ids = |app: &VectorcraftApp| app.session.active().unwrap().doc.layers.iter().map(|l| l.id).collect::>(); + let [bottom, top] = ids(&app)[..] else { panic!("two layers") }; + let ctx = egui::Context::default(); + // Rows: the top layer, the bottom layer, then empty space. + let (circles, _) = frame(&mut app, &ctx, vec![], false); + assert_eq!(circles.len(), 2); + drag(&mut app, &ctx, egui::pos2(150.0, circles[0].y), egui::pos2(150.0, circles[1].y + 3.0 * ROW_MEDIUM), false); + assert_eq!(ids(&app), vec![top, bottom], "the top layer is now the bottom one"); + } + #[test] fn dragging_rows_moves_them_into_other_layers_and_alt_copies() { let (mut app, [layer, sub, g, _, a, c]) = nested(); diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index bdc9a5f2c..a227243e9 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -534,6 +534,8 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur Keep selected in the Pencil and Paintbrush Tool Options is on by default; while it is off, the path a stroke draws or continues is not selected afterwards and the rest of the selection stays as it was (`path.freehand` `keepSelected`). The Blob Brush Tool Options have Keep selected and Merge only with selection, both off by default: while Keep selected is off, the blob a stroke draws or merges into is not selected afterwards, and while Merge only with selection is on, a stroke merges only with selected blobs of its color that it touches (`path.blob` `keepSelected` and `mergeOnlySelection`). - **Ruler guides as layer objects:** a guide goes on the current layer, shows, hides, locks and is deleted with it, is listed in the Layers panel (its row selects it) and moves to another layer (`guide.setLayer`, or its row dragged onto that layer's rows); guides on a hidden or locked layer can't be selected, moved or deleted, Duplicate copies a layer's guides and Merge Selected and Flatten Artwork keep them on the merged layer; guides from files saved before guides had layers go on the top visible, unlocked layer when the document opens. Not done: guides stacked among the art of their layer (they are listed above it), a guide row's own eye and lock. Lock Guides also locks the objects made into guides: they can't be selected, moved or deleted (#1543); they are drawn in the guide colour and style of Preferences, and a dragged selection snaps to ruler guides with Smart Guides on (#1583, fixes #1534). The Transform panel shows a selected ruler guide's X or Y, and typing a value moves it (#1659, part of #1597). This is not established by the documentation we could find: the public help pages on guides, rulers and the Transform, Control and Properties panels (read again on 2026-10-14) describe selecting and dragging unlocked guides and the panels' X and Y for a selection, not a guide's position in a panel, and the one public statement of it is a user's [community answer](https://community.adobe.com/questions-652/adding-a-new-custom-guide-785586), which is not documentation. It is kept as a convenience pending the owner's decision, with no parity claim and nothing counted towards a score. +- **Releasing ruler guides:** View › Guides › Release Guides turns the selected ruler guides into paths with no fill or stroke. An artboard guide becomes a line across its artboard, and a canvas guide becomes a line across the artboards (`view.guides.release {index?, ids?}`). +- **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) and a dimmed copy of the row under the pointer (layers dropped in the empty space under the last row go below the bottom layer, #2260; dropped on the trash rows are deleted, on New Layer duplicated, as in Illustrator), 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, each with a chip of its colour, 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. A document opens with only its top-level layers open; the rows left open are saved in the native file (nothing is written for the default) and reopen that way. Only the rows scrolled into view are laid out, from a row list made once per change: a layer of 5,000 groups of 10 paths, all open, draws the panel in 0.4 ms a frame instead of about 29 s (#638). - **Releasing ruler guides:** View › Guides › Release Guides turns the selected ruler guides into paths with no fill or stroke. An artboard guide becomes a line across its artboard, and a canvas guide becomes a line across the artboards (`view.guides.release {index?, ids?}`). A Cmd/Ctrl+Shift double-click with the Selection, Direct Selection or Group Selection tool releases the ruler guide or guide path under the pointer. - **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) and a dimmed copy of the row under the pointer (dropped on the trash rows are deleted, on New Layer duplicated, as in Illustrator), 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, each with a chip of its colour, 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. A document opens with only its top-level layers open; the rows left open are saved in the native file (nothing is written for the default) and reopen that way. Only the rows scrolled into view are laid out, from a row list made once per change: a layer of 5,000 groups of 10 paths, all open, draws the panel in 0.4 ms a frame instead of about 29 s (#638). The Filter button at the right of Search All keeps the rows that are of an object kind ticked under By Object (paths, shapes, text, images, effects, groups, linked files, guides) and in a state ticked under By Layer (locked, hidden), with the rows that hold them; a section with nothing ticked takes every row. The filter combines with the search, the button is lit while a filter is on, and Clear All in its popup turns the filter off (`ui.layersFilter`). The filter lasts the session.