diff --git a/crates/ui-egui/src/panels/effect_controls/panel.rs b/crates/ui-egui/src/panels/effect_controls/panel.rs index 122a6abf7..c17038fdd 100644 --- a/crates/ui-egui/src/panels/effect_controls/panel.rs +++ b/crates/ui-egui/src/panels/effect_controls/panel.rs @@ -268,8 +268,16 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) { hresp.widget_info(|| egui::WidgetInfo::selected(egui::Role::Button, true, active, &shown)); app.auto.add(&format!("effectControls.effect.{}", g.uid), r, &g.name); if hresp.clicked() || hresp.drag_started() || hresp.secondary_clicked() { - let add = ui.input(|i| i.modifiers.shift || i.modifiers.command); - if add && hresp.clicked() { + let (shift, command) = ui.input(|i| (i.modifiers.shift, i.modifiers.command)); + if shift && hresp.clicked() { + // A range from the last effect picked, which stays the end the next range starts from. + let from = app.session.state.selected_props.iter().rev().find_map(|(l, u)| fx.iter().position(|f| *l == layer.id && f.uid == *u)); + let from = from.unwrap_or(fi); + let range = fx.iter().enumerate().filter(|(i, _)| (from.min(fi)..=from.max(fi)).contains(i)); + let mut picked: Vec<_> = range.filter(|(i, _)| *i != from).map(|(_, f)| (layer.id, f.uid)).collect(); + picked.extend(fx.get(from).map(|f| (layer.id, f.uid))); + app.session.state.selected_props = picked; + } else if command && hresp.clicked() { if selected { app.session.state.selected_props.retain(|(l, u)| !(*l == layer.id && *u == g.uid)); } else { diff --git a/crates/ui-egui/tests/it/ui_effect_controls.rs b/crates/ui-egui/tests/it/ui_effect_controls.rs index d6138ba5c..357596681 100644 --- a/crates/ui-egui/tests/it/ui_effect_controls.rs +++ b/crates/ui-egui/tests/it/ui_effect_controls.rs @@ -319,6 +319,37 @@ fn command_clicking_an_effect_twirl_closes_and_opens_its_groups() { assert!(others.iter().all(|u| closed.contains(u)), "other effects stay as they were"); } +/// Shift-clicking effect headers selects the range from the last effect picked, which stays the +/// end the next range starts from; Cmd/Ctrl adds or takes out one effect and moves that end. +#[test] +fn shift_click_on_effect_headers_selects_a_range_and_command_toggles() { + let (app, x) = app(); + let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(|_| app); + let fx: Vec = layer(&h.state().session, x.small).effects().unwrap().groups().map(|g| g.uid).collect(); + // All twirled closed, so every header is on screen. + h.state_mut().ui.fx_closed.extend(fx.iter().copied()); + h.run_steps(3); + let pick = |h: &mut Harness<'_, EffectcraftApp>, i: usize, modifiers: egui::Modifiers| { + let at = rect_of(h, &format!("effectControls.effect.{}", fx[i])).center(); + h.event(egui::Event::ModifiersChanged(modifiers)); + click(h, at); + h.event(egui::Event::ModifiersChanged(Default::default())); + h.step(); + let picked = h.state().session.state.selected_props.iter().map(|(_, u)| *u).collect::>(); + fx.iter().enumerate().filter(|(_, u)| picked.contains(u)).map(|(i, _)| i).collect::>() + }; + let (shift, command) = (egui::Modifiers::SHIFT, egui::Modifiers::COMMAND); + assert_eq!(pick(&mut h, 3, shift), [3], "with nothing picked, the range is the effect itself"); + assert_eq!(pick(&mut h, 1, Default::default()), [1]); + assert_eq!(pick(&mut h, 3, shift), [1, 2, 3], "Shift takes in the effects between"); + assert_eq!(pick(&mut h, 0, shift), [0, 1], "the next range starts from the same effect"); + assert_eq!(pick(&mut h, 4, shift), [1, 2, 3, 4]); + assert_eq!(pick(&mut h, 2, command), [1, 3, 4], "Cmd/Ctrl takes one out"); + assert_eq!(pick(&mut h, 5, command), [1, 3, 4, 5], "and adds one"); + assert_eq!(pick(&mut h, 3, shift), [3, 4, 5], "which is where the next range starts"); + assert_eq!(pick(&mut h, 0, Default::default()), [0], "a plain click picks one"); +} + fn rect_of(h: &Harness<'_, EffectcraftApp>, id: &str) -> egui::Rect { let e = h.state().auto.previous.iter().chain(h.state().auto.elements.iter()).find(|e| e.id == id).unwrap_or_else(|| panic!("no {id}")).clone(); egui::Rect::from_min_size(egui::pos2(e.rect[0], e.rect[1]), egui::vec2(e.rect[2], e.rect[3]))