Merge pull request #4401 from dotCooCoo/feat/layers-alt-click-selects-layer

Layers: Alt-click on a layer name selects the layer's art
This commit is contained in:
BFlat
2026-10-29 10:57:39 +07:00
committed by GitHub
7 changed files with 74 additions and 4 deletions
+3 -1
View File
@@ -673,7 +673,9 @@ fn select_art(s: &mut Session, p: &Value) -> Result<Value> {
let add = switch_or(p, "add", false, C)?;
let st = s.doc()?;
let n = st.doc.node(id).ok_or(EngineError::NoNode(id))?;
let ids: Vec<NodeId> = if !st.doc.is_editable(id) || !in_isolation(&st.doc, st.isolation, id) {
// An isolated layer's own row selects the art inside it.
let isolated_layer = st.isolation == Some(id) && n.is_layer();
let ids: Vec<NodeId> = if !st.doc.is_editable(id) || !(isolated_layer || in_isolation(&st.doc, st.isolation, id)) {
vec![]
} else if n.is_layer() {
n.layer_art(true)
+14
View File
@@ -1602,6 +1602,20 @@ fn layers_panel_selects_nothing_outside_the_isolated_group() {
assert_eq!(selected(&s), vec![b]);
}
/// In isolation mode the selection column of the isolated layer's own row selects the art on it.
#[test]
fn layers_panel_selects_the_art_of_the_isolated_layer() {
let mut s = session();
let a = rect(&mut s, 0.0, 0.0, 50.0, 50.0);
let b = rect(&mut s, 100.0, 0.0, 50.0, 50.0);
let layer = s.active().unwrap().doc.layers[0].id;
s.execute("object.isolate", &json!({"id": layer.0})).unwrap();
s.execute("layer.selectAll", &json!({"id": layer.0})).unwrap();
let mut got = selected(&s);
got.sort_by_key(|i| i.0);
assert_eq!(got, vec![a, b]);
}
/// In isolation mode a recalled selection leaves out the objects outside the isolated group, and
/// Next Object Above selects nothing outside it.
#[test]
+3 -1
View File
@@ -5,7 +5,9 @@
//!
//! - Clicking a row highlights it and makes its layer current (`layer.setCurrent`); Shift-click
//! highlights the range of rows down to it, Ctrl/Cmd-click toggles one (`layer.highlight`). Panel
//! commands (Duplicate, Delete, Merge, Options…) act on the highlighted rows.
//! commands (Duplicate, Delete, Merge, Options…) act on the highlighted rows. Alt-click on a
//! layer's or sublayer's name also selects its art (`layer.selectAll`), as its selection square
//! does.
//! - The selection column selects a row's art (`layer.selectAll`, Shift adds or removes); a row
//! whose art is selected shows a square in its layer's colour, and dragging that square onto
//! another row moves the selected art there (Alt copies it).
+7 -1
View File
@@ -310,15 +310,21 @@ pub(super) fn row(ui: &mut Ui, view: &View, item: &Row, out: &mut Out) {
egui::DragAndDrop::set_payload(ui.ctx(), LayersDrag::Art);
}
let m = ui.input(|i| i.modifiers);
let on_name = ui.input(|i| i.pointer.interact_pos()).is_some_and(|p| p.x <= name_end.max(name_rect.left() + 20.0) && p.x >= name_rect.left());
if tresp.clicked() {
out.actions.push(("layer.target".into(), json!({"id": n.id.0, "add": m.shift})));
} else if sq_resp.clicked() {
out.actions.push(("layer.selectAll".into(), json!({"id": n.id.0, "add": m.shift})));
} else if resp.clicked() {
out.click = Some((n.id, m));
// Alt-click on a layer's or sublayer's name also selects its art, as its selection square does.
// Only the drawn text counts, not the wider rename target of a short name.
let on_text = ui.input(|i| i.pointer.interact_pos()).is_some_and(|p| p.x >= name_rect.left() && p.x <= name_end.min(name_rect.right()));
if m.alt && !m.shift && !m.command && n.is_layer() && on_text {
out.actions.push(("layer.selectAll".into(), json!({"id": n.id.0})));
}
}
if resp.double_clicked() {
let on_name = ui.input(|i| i.pointer.interact_pos()).is_some_and(|p| p.x <= name_end.max(name_rect.left() + 20.0) && p.x >= name_rect.left());
let on_thumb = thumb_rect.is_some_and(|th| ui.input(|i| i.pointer.interact_pos()).is_some_and(|p| th.contains(p)));
if on_name && !on_thumb {
ui.data_mut(|d| d.insert_temp(rename_id(), (n.id.0, n.identify_label(view.identify_xml_id))));
+44
View File
@@ -544,6 +544,50 @@ fn selection_squares_show_and_select_a_rows_art() {
let _ = (layer, sub, b);
}
/// Alt-click on a layer's or sublayer's name makes it current, as a plain click does, and selects
/// its art, as its selection square does. On another row, beside the name or with Shift or Ctrl/Cmd
/// held too, it is the click without Alt.
#[test]
fn alt_clicking_a_layer_name_selects_its_art() {
let (mut app, [layer, sub, g, _, a, c]) = nested();
let ctx = egui::Context::default();
let (circles, _) = frame(&mut app, &ctx, vec![], false);
// The rows' names start right of the thumbnail; the sublayer's row is one level deeper.
let name = |i: usize, depth: f32| egui::pos2(2.0 * COLUMN + 2.0 + 6.0 + depth * INDENT + 16.0 + 30.0, circles[i].y);
let state = |app: &VectorcraftApp| {
let st = app.session.active().unwrap();
(st.selection.objects.clone(), st.highlighted_rows(), st.current_layer())
};
click_with(&mut app, &ctx, name(1, 1.0), egui::Modifiers::ALT);
assert_eq!(state(&app), (vec![a, g], vec![sub], Some(sub)), "the sublayer's art");
click_with(&mut app, &ctx, name(0, 0.0), egui::Modifiers::ALT);
assert_eq!(state(&app), (vec![c, a, g], vec![layer], Some(layer)), "the layer's, its sublayer's too");
app.session.execute("select.none", &json!({})).unwrap();
// A group's row, the space right of a layer's name, Alt+Shift and Ctrl/Cmd+Alt select nothing.
click_with(&mut app, &ctx, name(2, 2.0), egui::Modifiers::ALT);
assert_eq!(state(&app), (vec![], vec![g], Some(sub)), "a group's row");
click_with(&mut app, &ctx, circles[0] - vec2(40.0, 0.0), egui::Modifiers::ALT);
assert_eq!(state(&app), (vec![], vec![layer], Some(layer)), "beside the name");
click_with(&mut app, &ctx, name(1, 1.0), egui::Modifiers::ALT | egui::Modifiers::SHIFT);
assert_eq!(state(&app).0, vec![], "Alt+Shift");
click_with(&mut app, &ctx, name(0, 0.0), egui::Modifiers::ALT | egui::Modifiers::COMMAND);
assert_eq!(state(&app).0, vec![], "Ctrl/Cmd+Alt");
}
/// Alt-click right of a one-letter layer name, inside the wider rename target, selects nothing.
#[test]
fn alt_clicking_beside_a_short_layer_name_selects_nothing() {
let (mut app, [_, sub, ..]) = nested();
app.session.execute("layer.setProps", &json!({"ids": [sub.0], "name": "I"})).unwrap();
let ctx = egui::Context::default();
let (circles, _) = frame(&mut app, &ctx, vec![], false);
// The sublayer's name area starts here; "I" ends about 5 points in, the rename target 20 points in.
let name_left = 2.0 * COLUMN + 2.0 + 6.0 + INDENT + 40.0;
click_with(&mut app, &ctx, egui::pos2(name_left + 14.0, circles[1].y), egui::Modifiers::ALT);
let st = app.session.active().unwrap();
assert_eq!((st.selection.objects.clone(), st.current_layer()), (vec![], Some(sub)));
}
#[test]
fn eye_and_lock_toggle_nested_rows_and_drag_down_the_column() {
let (mut app, [_, sub, g, b, a, _]) = nested();
+2 -1
View File
@@ -2117,7 +2117,8 @@ and `layerRows`, the rows highlighted in the panel.
set|add|toggle}` is Shift-click (a range) and Ctrl/Cmd-click (toggle). Selecting art makes its layer current.
Without `ids`, the panel commands below act on the highlighted rows, else on the current layer.
- **Selection column**: `layer.selectAll {id, add?}` selects a row's visible, unlocked art (a layer's sublayers'
too); `add` (Shift) adds it, or removes it when it's all selected.
too); `add` (Shift) adds it, or removes it when it's all selected. Alt-click on a layer's or sublayer's name runs
`layer.selectAll {id}` and then `layer.setCurrent {id}`.
- **Eye and lock**: `layer.setProps {ids?, visible?, locked?, name?}` for any rows, plus Layer Options for layers:
`template` (also locks the layer and dims its images to 50% unless given), `printable`, `preview` (off: the layer
draws and is clicked in outline, Ctrl/Cmd-click its eye), `dimImages` (0–100, or false) and `color` (an index 0–26,
+1
View File
@@ -709,6 +709,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
The panel menu's Save Selection submenu has Save Selection…, Update Selection and Edit Selection…, enabled and run as in the Select menu, and lists the document's saved selections: choosing one selects its objects again (`select.recall`).
`object.setProps` with `visible: false` or `locked: true` deselects every selected object that is then hidden or locked, the key object included, as the eye and lock columns do.
Ctrl/Cmd+Alt-clicking an eye switches the other layers to Outline and the row's layer to Preview, or switches all layers back to Preview when the others are in Outline (`layer.outlineOthers`, `layer.previewAll`).
Alt-clicking a layer's or sublayer's name makes it the current layer, as a click does, and selects its visible, unlocked art, as its selection square does (`layer.selectAll`, then `layer.setCurrent`); on a hidden or locked layer it clears the selection. The reference app's [Default keyboard shortcuts](https://web.archive.org/web/20260726060015/https://helpx.adobe.com/illustrator/using/default-keyboard-shortcuts.html) page lists Alt-click on the layer name for selecting all objects on the layer.
Object › Arrange › Send to Current Layer returns the error "the target layer is locked or hidden" and leaves the selected objects where they are when the current layer is hidden or locked, or is inside a hidden or locked layer.
Selected objects are deselected, the key object included, when a row drag moves them into a hidden layer or group, when `node.move` moves them into a hidden or locked layer or group, when Merge Selected moves them into a hidden or locked layer, when Collect in New Layer moves them into a new layer inside a hidden or locked layer, when Flatten Artwork moves them into a locked layer, and when applying a data set hides them.
Panel Options › Locate Selected Objects (beyond Illustrator, off by default): when the last selected object changes on the canvas or by a command, the panel opens the layers and groups around it and scrolls to its row, as Locate Object does, without highlighting the row; when Show Layers Only, a search or the filter leaves the object without a row, the panel opens no rows for it and scrolls as with the option off.