diff --git a/crates/ui-egui/src/panels/appearance.rs b/crates/ui-egui/src/panels/appearance.rs index 346449c9d..42137927d 100644 --- a/crates/ui-egui/src/panels/appearance.rs +++ b/crates/ui-egui/src/panels/appearance.rs @@ -1456,10 +1456,29 @@ fn drop_on_buttons(app: &mut VectorcraftApp, ui: &Ui, node: Option<&Node>, sel: && let Dragged::Effect(item, _) = what { select_row(app, ctx, item.map_or(Sel::None, Sel::Item)); + } else if removed && let Dragged::Effect(item, _) = what { + let gone: Vec = gone.iter().map(|k| *k as usize).collect(); + highlight_after_removal(app, ctx, n, sel, Rows::Effects(item), &gone); } } } +/// After the rows `gone` of kind `rows` were removed while the active row survived, the active row, +/// the highlight and its anchor move up past the removed rows below them; removed rows drop out of +/// the highlight. `n` is the object as it was before the removal. +fn highlight_after_removal(app: &mut VectorcraftApp, ctx: &egui::Context, n: &Node, sel: Sel, rows: Rows, gone: &[usize]) { + let h = highlight(ctx, n, sel); + if h.rows != rows { + return; + } + let shift = |k: usize| (!gone.contains(&k)).then(|| k - gone.iter().filter(|g| **g < k).count()); + let order = h.order.iter().filter_map(|k| shift(*k)).collect(); + let anchor = h.anchor.and_then(shift); + let active = sel.rows().filter(|(r, _)| *r == rows).and_then(|(_, a)| shift(a)); + set_active(app, ctx, active.map_or(Sel::None, |a| rows.sel(a))); + set_pstate(ctx, "ap-multi", Highlight { owner: Some(n.id), rows, order, anchor }); +} + /// The command a stack row `what` dropped on Duplicate Selected Item (`copy`) or Delete Selected /// Item runs: a fill/stroke row acts on the highlighted rows `items` when it is one of them, an /// effect row on the highlighted `effects` (list, positions) when it is one of them, else either on diff --git a/crates/ui-egui/src/panels/tests_appearance.rs b/crates/ui-egui/src/panels/tests_appearance.rs index a9415a591..4abb4be5d 100644 --- a/crates/ui-egui/src/panels/tests_appearance.rs +++ b/crates/ui-egui/src/panels/tests_appearance.rs @@ -1406,6 +1406,28 @@ fn a_drop_on_delete_that_removes_the_active_effect_leaves_no_effect_active() { assert_eq!(object_effects(&app), ["distort.twist"], "Twist was never highlighted"); } +/// Dragging an effect that isn't highlighted onto Delete removes only it: the highlighted effects +/// below it move up with their rows, so they stay highlighted and active, and Delete Selected Item +/// then removes them. +#[test] +fn a_drop_on_delete_above_the_active_effect_keeps_the_highlight_on_its_rows() { + let mut app = rect_with_three_effects(); + let ctx = drop_ctx(); + let delete = panel_button(&ctx, &mut app, "Delete Selected Item"); + let (glow, shadow, twist) = + (row_point(&ctx, &mut app, "Outer Glow", 0), row_point(&ctx, &mut app, "Drop Shadow", 0), row_point(&ctx, &mut app, "Twist", 0)); + // Drop Shadow, then Twist with Ctrl: both highlighted, Twist active. + click_mod(&ctx, &mut app, shadow, egui::Modifiers::NONE); + click_mod(&ctx, &mut app, twist, egui::Modifiers::COMMAND); + drag_in_panel(&ctx, &mut app, glow, delete); + assert_eq!(object_effects(&app), ["stylize.dropShadow", "distort.twist"]); + let lit = lit_texts(&ctx, &mut app); + assert!(["Drop Shadow", "Twist"].iter().all(|t| lit.iter().any(|l| l.contains(t))), "still highlighted: {lit:?}"); + let delete = panel_button(&ctx, &mut app, "Delete Selected Item"); + click(&ctx, &mut app, delete, appearance::show); + assert!(object_effects(&app).is_empty(), "both highlighted effects removed: {:?}", object_effects(&app)); +} + /// Whether Duplicate Selected Item and Delete Selected Item are enabled in a frame of the panel /// with input `events`. fn drop_buttons_on(ctx: &egui::Context, app: &mut VectorcraftApp, events: Vec) -> [bool; 2] {