mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 13:08:07 -06:00
Timeline: Cmd/Alt-click a group twirl opens its whole tree
Cmd-click (or Alt-click) the arrow of Effects, Contents, Masks or any other group in the Timeline twirls every group under it open, or closed, with it, as the layer's own arrow already did. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
9efe75330c
commit
f96663b1f5
@@ -2177,7 +2177,8 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
if *has_children {
|
||||
let tw = Rect::from_center_size(pos2(indent, cy), vec2(13.0, 13.0));
|
||||
if widgets::twirl(ui, tw, *open, egui::Id::new(("gtw", uid)), &t).clicked() {
|
||||
ui_actions.push(UiAct::ToggleGroup(*uid));
|
||||
// Alt/Cmd-click twirls the whole tree under the group (as on a layer).
|
||||
ui_actions.push(UiAct::ToggleGroup(layer.id.0, *uid, ui.input(|i| i.modifiers.alt || i.modifiers.command)));
|
||||
}
|
||||
app.auto.add(&format!("timeline.group.{uid}.twirl"), tw, name);
|
||||
}
|
||||
@@ -2246,7 +2247,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
actions.push(("prop.select".into(), json!({"layer": layer.id.0, "prop": uid, "selectKeys": false})));
|
||||
actions.push(("window.panel".into(), json!({"panel": "EffectControls"})));
|
||||
} else {
|
||||
ui_actions.push(UiAct::ToggleGroup(*uid));
|
||||
ui_actions.push(UiAct::ToggleGroup(layer.id.0, *uid, false));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3103,9 +3104,22 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
}
|
||||
}
|
||||
}
|
||||
UiAct::ToggleGroup(uid) => {
|
||||
if !app.ui.timeline.open_groups.remove(&uid) {
|
||||
app.ui.timeline.open_groups.insert(uid);
|
||||
UiAct::ToggleGroup(layer, uid, all) => {
|
||||
let tl = &mut app.ui.timeline;
|
||||
let open = !tl.open_groups.remove(&uid);
|
||||
if open {
|
||||
tl.open_groups.insert(uid);
|
||||
}
|
||||
if all && let Some(g) = comp.layer(LayerId(layer)).and_then(|l| find_group(&l.props, uid)) {
|
||||
let mut uids = vec![];
|
||||
walk_groups(g, &mut uids);
|
||||
for u in uids {
|
||||
if open {
|
||||
tl.open_groups.insert(u);
|
||||
} else {
|
||||
tl.open_groups.remove(&u);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
UiAct::SetTime(tt) => app.session.set_time(tt),
|
||||
@@ -3229,7 +3243,8 @@ fn layer_cells(
|
||||
|
||||
enum UiAct {
|
||||
ToggleLayer(u64, bool),
|
||||
ToggleGroup(u64),
|
||||
/// Layer, group uid, and whether the whole tree under the group follows.
|
||||
ToggleGroup(u64, u64, bool),
|
||||
SetTime(Tick),
|
||||
EndMerge,
|
||||
/// Composition Settings on its 3D Renderer tab.
|
||||
@@ -3243,6 +3258,11 @@ fn walk_groups(g: &PropGroup, out: &mut Vec<u64>) {
|
||||
}
|
||||
}
|
||||
|
||||
/// The group `uid` anywhere under `g`.
|
||||
fn find_group(g: &PropGroup, uid: u64) -> Option<&PropGroup> {
|
||||
g.groups().find_map(|sg| if sg.uid == uid { Some(sg) } else { find_group(sg, uid) })
|
||||
}
|
||||
|
||||
/// A stroke's Dashes "+" / "−" buttons (add or remove a Dash/Gap pair).
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn dash_buttons(
|
||||
|
||||
@@ -1570,6 +1570,52 @@ fn timeline_twirl_arrows_open_layers_and_groups() {
|
||||
assert!(!shown(&h, prop_uid(&h, "Box", "transform/position")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_clicking_a_group_twirl_opens_its_whole_tree() {
|
||||
// Cmd-click (or Alt-click) the arrow of Effects, Contents, Masks…: every group under it
|
||||
// twirls open with it, and closed again with it.
|
||||
let mut h = harness();
|
||||
let id = layer_id(&h, "Box");
|
||||
h.state_mut().session.execute("effect.apply", json!({"effect": "Lumetri Color", "layers": [id]})).unwrap();
|
||||
fn walk(g: &effectcraft_engine::project::PropGroup, out: &mut Vec<u64>) {
|
||||
for sg in g.groups() {
|
||||
out.push(sg.uid);
|
||||
walk(sg, out);
|
||||
}
|
||||
}
|
||||
let (effects, inner, transform) = {
|
||||
let comp = h.state().session.active_comp().unwrap().clone();
|
||||
let props = &comp.layer(LayerId(id)).unwrap().props;
|
||||
let fx = props.sub("effects").unwrap();
|
||||
let mut inner = vec![];
|
||||
walk(fx, &mut inner);
|
||||
(fx.uid, inner, props.sub("transform").unwrap().uid)
|
||||
};
|
||||
assert!(inner.len() > 1, "the effect has twirl-down groups of its own");
|
||||
let at = rect(&h, &format!("timeline.layer.{id}.twirl")).center();
|
||||
click(&mut h, at);
|
||||
let at = rect(&h, &format!("timeline.group.{effects}.twirl")).center();
|
||||
let cmd_click = |h: &mut Harness<'_, EffectcraftApp>| {
|
||||
h.input_mut().events.push(Event::ModifiersChanged(egui::Modifiers::COMMAND));
|
||||
h.input_mut().events.push(Event::PointerMoved(at));
|
||||
h.step();
|
||||
for pressed in [true, false] {
|
||||
h.input_mut().events.push(Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: egui::Modifiers::COMMAND });
|
||||
h.step();
|
||||
}
|
||||
h.input_mut().events.push(Event::ModifiersChanged(Default::default()));
|
||||
h.step();
|
||||
};
|
||||
cmd_click(&mut h);
|
||||
let open = h.state().ui.timeline.open_groups.clone();
|
||||
assert!(open.contains(&effects), "Effects twirls open");
|
||||
assert!(inner.iter().all(|u| open.contains(u)), "and so does everything under it");
|
||||
assert!(!open.contains(&transform), "other groups stay as they were");
|
||||
cmd_click(&mut h);
|
||||
let open = h.state().ui.timeline.open_groups.clone();
|
||||
assert!(!open.contains(&effects) && !inner.iter().any(|u| open.contains(u)), "Cmd-click again closes the tree");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn timeline_rename_commits_on_enter_and_on_click_away() {
|
||||
// The rename field (double-click a name, or Enter on a selected layer) used to grab the
|
||||
|
||||
Reference in New Issue
Block a user