diff --git a/crates/ui-egui/src/panels/layers.rs b/crates/ui-egui/src/panels/layers.rs index 42bd09a4a..313242bb4 100644 --- a/crates/ui-egui/src/panels/layers.rs +++ b/crates/ui-egui/src/panels/layers.rs @@ -176,6 +176,9 @@ struct Out { column: Option>, /// 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>, } /// 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); } diff --git a/crates/ui-egui/src/panels/layers/rows.rs b/crates/ui-egui/src/panels/layers/rows.rs index 000a3c07e..376c021c4 100644 --- a/crates/ui-egui/src/panels/layers/rows.rs +++ b/crates/ui-egui/src/panels/layers/rows.rs @@ -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::>(); out.actions.push(match click { widgets::RowClick::Only(id) => ("layer.setCurrent".into(), json!({"id": id.0})), diff --git a/crates/ui-egui/src/panels/tests_appearance.rs b/crates/ui-egui/src/panels/tests_appearance.rs index b7ed12486..0431d6dec 100644 --- a/crates/ui-egui/src/panels/tests_appearance.rs +++ b/crates/ui-egui/src/panels/tests_appearance.rs @@ -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]