mirror of
https://github.com/storytold/effectcraft.git
synced 2026-11-01 05:58:19 -06:00
* Dev profile: line tables only for workspace crates, no debug info for dependencies * xtask ci: dev-profile gate, nextest, tests without examples, wasm --keep-going * One integration-test binary per crate * xtask ci: run effectcraft-gpu's shared-device tests in one cargo test process * xtask ci: run every stage and report all failures * xtask ci: name the stage type (clippy type_complexity) * xtask ci: run every test pass and report all failures * cargo fmt
80 lines
4.0 KiB
Rust
80 lines
4.0 KiB
Rust
//! Opening the Graph Editor frames the graphs, as in After Effects: Fit Selection to View when
|
||
//! keys are selected (a key selected in the Timeline shows its property's graph even when the
|
||
//! layer isn't selected), else Fit All Graphs to View. It used to open on an empty 0–100 range
|
||
//! or with the curves off-view.
|
||
|
||
use effectcraft_engine::Session;
|
||
use effectcraft_ui_egui::EffectcraftApp;
|
||
use egui_kittest::Harness;
|
||
use serde_json::json;
|
||
|
||
/// A 20 s comp whose Box layer has Rotation keys 300° at 8 s and 720° at 12 s; the Timeline is
|
||
/// zoomed in on 0–4 s, so neither key is in view. Returns the harness and Rotation's uid.
|
||
fn harness(select_layer: bool) -> (Harness<'static, EffectcraftApp>, u64, u64) {
|
||
let mut s = Session::default();
|
||
s.execute("comp.new", json!({"name": "Fit", "width": 320, "height": 180, "frameRate": 30, "duration": 20})).unwrap();
|
||
s.execute("layer.newSolid", json!({"name": "Box", "color": "#e04020", "width": 80, "height": 80})).unwrap();
|
||
let id = s.active_comp().unwrap().layers[0].id;
|
||
for (t, v) in [(8.0, 300.0), (12.0, 720.0)] {
|
||
s.execute("prop.addKey", json!({"layer": id.0, "path": "transform/rotation", "time": t, "value": v})).unwrap();
|
||
}
|
||
let uid = s.active_comp().unwrap().layer(id).unwrap().props.prop("transform/rotation").unwrap().uid;
|
||
s.execute("layer.select", json!({"layers": if select_layer { vec![id.0] } else { vec![] }})).unwrap();
|
||
let mut h = Harness::builder().with_size(egui::vec2(1600.0, 1000.0)).build_eframe(|_| EffectcraftApp::new(s));
|
||
h.state_mut().ui.timeline.pps = Some(300.0);
|
||
h.state_mut().ui.timeline.start = 0.0;
|
||
h.run_steps(3);
|
||
(h, id.0, uid)
|
||
}
|
||
|
||
fn open_graph(h: &mut Harness<'_, EffectcraftApp>) {
|
||
let ctx = h.ctx.clone();
|
||
effectcraft_ui_egui::menus::invoke(h.state_mut(), &ctx, "timeline.graphEditor", json!({})).unwrap();
|
||
h.run_steps(4);
|
||
assert!(h.state().ui.timeline.graph_editor);
|
||
}
|
||
|
||
/// Every key of the property sits inside the plot, and the value range spans `lo..=hi`.
|
||
fn assert_framed(h: &Harness<'_, EffectcraftApp>, uid: u64, keys: &[usize], lo: f64, hi: f64) {
|
||
let plot = h.state().auto.find("timeline.graph").expect("graph plot").rect;
|
||
for i in keys {
|
||
let k = h.state().auto.find(&format!("timeline.graph.key.{uid}.0.{i}")).unwrap_or_else(|| panic!("graph key {i} not drawn")).rect;
|
||
let (cx, cy) = (k[0] + k[2] / 2.0, k[1] + k[3] / 2.0);
|
||
assert!(cx > plot[0] && cx < plot[0] + plot[2] && cy > plot[1] && cy < plot[1] + plot[3], "key {i} at ({cx}, {cy}) outside plot {plot:?}");
|
||
}
|
||
let (a, b) = h.state().ui.timeline.graph_range.expect("graph range");
|
||
assert!(a <= lo && b >= hi, "value range {a}..{b} doesn't contain {lo}..{hi}");
|
||
}
|
||
|
||
#[test]
|
||
fn opening_the_graph_editor_fits_the_selected_key() {
|
||
let (mut h, layer, uid) = harness(false);
|
||
h.state_mut().session.execute("keys.select", json!({"keys": [{"layer": layer, "prop": uid, "time": 12.0}]})).unwrap();
|
||
h.run_steps(2);
|
||
open_graph(&mut h);
|
||
// A lone key also frames its neighbour, so its curve shows.
|
||
assert_framed(&h, uid, &[0, 1], 300.0, 720.0);
|
||
assert!(!h.state().ui.timeline.graph_auto_zoom, "Fit Selection turns auto-zoom off");
|
||
}
|
||
|
||
#[test]
|
||
fn opening_the_graph_editor_with_no_keys_selected_fits_all_graphs() {
|
||
let (mut h, _, uid) = harness(true);
|
||
open_graph(&mut h);
|
||
assert_eq!(h.state().ui.timeline.pps, None, "the whole comp in time");
|
||
assert_framed(&h, uid, &[0, 1], 300.0, 720.0);
|
||
// Zooming away and toggling the Graph Editor off and on frames it again.
|
||
h.state_mut().ui.timeline.pps = Some(300.0);
|
||
h.state_mut().ui.timeline.start = 0.0;
|
||
open_graph_toggle_twice(&mut h);
|
||
assert_framed(&h, uid, &[0, 1], 300.0, 720.0);
|
||
}
|
||
|
||
fn open_graph_toggle_twice(h: &mut Harness<'_, EffectcraftApp>) {
|
||
let ctx = h.ctx.clone();
|
||
effectcraft_ui_egui::menus::invoke(h.state_mut(), &ctx, "timeline.graphEditor", json!({})).unwrap();
|
||
h.run_steps(2);
|
||
assert!(!h.state().ui.timeline.graph_editor);
|
||
open_graph(h);
|
||
}
|