Cmd/Alt-click twirls: Waveform row, Effect Controls, Project folders
Cmd-click (or Alt-click) a twirl arrow now takes the whole tree with it in three more places: Audio's Waveform row in the Timeline, an effect or effect group in Effect Controls, and a folder in the Project panel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f96663b1f5
commit
aa6c47395c
|
|
@ -646,6 +646,27 @@ impl<'a> FxScope<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
/// Twirl `g` closed (it was `open`) or open; with `all` (Alt/Cmd-click) every group under it follows.
|
||||
fn toggle_closed(app: &mut EffectcraftApp, g: &PropGroup, open: bool, all: bool) {
|
||||
fn walk(g: &PropGroup, out: &mut Vec<u64>) {
|
||||
for sg in g.groups() {
|
||||
out.push(sg.uid);
|
||||
walk(sg, out);
|
||||
}
|
||||
}
|
||||
let mut uids = vec![g.uid];
|
||||
if all {
|
||||
walk(g, &mut uids);
|
||||
}
|
||||
for u in uids {
|
||||
if open {
|
||||
app.ui.fx_closed.insert(u);
|
||||
} else {
|
||||
app.ui.fx_closed.remove(&u);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn group_rows(
|
||||
app: &mut EffectcraftApp,
|
||||
|
|
@ -705,11 +726,7 @@ fn group_rows(
|
|||
app.auto.add(&format!("effectControls.row.{}", sg.uid), r, &sg.name);
|
||||
app.auto.add(&format!("effectControls.group.{}.twirl", sg.uid), tw, &sg.name);
|
||||
if widgets::twirl(ui, tw, open, egui::Id::new(("ec-g", sg.uid)), &t).clicked() {
|
||||
if open {
|
||||
app.ui.fx_closed.insert(sg.uid);
|
||||
} else {
|
||||
app.ui.fx_closed.remove(&sg.uid);
|
||||
}
|
||||
toggle_closed(app, sg, open, ui.input(|i| i.modifiers.alt || i.modifiers.command));
|
||||
}
|
||||
let name = p.text(pos2(tw.max.x + 6.0, r.center().y), Align2::LEFT_CENTER, &sg.name, Tokens::ui(12.0), t.text);
|
||||
app.auto.add(&format!("effectControls.group.{}.name", sg.uid), name, &sg.name);
|
||||
|
|
@ -1114,11 +1131,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
|||
app.auto.add(&format!("effectControls.effect.{}.fx", g.uid), fxr, &g.name);
|
||||
let tw = Rect::from_center_size(pos2(r.min.x + 32.0, r.center().y), vec2(12.0, 12.0));
|
||||
if widgets::twirl(ui, tw, open, egui::Id::new(("ec-tw", g.uid)), &t).clicked() {
|
||||
if open {
|
||||
app.ui.fx_closed.insert(g.uid);
|
||||
} else {
|
||||
app.ui.fx_closed.remove(&g.uid);
|
||||
}
|
||||
toggle_closed(app, g, open, ui.input(|i| i.modifiers.alt || i.modifiers.command));
|
||||
}
|
||||
app.auto.add(&format!("effectControls.effect.{}.twirl", g.uid), tw, &g.name);
|
||||
let rename_id = egui::Id::new(("ec-rename", g.uid));
|
||||
|
|
|
|||
|
|
@ -579,10 +579,21 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
|||
egui::WidgetInfo::labeled(egui::WidgetType::Button, true, name)
|
||||
});
|
||||
if tresp.clicked() {
|
||||
if open {
|
||||
app.ui.project_open_folders.remove(&id.0);
|
||||
} else {
|
||||
app.ui.project_open_folders.insert(id.0);
|
||||
// Alt/Cmd-click twirls every folder inside it too.
|
||||
let mut ids = vec![*id];
|
||||
if ui.input(|i| i.modifiers.alt || i.modifiers.command) {
|
||||
let mut at = 0;
|
||||
while let Some(f) = ids.get(at).copied() {
|
||||
ids.extend(app.session.project.children(Some(f)).into_iter().filter(|c| c.is_folder()).map(|c| c.id));
|
||||
at += 1;
|
||||
}
|
||||
}
|
||||
for f in ids {
|
||||
if open {
|
||||
app.ui.project_open_folders.remove(&f.0);
|
||||
} else {
|
||||
app.ui.project_open_folders.insert(f.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
app.auto.add(&format!("project.item.{}.twirl", id.0), tw, &it.name);
|
||||
|
|
|
|||
|
|
@ -3252,6 +3252,10 @@ enum UiAct {
|
|||
}
|
||||
|
||||
fn walk_groups(g: &PropGroup, out: &mut Vec<u64>) {
|
||||
// Audio's Waveform row is not in the property tree; it twirls along.
|
||||
if g.match_id == "audio" {
|
||||
out.push(WAVE_BIT | g.uid);
|
||||
}
|
||||
for sg in g.groups() {
|
||||
out.push(sg.uid);
|
||||
walk_groups(sg, out);
|
||||
|
|
@ -3804,6 +3808,21 @@ fn value_editor(
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/// Alt/Cmd-click twirls Audio's Waveform row (not in the property tree) with the rest.
|
||||
#[test]
|
||||
fn the_waveform_row_twirls_with_its_tree() {
|
||||
let mut root = PropGroup::new(1, "", "");
|
||||
let mut audio = PropGroup::new(2, "audio", "Audio");
|
||||
audio.children.push(PropGroup::new(3, "x", "X").into());
|
||||
root.children.push(audio.into());
|
||||
let mut uids = vec![];
|
||||
walk_groups(&root, &mut uids);
|
||||
assert_eq!(uids, vec![2, WAVE_BIT | 2, 3]);
|
||||
let mut uids = vec![];
|
||||
walk_groups(find_group(&root, 2).unwrap(), &mut uids);
|
||||
assert_eq!(uids, vec![WAVE_BIT | 2, 3]);
|
||||
}
|
||||
|
||||
/// #710: with Time Display set to Frames the ruler labels are frame numbers (as under the
|
||||
/// CTI, from Start Numbering Frames At); otherwise seconds:frames.
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -220,6 +220,34 @@ fn keyer_eyedropper_picks_the_screen_from_the_effect_input() {
|
|||
assert!(img.get(160, 90)[3] < 0.01, "{:?}", img.get(160, 90));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_clicking_an_effect_twirl_closes_and_opens_its_groups() {
|
||||
let (app, x) = app();
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(|_| app);
|
||||
let fx = h.state_mut().session.execute("effect.apply", json!({"layers": [x.small], "effect": "Lumetri Color"})).unwrap()["effects"][0].as_u64().unwrap();
|
||||
// Only this effect open, so its header is on screen.
|
||||
let l = layer(&h.state().session, x.small);
|
||||
let others: Vec<u64> = l.effects().unwrap().groups().map(|g| g.uid).filter(|u| *u != fx).collect();
|
||||
h.state_mut().ui.fx_closed.extend(others.iter().copied());
|
||||
h.run_steps(3);
|
||||
let inner: Vec<u64> = l.effects().unwrap().groups().find(|g| g.uid == fx).unwrap().groups().map(|g| g.uid).collect();
|
||||
assert!(!inner.is_empty(), "the effect has twirl-down groups");
|
||||
let cmd_click = |h: &mut Harness<'_, EffectcraftApp>| {
|
||||
let at = rect_of(h, &format!("effectControls.effect.{fx}.twirl")).center();
|
||||
h.event(egui::Event::ModifiersChanged(egui::Modifiers::COMMAND));
|
||||
click(h, at);
|
||||
h.event(egui::Event::ModifiersChanged(Default::default()));
|
||||
h.step();
|
||||
};
|
||||
cmd_click(&mut h);
|
||||
let closed = h.state().ui.fx_closed.clone();
|
||||
assert!(closed.contains(&fx) && inner.iter().all(|u| closed.contains(u)), "the effect and its groups twirl closed");
|
||||
cmd_click(&mut h);
|
||||
let closed = h.state().ui.fx_closed.clone();
|
||||
assert!(!closed.contains(&fx) && !inner.iter().any(|u| closed.contains(u)), "and open again together");
|
||||
assert!(others.iter().all(|u| closed.contains(u)), "other effects stay as they were");
|
||||
}
|
||||
|
||||
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]))
|
||||
|
|
|
|||
|
|
@ -59,6 +59,31 @@ fn click(h: &mut Harness<'_, EffectcraftApp>, id: &str) {
|
|||
click_at(h, p);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_clicking_a_folder_twirl_opens_the_folders_inside_it() {
|
||||
let (mut h, folder, _, _) = harness();
|
||||
let s = &mut h.state_mut().session;
|
||||
let inner = s.execute("project.newFolder", json!({"name": "Inner"})).unwrap()["item"].as_u64().unwrap();
|
||||
let deep = s.execute("project.newFolder", json!({"name": "Deep"})).unwrap()["item"].as_u64().unwrap();
|
||||
s.execute("project.move", json!({"items": [inner], "folder": folder.0})).unwrap();
|
||||
s.execute("project.move", json!({"items": [deep], "folder": inner})).unwrap();
|
||||
s.execute("project.select", json!({"items": []})).unwrap();
|
||||
h.state_mut().ui.project_open_folders.clear();
|
||||
h.run_steps(3);
|
||||
let p = center(&h, &format!("project.item.{}.twirl", folder.0));
|
||||
let cmd_click = |h: &mut Harness<'_, EffectcraftApp>| {
|
||||
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::COMMAND));
|
||||
click_at(h, p);
|
||||
h.input_mut().events.push(Event::ModifiersChanged(Modifiers::NONE));
|
||||
h.step();
|
||||
};
|
||||
cmd_click(&mut h);
|
||||
let open = h.state().ui.project_open_folders.clone();
|
||||
assert!([folder.0, inner, deep].iter().all(|f| open.contains(f)), "the folder and every folder inside it open");
|
||||
cmd_click(&mut h);
|
||||
assert!(h.state().ui.project_open_folders.is_empty(), "and close again together");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn folder_twirl_opens_and_closes_the_folder_on_one_click() {
|
||||
let (mut h, folder, comp, _) = harness();
|
||||
|
|
|
|||
Loading…
Reference in New Issue