mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 04:57:54 -06:00
Merge pull request #2567 from infinityespi/perf/shared-layers
perf: share unchanged layers between undo states (scripts in big comps went quadratic)
This commit is contained in:
@@ -1296,7 +1296,7 @@ fn adv3d_bench_comp(p: &mut effectcraft_engine::project::Project) -> Option<Item
|
||||
set(l, "cameraOptions/highlightGain", Value::Scalar(20.0));
|
||||
set(l, "cameraOptions/highlightThreshold", Value::Scalar(200.0));
|
||||
});
|
||||
comp.layers = layers;
|
||||
comp.layers = layers.into();
|
||||
Some(p.add_item("Advanced 3D (bench)", effectcraft_engine::color::Label::None, None, ItemKind::Comp(comp.into())))
|
||||
}
|
||||
|
||||
|
||||
@@ -240,7 +240,7 @@ fn replace_with_precomp(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let ids: Vec<ItemId> = proj.items.keys().copied().filter(|c| *c != comp).collect();
|
||||
for cid in ids {
|
||||
if let Some(c) = proj.comp_mut(cid) {
|
||||
for l in c.layers.iter_mut().filter(|l| is_user(l)) {
|
||||
for l in c.layers.iter_mut_where(|l| is_user(l)) {
|
||||
l.source = LayerSource::Comp { item: comp };
|
||||
n += 1;
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ fn apply_settings(c: &mut Comp, p: &Value) {
|
||||
let dx = (c.width as f64 - ow as f64) * (a % 3) as f64 / 2.0;
|
||||
let dy = (c.height as f64 - oh as f64) * (a / 3) as f64 / 2.0;
|
||||
if dx != 0.0 || dy != 0.0 {
|
||||
for l in c.layers.iter_mut().filter(|l| l.parent.is_none()) {
|
||||
for l in c.layers.iter_mut_where(|l| l.parent.is_none()) {
|
||||
if let Some(tr) = l.transform_mut() {
|
||||
super::layer::offset_position(tr, dx, dy);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ fn crop(s: &mut Session, cid: effectcraft_project::ItemId, r: [f64; 4], label: &
|
||||
let (old_w, old_h) = (c.width, c.height);
|
||||
c.width = w;
|
||||
c.height = h;
|
||||
for l in c.layers.iter_mut().filter(|l| l.parent.is_none()) {
|
||||
for l in c.layers.iter_mut_where(|l| l.parent.is_none()) {
|
||||
if let Some(tr) = l.transform_mut() {
|
||||
super::layer::offset_position(tr, -x, -y);
|
||||
}
|
||||
|
||||
@@ -90,7 +90,7 @@ fn deselect_all(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
fn relink_copies(comp: &mut Comp, map: &[(LayerId, LayerId)]) {
|
||||
let present: Vec<LayerId> = comp.layers.iter().map(|l| l.id).collect();
|
||||
let link = |id: LayerId| map.iter().find(|(o, _)| *o == id).map(|(_, c)| *c).or(present.contains(&id).then_some(id));
|
||||
for l in comp.layers.iter_mut().filter(|l| map.iter().any(|(_, c)| *c == l.id)) {
|
||||
for l in comp.layers.iter_mut_where(|l| map.iter().any(|(_, c)| *c == l.id)) {
|
||||
l.parent = l.parent.and_then(link);
|
||||
l.track_matte = l.track_matte.and_then(|m| link(m.layer).map(|layer| TrackMatte { layer, ..m }));
|
||||
}
|
||||
@@ -685,7 +685,7 @@ fn label(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
}
|
||||
if let Some(comp) = proj.comp_mut(cid) {
|
||||
for l in comp.layers.iter_mut().filter(|l| ids.contains(&l.id)) {
|
||||
for l in comp.layers.iter_mut_where(|l| ids.contains(&l.id)) {
|
||||
l.label = lab;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -526,7 +526,7 @@ pub(super) fn set_parent(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let fixes = if compensate { parent_fixes(s, cid, &ids, parent) } else { vec![] };
|
||||
s.edit("Parent", None, |proj, _| {
|
||||
let comp = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
|
||||
for l in comp.layers.iter_mut().filter(|l| ids.contains(&l.id)) {
|
||||
for l in comp.layers.iter_mut_where(|l| ids.contains(&l.id)) {
|
||||
l.parent = parent;
|
||||
if let Some(f) = fixes.iter().find(|f| f.layer == l.id) {
|
||||
f.apply(l);
|
||||
|
||||
@@ -19,7 +19,7 @@ pub(super) fn set_switch(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let first = comp.layers.iter().find(|l| ids.contains(&l.id)).map(|l| switch_value(l, &name)).unwrap_or(Some(false));
|
||||
let Some(first) = first else { return Err(bad("layer.setSwitch", format!("unknown switch `{name}`"))) };
|
||||
let target = v.unwrap_or(!first);
|
||||
for l in comp.layers.iter_mut().filter(|l| ids.contains(&l.id)) {
|
||||
for l in comp.layers.iter_mut_where(|l| ids.contains(&l.id)) {
|
||||
set_switch_value(l, &name, target);
|
||||
last = target;
|
||||
}
|
||||
@@ -55,7 +55,7 @@ fn set_switch_others(s: &mut Session, cid: ItemId, ids: &[LayerId], name: &str,
|
||||
alone &= on == (ids.contains(&l.id) == active);
|
||||
}
|
||||
let others = value.unwrap_or(alone == active);
|
||||
for l in comp.layers.iter_mut().filter(|l| applies(l)) {
|
||||
for l in comp.layers.iter_mut_where(|l| applies(l)) {
|
||||
let v = if ids.contains(&l.id) { active } else { others };
|
||||
set_switch_value(l, name, v);
|
||||
}
|
||||
@@ -152,7 +152,7 @@ pub(super) fn set_comment(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let text = str_p(p, "comment").ok_or_else(|| bad("layer.setComment", "missing `comment`"))?.to_string();
|
||||
s.edit("Layer Comment", None, |proj, _| {
|
||||
let comp = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
|
||||
for l in comp.layers.iter_mut().filter(|l| ids.contains(&l.id)) {
|
||||
for l in comp.layers.iter_mut_where(|l| ids.contains(&l.id)) {
|
||||
l.comment = text.clone();
|
||||
}
|
||||
Ok(())
|
||||
@@ -169,7 +169,7 @@ pub(super) fn blend(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
s.edit("Blending Mode", None, |proj, _| {
|
||||
let comp = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
|
||||
for l in comp.layers.iter_mut().filter(|l| ids.contains(&l.id)) {
|
||||
for l in comp.layers.iter_mut_where(|l| ids.contains(&l.id)) {
|
||||
l.blend_mode = match mode {
|
||||
Some(m) => m,
|
||||
None if step > 0 => l.blend_mode.next(),
|
||||
|
||||
@@ -18,7 +18,7 @@ pub(super) fn timing(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let comp = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
|
||||
let fd = comp.frame_duration();
|
||||
let comp_dur = comp.duration;
|
||||
for l in comp.layers.iter_mut().filter(|l| ids.contains(&l.id)) {
|
||||
for l in comp.layers.iter_mut_where(|l| ids.contains(&l.id)) {
|
||||
let shift = |l: &mut Layer, d: Tick| {
|
||||
l.start_time += d;
|
||||
l.in_point += d;
|
||||
@@ -161,14 +161,14 @@ pub(super) fn arrange(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
for i in 1..n {
|
||||
if picked(&comp.layers[i]) && !picked(&comp.layers[i - 1]) {
|
||||
let run_end = (i..n).find(|j| !picked(&comp.layers[*j])).unwrap_or(n);
|
||||
comp.layers[i - 1..run_end].rotate_left(1);
|
||||
comp.layers.rotate_range_left(i - 1..run_end, 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i in (0..n.saturating_sub(1)).rev() {
|
||||
if picked(&comp.layers[i]) && !picked(&comp.layers[i + 1]) {
|
||||
let run_start = (0..=i).rev().find(|j| !picked(&comp.layers[*j])).map_or(0, |j| j + 1);
|
||||
comp.layers[run_start..=i + 1].rotate_right(1);
|
||||
comp.layers.rotate_range_right(run_start..=i + 1, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,7 @@ fn quality(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
q => return Err(bad("layer.quality", format!("quality must be best|draft|wireframe, not `{q}`"))),
|
||||
};
|
||||
s.edit("Layer Quality", None, |proj, _| {
|
||||
for l in proj.comp_mut(cid).ok_or(EngineError::NoComp)?.layers.iter_mut().filter(|l| ids.contains(&l.id)) {
|
||||
for l in proj.comp_mut(cid).ok_or(EngineError::NoComp)?.layers.iter_mut_where(|l| ids.contains(&l.id)) {
|
||||
l.switches.quality = q;
|
||||
}
|
||||
Ok(())
|
||||
@@ -84,7 +84,7 @@ fn sampling(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
q => q,
|
||||
};
|
||||
s.edit("Layer Sampling", None, |proj, _| {
|
||||
for l in proj.comp_mut(cid).ok_or(EngineError::NoComp)?.layers.iter_mut().filter(|l| ids.contains(&l.id)) {
|
||||
for l in proj.comp_mut(cid).ok_or(EngineError::NoComp)?.layers.iter_mut_where(|l| ids.contains(&l.id)) {
|
||||
l.switches.sampling = q;
|
||||
}
|
||||
Ok(())
|
||||
@@ -101,7 +101,7 @@ fn frame_blending(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
m => return Err(bad("layer.frameBlending", format!("mode must be off|frameMix|pixelMotion, not `{m}`"))),
|
||||
};
|
||||
s.edit("Frame Blending", None, |proj, _| {
|
||||
for l in proj.comp_mut(cid).ok_or(EngineError::NoComp)?.layers.iter_mut().filter(|l| ids.contains(&l.id)) {
|
||||
for l in proj.comp_mut(cid).ok_or(EngineError::NoComp)?.layers.iter_mut_where(|l| ids.contains(&l.id)) {
|
||||
l.switches.frame_blend = m;
|
||||
}
|
||||
Ok(())
|
||||
@@ -116,7 +116,7 @@ fn hide_other_video(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
s.edit("Hide Other Video", None, |proj, _| {
|
||||
// Every layer has a Video switch (nulls, cameras and lights included, as the Timeline's
|
||||
// eye cell shows): the selection stays shown.
|
||||
for l in proj.comp_mut(cid).ok_or(EngineError::NoComp)?.layers.iter_mut().filter(|l| !ids.contains(&l.id)) {
|
||||
for l in proj.comp_mut(cid).ok_or(EngineError::NoComp)?.layers.iter_mut_where(|l| !ids.contains(&l.id)) {
|
||||
l.switches.video = false;
|
||||
}
|
||||
Ok(())
|
||||
@@ -130,7 +130,7 @@ fn show_all_video(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let c = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
|
||||
// Track mattes stay hidden (their video switch is off by design).
|
||||
let mattes: Vec<LayerId> = c.layers.iter().filter_map(|l| l.track_matte.map(|m| m.layer)).collect();
|
||||
for l in c.layers.iter_mut().filter(|l| !mattes.contains(&l.id)) {
|
||||
for l in c.layers.iter_mut_where(|l| !mattes.contains(&l.id)) {
|
||||
l.switches.video = true;
|
||||
}
|
||||
Ok(())
|
||||
@@ -154,7 +154,7 @@ fn expressions(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let on = b_p(p, "enabled").unwrap_or(true);
|
||||
let n = s.edit(if on { "Enable Expressions" } else { "Disable Expressions" }, None, |proj, _| {
|
||||
let mut n = 0;
|
||||
for l in proj.comp_mut(cid).ok_or(EngineError::NoComp)?.layers.iter_mut().filter(|l| ids.contains(&l.id)) {
|
||||
for l in proj.comp_mut(cid).ok_or(EngineError::NoComp)?.layers.iter_mut_where(|l| ids.contains(&l.id)) {
|
||||
l.props.walk_mut(&mut |pr| {
|
||||
if let Some(e) = &mut pr.expr {
|
||||
e.enabled = on;
|
||||
@@ -586,7 +586,7 @@ fn markers_lock(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let r = s.edit("Lock Markers", None, |proj, _| {
|
||||
let c = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
|
||||
let target = v.unwrap_or_else(|| !c.layers.iter().find(|l| ids.contains(&l.id)).is_some_and(|l| l.markers_locked));
|
||||
for l in c.layers.iter_mut().filter(|l| ids.contains(&l.id)) {
|
||||
for l in c.layers.iter_mut_where(|l| ids.contains(&l.id)) {
|
||||
l.markers_locked = target;
|
||||
}
|
||||
Ok(target)
|
||||
@@ -597,7 +597,7 @@ fn markers_lock(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
fn delete_all_markers(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let (cid, ids) = layers_p(s, p)?;
|
||||
s.edit("Delete All Markers", None, |proj, _| {
|
||||
for l in proj.comp_mut(cid).ok_or(EngineError::NoComp)?.layers.iter_mut().filter(|l| ids.contains(&l.id) && !l.markers_locked) {
|
||||
for l in proj.comp_mut(cid).ok_or(EngineError::NoComp)?.layers.iter_mut_where(|l| ids.contains(&l.id) && !l.markers_locked) {
|
||||
l.markers.clear();
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -34,7 +34,7 @@ fn time_stretch(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let fd = comp_rate.frame_duration();
|
||||
s.edit(if reverse { "Time-Reverse Layer" } else { "Time Stretch" }, None, |proj, _| {
|
||||
let comp = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
|
||||
for l in comp.layers.iter_mut().filter(|l| ids.contains(&l.id)) {
|
||||
for l in comp.layers.iter_mut_where(|l| ids.contains(&l.id)) {
|
||||
let old = l.stretch;
|
||||
if reverse {
|
||||
// Keep the in/out points; play the visible span backwards.
|
||||
@@ -109,7 +109,7 @@ fn enable_time_remap(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let mut next = proj.next_id;
|
||||
let comp = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
|
||||
let mut on = false;
|
||||
for l in comp.layers.iter_mut().filter(|l| ids.contains(&l.id) && remappable(l)) {
|
||||
for l in comp.layers.iter_mut_where(|l| ids.contains(&l.id) && remappable(l)) {
|
||||
let has = l.props.get("timeRemap").is_some();
|
||||
let target = want.unwrap_or(!has);
|
||||
on |= target;
|
||||
|
||||
@@ -277,7 +277,7 @@ fn stereo_rig(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
set_expr(&mut cam, "cameraOptions/zoom", ze);
|
||||
cam.auto_orient = AutoOrient::TowardsPointOfInterest;
|
||||
let comp = proj.comp_mut(eid).ok_or(EngineError::NoComp)?;
|
||||
comp.layers = vec![cam, nest];
|
||||
comp.layers = vec![cam, nest].into();
|
||||
eye_comps.push(eid);
|
||||
}
|
||||
// The output comp: both eyes through 3D Glasses.
|
||||
@@ -300,7 +300,7 @@ fn stereo_rig(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
fxg.children.push(g.into());
|
||||
}
|
||||
let (lid, rid) = (left.id, right.id);
|
||||
proj.comp_mut(oid).ok_or(EngineError::NoComp)?.layers = vec![left, right];
|
||||
proj.comp_mut(oid).ok_or(EngineError::NoComp)?.layers = vec![left, right].into();
|
||||
st.project_selection = vec![oid];
|
||||
Ok(json!({
|
||||
"controls": ctl_id.0,
|
||||
|
||||
@@ -544,7 +544,7 @@ fn auto_orient(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
};
|
||||
s.edit("Auto-Orient", None, |proj, _| {
|
||||
let comp = proj.comp_mut(cid).ok_or(EngineError::NoComp)?;
|
||||
for l in comp.layers.iter_mut().filter(|l| ids.contains(&l.id)) {
|
||||
for l in comp.layers.iter_mut_where(|l| ids.contains(&l.id)) {
|
||||
let rig = l.is_camera() || l.is_light();
|
||||
let ok = match mode {
|
||||
AutoOrient::Off | AutoOrient::AlongPath => true,
|
||||
|
||||
@@ -121,7 +121,7 @@ fn lower_third(p: &mut Project) -> Comp {
|
||||
[440.0, 914.0],
|
||||
);
|
||||
anim(&mut title, "transform/opacity", keys(&[(0.4, Value::Scalar(0.0)), (0.9, Value::Scalar(100.0))]));
|
||||
c.layers = vec![title, bar];
|
||||
c.layers = vec![title, bar].into();
|
||||
c
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ fn showcase_3d(p: &mut Project, solids: ItemId) -> Comp {
|
||||
);
|
||||
anim(&mut caption, "transform/opacity", keys(&[(0.3, Value::Scalar(0.0)), (1.2, Value::Scalar(100.0))]));
|
||||
|
||||
c.layers = vec![caption, cam, key, fill, title, left, right, x1, x2, floor];
|
||||
c.layers = vec![caption, cam, key, fill, title, left, right, x1, x2, floor].into();
|
||||
c
|
||||
}
|
||||
|
||||
@@ -463,7 +463,7 @@ pub fn demo_project() -> Project {
|
||||
lower.out_point = t(9.5);
|
||||
|
||||
// Stack: top first.
|
||||
comp.layers = vec![lower, title, sub, line, burst, rings, null, bg];
|
||||
comp.layers = vec![lower, title, sub, line, burst, rings, null, bg].into();
|
||||
for l in &mut comp.layers {
|
||||
if l.name == "Controller" {
|
||||
l.switches.video = false;
|
||||
|
||||
@@ -686,7 +686,7 @@ impl Session {
|
||||
continue;
|
||||
}
|
||||
if let Some(c) = proj.comp_mut(cid) {
|
||||
for l in c.layers.iter_mut().filter(|l| matches!(l.source, LayerSource::Comp { item } if item == comp)) {
|
||||
for l in c.layers.iter_mut_where(|l| matches!(l.source, LayerSource::Comp { item } if item == comp)) {
|
||||
l.source = LayerSource::Footage { item: fid };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,7 +180,7 @@ pub fn large_project(spec: &LargeSpec) -> Project {
|
||||
*n += 1;
|
||||
layers.push(l);
|
||||
}
|
||||
comp.layers = layers;
|
||||
comp.layers = layers.into();
|
||||
};
|
||||
let others = spec.comps.saturating_sub(1);
|
||||
let depth = spec.nest_depth.min(others);
|
||||
@@ -215,7 +215,7 @@ pub fn large_project(spec: &LargeSpec) -> Project {
|
||||
n += 1;
|
||||
layers.push(l);
|
||||
}
|
||||
main.layers = layers;
|
||||
main.layers = layers.into();
|
||||
p.add_item("Main", crate::color::Label::None, None, ItemKind::Comp(Arc::new(main)));
|
||||
// `Main` must be the first comp `replace_project` opens: give it the smallest id.
|
||||
let main_id = p.items.values().find(|i| i.name == "Main").map(|i| i.id).expect("main");
|
||||
|
||||
@@ -41,6 +41,7 @@ pub(crate) mod roto;
|
||||
mod sequence;
|
||||
mod session;
|
||||
mod settings;
|
||||
mod shared_layers;
|
||||
mod shortcut_cmds;
|
||||
pub(crate) mod stubs;
|
||||
mod styles;
|
||||
|
||||
@@ -550,7 +550,7 @@ fn opening_an_old_project_upgrades_effect_instances() {
|
||||
effectcraft_project::ItemKind::Comp(c) => c,
|
||||
_ => panic!("not a composition"),
|
||||
});
|
||||
let l = comp.layers.iter_mut().find(|l| l.id == LayerId(a)).unwrap();
|
||||
let l = comp.layers.find_mut(|l| l.id == LayerId(a)).unwrap();
|
||||
for n in &mut l.props.sub_mut("effects").unwrap().children {
|
||||
let Node::Group(g) = n else { continue };
|
||||
if g.match_id == "ec.blur.gaussian" {
|
||||
|
||||
@@ -86,7 +86,7 @@ fn imported_files_go_into_the_comp_where_they_were_dropped() {
|
||||
s.execute("file.import", params).unwrap();
|
||||
let comp = s.active_comp().unwrap();
|
||||
assert_eq!(comp.layers.iter().map(|l| l.name.as_str()).collect::<Vec<_>>(), ["a.png", "b.png", "Bg"]);
|
||||
for l in &comp.layers[..2] {
|
||||
for l in comp.layers.iter().take(2) {
|
||||
assert_eq!(l.props.prop("transform/position").map(|p| p.value.clone()), Some(effectcraft_keyframe::Value::Vec3([40.0, 50.0, 0.0])));
|
||||
assert_eq!(l.in_point, effectcraft_time::Tick::from_seconds_f64(1.0));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
//! An edit copies only the layer it changes: the others stay shared with the undo state, so an
|
||||
//! edit's cost doesn't grow with the size of the comp (scripts setting many keys went quadratic).
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use crate::Session;
|
||||
|
||||
#[test]
|
||||
fn a_key_on_one_layer_leaves_the_other_layers_shared_with_undo() {
|
||||
let mut s = Session::default();
|
||||
s.execute("comp.new", json!({"name": "Big", "duration": 10, "frameRate": 30})).unwrap();
|
||||
let ids: Vec<u64> = (0..20).map(|i| s.execute("layer.newNull", json!({"name": format!("N{i}")})).unwrap()["layer"].as_u64().unwrap()).collect();
|
||||
let cid = s.active_comp_id().unwrap();
|
||||
let before = s.project.clone();
|
||||
s.execute("prop.addKey", json!({"layer": ids[7], "path": "transform/position", "time": 1.0, "value": [5, 5]})).unwrap();
|
||||
let (old, new) = (before.comp(cid).unwrap(), s.project.comp(cid).unwrap());
|
||||
assert_eq!(old.layers.len(), new.layers.len());
|
||||
let edited = new.layers.iter().position(|l| l.id.0 == ids[7]).unwrap();
|
||||
for i in 0..new.layers.len() {
|
||||
assert_eq!(new.layers.same_at(&old.layers, i), i != edited, "layer {i} shared: {}", new.layers.same_at(&old.layers, i));
|
||||
}
|
||||
// Undo brings back the very same state, and the edited layer is the only one that differed.
|
||||
s.execute("edit.undo", json!({})).unwrap();
|
||||
assert!(Arc::ptr_eq(&s.project, &before));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layers_save_and_load_as_a_plain_list() {
|
||||
let mut s = Session::default();
|
||||
s.execute("comp.new", json!({"name": "Round trip", "duration": 5, "frameRate": 25})).unwrap();
|
||||
for i in 0..3 {
|
||||
s.execute("layer.newNull", json!({"name": format!("N{i}")})).unwrap();
|
||||
}
|
||||
let cid = s.active_comp_id().unwrap();
|
||||
let comp = s.project.comp(cid).unwrap();
|
||||
let text = serde_json::to_value(&comp.layers).unwrap();
|
||||
assert_eq!(text.as_array().map(Vec::len), Some(3), "a JSON array of layers, as before");
|
||||
let back: effectcraft_project::Layers = serde_json::from_value(text).unwrap();
|
||||
assert_eq!(&back, &comp.layers);
|
||||
}
|
||||
@@ -61,7 +61,7 @@ fn project() -> (Project, ItemId) {
|
||||
}
|
||||
box_.blend_mode = BlendMode::Screen;
|
||||
|
||||
comp.layers = vec![box_, ring, solid];
|
||||
comp.layers = vec![box_, ring, solid].into();
|
||||
let cid = p.add_item("Comp 1", Label::Sandstone, None, ItemKind::Comp(comp.into()));
|
||||
(p, cid)
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ fn fx() -> Fx {
|
||||
|
||||
let ids = (a.id, b.id, title.id, shape.id);
|
||||
let c = p.comp_mut(cid).unwrap();
|
||||
c.layers = vec![a, b, title, shape];
|
||||
c.layers = vec![a, b, title, shape].into();
|
||||
c.markers.push(Marker { time: secs(1.5), comment: "comp marker".into(), ..Default::default() });
|
||||
|
||||
let other = Comp::new(64, 32, FrameRate::FPS_24, Tick::from_seconds_f64(3.0));
|
||||
|
||||
@@ -498,7 +498,7 @@ fn layer_styles_collapsed_precomps_and_cpu_fallbacks() {
|
||||
b.blend_mode = BlendMode::Difference;
|
||||
(a, b)
|
||||
};
|
||||
s.p.comp_mut(inner_id).unwrap().layers = vec![b, a];
|
||||
s.p.comp_mut(inner_id).unwrap().layers = vec![b, a].into();
|
||||
let bg = s.footage(97, 61);
|
||||
s.push(bg);
|
||||
let mut pre = build::layer(&mut s.p, &s.comp, "Inner", LayerSource::Comp { item: inner_id }, (60, 40), None);
|
||||
|
||||
@@ -83,7 +83,7 @@ fn track_mattes_limit_gpu_adjustments_like_the_cpu() {
|
||||
set(&mut matte, "transform/rotation", Value::Scalar(10.0));
|
||||
let mid = s.push(matte);
|
||||
let c = s.p.comp_mut(s.cid).unwrap();
|
||||
let adj = c.layers.iter_mut().find(|l| l.switches.adjustment).unwrap();
|
||||
let adj = c.layers.find_mut(|l| l.switches.adjustment).unwrap();
|
||||
adj.track_matte = Some(TrackMatte { layer: mid, kind });
|
||||
check(&format!("matted adjustment {kind:?} {depth:?}"), compare_at(&s, opts(), Tick::ZERO), 0.0);
|
||||
}
|
||||
|
||||
@@ -210,7 +210,7 @@ fn gpu_matches_cpu_wide_penumbrae() {
|
||||
set(&mut comp.layers[0], "lightOptions/shadowDiffusion", Value::Scalar(30.0));
|
||||
let s = scene(&p, cid);
|
||||
let (cpu, gt) = (adv::raster::render(&s), g.raster_3d(&s).expect("gpu raster"));
|
||||
for l in &mut p.comp_mut(cid).unwrap().layers[..2] {
|
||||
for l in p.comp_mut(cid).unwrap().layers.iter_mut().take(2) {
|
||||
set(l, "lightOptions/castsShadows", Value::Bool(false));
|
||||
}
|
||||
let unshadowed = adv::raster::render(&scene(&p, cid));
|
||||
@@ -280,7 +280,7 @@ pub(crate) fn dof_mb_project() -> (Project, ItemId) {
|
||||
c.enable_motion_blur = true;
|
||||
c.motion_blur_samples = 6;
|
||||
// The sphere moves 40 px per frame.
|
||||
let sphere = c.layers.iter_mut().find(|l| matches!(l.source, LayerSource::Primitive { kind: PrimitiveKind::Sphere })).unwrap();
|
||||
let sphere = c.layers.find_mut(|l| matches!(l.source, LayerSource::Primitive { kind: PrimitiveKind::Sphere })).unwrap();
|
||||
sphere.switches.motion_blur = true;
|
||||
sphere.props.prop_mut("transform/position").unwrap().keys = vec![
|
||||
effectcraft_keyframe::Keyframe::new(Tick::ZERO, Value::Vec3([80.0, 95.0, 0.0])),
|
||||
@@ -449,7 +449,7 @@ fn gpu_compositor_draws_advanced_3d_runs_on_the_device() {
|
||||
eprintln!("walk: {:.3} % beyond 1/255 (max {max:.4})", share * 100.0);
|
||||
assert!(share <= 0.01, "{:.3} % of pixels differ", share * 100.0);
|
||||
// A blend mode in the run takes the 2D compositing path (`SplitRun`, also on the GPU).
|
||||
p.comp_mut(cid).unwrap().layers.iter_mut().find(|l| l.switches.three_d && !l.is_light() && l.source.is_av()).unwrap().blend_mode =
|
||||
p.comp_mut(cid).unwrap().layers.find_mut(|l| l.switches.three_d && !l.is_light() && l.source.is_av()).unwrap().blend_mode =
|
||||
effectcraft_color::BlendMode::Screen;
|
||||
let (ctx, run) = run_of(&p, cid);
|
||||
assert!(Renderer::new(&p, &NoFootage, RenderOpts::default()).prepare_adv_run(&ctx, &run, (240, 160)).is_none());
|
||||
@@ -570,12 +570,12 @@ fn gpu_compositor_draws_split_advanced_3d_runs_and_skies() {
|
||||
});
|
||||
}
|
||||
let c = p.comp_mut(cid).unwrap();
|
||||
let env = c.layers.iter_mut().find(|l| l.environment).unwrap();
|
||||
let env = c.layers.find_mut(|l| l.environment).unwrap();
|
||||
env.props.sub_mut("effects").unwrap().children.push(ramp.into());
|
||||
let ids: Vec<_> = c.layers.iter().filter(|l| l.switches.three_d && !l.is_light() && !l.environment && l.source.is_av()).map(|l| l.id).collect();
|
||||
let top = c.layers[0].id;
|
||||
fn by_id(c: &mut Comp, id: effectcraft_project::LayerId) -> &mut effectcraft_project::Layer {
|
||||
c.layers.iter_mut().find(|l| l.id == id).unwrap()
|
||||
c.layers.find_mut(|l| l.id == id).unwrap()
|
||||
}
|
||||
match case {
|
||||
0 => by_id(c, ids[1]).blend_mode = effectcraft_color::BlendMode::Screen,
|
||||
@@ -593,7 +593,7 @@ fn gpu_compositor_draws_split_advanced_3d_runs_and_skies() {
|
||||
}
|
||||
_ => {
|
||||
// An environment background behind the run, and the 2D track matte.
|
||||
c.layers.iter_mut().find(|l| l.environment).unwrap().environment_background = true;
|
||||
c.layers.find_mut(|l| l.environment).unwrap().environment_background = true;
|
||||
by_id(c, ids[0]).track_matte = Some(effectcraft_project::TrackMatte { layer: top, kind: effectcraft_project::MatteKind::Alpha });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ fn echo_and_posterize_time() {
|
||||
pos.keys = vec![Keyframe::new(Tick::ZERO, crate::tests::v3(10.0, 12.0)), Keyframe::new(Tick::from_seconds_f64(1.0), crate::tests::v3(50.0, 30.0))];
|
||||
let rot = a.props.prop_mut("transform/rotation").unwrap();
|
||||
rot.keys = vec![Keyframe::new(Tick::ZERO, Value::Scalar(0.0)), Keyframe::new(Tick::from_seconds_f64(1.0), Value::Scalar(90.0))];
|
||||
s.p.comp_mut(inner_id).unwrap().layers = vec![a];
|
||||
s.p.comp_mut(inner_id).unwrap().layers = vec![a].into();
|
||||
let mut l = build::layer(&mut s.p, &s.comp, "Inner", LayerSource::Comp { item: inner_id }, (60, 40), None);
|
||||
s.effect(&mut l, id, &vals);
|
||||
set(&mut l, "transform/rotation", Value::Scalar(8.0));
|
||||
|
||||
@@ -40,7 +40,7 @@ fn moving(id: &str, vals: &[(&str, Value)], map: Option<&str>) {
|
||||
pos.keys = vec![Keyframe::new(Tick::ZERO, v3(10.0, 12.0)), Keyframe::new(Tick::from_seconds_f64(1.0), v3(50.0, 30.0))];
|
||||
let rot = a.props.prop_mut("transform/rotation").unwrap();
|
||||
rot.keys = vec![Keyframe::new(Tick::ZERO, Value::Scalar(0.0)), Keyframe::new(Tick::from_seconds_f64(1.0), Value::Scalar(90.0))];
|
||||
s.p.comp_mut(inner_id).unwrap().layers = vec![a];
|
||||
s.p.comp_mut(inner_id).unwrap().layers = vec![a].into();
|
||||
let mut l = build::layer(&mut s.p, &s.comp, "Inner", LayerSource::Comp { item: inner_id }, (60, 40), None);
|
||||
s.effect(&mut l, id, &vals);
|
||||
set(&mut l, "transform/rotation", Value::Scalar(8.0));
|
||||
|
||||
@@ -164,7 +164,7 @@ fn color_stabilizer() {
|
||||
pos.keys = vec![Keyframe::new(Tick::ZERO, crate::tests::v3(22.0, 18.0)), Keyframe::new(Tick::from_seconds_f64(1.0), crate::tests::v3(38.0, 24.0))];
|
||||
let op = a.props.prop_mut("transform/opacity").unwrap();
|
||||
op.keys = vec![Keyframe::new(Tick::ZERO, Value::Scalar(100.0)), Keyframe::new(Tick::from_seconds_f64(1.0), Value::Scalar(40.0))];
|
||||
s.p.comp_mut(inner_id).unwrap().layers = vec![a];
|
||||
s.p.comp_mut(inner_id).unwrap().layers = vec![a].into();
|
||||
let mut l = build::layer(&mut s.p, &s.comp, "Inner", LayerSource::Comp { item: inner_id }, (60, 40), None);
|
||||
let vals = [
|
||||
("stabilize", e(mode)),
|
||||
|
||||
@@ -177,7 +177,7 @@ fn mattes_preserve_transparency_precomps_and_adjustment_splits() {
|
||||
let mid = s.push(m);
|
||||
let layers = &mut s.p.comp_mut(s.cid).unwrap().layers;
|
||||
layers[1].track_matte = Some(TrackMatte { layer: mid, kind: MatteKind::Luma });
|
||||
if let Some(far) = layers.iter_mut().find(|l| l.switches.three_d && l.name == "Pattern") {
|
||||
if let Some(far) = layers.find_mut(|l| l.switches.three_d && l.name == "Pattern") {
|
||||
far.preserve_transparency = true;
|
||||
}
|
||||
// Move the matte below the bottom so the 3D run stays one run.
|
||||
@@ -203,7 +203,7 @@ fn mattes_preserve_transparency_precomps_and_adjustment_splits() {
|
||||
a.switches.three_d = true;
|
||||
set(&mut a, "transform/rotationX", Value::Scalar(40.0));
|
||||
let b = s.solid([0.2, 0.8, 0.4], 30, 20);
|
||||
s.p.comp_mut(inner_id).unwrap().layers = vec![b, a];
|
||||
s.p.comp_mut(inner_id).unwrap().layers = vec![b, a].into();
|
||||
let mut pre = build::layer(&mut s.p, &s.comp, "Inner", LayerSource::Comp { item: inner_id }, (80, 60), None);
|
||||
pre.switches.collapse = true;
|
||||
pre.switches.three_d = true;
|
||||
@@ -299,7 +299,7 @@ fn two_d_layer_with_a_three_d_track_matte() {
|
||||
let layers = &mut s.p.comp_mut(s.cid).unwrap().layers;
|
||||
let fi = layers.iter().position(|l| l.id == fid).unwrap();
|
||||
layers[fi].track_matte = Some(TrackMatte { layer: mid, kind });
|
||||
if let Some(ml) = layers.iter_mut().find(|l| l.id == mid) {
|
||||
if let Some(ml) = layers.find_mut(|l| l.id == mid) {
|
||||
ml.switches.video = false;
|
||||
}
|
||||
check(&format!("3d matte {kind:?} {depth:?}"), compare_at(&s, opts(), Tick::ZERO), 0.005);
|
||||
|
||||
@@ -509,7 +509,7 @@ fn shapes_from_vector_layer_keeps_images_as_footage_layers() {
|
||||
assert_eq!(names, ["images.pdf Outlines", "Image", "Image", "images.pdf Outlines 1", "images.pdf"]);
|
||||
assert_eq!(c.layers[0].id.0, sid);
|
||||
assert_eq!(r["layers"].as_array().unwrap().len(), 2);
|
||||
for l in &c.layers[1..3] {
|
||||
for l in c.layers.iter().skip(1).take(2) {
|
||||
assert_eq!(l.parent.map(|p| p.0), Some(sid), "{}", l.name);
|
||||
assert!(matches!(l.source, effectcraft_project::LayerSource::Footage { .. }));
|
||||
}
|
||||
|
||||
@@ -411,7 +411,7 @@ fn ae_project() -> (Project, ItemId) {
|
||||
let mut ls = effectcraft_project::build::layer(&mut p, &c, "tone", LayerSource::Footage { item: s }, (0, 0), None);
|
||||
ls.out_point = t(3.0);
|
||||
ls.props.prop_mut("audio/levels").unwrap().value = Value::Vec2([-3.0, -3.0]);
|
||||
c.layers = vec![lt, lp, la, ls];
|
||||
c.layers = vec![lt, lp, la, ls].into();
|
||||
let cid = comp_item(&mut p, "Main", c);
|
||||
(p, cid)
|
||||
}
|
||||
|
||||
@@ -87,8 +87,8 @@ pub struct Comp {
|
||||
pub display_start: Tick,
|
||||
pub background: [f32; 3],
|
||||
pub work_area: (Tick, Tick),
|
||||
/// Layer #1 first (top of the stack).
|
||||
pub layers: Vec<Layer>,
|
||||
/// Layer #1 first (top of the stack). Each layer is shared between undo states until edited.
|
||||
pub layers: super::Layers,
|
||||
#[serde(default)]
|
||||
pub markers: Vec<Marker>,
|
||||
pub shutter_angle: f64,
|
||||
@@ -188,7 +188,7 @@ impl Comp {
|
||||
display_start: Tick::ZERO,
|
||||
background: [0.0, 0.0, 0.0],
|
||||
work_area: (Tick::ZERO, duration),
|
||||
layers: vec![],
|
||||
layers: super::Layers::new(),
|
||||
markers: vec![],
|
||||
shutter_angle: 180.0,
|
||||
shutter_phase: -90.0,
|
||||
@@ -239,7 +239,7 @@ impl Comp {
|
||||
}
|
||||
|
||||
pub fn layer_mut(&mut self, id: LayerId) -> Option<&mut Layer> {
|
||||
self.layers.iter_mut().find(|l| l.id == id)
|
||||
self.layers.find_mut(|l| l.id == id)
|
||||
}
|
||||
|
||||
/// 1-based layer number (#) of a layer.
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
//! A comp's layer stack with structural sharing.
|
||||
//!
|
||||
//! Every undoable edit keeps the previous project for undo, so the comp it touches is shared and
|
||||
//! `Arc::make_mut` copies it. With the layers stored inline that copy is the whole stack, every
|
||||
//! layer and every keyframe, so an edit costs time in proportion to the comp's size and a script
|
||||
//! that sets many keys in a big comp goes quadratic. Here each layer sits behind its own `Arc`:
|
||||
//! copying the stack copies pointers, and only the layer an edit actually changes is copied.
|
||||
//!
|
||||
//! The API mirrors the `Vec<Layer>` it replaces. Serialized form is identical (a list of layers).
|
||||
|
||||
use std::ops::{Index, IndexMut};
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
use super::layer::Layer;
|
||||
|
||||
/// Layer #1 first (top of the stack).
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct Layers(Vec<Arc<Layer>>);
|
||||
|
||||
impl Layers {
|
||||
pub const fn new() -> Self {
|
||||
Self(Vec::new())
|
||||
}
|
||||
pub fn with_capacity(n: usize) -> Self {
|
||||
Self(Vec::with_capacity(n))
|
||||
}
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.0.is_empty()
|
||||
}
|
||||
pub fn iter(&self) -> impl DoubleEndedIterator<Item = &Layer> + ExactSizeIterator + Clone {
|
||||
self.0.iter().map(|l| &**l)
|
||||
}
|
||||
/// Mutable access to every layer: each one that is still shared with an undo state is copied,
|
||||
/// so prefer [`Layers::get_mut`] (or `iter` to find, then `get_mut`) when only some change.
|
||||
pub fn iter_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut Layer> + ExactSizeIterator {
|
||||
self.0.iter_mut().map(Arc::make_mut)
|
||||
}
|
||||
/// Mutable access to just the layers `keep` accepts. `keep` reads each layer without copying
|
||||
/// it; only the matching ones are copied (if shared). Use this, not `iter_mut().filter(..)`.
|
||||
pub fn iter_mut_where<'a>(&'a mut self, mut keep: impl FnMut(&Layer) -> bool + 'a) -> impl Iterator<Item = &'a mut Layer> + 'a {
|
||||
self.0.iter_mut().filter(move |l| keep(l)).map(Arc::make_mut)
|
||||
}
|
||||
/// The first layer `pred` accepts, copied only if it is that one. Use this, not `iter_mut().find(..)`.
|
||||
pub fn find_mut(&mut self, mut pred: impl FnMut(&Layer) -> bool) -> Option<&mut Layer> {
|
||||
self.0.iter_mut().find(|l| pred(l)).map(Arc::make_mut)
|
||||
}
|
||||
pub fn get(&self, i: usize) -> Option<&Layer> {
|
||||
self.0.get(i).map(|l| &**l)
|
||||
}
|
||||
pub fn get_mut(&mut self, i: usize) -> Option<&mut Layer> {
|
||||
self.0.get_mut(i).map(Arc::make_mut)
|
||||
}
|
||||
pub fn first(&self) -> Option<&Layer> {
|
||||
self.get(0)
|
||||
}
|
||||
pub fn last(&self) -> Option<&Layer> {
|
||||
self.0.last().map(|l| &**l)
|
||||
}
|
||||
pub fn first_mut(&mut self) -> Option<&mut Layer> {
|
||||
self.get_mut(0)
|
||||
}
|
||||
pub fn last_mut(&mut self) -> Option<&mut Layer> {
|
||||
self.0.last_mut().map(Arc::make_mut)
|
||||
}
|
||||
pub fn push(&mut self, l: Layer) {
|
||||
self.0.push(Arc::new(l));
|
||||
}
|
||||
pub fn insert(&mut self, i: usize, l: Layer) {
|
||||
self.0.insert(i, Arc::new(l));
|
||||
}
|
||||
pub fn remove(&mut self, i: usize) -> Layer {
|
||||
Arc::unwrap_or_clone(self.0.remove(i))
|
||||
}
|
||||
pub fn pop(&mut self) -> Option<Layer> {
|
||||
self.0.pop().map(Arc::unwrap_or_clone)
|
||||
}
|
||||
pub fn retain(&mut self, mut f: impl FnMut(&Layer) -> bool) {
|
||||
self.0.retain(|l| f(l));
|
||||
}
|
||||
pub fn swap(&mut self, a: usize, b: usize) {
|
||||
self.0.swap(a, b);
|
||||
}
|
||||
pub fn truncate(&mut self, n: usize) {
|
||||
self.0.truncate(n);
|
||||
}
|
||||
pub fn clear(&mut self) {
|
||||
self.0.clear();
|
||||
}
|
||||
pub fn reverse(&mut self) {
|
||||
self.0.reverse();
|
||||
}
|
||||
pub fn sort_by_key<K: Ord>(&mut self, mut f: impl FnMut(&Layer) -> K) {
|
||||
self.0.sort_by_key(|l| f(l));
|
||||
}
|
||||
pub fn sort_by(&mut self, mut f: impl FnMut(&Layer, &Layer) -> std::cmp::Ordering) {
|
||||
self.0.sort_by(|a, b| f(a, b));
|
||||
}
|
||||
pub fn rotate_left(&mut self, n: usize) {
|
||||
self.0.rotate_left(n);
|
||||
}
|
||||
pub fn rotate_right(&mut self, n: usize) {
|
||||
self.0.rotate_right(n);
|
||||
}
|
||||
/// `layers[range].rotate_left(n)` (a slice of `Arc`s can't be handed out as a `&mut [Layer]`).
|
||||
pub fn rotate_range_left(&mut self, r: impl std::slice::SliceIndex<[Arc<Layer>], Output = [Arc<Layer>]>, n: usize) {
|
||||
if let Some(s) = self.0.get_mut(r) {
|
||||
s.rotate_left(n);
|
||||
}
|
||||
}
|
||||
pub fn rotate_range_right(&mut self, r: impl std::slice::SliceIndex<[Arc<Layer>], Output = [Arc<Layer>]>, n: usize) {
|
||||
if let Some(s) = self.0.get_mut(r) {
|
||||
s.rotate_right(n);
|
||||
}
|
||||
}
|
||||
pub fn split_off(&mut self, at: usize) -> Layers {
|
||||
Layers(self.0.split_off(at))
|
||||
}
|
||||
pub fn append(&mut self, other: &mut Layers) {
|
||||
self.0.append(&mut other.0);
|
||||
}
|
||||
/// Remove a range of layers and return them (owned).
|
||||
pub fn drain(&mut self, r: impl std::ops::RangeBounds<usize>) -> std::vec::IntoIter<Layer> {
|
||||
self.0.drain(r).map(Arc::unwrap_or_clone).collect::<Vec<_>>().into_iter()
|
||||
}
|
||||
/// Whether `self` and `other` hold the very same (unchanged) layer at `i`: a cheap "did this
|
||||
/// layer change" test between two project states.
|
||||
pub fn same_at(&self, other: &Layers, i: usize) -> bool {
|
||||
matches!((self.0.get(i), other.0.get(i)), (Some(a), Some(b)) if Arc::ptr_eq(a, b))
|
||||
}
|
||||
/// The shared layers themselves, for parallel iteration (`rayon` over `&[Arc<Layer>]`).
|
||||
pub fn as_arcs(&self) -> &[Arc<Layer>] {
|
||||
&self.0
|
||||
}
|
||||
pub fn to_vec(&self) -> Vec<Layer> {
|
||||
self.iter().cloned().collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Layers {
|
||||
type Output = Layer;
|
||||
fn index(&self, i: usize) -> &Layer {
|
||||
&self.0[i]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<usize> for Layers {
|
||||
fn index_mut(&mut self, i: usize) -> &mut Layer {
|
||||
Arc::make_mut(&mut self.0[i])
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<Layer>> for Layers {
|
||||
fn from(v: Vec<Layer>) -> Self {
|
||||
Self(v.into_iter().map(Arc::new).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Layer> for Layers {
|
||||
fn from_iter<I: IntoIterator<Item = Layer>>(it: I) -> Self {
|
||||
Self(it.into_iter().map(Arc::new).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl Extend<Layer> for Layers {
|
||||
fn extend<I: IntoIterator<Item = Layer>>(&mut self, it: I) {
|
||||
self.0.extend(it.into_iter().map(Arc::new));
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoIterator for Layers {
|
||||
type Item = Layer;
|
||||
type IntoIter = std::iter::Map<std::vec::IntoIter<Arc<Layer>>, fn(Arc<Layer>) -> Layer>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter().map(Arc::unwrap_or_clone as fn(Arc<Layer>) -> Layer)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Layers {
|
||||
type Item = &'a Layer;
|
||||
type IntoIter = std::iter::Map<std::slice::Iter<'a, Arc<Layer>>, fn(&'a Arc<Layer>) -> &'a Layer>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter().map(|l| &**l)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a mut Layers {
|
||||
type Item = &'a mut Layer;
|
||||
type IntoIter = std::iter::Map<std::slice::IterMut<'a, Arc<Layer>>, fn(&'a mut Arc<Layer>) -> &'a mut Layer>;
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.iter_mut().map(Arc::make_mut)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for Layers {
|
||||
fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
|
||||
s.collect_seq(self.iter())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for Layers {
|
||||
fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
|
||||
Vec::<Layer>::deserialize(d).map(Layers::from)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
//! The EffectCraft document model.
|
||||
//!
|
||||
//! `Project` → items (folders, compositions, footage, solids) → `Comp` → `Layer` → property tree
|
||||
//! ([`props`]). Compositions sit behind `Arc` so undo snapshots share everything that did not
|
||||
//! change.
|
||||
//! ([`props`]). Compositions, and each layer within them ([`Layers`]), sit behind `Arc` so undo
|
||||
//! snapshots share everything that did not change.
|
||||
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
@@ -33,12 +33,14 @@ mod comp;
|
||||
mod footage;
|
||||
mod item;
|
||||
mod layer;
|
||||
mod layers;
|
||||
mod settings;
|
||||
|
||||
pub use comp::{Comp, Guide, Renderer, Resolution};
|
||||
pub use footage::{AlphaMode, CineonSettings, CineonUnits, FieldOrder, Footage, FootageKind, Proxy, Solid, SourceLayer, cineon_log_to_lin};
|
||||
pub use item::{Item, ItemId, ItemKind, LayerId};
|
||||
pub use layer::{AutoOrient, FrameBlend, Layer, LayerSource, LightKind, MatteKind, PrimitiveKind, Quality, Sampling, Switches, TrackMatte};
|
||||
pub use layers::Layers;
|
||||
pub use settings::{
|
||||
APP_VERSION, BitDepth, ColorEngine, CuePoint, HdrMode, Marker, ProjectError, ProjectSettings, SCHEMA_EFFECT_SPACE, SCHEMA_VERSION, SavedView,
|
||||
TimeDisplayStyle, is_newer_version, saved_by, saved_view,
|
||||
|
||||
@@ -470,7 +470,7 @@ mod tests {
|
||||
let mut ids = Ids(&mut p.next_id);
|
||||
add_style(&mut a, &mut ids, "dropShadow", &gl, true);
|
||||
add_style(&mut b, &mut ids, "bevelEmboss", &gl, true);
|
||||
comp.layers = vec![a, b];
|
||||
comp.layers = vec![a, b].into();
|
||||
comp.layers[1].props.sub_mut(GROUP).unwrap().sub_mut(BLENDING).unwrap().get_mut("globalLightAngle").unwrap().value = Value::Scalar(45.0);
|
||||
assert!(needs_sync(&comp));
|
||||
sync_comp(&mut comp);
|
||||
|
||||
@@ -406,7 +406,7 @@ mod tests {
|
||||
pr.value = effectcraft_project::Value::Vec2([-6.0206, -96.0]);
|
||||
}
|
||||
let mut comp = comp;
|
||||
comp.layers = vec![l1, l2];
|
||||
comp.layers = vec![l1, l2].into();
|
||||
let cid = p.add_item("C", effectcraft_color::Label::Sandstone, None, ItemKind::Comp(comp.into()));
|
||||
assert!(comp_has_audio(&p, cid));
|
||||
let rate = 1000;
|
||||
@@ -452,11 +452,11 @@ mod tests {
|
||||
let base = Comp::new(64, 64, FrameRate::new(25, 1), Tick::from_seconds_f64(2.0));
|
||||
let tone = build::layer(&mut p, &base, "tone", LayerSource::Footage { item: fid }, (0, 0), None);
|
||||
let mut inner = base.clone();
|
||||
inner.layers = vec![tone];
|
||||
inner.layers = vec![tone].into();
|
||||
let inner_id = p.add_item("Inner", effectcraft_color::Label::Sandstone, None, ItemKind::Comp(inner.into()));
|
||||
let pre = build::layer(&mut p, &base, "pre", LayerSource::Comp { item: inner_id }, (64, 64), None);
|
||||
let mut outer = base.clone();
|
||||
outer.layers = vec![pre];
|
||||
outer.layers = vec![pre].into();
|
||||
let outer_id = p.add_item("Outer", effectcraft_color::Label::Sandstone, None, ItemKind::Comp(outer.into()));
|
||||
let rate = 8000;
|
||||
let t = Tick::from_seconds_f64(0.5);
|
||||
@@ -483,7 +483,7 @@ mod tests {
|
||||
let mut tone = build::layer(&mut p, &base, "tone", LayerSource::Footage { item: fid }, (0, 0), None);
|
||||
tone.stretch = stretch;
|
||||
let mut comp = base.clone();
|
||||
comp.layers = vec![tone];
|
||||
comp.layers = vec![tone].into();
|
||||
let cid = p.add_item("C", effectcraft_color::Label::Sandstone, None, ItemKind::Comp(comp.into()));
|
||||
let n = 2000usize;
|
||||
let t = Tick::from_seconds_f64(0.5);
|
||||
|
||||
@@ -1278,7 +1278,7 @@ mod tests {
|
||||
let f = Footage { path: "/virtual.mov".into(), width: 16, height: 9, duration: Tick::from_seconds_f64(1.0), has_video: true, ..Default::default() };
|
||||
let fid = p.add_item("virtual.mov", effectcraft_color::Label::None, None, ItemKind::Footage(f));
|
||||
let mut comp = Comp::new(16, 9, FrameRate::FPS_30, Tick::from_seconds_f64(1.0));
|
||||
comp.layers = vec![build::layer(&mut p, &comp, "clip", LayerSource::Footage { item: fid }, (16, 9), None)];
|
||||
comp.layers = vec![build::layer(&mut p, &comp, "clip", LayerSource::Footage { item: fid }, (16, 9), None)].into();
|
||||
let cid = p.add_item("C", effectcraft_color::Label::None, None, ItemKind::Comp(comp.into()));
|
||||
|
||||
let id = |mark: u64| move |path: &str| (path == "/virtual.mov").then_some((8, mark));
|
||||
|
||||
+16
-16
@@ -314,7 +314,7 @@ fn particle_world_scene(pan: f64) -> (Project, ItemId) {
|
||||
let Value::Vec3(v) = pr.value else { panic!("{path}: {:?}", pr.value) };
|
||||
pr.value = Value::Vec3([v[0] + pan, v[1], v[2]]);
|
||||
}
|
||||
p.comp_mut(cid).unwrap().layers = vec![cam, l];
|
||||
p.comp_mut(cid).unwrap().layers = vec![cam, l].into();
|
||||
(p, cid)
|
||||
}
|
||||
|
||||
@@ -373,7 +373,7 @@ fn burst_scene_keys(cut: Option<f64>, start: f64, stretch: f64, hold: bool) -> (
|
||||
l.in_point = l.start_time;
|
||||
l.out_point = Tick::from_seconds_f64(4.0);
|
||||
l.stretch = stretch;
|
||||
p.comp_mut(cid).unwrap().layers = vec![l];
|
||||
p.comp_mut(cid).unwrap().layers = vec![l].into();
|
||||
(p, cid)
|
||||
}
|
||||
|
||||
@@ -507,7 +507,7 @@ fn system_scene(id: &str, rate: Option<f64>) -> (Project, ItemId) {
|
||||
let key = |t: f64, v: f64| Keyframe::new(Tick::from_seconds_f64(t), Value::Scalar(v));
|
||||
fx.unwrap().prop_mut("birthRate").unwrap().keys = vec![key(0.0, 3.0), key(0.5, 3.0), key(0.5 + 1.0 / 30.0, 0.0)];
|
||||
}
|
||||
p.comp_mut(cid).unwrap().layers = vec![l];
|
||||
p.comp_mut(cid).unwrap().layers = vec![l].into();
|
||||
(p, cid)
|
||||
}
|
||||
|
||||
@@ -614,7 +614,7 @@ fn cache_scene() -> (Project, ItemId) {
|
||||
vec![Keyframe::new(Tick::ZERO, Value::Vec3([20.0, 70.0, 0.0])), Keyframe::new(Tick::from_seconds_f64(1.0), Value::Vec3([120.0, 70.0, 0.0]))];
|
||||
add_effect_200(&mut p, &mut text, "ec.blur.gaussian", &[("blurriness", Value::Scalar(6.0))]);
|
||||
let c = p.comp_mut(cid).unwrap();
|
||||
c.layers = vec![text, shape, bg];
|
||||
c.layers = vec![text, shape, bg].into();
|
||||
(p, cid)
|
||||
}
|
||||
|
||||
@@ -854,7 +854,7 @@ fn edge_pinning_displaces_all_of_a_centred_shape_layer() {
|
||||
p.comp_mut(cid).unwrap().layers.push(l.clone());
|
||||
let plain = render_frame(&p, cid, Tick::ZERO, 1.0);
|
||||
add_effect_200(&mut p, &mut l, id, &vals);
|
||||
p.comp_mut(cid).unwrap().layers = vec![l];
|
||||
p.comp_mut(cid).unwrap().layers = vec![l].into();
|
||||
let warped = render_frame(&p, cid, Tick::ZERO, 1.0);
|
||||
// Alpha change per comp quadrant (TL, TR, BL, BR): the rectangle's edges move in each.
|
||||
let mut q = [0.0f32; 4];
|
||||
@@ -888,7 +888,7 @@ fn default_effect_points_sit_at_the_centre_of_a_shape_layer() {
|
||||
p.comp_mut(cid).unwrap().layers.push(l.clone());
|
||||
let plain = render_frame(&p, cid, Tick::ZERO, 1.0);
|
||||
add_effect_200(&mut p, &mut l, id, &vals);
|
||||
p.comp_mut(cid).unwrap().layers = vec![l];
|
||||
p.comp_mut(cid).unwrap().layers = vec![l].into();
|
||||
let warped = render_frame(&p, cid, Tick::ZERO, 1.0);
|
||||
// Alpha change per comp quadrant (TL, TR, BL, BR): the top and bottom edges move in each.
|
||||
let mut q = [0.0f32; 4];
|
||||
@@ -917,7 +917,7 @@ fn effects_on_text_reach_past_the_text_bounds() {
|
||||
let plain = render_frame(&p, cid, Tick::ZERO, 1.0);
|
||||
let painted = plain.data.iter().filter(|px| px[3] > 0.5).count();
|
||||
add_effect_200(&mut p, &mut l, "ec.distort.cctiler", &[("scale", Value::Scalar(25.0))]);
|
||||
p.comp_mut(cid).unwrap().layers = vec![l];
|
||||
p.comp_mut(cid).unwrap().layers = vec![l].into();
|
||||
let tiled = render_frame(&p, cid, Tick::ZERO, 1.0);
|
||||
let (mut lo, mut hi) = (u32::MAX, 0u32);
|
||||
for y in 0..100 {
|
||||
@@ -951,7 +951,7 @@ fn cc_smear_trails_text_past_its_bounds() {
|
||||
"ec.distort.ccsmear",
|
||||
&[("from", Value::Vec2([110.0, 45.0])), ("to", Value::Vec2([185.0, 45.0])), ("reach", Value::Scalar(100.0)), ("radius", Value::Scalar(40.0))],
|
||||
);
|
||||
p.comp_mut(cid).unwrap().layers = vec![l];
|
||||
p.comp_mut(cid).unwrap().layers = vec![l].into();
|
||||
let smeared = right(&render_frame(&p, cid, Tick::ZERO, 1.0));
|
||||
assert!(plain > 40 && smeared > plain + 25, "CC Smear trails the glyphs past the text bounds: right edge {plain} → {smeared}");
|
||||
}
|
||||
@@ -974,7 +974,7 @@ fn layer_parameters_see_a_whole_shape_layer() {
|
||||
let matte_id = shape.id;
|
||||
let mut s = solid(&mut p, &comp, [0.0, 0.0, 1.0], 200, 100);
|
||||
add_effect_200(&mut p, &mut s, "ec.channel.setmatte", &[("takeMatteFromLayer", Value::Layer(Some(matte_id.0)))]);
|
||||
p.comp_mut(cid).unwrap().layers = vec![s, shape];
|
||||
p.comp_mut(cid).unwrap().layers = vec![s, shape].into();
|
||||
let f = render_frame(&p, cid, Tick::ZERO, 1.0);
|
||||
// The matte covers x 50..150, y 25..75: kept inside (all four quadrants), gone outside.
|
||||
for (x, y) in [(60, 30), (140, 30), (60, 70), (140, 70)] {
|
||||
@@ -1064,7 +1064,7 @@ fn self_inputs_share_source_semantics_across_effects_and_times() {
|
||||
"ec.channel.blend",
|
||||
&[("blendWithLayer", Value::Layer(Some(id))), ("blendWithLayerSource", Value::Enum(0)), ("blendWithOriginal", Value::Scalar(0.0))],
|
||||
);
|
||||
p.comp_mut(cid).unwrap().layers = vec![l];
|
||||
p.comp_mut(cid).unwrap().layers = vec![l].into();
|
||||
let c = p.comp(cid).unwrap();
|
||||
let ctx = crate::eval::EvalCtx::new(&p, cid, c, Tick::ZERO);
|
||||
assert!(crate::cache::input_key(&ctx, &c.layers[0], 1.0, false, false, 1).is_some());
|
||||
@@ -1082,7 +1082,7 @@ fn timed_source_reads_preserve_other_layer_masks() {
|
||||
let mask = build::mask(&mut Ids(&mut next), "Left", ShapePath::rect([50.0, 50.0], 80.0, 80.0), MaskMode::Add, [255, 255, 0]);
|
||||
p.next_id = next;
|
||||
other.props.sub_mut("masks").unwrap().children.push(mask.into());
|
||||
p.comp_mut(cid).unwrap().layers = vec![owner.clone(), other.clone()];
|
||||
p.comp_mut(cid).unwrap().layers = vec![owner.clone(), other.clone()].into();
|
||||
let ctx = crate::EvalCtx::new(&p, cid, p.comp(cid).unwrap(), Tick::ZERO);
|
||||
let renderer = crate::Renderer::new(&p, &crate::NoFootage, crate::RenderOpts::default());
|
||||
let host = crate::FxHost { r: &renderer, ctx: &ctx, layer: &owner, origin: [0.0; 2], index: Default::default(), original: None };
|
||||
@@ -1103,7 +1103,7 @@ fn layer_dependencies_remain_cacheable_and_invalidate_only_related_edits() {
|
||||
let unrelated = solid(&mut p, &comp, [0.2, 0.3, 0.4], 200, 100);
|
||||
add_effect_200(&mut p, &mut source, "ec.channel.blend", &[("blendWithLayer", Value::Layer(Some(third.id.0))), ("blendWithOriginal", Value::Scalar(0.0))]);
|
||||
add_effect_200(&mut p, &mut owner, "ec.channel.blend", &[("blendWithLayer", Value::Layer(Some(source.id.0))), ("blendWithOriginal", Value::Scalar(0.0))]);
|
||||
p.comp_mut(cid).unwrap().layers = vec![owner, source, third, unrelated];
|
||||
p.comp_mut(cid).unwrap().layers = vec![owner, source, third, unrelated].into();
|
||||
let key = |p: &Project, time: f64, limit| {
|
||||
let comp = p.comp(cid).unwrap();
|
||||
let ctx = crate::EvalCtx::new(p, cid, comp, Tick::from_seconds_f64(time));
|
||||
@@ -1144,12 +1144,12 @@ fn precomp_dependencies_hash_nested_sources_and_evaluated_animation() {
|
||||
child.props.prop_mut("transform/opacity").unwrap().keys =
|
||||
vec![Keyframe::new(Tick::ZERO, Value::Scalar(100.0)), Keyframe::new(Tick::from_seconds_f64(1.0), Value::Scalar(50.0))];
|
||||
let mut nested = comp.clone();
|
||||
nested.layers = vec![child];
|
||||
nested.layers = vec![child].into();
|
||||
let nested_id = p.add_item("Nested", Label::Sandstone, None, ItemKind::Comp(nested.into()));
|
||||
let pre = build::layer(&mut p, &comp, "Precomp", LayerSource::Comp { item: nested_id }, (200, 100), None);
|
||||
let mut owner = solid(&mut p, &comp, [1.0, 0.0, 0.0], 200, 100);
|
||||
add_effect_200(&mut p, &mut owner, "ec.channel.blend", &[("blendWithLayer", Value::Layer(Some(pre.id.0))), ("blendWithOriginal", Value::Scalar(0.0))]);
|
||||
p.comp_mut(cid).unwrap().layers = vec![owner, pre];
|
||||
p.comp_mut(cid).unwrap().layers = vec![owner, pre].into();
|
||||
let key = |p: &Project, time| {
|
||||
let c = p.comp(cid).unwrap();
|
||||
crate::cache::layer_key(&crate::EvalCtx::new(p, cid, c, time), &c.layers[0], 1.0, false, false).unwrap()
|
||||
@@ -1224,7 +1224,7 @@ fn scene_reader_dependency(pan: f64) -> (Project, ItemId) {
|
||||
let Value::Vec3(v) = pr.value else { panic!("{path}: {:?}", pr.value) };
|
||||
pr.value = Value::Vec3([v[0] + pan, v[1], v[2]]);
|
||||
}
|
||||
p.comp_mut(cid).unwrap().layers = vec![cam, owner, source];
|
||||
p.comp_mut(cid).unwrap().layers = vec![cam, owner, source].into();
|
||||
(p, cid)
|
||||
}
|
||||
|
||||
@@ -1336,7 +1336,7 @@ fn instance_scene(pan: f64) -> (Project, ItemId) {
|
||||
}
|
||||
let mut owner = solid(&mut p, &comp, [1.0, 1.0, 1.0], 200, 100);
|
||||
add_effect_200(&mut p, &mut owner, "ec.3d.instance3d", &[("layer", Value::Layer(Some(model.id.0)))]);
|
||||
p.comp_mut(cid).unwrap().layers = vec![cam, owner, model];
|
||||
p.comp_mut(cid).unwrap().layers = vec![cam, owner, model].into();
|
||||
(p, cid)
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ fn comp_with(id: Option<&str>, vals: &[(&str, Value)], solid: bool) -> (Project,
|
||||
l.props.sub_mut("effects").unwrap().children.push(g.into());
|
||||
}
|
||||
let mut comp = comp;
|
||||
comp.layers = vec![l];
|
||||
comp.layers = vec![l].into();
|
||||
let cid = p.add_item("C", effectcraft_color::Label::Sandstone, None, ItemKind::Comp(comp.into()));
|
||||
(p, cid)
|
||||
}
|
||||
@@ -272,7 +272,7 @@ fn audio_spectrum_follows_time_through_the_layer_cache() {
|
||||
g.prop_mut("audioLayer").unwrap().value = Value::Layer(Some(a.id.0));
|
||||
s.props.sub_mut("effects").unwrap().children.push(g.into());
|
||||
let mut comp = comp;
|
||||
comp.layers = vec![s, a];
|
||||
comp.layers = vec![s, a].into();
|
||||
let cid = p.add_item("C", effectcraft_color::Label::Sandstone, None, ItemKind::Comp(comp.into()));
|
||||
|
||||
let src = Signal(late_tone);
|
||||
|
||||
@@ -53,17 +53,17 @@ fn scene(collapse: bool, flatten: bool) -> (Project, ItemId) {
|
||||
a.parent = Some(null.id);
|
||||
b.parent = Some(null.id);
|
||||
let mut c = outer.clone();
|
||||
c.layers = vec![null, b, a, bg];
|
||||
c.layers = vec![null, b, a, bg].into();
|
||||
p.add_item("Outer", Label::Sandstone, None, ItemKind::Comp(c.into()))
|
||||
} else {
|
||||
let mut inner = inner;
|
||||
inner.layers = vec![b, a];
|
||||
inner.layers = vec![b, a].into();
|
||||
let iid = p.add_item("Inner", Label::Sandstone, None, ItemKind::Comp(inner.into()));
|
||||
let mut pre = build::layer(&mut p, &outer, "Inner", LayerSource::Comp { item: iid }, (100, 100), None);
|
||||
xf(&mut pre);
|
||||
pre.switches.collapse = collapse;
|
||||
let mut c = outer.clone();
|
||||
c.layers = vec![pre, bg];
|
||||
c.layers = vec![pre, bg].into();
|
||||
p.add_item("Outer", Label::Sandstone, None, ItemKind::Comp(c.into()))
|
||||
};
|
||||
(p, cid)
|
||||
@@ -154,7 +154,7 @@ fn referenced_layer_overrides_change_the_owner_cache_key() {
|
||||
p.next_id = next;
|
||||
b.props.sub_mut("effects").unwrap().children.push(fx.into());
|
||||
let mut c = comp;
|
||||
c.layers = vec![b, a];
|
||||
c.layers = vec![b, a].into();
|
||||
let cid = p.add_item("Comp", Label::Sandstone, None, ItemKind::Comp(c.into()));
|
||||
let comp = p.comp(cid).unwrap();
|
||||
let b = comp.layer(bid).unwrap();
|
||||
@@ -182,7 +182,7 @@ fn collapsed_3d_layers_join_the_parent_scene() {
|
||||
let mut blue = solid(&mut p, &inner, [0.0, 0.0, 1.0], (60, 60), [100.0, 100.0, -200.0]);
|
||||
blue.switches.three_d = true;
|
||||
let mut inner = inner;
|
||||
inner.layers = vec![blue];
|
||||
inner.layers = vec![blue].into();
|
||||
let iid = p.add_item("Inner", Label::Sandstone, None, ItemKind::Comp(inner.into()));
|
||||
let mut pre = build::layer(&mut p, &outer, "Inner", LayerSource::Comp { item: iid }, (200, 200), None);
|
||||
pre.switches.three_d = true;
|
||||
@@ -190,7 +190,7 @@ fn collapsed_3d_layers_join_the_parent_scene() {
|
||||
let mut red = solid(&mut p, &outer, [1.0, 0.0, 0.0], (100, 100), [100.0, 100.0, 0.0]);
|
||||
red.switches.three_d = true;
|
||||
let mut c = outer;
|
||||
c.layers = vec![red, pre];
|
||||
c.layers = vec![red, pre].into();
|
||||
let cid = p.add_item("Outer", Label::Sandstone, None, ItemKind::Comp(c.into()));
|
||||
render_frame(&p, cid, Tick::ZERO, 1.0).get(100, 100)
|
||||
};
|
||||
@@ -228,11 +228,11 @@ fn scaled_square(cr: bool, via_parent: bool) -> Image {
|
||||
set(&mut null, "transform/scale", Value::Vec3([800.0, 800.0, 100.0]));
|
||||
set(&mut l, "transform/position", Value::Vec3([0.0, 0.0, 0.0]));
|
||||
l.parent = Some(null.id);
|
||||
c.layers = vec![l, null];
|
||||
c.layers = vec![l, null].into();
|
||||
} else {
|
||||
set(&mut l, "transform/position", Value::Vec3([100.3, 100.3, 0.0]));
|
||||
set(&mut l, "transform/scale", Value::Vec3([800.0, 800.0, 100.0]));
|
||||
c.layers = vec![l];
|
||||
c.layers = vec![l].into();
|
||||
}
|
||||
let cid = p.add_item("Comp", Label::Sandstone, None, ItemKind::Comp(c.into()));
|
||||
render_frame(&p, cid, Tick::ZERO, 1.0)
|
||||
@@ -261,7 +261,7 @@ fn wireframe_quality_draws_the_layer_bounds() {
|
||||
let mut l = solid(&mut p, &comp, [1.0, 0.0, 0.0], (40, 40), [50.0, 50.0, 0.0]);
|
||||
l.switches.quality = Quality::Wireframe;
|
||||
let mut c = comp;
|
||||
c.layers = vec![l];
|
||||
c.layers = vec![l].into();
|
||||
let cid = p.add_item("Comp", Label::Sandstone, None, ItemKind::Comp(c.into()));
|
||||
let img = render_frame(&p, cid, Tick::ZERO, 1.0);
|
||||
assert_eq!(img.get(50, 50)[3], 0.0, "no fill");
|
||||
@@ -272,7 +272,7 @@ fn wireframe_quality_draws_the_layer_bounds() {
|
||||
let mut l = solid(&mut p, &comp, [1.0, 1.0, 1.0], (40, 40), [50.5, 50.0, 0.0]);
|
||||
l.switches.quality = Quality::Draft;
|
||||
let mut c = comp;
|
||||
c.layers = vec![l];
|
||||
c.layers = vec![l].into();
|
||||
let cid = p.add_item("Comp", Label::Sandstone, None, ItemKind::Comp(c.into()));
|
||||
let img = render_frame(&p, cid, Tick::ZERO, 1.0);
|
||||
assert!(img.data.iter().all(|q| q[3] == 0.0 || q[3] == 1.0));
|
||||
@@ -290,13 +290,13 @@ fn collapsed_precomp_of_another_size_renders_through_the_parent_camera() {
|
||||
let mut blue = solid(&mut p, &inner, [0.0, 0.0, 1.0], (40, 40), [500.0, 300.0, 0.0]);
|
||||
blue.switches.three_d = true;
|
||||
let mut inner = inner;
|
||||
inner.layers = vec![blue];
|
||||
inner.layers = vec![blue].into();
|
||||
let iid = p.add_item("Scene", Label::Sandstone, None, ItemKind::Comp(inner.into()));
|
||||
let mut pre = build::layer(&mut p, &outer, "Scene", LayerSource::Comp { item: iid }, (1000, 600), None);
|
||||
pre.switches.three_d = three_d_precomp;
|
||||
pre.switches.collapse = true;
|
||||
let mut c = outer.clone();
|
||||
c.layers = vec![pre];
|
||||
c.layers = vec![pre].into();
|
||||
if camera {
|
||||
let cam = build::layer(&mut p, &outer, "Camera", LayerSource::Camera, (300, 200), None);
|
||||
c.layers.insert(0, cam);
|
||||
@@ -327,7 +327,7 @@ fn text_scaled_up_stays_sharp_without_the_switch() {
|
||||
set(&mut l, "transform/scale", Value::Vec3([scale, scale, 100.0]));
|
||||
assert!(!l.switches.collapse);
|
||||
let mut c = comp;
|
||||
c.layers = vec![l];
|
||||
c.layers = vec![l].into();
|
||||
let cid = p.add_item("Comp", Label::Sandstone, None, ItemKind::Comp(c.into()));
|
||||
render_frame(&p, cid, Tick::ZERO, 1.0)
|
||||
};
|
||||
@@ -351,7 +351,7 @@ fn unscaled_text_rasterises_at_the_output_scale() {
|
||||
let mut comp = Comp::new(240, 160, FrameRate::FPS_30, Tick::from_seconds_f64(2.0));
|
||||
let mut l = build::layer(&mut p, &comp, "Text", LayerSource::Text, (240, 160), None);
|
||||
l.props.prop_mut("text/sourceText").unwrap().value = Value::Text(Box::new(TextDoc { text: "HE".into(), size: 30.0, ..Default::default() }));
|
||||
comp.layers = vec![l];
|
||||
comp.layers = vec![l].into();
|
||||
let cid = p.add_item("Comp", Label::Sandstone, None, ItemKind::Comp(comp.into()));
|
||||
for i in 0..500 {
|
||||
let pos = [3.1 + i as f64 * 0.37, 7.3 + i as f64 * 0.29, 0.0];
|
||||
@@ -387,7 +387,7 @@ fn effects_on_a_scaled_shape_follow_its_scale() {
|
||||
set(&mut l, "transform/position", Value::Vec3([100.0, 100.0, 0.0]));
|
||||
set(&mut l, "transform/scale", Value::Vec3([scale, scale, 100.0]));
|
||||
let mut c = comp;
|
||||
c.layers = vec![l];
|
||||
c.layers = vec![l].into();
|
||||
let cid = p.add_item("Comp", Label::Sandstone, None, ItemKind::Comp(c.into()));
|
||||
render_frame(&p, cid, Tick::ZERO, 1.0)
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ fn scene(nest: Nest) -> (Project, ItemId, ItemId) {
|
||||
guide.switches.guide = true;
|
||||
let bg = solid(&mut p, &inner, [1.0, 0.0, 0.0], (100, 100), three_d);
|
||||
let mut inner = inner;
|
||||
inner.layers = vec![guide, bg];
|
||||
inner.layers = vec![guide, bg].into();
|
||||
let iid = p.add_item("Inner", Label::Sandstone, None, ItemKind::Comp(inner.into()));
|
||||
let mut outer = Comp::new(100, 100, FrameRate::FPS_30, Tick::from_seconds_f64(2.0));
|
||||
if matches!(nest, Nest::CollapsedAdvanced) {
|
||||
@@ -49,7 +49,7 @@ fn scene(nest: Nest) -> (Project, ItemId, ItemId) {
|
||||
let mut pre = build::layer(&mut p, &outer, "Inner", LayerSource::Comp { item: iid }, (100, 100), None);
|
||||
pre.switches.collapse = !matches!(nest, Nest::Precomp);
|
||||
pre.switches.three_d = three_d;
|
||||
outer.layers = vec![pre];
|
||||
outer.layers = vec![pre].into();
|
||||
let oid = p.add_item("Outer", Label::Sandstone, None, ItemKind::Comp(outer.into()));
|
||||
(p, iid, oid)
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ fn adjustment_scene(out: f64, scale: f64) -> (Project, ItemId) {
|
||||
tile_then_scale(&mut p, &mut adj, out, scale);
|
||||
let mut layers = vec![adj];
|
||||
layers.extend(picture(&mut p, &comp));
|
||||
p.comp_mut(cid).unwrap().layers = layers;
|
||||
p.comp_mut(cid).unwrap().layers = layers.into();
|
||||
(p, cid)
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ fn layer_scene(out: f64, scale: f64) -> (Project, ItemId) {
|
||||
let inner = Comp::new(W, H, FrameRate::FPS_30, Tick::from_seconds_f64(1.0));
|
||||
let iid = p.add_item("Picture", Label::Sandstone, None, ItemKind::Comp(inner.clone().into()));
|
||||
let layers = picture(&mut p, &inner);
|
||||
p.comp_mut(iid).unwrap().layers = layers;
|
||||
p.comp_mut(iid).unwrap().layers = layers.into();
|
||||
let mut pre = build::layer(&mut p, &comp, "Picture", LayerSource::Comp { item: iid }, (W, H), None);
|
||||
tile_then_scale(&mut p, &mut pre, out, scale);
|
||||
p.comp_mut(cid).unwrap().layers.push(pre);
|
||||
@@ -151,7 +151,7 @@ fn adjustment_motion_tile_matches_the_after_effects_frames() {
|
||||
}
|
||||
adj.props.sub_mut("effects").unwrap().children.push(g.into());
|
||||
}
|
||||
p.comp_mut(cid).unwrap().layers = vec![adj, red];
|
||||
p.comp_mut(cid).unwrap().layers = vec![adj, red].into();
|
||||
// Quarter resolution: comp pixel (x, y) is output pixel (x / 4, y / 4).
|
||||
let img = render_frame(&p, cid, Tick::ZERO, 0.25);
|
||||
let at = |x: f64, y: f64| img.get((x / 4.0) as i64, (y / 4.0) as i64);
|
||||
|
||||
@@ -29,7 +29,7 @@ fn project(three_d: bool) -> (Project, effectcraft_project::ItemId) {
|
||||
}
|
||||
layers.push(l);
|
||||
}
|
||||
p.comp_mut(cid).unwrap().layers = layers;
|
||||
p.comp_mut(cid).unwrap().layers = layers.into();
|
||||
(p, cid)
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ fn scene(color: [f32; 3], bg: Option<[f32; 3]>, f: impl FnOnce(&mut Project, &Co
|
||||
if let Some(bg) = bg {
|
||||
layers.push(solid(&mut p, &comp, bg, 200, 100));
|
||||
}
|
||||
p.comp_mut(cid).unwrap().layers = layers;
|
||||
p.comp_mut(cid).unwrap().layers = layers.into();
|
||||
(p, cid)
|
||||
}
|
||||
|
||||
|
||||
@@ -523,6 +523,7 @@ fn gather<'a>(r: &Renderer<'a>, ctx: &EvalCtx<'a>, run: &[&'a Layer], out: (u32,
|
||||
extra = ctx
|
||||
.comp
|
||||
.layers
|
||||
.as_arcs()
|
||||
.par_iter()
|
||||
.filter(|l| l.switches.three_d && l.has_video() && l.is_active_at(ctx.time) && !in_run.contains(&l.id) && !l.switches.adjustment)
|
||||
.filter(|l| Material::of(ctx, l).casts_shadows != 0)
|
||||
|
||||
@@ -532,7 +532,7 @@ mod tests {
|
||||
let item = p.add_item("long.flac", Label::SeaFoam, None, ItemKind::Footage(footage));
|
||||
let mut comp = Comp::new(64, 36, FrameRate::new(30, 1), Tick::from_seconds_f64(10.0));
|
||||
let layer = build::layer(&mut p, &comp, "long.flac", LayerSource::Footage { item }, (0, 0), None);
|
||||
comp.layers = vec![layer];
|
||||
comp.layers = vec![layer].into();
|
||||
let cid = p.add_item("Comp", Label::Sandstone, None, ItemKind::Comp(comp.into()));
|
||||
let src = RenderSource {
|
||||
project: Arc::new(p),
|
||||
|
||||
@@ -124,7 +124,7 @@ fn comp(before: &Comp, after: &Comp, now: &Comp) -> Comp {
|
||||
layers.insert(at.min(layers.len()), back);
|
||||
}
|
||||
}
|
||||
merged.layers = layers;
|
||||
merged.layers = layers.into();
|
||||
merged
|
||||
}
|
||||
|
||||
|
||||
@@ -1528,8 +1528,8 @@ fn shift_range(ctx: &egui::Context, cid: ItemId, comp: &Comp, rows: &[Row], sele
|
||||
let anchor = ctx.data(|d| d.get_temp::<LayerId>(anchor_id(cid))).or_else(|| selected.first().copied())?;
|
||||
let a = comp.layers.iter().position(|l| l.id == anchor)?;
|
||||
let b = comp.layers.iter().position(|l| l.id == layer)?;
|
||||
let span = comp.layers.get(a.min(b)..=a.max(b))?;
|
||||
Some(span.iter().filter(|l| !l.switches.locked && rows.iter().any(|r| r.layer == l.id)).map(|l| l.id.0).collect())
|
||||
let span = comp.layers.iter().skip(a.min(b)).take(a.max(b) - a.min(b) + 1);
|
||||
Some(span.filter(|l| !l.switches.locked && rows.iter().any(|r| r.layer == l.id)).map(|l| l.id.0).collect())
|
||||
}
|
||||
|
||||
/// Set on the frame the reorder drag is released.
|
||||
|
||||
@@ -602,7 +602,7 @@ pub fn popup_menu(ui: &mut Ui, id: egui::Id, pos: egui::Pos2, options: &[String]
|
||||
pub fn layer_picker(
|
||||
ui: &mut Ui,
|
||||
rect: Rect,
|
||||
layers: &[effectcraft_engine::project::Layer],
|
||||
layers: &effectcraft_engine::project::Layers,
|
||||
current: Option<u64>,
|
||||
t: &Tokens,
|
||||
id: egui::Id,
|
||||
|
||||
@@ -236,7 +236,7 @@ fn dragging_selected_project_items_adds_every_item_to_the_viewer() {
|
||||
let to = comp_to_screen(&h.ctx, [80.0, 45.0]).unwrap();
|
||||
drag(&mut h, from, to, Modifiers::NONE);
|
||||
assert_eq!(stack(&h), ["Clip", "Other", "Top", "Middle", "Bottom"]);
|
||||
for layer in &h.state().session.active_comp().unwrap().layers[..2] {
|
||||
for layer in h.state().session.active_comp().unwrap().layers.iter().take(2) {
|
||||
let Some(KV::Vec3(p)) = layer.props.prop("transform/position").map(|p| p.value.clone()) else { panic!("no position") };
|
||||
let tol = 1.0 / last_fit(&h.ctx) as f64 + 1e-6;
|
||||
assert!((p[0] - 80.0).abs() <= tol && (p[1] - 45.0).abs() <= tol, "{p:?}");
|
||||
|
||||
@@ -70,7 +70,7 @@ fn choose(h: &mut Harness<'_, EffectcraftApp>, kind: PrimitiveKind) {
|
||||
}
|
||||
|
||||
fn layers(h: &Harness<'_, EffectcraftApp>) -> Vec<Layer> {
|
||||
h.state().session.active_comp().unwrap().layers.clone()
|
||||
h.state().session.active_comp().unwrap().layers.to_vec()
|
||||
}
|
||||
|
||||
fn renderer(h: &Harness<'_, EffectcraftApp>) -> Renderer {
|
||||
|
||||
Reference in New Issue
Block a user