mirror of
https://github.com/storytold/effectcraft.git
synced 2026-10-30 21:16:52 -06:00
Clipboard: effect properties in twirl-down groups, numbered as After Effects does (#1908)
This commit is contained in:
@@ -181,16 +181,50 @@ fn mask(chain: &[&Node], pr: &Property) -> Option<Channel> {
|
||||
}
|
||||
|
||||
/// An effect property: `Effects`, the effect's name (not a custom one) and place on the layer,
|
||||
/// then the property's name and place, counting the effect's hidden input layer as the first.
|
||||
/// then the property's name and After Effects index. Properties in twirl-down groups are
|
||||
/// written without their group, as After Effects does.
|
||||
fn effect(chain: &[&Node], pr: &Property) -> Option<Channel> {
|
||||
let [Node::Group(effects), Node::Group(fx), _] = chain else { return None };
|
||||
let [Node::Group(effects), Node::Group(fx), ..] = chain else { return None };
|
||||
let GroupKind::Effect { effect } = &fx.kind else { return None };
|
||||
let name = effectcraft_effects::find(effect).map_or(fx.name.as_str(), |spec| spec.name);
|
||||
let (labels, values) = by_ui(pr)?;
|
||||
let header = format!("Effects\t{name} #{}\t{} #{}", place(effects, fx.uid)?, pr.name, place(fx, pr.uid)? + 1);
|
||||
let header = format!("Effects\t{name} #{}\t{} #{}", place(effects, fx.uid)?, pr.name, effect_index(fx, pr.uid)?);
|
||||
Some(Channel { header, labels, values })
|
||||
}
|
||||
|
||||
/// After Effects' index of an effect property: the effect's hidden input layer is #1, and a
|
||||
/// twirl-down group takes an index where it opens and another where it closes. The hidden
|
||||
/// source choice EffectCraft keeps beside a layer parameter has none.
|
||||
fn effect_index(fx: &PropGroup, uid: Uid) -> Option<usize> {
|
||||
fn count(g: &PropGroup, uid: Uid, n: &mut usize) -> bool {
|
||||
for c in &g.children {
|
||||
match c {
|
||||
Node::Prop(p) if is_layer_source(g, p) => {}
|
||||
Node::Prop(p) => {
|
||||
*n += 1;
|
||||
if p.uid == uid {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Node::Group(sub) => {
|
||||
*n += 1;
|
||||
if count(sub, uid, n) {
|
||||
return true;
|
||||
}
|
||||
*n += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
let mut n = 1;
|
||||
count(fx, uid, &mut n).then_some(n)
|
||||
}
|
||||
|
||||
fn is_layer_source(g: &PropGroup, p: &Property) -> bool {
|
||||
p.ui == ParamUi::Hidden && g.props().any(|l| l.ui == ParamUi::Layer && effectcraft_effects::layer_source_id(&l.match_id) == p.match_id)
|
||||
}
|
||||
|
||||
/// 1-based position of a child in its group.
|
||||
fn place(group: &PropGroup, uid: Uid) -> Option<usize> {
|
||||
group.children.iter().position(|c| c.uid() == uid).map(|i| i + 1)
|
||||
|
||||
@@ -186,7 +186,8 @@ fn copy_props(s: &mut Session, lid: LayerId, paths: &[&str]) -> String {
|
||||
|
||||
#[test]
|
||||
fn effect_properties_are_written_as_after_effects_does() {
|
||||
// The blocks After Effects 2019 wrote for the same effects and values.
|
||||
// The blocks After Effects 2019 wrote for the same effects and values (2019 has no Dropdown
|
||||
// Menu Control: that block follows its other popups, written 1-based with no unit).
|
||||
let (mut s, l) = session();
|
||||
for fx in ["Fill", "Checkbox Control", "Dropdown Menu Control", "Slider Control", "Point Control"] {
|
||||
s.execute("effect.apply", json!({"layer": l.0, "effect": fx})).unwrap();
|
||||
@@ -216,6 +217,27 @@ fn effect_properties_are_written_as_after_effects_does() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn effect_properties_have_after_effects_indices() {
|
||||
// After Effects 2019 numbered Fractal Noise's Rotation (in the Transform group) #9,
|
||||
// Complexity (after it) #17 and Evolution #25: a group takes an index where it opens and
|
||||
// closes, and its properties are written without it. Set Matte's Use For Matte is #3: the
|
||||
// layer parameter before it has no extra index for its source choice.
|
||||
let (mut s, l) = session();
|
||||
for fx in ["Fractal Noise", "Set Matte"] {
|
||||
s.execute("effect.apply", json!({"layer": l.0, "effect": fx})).unwrap();
|
||||
}
|
||||
let text = copy_props(&mut s, l, &["effects/#1/transform/rotation", "effects/#1/complexity", "effects/#1/evolution", "effects/#2/takeMatteFrom"]);
|
||||
for header in [
|
||||
"Effects\tFractal Noise #1\tRotation #9\n",
|
||||
"Effects\tFractal Noise #1\tComplexity #17\n",
|
||||
"Effects\tFractal Noise #1\tEvolution #25\n",
|
||||
"Effects\tSet Matte #2\tUse For Matte #3\n",
|
||||
] {
|
||||
assert!(text.contains(header), "{header}in\n{text}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mask_properties_are_written_as_after_effects_does() {
|
||||
// The blocks After Effects 2019 wrote for the same masks and values: masks go by place, not
|
||||
|
||||
Reference in New Issue
Block a user