Merge pull request #763 from vangel76/script-758-callable-props
#758: layers, property groups and properties are callable
This commit is contained in:
commit
fef320e9dd
|
|
@ -83,6 +83,8 @@ pub fn run_code(s: &mut Session, code: &str, name: &str) -> Outcome {
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
#[cfg(test)]
|
||||
mod tests_callable;
|
||||
#[cfg(test)]
|
||||
mod tests_ease_presets;
|
||||
#[cfg(test)]
|
||||
mod tests_panels;
|
||||
|
|
|
|||
|
|
@ -340,6 +340,25 @@ var __attr = {
|
|||
},
|
||||
};
|
||||
|
||||
// Layers, property groups and properties are callable, like After Effects: `layer("Effects")`,
|
||||
// `fx(1)` and `effect("Slider Control")("Slider")` are `.property(...)`. A Proxy is callable
|
||||
// only around a function, so the object's state moves onto an arrow function (no `prototype`;
|
||||
// its own `name` and `length` are removed so the object's getters show) with the object's
|
||||
// prototype; `instanceof` and attribute access are unchanged.
|
||||
function __callable(o) {
|
||||
var f = () => undefined;
|
||||
delete f.name;
|
||||
delete f.length;
|
||||
Object.setPrototypeOf(f, Object.getPrototypeOf(o));
|
||||
var keys = Object.keys(o);
|
||||
for (var i = 0; i < keys.length; i++) f[keys[i]] = o[keys[i]];
|
||||
var self = new Proxy(f, {
|
||||
get: __attr.get,
|
||||
apply: function (t, thisArg, args) { return self.property(args[0]); },
|
||||
});
|
||||
return self;
|
||||
}
|
||||
|
||||
// 1-based collections with `[n]` access.
|
||||
var __coll = {
|
||||
get: function (t, k, r) {
|
||||
|
|
@ -604,7 +623,7 @@ function PropertyGroup(layer, uid) {
|
|||
this.__layer = layer;
|
||||
this.__uid = uid;
|
||||
this.__kind = "PropertyGroup";
|
||||
return new Proxy(this, __attr);
|
||||
return __callable(this);
|
||||
}
|
||||
PropertyGroup.prototype = Object.create(PropertyBase.prototype);
|
||||
PropertyGroup.prototype.constructor = PropertyGroup;
|
||||
|
|
@ -647,7 +666,7 @@ function MaskPropertyGroup(layer, uid) {
|
|||
this.__layer = layer;
|
||||
this.__uid = uid;
|
||||
this.__kind = "MaskPropertyGroup";
|
||||
return new Proxy(this, __attr);
|
||||
return __callable(this);
|
||||
}
|
||||
MaskPropertyGroup.prototype = Object.create(PropertyGroup.prototype);
|
||||
MaskPropertyGroup.prototype.constructor = MaskPropertyGroup;
|
||||
|
|
@ -679,7 +698,7 @@ function Property(layer, uid) {
|
|||
this.__layer = layer;
|
||||
this.__uid = uid;
|
||||
this.__kind = "Property";
|
||||
return new Proxy(this, __attr);
|
||||
return __callable(this);
|
||||
}
|
||||
Property.prototype = Object.create(PropertyBase.prototype);
|
||||
Property.prototype.constructor = Property;
|
||||
|
|
@ -981,7 +1000,7 @@ Layer.prototype = {
|
|||
this.__comp = comp;
|
||||
this.__id = id;
|
||||
this.__kind = kind;
|
||||
return new Proxy(this, __attr);
|
||||
return __callable(this);
|
||||
},
|
||||
__info: function () { return __get("layer", { comp: this.__comp, layer: this.__id }); },
|
||||
__p: function (o) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
//! Layers, property groups and properties are callable like After Effects (#758):
|
||||
//! `layer("ADBE Effect Parade")`, `fxGroup(1)` and `fx("Slider")` are `.property(...)`.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use effectcraft_engine::Session;
|
||||
use effectcraft_engine::keyframe::Value as KV;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{Outcome, run_code};
|
||||
|
||||
fn session() -> Session {
|
||||
let mut s = Session { expr: Some(Arc::new(effectcraft_expr::Expressions)), expr_check: Some(effectcraft_expr::check_syntax), ..Default::default() };
|
||||
crate::install(&mut s);
|
||||
s
|
||||
}
|
||||
|
||||
fn ok(s: &mut Session, code: &str) -> Outcome {
|
||||
let o = run_code(s, code, "test.jsx");
|
||||
assert!(o.error.is_none(), "script failed: {:?}\noutput: {:?}", o.error, o.output);
|
||||
o
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layers_groups_and_properties_are_callable() {
|
||||
let mut s = session();
|
||||
let o = ok(
|
||||
&mut s,
|
||||
r#"
|
||||
var c = app.project.items.addComp("M", 200, 200, 1, 2, 30);
|
||||
var l = c.layers.addSolid([1, 1, 1], "S", 200, 200, 1);
|
||||
l("ADBE Effect Parade").addProperty("ADBE Slider Control");
|
||||
var fxGroup = l("Effects");
|
||||
var fx = fxGroup(1);
|
||||
fx("Slider").setValue(42);
|
||||
l("ADBE Mask Parade").addProperty("ADBE Mask Atom");
|
||||
var pos = l("ADBE Transform Group")("ADBE Position");
|
||||
[
|
||||
l("ADBE Effect Parade").matchName, fxGroup.numProperties, fx.matchName, fx.name,
|
||||
l("Effects")("Slider Control")("Slider").value, fxGroup(1)(1).value,
|
||||
l("Masks")(1).matchName, l("Masks")(1)("ADBE Mask Shape").name,
|
||||
pos.matchName, pos.name, pos.propertyIndex, pos.parentProperty("Opacity").value,
|
||||
pos.parentProperty.parentProperty(1).matchName === l.property(1).matchName,
|
||||
l("Nope") === null, fxGroup(9) === null, pos("anything") === null,
|
||||
l instanceof AVLayer, fxGroup instanceof PropertyGroup, pos instanceof Property, l.name,
|
||||
l.transform.position.matchName, l.Effects(1).matchName
|
||||
]
|
||||
"#,
|
||||
);
|
||||
assert_eq!(
|
||||
o.result,
|
||||
json!([
|
||||
"ADBE Effect Parade",
|
||||
1,
|
||||
"ADBE Slider Control",
|
||||
"Slider Control",
|
||||
42,
|
||||
42,
|
||||
"ADBE Mask Atom",
|
||||
"Mask Path",
|
||||
"ADBE Position",
|
||||
"Position",
|
||||
2,
|
||||
100,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
"S",
|
||||
"ADBE Position",
|
||||
"ADBE Slider Control"
|
||||
])
|
||||
);
|
||||
let c = s.project.items.values().find_map(|i| i.as_comp()).expect("comp");
|
||||
let l = c.layers.first().expect("layer");
|
||||
assert_eq!(l.props.prop("effects/#1/slider").map(|p| p.value.clone()), Some(KV::Scalar(42.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn calling_without_a_name_is_a_script_error() {
|
||||
let mut s = session();
|
||||
for code in [
|
||||
"var c = app.project.items.addComp('M', 100, 100, 1, 1, 25); var l = c.layers.addNull(); l();",
|
||||
"var c = app.project.items.addComp('M', 100, 100, 1, 1, 25); var l = c.layers.addNull(); l('Transform')(null);",
|
||||
"var c = app.project.items.addComp('M', 100, 100, 1, 1, 25); var l = c.layers.addNull(); new l('Transform');",
|
||||
] {
|
||||
let o = run_code(&mut s, code, "test.jsx");
|
||||
assert!(o.error.is_some(), "expected an error for {code}");
|
||||
}
|
||||
// A called layer still reports itself as a layer, and its JSON result is unchanged.
|
||||
let o = ok(&mut s, "var c = app.project.items.addComp('N', 100, 100, 1, 1, 25); var l = c.layers.addNull(); l.name = 'Ctl'; l");
|
||||
assert_eq!(o.result, json!({ "type": "[object AVLayer]", "name": "Ctl" }));
|
||||
}
|
||||
|
|
@ -18,7 +18,7 @@ our own scripts; no real-world script library has been run against both apps.
|
|||
| Expression object model | `thisComp`, `thisLayer`, `wiggle`, `loopOut`, `valueAtTime`, `sampleImage`, `footage()`, text style API… | Same members, from the reference | good | `crates/expr/src/tests.rs`; behaviour of edge cases unmeasured (G1) |
|
||||
| Expression editing | Inline field, error bar, pick whip, Expression Language menu, external editors | Inline field that grows, error bar, pick whips (property and value), language menu | partial | External editor support (#364) |
|
||||
| Expression performance | Cached, multi-frame | Per-thread boa contexts | unknown | No benchmark |
|
||||
| Scripting object model | `app`, `Project`, items, layers, properties, keys, eases, render queue, `TextDocument`, `Shape`, markers, `File`/`Folder`, `Socket`, `app.settings` | Same classes, edits run through undoable engine commands; match names table | good | `crates/script` tests (≈ 10 test files) |
|
||||
| Scripting object model | `app`, `Project`, items, layers, properties, keys, eases, render queue, `TextDocument`, `Shape`, markers, `File`/`Folder`, `Socket`, `app.settings` | Same classes, edits run through undoable engine commands; match names table; layers, groups and properties are callable (`layer("Effects")(1)` is `.property(...)`) | good | `crates/script` tests (≈ 10 test files) |
|
||||
| ScriptUI | Dialogs, palettes, dockable panels, resource strings, `onDraw` / ScriptUIGraphics | Same, dockable panels reopen at launch | good | `ui_scriptui`, `tests_ui*` |
|
||||
| Scripts menu, ScriptUI Panels folder | yes | yes | same | [plugins.md](plugins.md) |
|
||||
| Startup / Shutdown folders | yes | not run | missing | plugins.md |
|
||||
|
|
|
|||
Loading…
Reference in New Issue