mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-13 17:44:53 +00:00
Timeline: the label swatch and the Modes column act on the selection (#1849)
The label swatch's menu, the Blending Mode menu and Preserve Transparency passed the clicked layer alone, so a colour or a mode had to be set one layer at a time. They now act on the whole selection, with the Modes column edits going only to the selected layers that have video, as its cells do. The preserve-transparency checkbox also gets an automation id, like the cells around it.
This commit is contained in:
@@ -1341,6 +1341,12 @@ fn switch_targets(comp: &Comp, project: &Project, selected: &[LayerId], layer: u
|
||||
edited_layers(LayerId(layer), selected).into_iter().filter(|l| comp.layer(LayerId(*l)).is_some_and(|l| switch_applies(l, name, project))).collect()
|
||||
}
|
||||
|
||||
/// The layers a Modes column edit on `layer`'s row (its blending mode, Preserve Transparency)
|
||||
/// acts on: [`edited_layers`] that have video.
|
||||
fn mode_targets(comp: &Comp, layer: LayerId, selected: &[LayerId]) -> Vec<u64> {
|
||||
edited_layers(layer, selected).into_iter().filter(|l| comp.layer(LayerId(*l)).is_some_and(|l| l.source.is_av())).collect()
|
||||
}
|
||||
|
||||
/// A layer's switch (A/V Features and Switches columns) in `row`, with the
|
||||
/// [`switch_gesture`].
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
@@ -2077,7 +2083,7 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
widgets::open_popup(ui, pop);
|
||||
}
|
||||
if let Some(lab) = widgets::label_popup(ui, pop, lr.left_bottom(), layer.label, |l| app.session.prefs.label_name(l), &t) {
|
||||
actions.push(("edit.label".into(), json!({"layers": [layer.id.0], "label": lab.name()})));
|
||||
actions.push(("edit.label".into(), json!({"layers": edited_layers(layer.id, &selected), "label": lab.name()})));
|
||||
}
|
||||
}
|
||||
if vis.num {
|
||||
@@ -2230,11 +2236,12 @@ pub fn show(app: &mut EffectcraftApp, ui: &mut egui::Ui, rect: Rect) {
|
||||
.flat_map(|m| if m.ends_group() { vec![m.label().to_string(), "-".to_string()] } else { vec![m.label().to_string()] })
|
||||
.collect();
|
||||
if let Some(i) = widgets::popup_menu(ui, pop, mr.left_bottom(), &opts, opts.iter().position(|o| o == layer.blend_mode.label())) {
|
||||
actions.push(("layer.setBlendMode".into(), json!({"layers": [layer.id.0], "mode": opts[i]})));
|
||||
actions.push(("layer.setBlendMode".into(), json!({"layers": mode_targets(&comp, layer.id, &selected), "mode": opts[i]})));
|
||||
}
|
||||
let tr = Rect::from_center_size(pos2(cw.mode + 76.0 + 6.0, cy), vec2(14.0, 14.0));
|
||||
app.auto.add(&format!("timeline.layer.{}.preserveTransparency", layer.id.0), tr, "Preserve Transparency");
|
||||
if widgets::checkbox(ui, tr, layer.preserve_transparency, &t, egui::Id::new(("pt", layer.id.0))).clicked() {
|
||||
actions.push(("layer.setSwitch".into(), json!({"layers": [layer.id.0], "switch": "preserveTransparency"})));
|
||||
actions.push(("layer.setSwitch".into(), json!({"layers": mode_targets(&comp, layer.id, &selected), "switch": "preserveTransparency"})));
|
||||
}
|
||||
// Track matte: layer picker + kind.
|
||||
let mr = Rect::from_min_size(pos2(cw.trkmat + 2.0, cy - 9.0), vec2(104.0, 18.0));
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
//! A Timeline switch clicked on one of several selected layers applies to the whole selection
|
||||
//! (#1849), as in After Effects. The engine commands already took several layers; the row
|
||||
//! A Timeline row control clicked on one of several selected layers applies to the whole
|
||||
//! selection (#1849), as in After Effects: the A/V Features and Switches columns, the label
|
||||
//! swatch and the Modes column. The engine commands already took several layers; the row
|
||||
//! controls used to pass the clicked layer alone.
|
||||
|
||||
use effectcraft_engine::Session;
|
||||
use effectcraft_engine::color::{BlendMode, Label};
|
||||
use effectcraft_engine::project::{Layer, LayerId, Switches};
|
||||
use effectcraft_ui_egui::EffectcraftApp;
|
||||
use egui::{Event, Modifiers, Pos2, Rect, pos2, vec2};
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use serde_json::json;
|
||||
|
||||
/// Comp "Main" with solids A (top) to D (bottom); returns their ids top to bottom.
|
||||
@@ -49,6 +52,11 @@ fn click(h: &mut Harness<'_, EffectcraftApp>, id: &str) {
|
||||
drag(h, p, p, 1);
|
||||
}
|
||||
|
||||
/// A click at `p` (a menu entry, found by its label).
|
||||
fn click_at(h: &mut Harness<'_, EffectcraftApp>, p: Pos2) {
|
||||
drag(h, p, p, 1);
|
||||
}
|
||||
|
||||
fn alt_click(h: &mut Harness<'_, EffectcraftApp>, id: &str) {
|
||||
let p = rect(h, id).center();
|
||||
let modifiers = Modifiers::ALT;
|
||||
@@ -102,6 +110,10 @@ fn motion_blur(l: &Layer) -> bool {
|
||||
l.switches.motion_blur
|
||||
}
|
||||
|
||||
fn preserved(l: &Layer) -> bool {
|
||||
l.preserve_transparency
|
||||
}
|
||||
|
||||
/// #1849: a click on the Lock, 3D, Solo, Shy or Motion Blur switch of a selected layer gives the
|
||||
/// switch to every selected layer, in one undo step.
|
||||
#[test]
|
||||
@@ -177,3 +189,39 @@ fn alt_click_sets_the_selected_layers_apart() {
|
||||
alt_click(&mut h, &format!("timeline.layer.{}.video", ids[0]));
|
||||
assert_eq!(states(&h, &ids, video), [true; 4]);
|
||||
}
|
||||
|
||||
/// The label swatch menu of a selected layer sets the label of every selected layer.
|
||||
#[test]
|
||||
fn the_label_menu_sets_the_selections_label() {
|
||||
let (mut h, ids) = harness();
|
||||
select(&mut h, &[ids[0], ids[2]]);
|
||||
let green = h.state().session.prefs.label_name(Label::Green);
|
||||
click(&mut h, &format!("timeline.layer.{}.label", ids[0]));
|
||||
let entry = h.query_by_label(&green).expect("the label menu is open").rect();
|
||||
click_at(&mut h, entry.center());
|
||||
let label = |id: u64| h.state().session.active_comp().unwrap().layer(LayerId(id)).unwrap().label;
|
||||
assert_eq!([label(ids[0]), label(ids[1]), label(ids[2]), label(ids[3])], [Label::Green, Label::Red, Label::Green, Label::Red]);
|
||||
}
|
||||
|
||||
/// The Modes column: the Blending Mode menu and the Preserve Transparency checkbox of a selected
|
||||
/// layer set both selected layers, one undo step each.
|
||||
#[test]
|
||||
fn the_modes_column_sets_the_whole_selection() {
|
||||
let (mut h, ids) = harness();
|
||||
h.state_mut().ui.timeline.show_modes = true;
|
||||
h.run_steps(3);
|
||||
select(&mut h, &[ids[0], ids[2]]);
|
||||
|
||||
click(&mut h, &format!("timeline.layer.{}.mode", ids[0]));
|
||||
let entry = h.query_by_label("Multiply").expect("the blend mode menu is open").rect();
|
||||
click_at(&mut h, entry.center());
|
||||
let mode = |id: u64| h.state().session.active_comp().unwrap().layer(LayerId(id)).unwrap().blend_mode;
|
||||
assert_eq!([mode(ids[0]), mode(ids[1]), mode(ids[2]), mode(ids[3])], [BlendMode::Multiply, BlendMode::Normal, BlendMode::Multiply, BlendMode::Normal]);
|
||||
|
||||
click(&mut h, &format!("timeline.layer.{}.preserveTransparency", ids[0]));
|
||||
assert_eq!(states(&h, &ids, preserved), [true, false, true, false]);
|
||||
|
||||
h.state_mut().session.execute("edit.undo", json!({})).unwrap();
|
||||
h.run_steps(2);
|
||||
assert_eq!(states(&h, &ids, preserved), [false; 4], "one undo step put both back");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user