Layers: row keys follow the click and release after the last row (#5870)

This commit is contained in:
Francisco Herrera
2026-11-09 02:03:08 +07:00
committed by bflatastic
parent 5b98ed3b7d
commit a3ed60650e
3 changed files with 53 additions and 6 deletions
+13 -2
View File
@@ -176,6 +176,9 @@ struct Out {
column: Option<Option<ColumnDrag>>,
/// Rows to open or close, and whether to open them.
toggle: Vec<(NodeId, bool)>,
/// The highlighted rows after this frame's row click, when one happened: the click's action
/// runs after `delete_key`, so `view.rows` is still the state before it.
rows_after_click: Option<Vec<NodeId>>,
}
/// The panel state that says the Layers panel's rows have the keyboard ([`super::has_row_keys`]).
@@ -365,10 +368,13 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
// left (the last one was deleted) the panel gives the keys back, so Delete clears the selected
// art again instead of doing nothing.
let pressed_outside = ui.input(|i| i.pointer.any_pressed() && i.pointer.interact_pos().is_some_and(|p| !list_rect.contains(p)));
let focus = super::has_row_keys(ui.ctx(), ROW_KEYS) && !pressed_outside && !view.rows.is_empty();
// A click's highlight action runs after this block, so read the rows it will leave, not the
// snapshot `view.rows` took before it.
let rows = out.rows_after_click.as_deref().unwrap_or(&view.rows);
let focus = super::has_row_keys(ui.ctx(), ROW_KEYS) && !pressed_outside && !rows.is_empty();
super::set_row_keys(ui.ctx(), ROW_KEYS, focus);
if focus
&& !view.rows.is_empty()
&& !rows.is_empty()
&& ui.ctx().memory(|m| m.focused().is_none())
&& ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Delete) || i.consume_key(egui::Modifiers::NONE, egui::Key::Backspace))
{
@@ -378,6 +384,11 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
for (c, p) in out.actions {
app.run(&c, p).ok();
}
// A deletion that took the last highlighted row leaves the panel nothing to delete. Release the
// keys now, not at the next draw, so the very next frame's Delete reaches Edit › Clear.
if app.session.active().is_some_and(|st| st.highlighted_rows().is_empty()) {
super::set_row_keys(ui.ctx(), ROW_KEYS, false);
}
see_selection(app, ui, selected);
}
+3
View File
@@ -91,6 +91,9 @@ pub(super) fn resolve_click(ui: &Ui, view: &View, list: &[Row], out: &mut Out) {
if click.moves_anchor() {
ui.data_mut(|d| d.insert_temp(key("anchor"), id));
}
let mut rows = view.rows.clone();
widgets::apply_row_click(&mut rows, &click);
out.rows_after_click = Some(rows);
let ids = |rows: &[NodeId]| rows.iter().map(|r| r.0).collect::<Vec<_>>();
out.actions.push(match click {
widgets::RowClick::Only(id) => ("layer.setCurrent".into(), json!({"id": id.0})),
+37 -4
View File
@@ -1348,10 +1348,15 @@ fn app_click(ctx: &egui::Context, app: &mut VectorcraftApp, pos: egui::Pos2, f:
}
}
/// `key` pressed and released in an app-like frame of `f`, then one more frame.
fn app_key(ctx: &egui::Context, app: &mut VectorcraftApp, key: egui::Key, f: fn(&mut VectorcraftApp, &mut egui::Ui)) {
/// `key` pressed and released in one app-like frame of `f`, with no idle frame after it.
fn app_key_now(ctx: &egui::Context, app: &mut VectorcraftApp, key: egui::Key, f: fn(&mut VectorcraftApp, &mut egui::Ui)) {
let ev = |pressed| egui::Event::Key { key, physical_key: None, pressed, repeat: false, modifiers: egui::Modifiers::NONE };
app_frame(ctx, app, vec![ev(true), ev(false)], f);
}
/// `key` pressed and released in an app-like frame of `f`, then one more frame.
fn app_key(ctx: &egui::Context, app: &mut VectorcraftApp, key: egui::Key, f: fn(&mut VectorcraftApp, &mut egui::Ui)) {
app_key_now(ctx, app, key, f);
app_frame(ctx, app, vec![], f);
}
@@ -1493,13 +1498,41 @@ fn delete_after_the_last_highlighted_layer_row_is_deleted_clears_the_selection()
let texts = frame_events(&ctx, &mut app, vec![], boxed_layers);
let row = text_rect(&texts, "Layer 2").center();
app_click(&ctx, &mut app, row, boxed_layers);
app_key(&ctx, &mut app, egui::Key::Delete, boxed_layers);
app_key_now(&ctx, &mut app, egui::Key::Delete, boxed_layers);
assert_eq!(app.session.active().unwrap().doc.layers.len(), 1, "Layer 2 is deleted");
assert!(app.session.active().unwrap().selection.contains(rect), "the rectangle stays selected");
app_key(&ctx, &mut app, egui::Key::Delete, boxed_layers);
app_key_now(&ctx, &mut app, egui::Key::Delete, boxed_layers);
assert!(app.session.active().unwrap().doc.node(rect).is_none(), "Delete clears the selected object once the panel has no rows");
}
/// Clicking a row right after the last highlighted row was deleted restores the row keys: Delete
/// then deletes that row, not the selected art.
#[test]
fn clicking_a_row_after_the_last_one_was_deleted_keeps_delete_with_the_layers_panel() {
let ctx = drop_ctx();
let mut app = app_with_rect();
run(&mut app, "layer.new", json!({}));
run(&mut app, "layer.new", json!({}));
let rect = first_selected(&app).unwrap().id;
let texts = frame_events(&ctx, &mut app, vec![], boxed_layers);
let layer2 = text_rect(&texts, "Layer 2").center();
app_click(&ctx, &mut app, layer2, boxed_layers);
app_key_now(&ctx, &mut app, egui::Key::Delete, boxed_layers);
assert_eq!(app.session.active().unwrap().doc.layers.len(), 2, "Layer 2 is deleted");
// Click Layer 3 and press Delete in the very next frame: the click's highlight action runs at
// the end of its frame, so the panel must read the rows the click will leave.
let texts = frame_events(&ctx, &mut app, vec![], boxed_layers);
let layer3 = text_rect(&texts, "Layer 3").center();
let button =
|pressed| egui::Event::PointerButton { pos: layer3, button: egui::PointerButton::Primary, pressed, modifiers: egui::Modifiers::NONE };
for e in [egui::Event::PointerMoved(layer3), button(true), button(false)] {
app_frame(&ctx, &mut app, vec![e], boxed_layers);
}
app_key_now(&ctx, &mut app, egui::Key::Delete, boxed_layers);
assert_eq!(app.session.active().unwrap().doc.layers.len(), 1, "Delete removes the clicked row");
assert!(app.session.active().unwrap().doc.node(rect).is_some(), "the rectangle on Layer 1 stays");
}
/// A tool that claims Delete and Backspace (the Artboard tool's active artboard) leaves them to a
/// clicked Layers row.
#[test]