mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-15 03:37:04 -04:00
#402 made Effect Controls' rows compact and aligned, as After Effects draws them, and also added thin separator lines between parameter rows in Effect Controls and the Timeline, with a Settings > Appearance > Show Parameter Row Separators preference. After Effects has neither, so the lines, the preference and the Tokens field go; the compact rows, the alignment and the automation ids stay. Saved settings with the old key load as before (unknown keys are ignored). Test: ui_parameter_spacing keeps the row geometry, hit target and nesting tests; the separator tests go with the feature. Co-Authored-By: Claude Opus 5.5 <[email protected]>
105 lines
6.0 KiB
Rust
105 lines
6.0 KiB
Rust
use effectcraft_engine::Session;
|
|
use effectcraft_ui_egui::{EffectcraftApp, dock::PanelKind};
|
|
use egui::{Event, Modifiers, PointerButton, pos2, vec2};
|
|
use egui_kittest::Harness;
|
|
use serde_json::json;
|
|
|
|
fn setup() -> (EffectcraftApp, u64, u64, u64) {
|
|
let mut s = Session::default();
|
|
s.execute("comp.new", json!({"width":640,"height":360,"duration":4})).unwrap();
|
|
let lid = s.execute("layer.newSolid", json!({"name":"Plate","color":"#406080"})).unwrap()["layer"].as_u64().unwrap();
|
|
let checkbox = s.execute("effect.apply", json!({"effect":"Checkbox Control"})).unwrap()["effects"][0].as_u64().unwrap();
|
|
let point = s.execute("effect.apply", json!({"effect":"Point Control"})).unwrap()["effects"][0].as_u64().unwrap();
|
|
let fx = s.active_comp().unwrap().layers[0].effects().unwrap();
|
|
let param = |uid, id| fx.groups().find(|g| g.uid == uid).unwrap().get(id).unwrap().uid;
|
|
let (checkbox, point) = (param(checkbox, "checkbox"), param(point, "point"));
|
|
let mut a = EffectcraftApp::new(s);
|
|
a.show_panel(PanelKind::EffectControls);
|
|
a.ui.timeline.search = "Checkbox".into();
|
|
(a, lid, checkbox, point)
|
|
}
|
|
|
|
fn click(h: &mut Harness<'_, EffectcraftApp>, id: &str) {
|
|
let r = h.state().auto.find(id).unwrap_or_else(|| panic!("no {id}")).rect;
|
|
let p = pos2(r[0] + r[2] / 2.0, r[1] + r[3] / 2.0);
|
|
h.event(Event::PointerMoved(p));
|
|
h.step();
|
|
for pressed in [true, false] {
|
|
h.event(Event::PointerButton { pos: p, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE });
|
|
}
|
|
h.run_steps(3);
|
|
}
|
|
|
|
#[test]
|
|
fn fixed_compact_rows_preserve_checkbox_and_point_hit_targets() {
|
|
let (a, lid, checkbox, point) = setup();
|
|
let mut h = Harness::builder().with_size(vec2(1500.0, 1000.0)).build_eframe(|_| a);
|
|
h.run_steps(4);
|
|
let fx = h.state().auto.find(&format!("effectControls.row.{checkbox}")).unwrap().rect;
|
|
let tl = h.state().auto.find(&format!("timeline.prop.{checkbox}.row")).unwrap().rect;
|
|
assert_eq!(fx[3], 20.0);
|
|
assert_eq!(tl[3], 19.0);
|
|
let value = |h: &Harness<'_, EffectcraftApp>| h.state().session.active_comp().unwrap().layers[0].props.find(checkbox).unwrap().value.clone();
|
|
let before = value(&h);
|
|
click(&mut h, &format!("effectControls.prop.{checkbox}.value"));
|
|
assert_ne!(value(&h), before);
|
|
click(&mut h, &format!("effectControls.prop.{point}.crosshair"));
|
|
assert_eq!(h.state().ui.fx_pick.as_ref().map(|p| (p.layer, p.prop)), Some((lid, point)));
|
|
click(&mut h, &format!("effectControls.prop.{point}.crosshair"));
|
|
assert!(h.state().ui.fx_pick.is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn nested_groups_keep_indentation_value_alignment_and_effect_gaps_at_fixed_compact_spacing() {
|
|
let mut s = Session::default();
|
|
s.execute("comp.new", json!({"width":64,"height":36,"duration":1})).unwrap();
|
|
s.execute("layer.newSolid", json!({"name":"Plate","color":"#406080"})).unwrap();
|
|
let fx = s.execute("effect.apply", json!({"effect":"Lumetri Color"})).unwrap()["effects"][0].as_u64().unwrap();
|
|
let next = s.execute("effect.apply", json!({"effect":"Checkbox Control"})).unwrap()["effects"][0].as_u64().unwrap();
|
|
let g = s.active_comp().unwrap().layers[0].props.find_group(fx).unwrap();
|
|
let basic = g.sub("basicCorrection").unwrap();
|
|
let white = basic.sub("whiteBalance").unwrap();
|
|
let (root, basic_id, white_id) = (g.get("highDynamicRange").unwrap().uid, basic.uid, white.uid);
|
|
let (temp, tint, sat) = (white.get("temperature").unwrap().uid, white.get("tint").unwrap().uid, basic.get("saturation").unwrap().uid);
|
|
let closed: Vec<_> = g.groups().filter(|g| g.uid != basic_id).chain(basic.groups().filter(|g| g.uid != white_id)).map(|g| g.uid).collect();
|
|
let mut a = EffectcraftApp::new(s);
|
|
a.show_panel(PanelKind::EffectControls);
|
|
a.ui.fx_closed.extend(closed);
|
|
a.ui.timeline.search = "Temperature".into();
|
|
let mut h = Harness::builder().with_size(vec2(1500.0, 1800.0)).build_eframe(|_| a);
|
|
h.run_steps(4);
|
|
let rect = |h: &Harness<'_, EffectcraftApp>, id: String| h.state().auto.find(&id).unwrap_or_else(|| panic!("no {id}")).rect;
|
|
let name_x = |kind: &str, uid| rect(&h, format!("effectControls.{kind}.{uid}.name"))[0];
|
|
assert!(name_x("effect", fx) < name_x("group", basic_id));
|
|
assert_eq!(name_x("prop", root), name_x("group", basic_id), "siblings align even when their icons differ");
|
|
assert!(name_x("group", basic_id) < name_x("group", white_id));
|
|
assert!(name_x("group", white_id) < name_x("prop", temp));
|
|
assert_eq!(name_x("prop", temp), name_x("prop", tint));
|
|
assert_eq!(rect(&h, format!("effectControls.prop.{temp}.value"))[0], rect(&h, format!("effectControls.prop.{sat}.value"))[0]);
|
|
let row = rect(&h, format!("effectControls.row.{temp}"));
|
|
assert_eq!(rect(&h, format!("effectControls.row.{tint}"))[1], row[1] + 20.0);
|
|
let top = rect(&h, format!("effectControls.effect.{next}"))[1];
|
|
let last = h
|
|
.state()
|
|
.auto
|
|
.elements
|
|
.iter()
|
|
.filter(|e| e.id.starts_with("effectControls.row.") && e.rect[1] < top)
|
|
.map(|e| e.rect[1] + e.rect[3])
|
|
.fold(0.0, f32::max);
|
|
assert_eq!(top - last, 4.0, "the gap between effects is preserved");
|
|
click(&mut h, &format!("effectControls.prop.{temp}.twirl"));
|
|
assert_eq!(rect(&h, format!("effectControls.row.{tint}"))[1], row[1] + 20.0 + 34.0);
|
|
click(&mut h, &format!("effectControls.prop.{temp}.twirl"));
|
|
click(&mut h, &format!("effectControls.group.{white_id}.twirl"));
|
|
assert!(h.state().auto.find(&format!("effectControls.row.{temp}")).is_none());
|
|
assert_eq!(rect(&h, format!("effectControls.effect.{next}"))[1], top - 2.0 * 20.0);
|
|
click(&mut h, &format!("effectControls.group.{white_id}.twirl"));
|
|
assert_eq!(rect(&h, format!("effectControls.row.{temp}")), row);
|
|
// Timeline search reveals the same nesting, with each child farther in than its parent.
|
|
let tl_x = |kind: &str, uid| rect(&h, format!("timeline.{kind}.{uid}.name"))[0];
|
|
assert!(tl_x("group", fx) < tl_x("group", basic_id));
|
|
assert!(tl_x("group", basic_id) < tl_x("group", white_id));
|
|
assert!(tl_x("group", white_id) < tl_x("prop", temp));
|
|
}
|