effectcraft/crates/engine/src/menus.rs

947 lines
38 KiB
Rust

//! The menu bar, laid out like After Effects' (same menus, order, submenus, separators and
//! labels). Every entry runs a registered command (`commands::find`), optionally with bound
//! parameters (e.g. `layer.setBlendMode {"mode":"Multiply"}`). Frontends render this tree; agents
//! read it through `ui.menu.list` / the command registry.
//!
//! The tree is written in a small indented text format (two spaces per level):
//!
//! ```text
//! Layer top-level menu
//! Blending Mode submenu (has deeper lines below it)
//! Multiply | layer.setBlendMode {"mode":"Multiply"}
//! Next Blending Mode | layer.setBlendMode {"step":1} | Shift+=
//! --- separator
//! @effects the effect categories (Effect menu)
//! [mac] Quit EffectCraft | app.quit only on macOS ([!mac] = everywhere else)
//! ```
//!
//! An entry's shortcut is the command's default shortcut unless the line gives one (needed for
//! entries with bound parameters). Adobe-service entries (Team Projects, Libraries, Bridge, Media
//! Encoder, Essential Graphics, Behance, Creative Cloud…) are intentionally absent.
use std::sync::OnceLock;
use serde::Serialize;
use serde_json::Value;
use crate::Session;
use crate::commands::{self, CommandSpec};
/// One node of the menu tree.
#[derive(Clone, Debug, Serialize)]
#[serde(tag = "type", rename_all = "camelCase")]
pub enum MenuNode {
Item(MenuEntry),
Separator,
Submenu { label: String, children: Vec<MenuNode> },
}
/// A clickable menu entry.
#[derive(Clone, Debug, Serialize)]
pub struct MenuEntry {
pub label: String,
pub command: String,
/// Parameters bound to the entry (`null` = none).
#[serde(skip_serializing_if = "Value::is_null")]
pub params: Value,
pub shortcut: Option<String>,
}
impl MenuEntry {
pub fn spec(&self) -> Option<&'static CommandSpec> {
commands::find(&self.command)
}
pub fn params_or_empty(&self) -> Value {
if self.params.is_null() { Value::Object(Default::default()) } else { self.params.clone() }
}
}
/// Top-level menus in order (macOS has the application menu first).
pub fn top_level() -> Vec<&'static str> {
menu_bar()
.iter()
.filter_map(|n| match n {
MenuNode::Submenu { label, .. } => Some(label.as_str()),
_ => None,
})
.collect()
}
/// The menu bar for this platform.
pub fn menu_bar() -> &'static [MenuNode] {
static BAR: OnceLock<Vec<MenuNode>> = OnceLock::new();
BAR.get_or_init(|| parse(TREE, cfg!(target_os = "macos")).expect("menu tree parses"))
}
/// Every entry with its submenu path (`["Layer", "Blending Mode"]`), depth first.
pub fn entries() -> Vec<(Vec<String>, &'static MenuEntry)> {
fn rec(nodes: &'static [MenuNode], path: &mut Vec<String>, out: &mut Vec<(Vec<String>, &'static MenuEntry)>) {
for n in nodes {
match n {
MenuNode::Item(e) => out.push((path.clone(), e)),
MenuNode::Separator => {}
MenuNode::Submenu { label, children } => {
path.push(label.clone());
rec(children, path, out);
path.pop();
}
}
}
}
let mut out = vec![];
rec(menu_bar(), &mut vec![], &mut out);
out
}
/// Check-mark state of an entry (`None` = not a toggle / radio entry). Frontend-only toggles
/// (rulers, grid…) are answered by the frontend.
pub fn checked(s: &Session, command: &str, params: &Value) -> Option<bool> {
let comp = s.active_comp()?;
let layer = s.state.selected_layers.first().and_then(|l| comp.layer(*l));
let pstr = |k: &str| params.get(k).and_then(Value::as_str);
match command {
"layer.setBlendMode" => {
let want = effectcraft_color::BlendMode::from_name(pstr("mode")?)?;
Some(layer?.blend_mode == want)
}
"layer.setSwitch" => {
let l = layer?;
let sw = &l.switches;
Some(match pstr("switch")? {
"shy" => sw.shy,
"lock" => sw.locked,
"audio" => sw.audio,
"video" => sw.video,
"solo" => sw.solo,
"fx" => sw.effects,
"collapse" => sw.collapse,
"motionBlur" => sw.motion_blur,
"adjustment" => sw.adjustment,
"threeD" => sw.three_d,
"guide" => sw.guide,
"preserveTransparency" => l.preserve_transparency,
_ => return None,
})
}
"layer.quality" => {
use effectcraft_project::Quality::*;
let q = layer?.switches.quality;
Some(matches!((pstr("quality")?, q), ("best", Best) | ("draft", Draft) | ("wireframe", Wireframe)))
}
"layer.sampling" => {
use effectcraft_project::Sampling::*;
let q = layer?.switches.sampling;
Some(matches!((pstr("sampling")?, q), ("bilinear", Bilinear) | ("bicubic", Bicubic)))
}
"layer.frameBlending" => {
use effectcraft_project::FrameBlend::*;
let q = layer?.switches.frame_blend;
Some(matches!((pstr("mode")?, q), ("off", Off) | ("frameMix", FrameMix) | ("pixelMotion", PixelMotion)))
}
"layer.trackMatte" => {
use effectcraft_project::MatteKind::*;
let m = layer?.track_matte;
Some(match (pstr("op")?, m) {
("none", None) => true,
("alpha", Some(m)) => m.kind == Alpha,
("alphaInverted", Some(m)) => m.kind == AlphaInverted,
("luma", Some(m)) => m.kind == Luma,
("lumaInverted", Some(m)) => m.kind == LumaInverted,
_ => false,
})
}
"layer.markersLock" => Some(layer?.markers_locked),
_ => None,
}
}
/// Parse the tree text. `mac` selects `[mac]` / `[!mac]` lines.
pub fn parse(text: &str, mac: bool) -> Result<Vec<MenuNode>, String> {
struct Line<'a> {
depth: usize,
body: &'a str,
}
let mut lines = vec![];
// Depth of a skipped (other-platform) line: its whole subtree is skipped too.
let mut skip_below: Option<usize> = None;
for (n, raw) in text.lines().enumerate() {
if raw.trim().is_empty() || raw.trim_start().starts_with("//") {
continue;
}
let indent = raw.len() - raw.trim_start().len();
if indent % 2 != 0 {
return Err(format!("line {}: odd indentation", n + 1));
}
let depth = indent / 2;
match skip_below {
Some(d) if depth > d => continue,
_ => skip_below = None,
}
let mut body = raw.trim();
let tagged = |tag: &str, keep: bool, body: &mut &str| -> Option<bool> {
let rest = body.strip_prefix(tag)?;
*body = rest;
Some(keep)
};
if let Some(false) = tagged("[mac] ", mac, &mut body).or_else(|| tagged("[!mac] ", !mac, &mut body)) {
skip_below = Some(depth);
continue;
}
lines.push(Line { depth, body });
}
fn build(lines: &[Line], i: &mut usize, depth: usize) -> Result<Vec<MenuNode>, String> {
let mut out = vec![];
while *i < lines.len() {
let l = &lines[*i];
if l.depth < depth {
break;
}
if l.depth > depth {
return Err(format!("unexpected indentation at `{}`", l.body));
}
*i += 1;
if l.body == "---" {
out.push(MenuNode::Separator);
continue;
}
if l.body == "@effects" {
out.extend(effect_categories());
continue;
}
let mut parts = l.body.split(" | ");
let label = parts.next().unwrap_or_default().trim().to_string();
match parts.next() {
Some(cmd) => {
let cmd = cmd.trim();
let (command, params) = match cmd.split_once(' ') {
Some((c, json)) => (c.to_string(), serde_json::from_str(json.trim()).map_err(|e| format!("`{}`: {e}", l.body))?),
None => (cmd.to_string(), Value::Null),
};
// Entries with bound params state their own shortcut; plain entries use the command's.
let inherited = if params.is_null() { commands::find(&command).and_then(|c| c.shortcut).map(str::to_string) } else { None };
let shortcut = parts.next().map(|s| s.trim().to_string()).or(inherited);
out.push(MenuNode::Item(MenuEntry { label, command, params, shortcut }));
}
None => {
let children = build(lines, i, depth + 1)?;
if children.is_empty() {
return Err(format!("`{label}` has neither a command nor children"));
}
out.push(MenuNode::Submenu { label, children });
}
}
}
Ok(out)
}
let mut i = 0;
build(&lines, &mut i, 0)
}
/// Effect ▸ category submenus from the effect registry.
fn effect_categories() -> Vec<MenuNode> {
let mut cats: Vec<(&str, Vec<MenuNode>)> = vec![];
for e in crate::effects::registry() {
let entry =
MenuNode::Item(MenuEntry { label: e.name.into(), command: "effect.apply".into(), params: serde_json::json!({"effect": e.id}), shortcut: None });
match cats.iter_mut().find(|(c, _)| *c == e.category) {
Some((_, v)) => v.push(entry),
None => cats.push((e.category, vec![entry])),
}
}
cats.sort_by_key(|c| c.0.to_ascii_lowercase());
for (_, v) in &mut cats {
v.sort_by_key(|n| match n {
MenuNode::Item(e) => e.label.to_ascii_lowercase(),
_ => String::new(),
});
}
cats.into_iter().map(|(c, children)| MenuNode::Submenu { label: c.to_string(), children }).collect()
}
/// The After Effects menu layout. Labels use "..." like AE's macOS menus.
pub const TREE: &str = r#"
[mac] EffectCraft
About EffectCraft... | app.about
---
Settings...
General... | app.settings {"page":"general"} | Cmd+Alt+;
Startup & Repair... | app.settings {"page":"startup"}
Project... | app.settings {"page":"project"}
Composition... | app.settings {"page":"composition"}
Previews... | app.settings {"page":"previews"}
Appearance... | app.settings {"page":"appearance"}
Grids & Guides... | app.settings {"page":"grids"}
Labels... | app.settings {"page":"labels"}
Type... | app.settings {"page":"type"}
Import... | app.settings {"page":"import"}
Export... | app.settings {"page":"export"}
Audio... | app.settings {"page":"audio"}
Disk... | app.settings {"page":"disk"}
Memory & CPU... | app.settings {"page":"memory"}
Video... | app.settings {"page":"video"}
3D... | app.settings {"page":"3d"}
Scripting & Expressions... | app.settings {"page":"scripting"}
---
Hide EffectCraft | app.hide
Hide Others | app.hideOthers
Show All | app.showAll
---
Quit EffectCraft | app.quit
File
New
New Project | file.newProject
New Folder | project.newFolder
Open Project... | file.open
---
Close | file.close
Close Project | file.closeProject
Save | file.save
Save As
Save As... | file.saveAs
Save a Copy... | file.saveCopy
Increment and Save | file.incrementAndSave
Revert | file.revert
---
Import
File... | file.import
Multiple Files... | file.importMultiple
Placeholder... | file.importPlaceholder
Solid... | file.importSolid
Lottie... | file.importLottie
Export
Add to Render Queue | renderQueue.add
Lottie JSON... | file.exportLottie
---
Find | app.find
---
Add Footage to Comp | layer.addItem
New Comp from Selection... | file.newCompFromSelection
---
Dependencies
Collect Files... | file.collectFiles
Consolidate All Footage | file.consolidateFootage
Remove Unused Footage | file.removeUnusedFootage
Reduce Project | file.reduceProject
Find Missing Effects | file.findMissing {"what":"effects"}
Find Missing Fonts | file.findMissing {"what":"fonts"}
Find Missing Footage | file.findMissing {"what":"footage"}
---
Scripts
Run Script File... | file.runScript
---
Create Proxy
Still... | file.createProxy {"kind":"still"}
Movie... | file.createProxy {"kind":"movie"}
Set Proxy
File... | file.setProxy
None | file.setProxyNone
Interpret Footage
Main... | file.interpretFootage
Proxy... | file.interpretProxy
Remember Interpretation | file.rememberInterpretation
Apply Interpretation | file.applyInterpretation
Replace Footage
File... | file.replaceFootage
Placeholder... | file.replaceWithPlaceholder
Solid... | file.replaceWithSolid
Reload Footage | file.reloadFootage
Reveal in Finder | file.revealInFinder
---
Project Settings... | file.projectSettings
[!mac] ---
[!mac] Exit | app.quit
Edit
Undo | edit.undo
Redo | edit.redo
---
Quick Apply... | app.commandPalette
---
Cut | edit.cut
Copy | edit.copy
Copy with Property Links | edit.copyWithPropertyLinks
Copy with Relative Property Links | edit.copyWithRelativePropertyLinks
Copy Expression Only | edit.copyExpressionOnly
Paste | edit.paste
Paste Reversed Keyframes | edit.pasteReversedKeyframes
Paste Text and Match Formatting | edit.pasteTextMatchFormatting
Paste Text Formatting Only | edit.pasteTextFormattingOnly
Clear | edit.clear
---
Duplicate | edit.duplicate
Split Layer | edit.splitLayer
Lift Work Area | edit.liftWorkArea
Extract Work Area | edit.extractWorkArea
Select All | edit.selectAll
Deselect All | edit.deselectAll
---
Label
Select Label Group | edit.selectLabelGroup
---
None | edit.label {"label":"None"}
Red | edit.label {"label":"Red"}
Yellow | edit.label {"label":"Yellow"}
Aqua | edit.label {"label":"Aqua"}
Pink | edit.label {"label":"Pink"}
Lavender | edit.label {"label":"Lavender"}
Peach | edit.label {"label":"Peach"}
Sea Foam | edit.label {"label":"Sea Foam"}
Blue | edit.label {"label":"Blue"}
Green | edit.label {"label":"Green"}
Purple | edit.label {"label":"Purple"}
Orange | edit.label {"label":"Orange"}
Brown | edit.label {"label":"Brown"}
Fuchsia | edit.label {"label":"Fuchsia"}
Cyan | edit.label {"label":"Cyan"}
Sandstone | edit.label {"label":"Sandstone"}
Dark Green | edit.label {"label":"Dark Green"}
---
Edit Label Colors... | app.settings {"page":"labels"}
Select Keyframe Label Group
On Selected Layers | keys.selectLabelGroup {"scope":"selected"}
On All Layers | keys.selectLabelGroup {"scope":"all"}
Visible Keyframes on Selected Layers | keys.selectLabelGroup {"scope":"visibleSelected"}
Visible Keyframes on All Layers | keys.selectLabelGroup {"scope":"visibleAll"}
---
Purge
All Cache... | edit.purge {"what":"all"}
All Memory & Disk Cache... | edit.purge {"what":"memoryAndDisk"}
All Memory | edit.purge {"what":"memory"}
All Disk Cache... | edit.purge {"what":"disk"}
All 3D Cache... | edit.purge {"what":"3d"}
Undo | edit.purgeUndo
Image Cache Memory | edit.purge {"what":"image"}
Snapshot | edit.purge {"what":"snapshot"}
---
Edit Original... | edit.editOriginal
---
Templates
Render Settings... | app.templates {"kind":"renderSettings"}
Output Module... | app.templates {"kind":"outputModule"}
Keyboard Shortcuts | app.keyboardShortcuts
[!mac] Preferences
[!mac] General... | app.settings {"page":"general"} | Cmd+Alt+;
[!mac] Startup & Repair... | app.settings {"page":"startup"}
[!mac] Project... | app.settings {"page":"project"}
[!mac] Composition... | app.settings {"page":"composition"}
[!mac] Previews... | app.settings {"page":"previews"}
[!mac] Appearance... | app.settings {"page":"appearance"}
[!mac] Grids & Guides... | app.settings {"page":"grids"}
[!mac] Labels... | app.settings {"page":"labels"}
[!mac] Type... | app.settings {"page":"type"}
[!mac] Import... | app.settings {"page":"import"}
[!mac] Export... | app.settings {"page":"export"}
[!mac] Audio... | app.settings {"page":"audio"}
[!mac] Disk... | app.settings {"page":"disk"}
[!mac] Memory & CPU... | app.settings {"page":"memory"}
[!mac] Video... | app.settings {"page":"video"}
[!mac] 3D... | app.settings {"page":"3d"}
[!mac] Scripting & Expressions... | app.settings {"page":"scripting"}
Composition
New Composition... | comp.new
---
Composition Settings... | comp.settings
Set Poster Time | comp.setPosterTime
Trim Comp to Work Area | comp.trimToWorkArea
Crop Comp to Region of Interest | comp.cropToRegionOfInterest
Crop Comp to Selected Layer(s) Bounds | comp.cropToLayerBounds
---
Add to Render Queue | renderQueue.add
Add Output Module | render.addOutputModule
---
Preview
Play Current Preview | playback.toggle
Cache Frames When Idle | playback.cacheWhenIdle
Audio | playback.audio
Save Frame As
File... | comp.saveFrameAs
Pre-render... | render.preRender
Save Current Preview... | render.saveCurrentPreview
---
Responsive Design — Time
Create Intro | comp.responsiveTime {"op":"intro"}
Create Outro | comp.responsiveTime {"op":"outro"}
Create Protected Region from Work Area | comp.responsiveTime {"op":"workArea"}
---
Composition Flowchart | comp.flowchart
Composition Mini-Flowchart | comp.miniFlowchart
Layer
New
Text | layer.newText
Solid... | layer.newSolid
Light... | layer.newLight
Camera... | layer.newCamera
Null Object | layer.newNull
Shape Layer | layer.newShape
Adjustment Layer | layer.newAdjustment
Content-Aware Fill Layer... | layer.newContentAwareFill
Cube | layer.new3dPrimitive {"kind":"cube"}
Sphere | layer.new3dPrimitive {"kind":"sphere"}
Plane | layer.new3dPrimitive {"kind":"plane"}
Torus | layer.new3dPrimitive {"kind":"torus"}
Cone | layer.new3dPrimitive {"kind":"cone"}
Cylinder | layer.new3dPrimitive {"kind":"cylinder"}
Layer Settings... | layer.settings
---
Open Layer | layer.openLayer
Open Layer Source | layer.openSource
Reveal in Finder | layer.revealInFinder
---
Mask
New Mask | layer.addMask
Mask Shape... | layer.mask.shape
Mask Feather... | layer.mask.set {"field":"feather"} | Cmd+Shift+F
Mask Opacity... | layer.mask.set {"field":"opacity"}
Mask Expansion... | layer.mask.set {"field":"expansion"}
Reset Mask | layer.mask.reset
Remove Mask | layer.mask.remove
Remove All Masks | layer.mask.removeAll
Mode
None | layer.mask.mode {"mode":"None"}
Add | layer.mask.mode {"mode":"Add"}
Subtract | layer.mask.mode {"mode":"Subtract"}
Intersect | layer.mask.mode {"mode":"Intersect"}
Lighten | layer.mask.mode {"mode":"Lighten"}
Darken | layer.mask.mode {"mode":"Darken"}
Difference | layer.mask.mode {"mode":"Difference"}
Inverted | layer.mask.invert
Locked | layer.mask.lock
Motion Blur
Same As Layer | layer.mask.motionBlur {"mode":"sameAsLayer"}
On | layer.mask.motionBlur {"mode":"on"}
Off | layer.mask.motionBlur {"mode":"off"}
Feather Falloff
Smooth | layer.mask.featherFalloff {"mode":"smooth"}
Linear | layer.mask.featherFalloff {"mode":"linear"}
Unlock All Masks | layer.mask.unlockAll
Lock Other Masks | layer.mask.lockOthers
Hide Locked Masks | layer.mask.hideLocked
Mask and Shape Path
RotoBezier | path.rotoBezier
Closed | mask.setClosed
Convert To Bezier Path | path.convertToBezier
Group Shapes | path.groupShapes
Ungroup Shapes | path.ungroupShapes
Set First Vertex | path.setFirstVertex
Free Transform Points | path.freeTransform
Quality
Best | layer.quality {"quality":"best"} | Cmd+U
Draft | layer.quality {"quality":"draft"} | Cmd+Shift+U
Wireframe | layer.quality {"quality":"wireframe"} | Cmd+Alt+Shift+U
---
Bilinear | layer.sampling {"sampling":"bilinear"}
Bicubic | layer.sampling {"sampling":"bicubic"}
Switches
Hide Other Video | layer.hideOtherVideo
Show All Video | layer.showAllVideo
Unlock All Layers | layer.unlockAll
Enable Expressions | layer.expressions {"enabled":true}
Disable Expressions | layer.expressions {"enabled":false}
---
Shy | layer.setSwitch {"switch":"shy"}
Lock | layer.setSwitch {"switch":"lock"} | Cmd+L
Audio | layer.setSwitch {"switch":"audio"}
Video | layer.setSwitch {"switch":"video"}
Solo | layer.setSwitch {"switch":"solo"}
Effect | layer.setSwitch {"switch":"fx"}
Collapse | layer.setSwitch {"switch":"collapse"}
Motion Blur | layer.setSwitch {"switch":"motionBlur"}
Adjustment Layer | layer.setSwitch {"switch":"adjustment"}
Transform
Reset | layer.transform {"op":"reset"}
Anchor Point... | layer.setTransform {"prop":"anchor"}
Position... | layer.setTransform {"prop":"position"} | Cmd+Shift+P
Scale... | layer.setTransform {"prop":"scale"}
Orientation... | layer.setTransform {"prop":"orientation"} | Cmd+Alt+Shift+R
Rotation... | layer.setTransform {"prop":"rotation"} | Cmd+Shift+R
Opacity... | layer.setTransform {"prop":"opacity"} | Cmd+Shift+O
---
Flip Horizontal | layer.transform {"op":"flipH"}
Flip Vertical | layer.transform {"op":"flipV"}
Center In View | layer.transform {"op":"center"}
Center Anchor Point in Layer Content | layer.centerAnchor
Fit to Comp | layer.transform {"op":"fit"} | Cmd+Alt+F
Fit to Comp Width | layer.transform {"op":"fitWidth"} | Cmd+Alt+Shift+H
Fit to Comp Height | layer.transform {"op":"fitHeight"} | Cmd+Alt+Shift+G
---
Auto-Orient... | layer.autoOrient
Time
Enable Time Remapping | layer.enableTimeRemap
Time-Reverse Layer | layer.timeReverse
Time Stretch... | layer.timeStretch
Freeze Frame | layer.freezeFrame
Freeze On Last Frame | layer.freezeOnLastFrame
Align Video to Data | layer.alignVideoToData
Frame Blending
Off | layer.frameBlending {"mode":"off"}
Frame Mix | layer.frameBlending {"mode":"frameMix"}
Pixel Motion | layer.frameBlending {"mode":"pixelMotion"}
3D Layer | layer.setSwitch {"switch":"threeD"}
Guide Layer | layer.setSwitch {"switch":"guide"}
Environment Layer | layer.environment
Markers
Add Marker | layer.addMarker
Update Markers From Source | layer.updateMarkersFromSource
Lock Markers | layer.markersLock
Delete All Markers | layer.deleteAllMarkers
---
Preserve Transparency | layer.setSwitch {"switch":"preserveTransparency"}
Blending Mode
Normal | layer.setBlendMode {"mode":"Normal"}
Dissolve | layer.setBlendMode {"mode":"Dissolve"}
Dancing Dissolve | layer.setBlendMode {"mode":"Dancing Dissolve"}
---
Darken | layer.setBlendMode {"mode":"Darken"}
Multiply | layer.setBlendMode {"mode":"Multiply"}
Color Burn | layer.setBlendMode {"mode":"Color Burn"}
Classic Color Burn | layer.setBlendMode {"mode":"Classic Color Burn"}
Linear Burn | layer.setBlendMode {"mode":"Linear Burn"}
Darker Color | layer.setBlendMode {"mode":"Darker Color"}
---
Add | layer.setBlendMode {"mode":"Add"}
Lighten | layer.setBlendMode {"mode":"Lighten"}
Screen | layer.setBlendMode {"mode":"Screen"}
Color Dodge | layer.setBlendMode {"mode":"Color Dodge"}
Classic Color Dodge | layer.setBlendMode {"mode":"Classic Color Dodge"}
Linear Dodge | layer.setBlendMode {"mode":"Linear Dodge"}
Lighter Color | layer.setBlendMode {"mode":"Lighter Color"}
---
Overlay | layer.setBlendMode {"mode":"Overlay"}
Soft Light | layer.setBlendMode {"mode":"Soft Light"}
Hard Light | layer.setBlendMode {"mode":"Hard Light"}
Linear Light | layer.setBlendMode {"mode":"Linear Light"}
Vivid Light | layer.setBlendMode {"mode":"Vivid Light"}
Pin Light | layer.setBlendMode {"mode":"Pin Light"}
Hard Mix | layer.setBlendMode {"mode":"Hard Mix"}
---
Difference | layer.setBlendMode {"mode":"Difference"}
Classic Difference | layer.setBlendMode {"mode":"Classic Difference"}
Exclusion | layer.setBlendMode {"mode":"Exclusion"}
Subtract | layer.setBlendMode {"mode":"Subtract"}
Divide | layer.setBlendMode {"mode":"Divide"}
---
Hue | layer.setBlendMode {"mode":"Hue"}
Saturation | layer.setBlendMode {"mode":"Saturation"}
Color | layer.setBlendMode {"mode":"Color"}
Luminosity | layer.setBlendMode {"mode":"Luminosity"}
---
Stencil Alpha | layer.setBlendMode {"mode":"Stencil Alpha"}
Stencil Luma | layer.setBlendMode {"mode":"Stencil Luma"}
Silhouette Alpha | layer.setBlendMode {"mode":"Silhouette Alpha"}
Silhouette Luma | layer.setBlendMode {"mode":"Silhouette Luma"}
---
Alpha Add | layer.setBlendMode {"mode":"Alpha Add"}
Luminescent Premul | layer.setBlendMode {"mode":"Luminescent Premul"}
Next Blending Mode | layer.setBlendMode {"step":1} | Shift+=
Previous Blending Mode | layer.setBlendMode {"step":-1} | Shift+-
Track Matte
No Track Matte | layer.trackMatte {"op":"none"}
Alpha Matte | layer.trackMatte {"op":"alpha"}
Alpha Inverted Matte | layer.trackMatte {"op":"alphaInverted"}
Luma Matte | layer.trackMatte {"op":"luma"}
Luma Inverted Matte | layer.trackMatte {"op":"lumaInverted"}
---
Matte with Layer Above | layer.trackMatte {"op":"above"}
Matte with Layer Below | layer.trackMatte {"op":"below"}
Layer Styles
Convert to Editable Styles | layer.style.convertToEditable
Show All | layer.style.showAll
Remove All | layer.style.removeAll
---
Drop Shadow | layer.style.dropShadow
Inner Shadow | layer.style.innerShadow
Outer Glow | layer.style.outerGlow
Inner Glow | layer.style.innerGlow
Bevel and Emboss | layer.style.bevelEmboss
Satin | layer.style.satin
Color Overlay | layer.style.colorOverlay
Gradient Overlay | layer.style.gradientOverlay
Stroke | layer.style.stroke
---
Arrange
Bring Layer to Front | layer.arrange {"to":"front"} | Cmd+Shift+]
Bring Layer Forward | layer.arrange {"to":"forward"} | Cmd+]
Send Layer Backward | layer.arrange {"to":"backward"} | Cmd+[
Send Layer to Back | layer.arrange {"to":"back"} | Cmd+Shift+[
---
Reveal
Reveal in Finder | layer.revealInFinder
Reveal Layer Source in Project | layer.revealSource
Reveal Layer in Project Flowchart | comp.flowchart
Reveal Composition in Project | comp.revealInProject
Reveal Expression Errors | layer.revealExpressionErrors
Create
Convert to Editable Text | layer.create {"op":"editableText"}
Create Shapes from Text | layer.create {"op":"shapesFromText"}
Create Masks from Text | layer.create {"op":"masksFromText"}
Create Shapes from Vector Layer | layer.create {"op":"shapesFromVector"}
Create Keyframes from Data | layer.create {"op":"keyframesFromData"}
Null Controllers for Positional Points | layer.create {"op":"nullsPositional"}
Null Controllers for Path Points | layer.create {"op":"nullsPathPoints"}
Nulls Following Path Points | layer.create {"op":"nullsFollowing"}
Null Tracing Along Path | layer.create {"op":"nullTracing"}
Create 3D Layer Instance | layer.create {"op":"instance3d"}
---
Camera
Create Camera from 3D View | camera.fromView
Create Stereo 3D Rig | camera.stereoRig
Create Orbit Null | camera.orbitNull
Create Cameras from 3D Model | camera.fromModel
Link Focus Distance to Point of Interest | camera.linkFocusToPoi
Link Focus Distance to Layer | camera.linkFocusToLayer
Set Focus Distance to Layer | camera.setFocusToLayer
Camera Settings... | layer.cameraSettings
Reset 3D View | view.reset3DView
Light
Create Lights from 3D Model | light.fromModel
Control Light with Camera | light.controlWithCamera
Create Environment Light Background Layer | light.environmentBackground
Material
Reveal Material Source in Project | material.revealSource
Reset Material | material.reset
Duplicate and Assign Material | material.duplicateAssign
Auto-trace... | layer.autoTrace
Pre-compose... | layer.precompose
Scene Edit Detection... | layer.sceneEditDetection
Effect
Effect Controls | window.panel {"panel":"effectControls"} | F3
Last Effect | effect.applyLast
Remove All | effect.removeAll
---
Manage Effects... | effect.manage
---
@effects
Animation
Save Animation Preset... | anim.savePreset
Apply Animation Preset... | anim.applyPreset
Browse Presets... | anim.browsePresets
Text Animation Presets
Typewriter | layer.applyTextPreset {"preset":"typewriter"}
Fade Up Characters | layer.applyTextPreset {"preset":"fadeUpCharacters"}
Bounce In Words | layer.applyTextPreset {"preset":"bounceInWords"}
Tracking In | layer.applyTextPreset {"preset":"trackingIn"}
Scramble | layer.applyTextPreset {"preset":"scramble"}
Blur In | layer.applyTextPreset {"preset":"blurIn"}
Jitter | layer.applyTextPreset {"preset":"jitter"}
Drop In Lines | layer.applyTextPreset {"preset":"dropInLines"}
---
Add Keyframe | anim.addKeyframe
Toggle Hold Keyframe | keys.toggleHold
Keyframe Interpolation... | keys.interpolation
Keyframe Velocity... | keys.velocity
Keyframe Assistant
Convert Audio to Keyframes | keys.audioToKeyframes
Convert Expression to Keyframes | prop.convertExpressionToKeyframes
Create Keyframes from Data | layer.create {"op":"keyframesFromData"}
Easy Ease | keys.easyEase
Easy Ease In | keys.easyEaseIn
Easy Ease Out | keys.easyEaseOut
Exponential Scale | keys.exponentialScale
Null Controllers for Path Points | layer.create {"op":"nullsPathPoints"}
Null Controllers for Positional Points | layer.create {"op":"nullsPositional"}
Null Tracing Along Path | layer.create {"op":"nullTracing"}
Nulls Following Path Points | layer.create {"op":"nullsFollowing"}
RPF Camera Import | keys.rpfCameraImport
Sequence Layers... | layer.sequence
Time-Reverse Keyframes | keys.timeReverse
---
Animate Text
Enable Per-character 3D | layer.enablePerChar3D {"toggle":true}
---
Anchor Point | layer.addTextAnimator {"properties":["anchor"]}
Position | layer.addTextAnimator {"properties":["position"]}
Scale | layer.addTextAnimator {"properties":["scale"]}
Skew | layer.addTextAnimator {"properties":["skew"]}
Rotation | layer.addTextAnimator {"properties":["rotation"]}
Opacity | layer.addTextAnimator {"properties":["opacity"]}
---
All Transform Properties | layer.addTextAnimator {"properties":["transformAll"]}
---
Fill Color
RGB | layer.addTextAnimator {"properties":["fillColor"]}
Hue | layer.addTextAnimator {"properties":["fillHue"]}
Saturation | layer.addTextAnimator {"properties":["fillSaturation"]}
Brightness | layer.addTextAnimator {"properties":["fillBrightness"]}
Opacity | layer.addTextAnimator {"properties":["fillOpacity"]}
Stroke Color
RGB | layer.addTextAnimator {"properties":["strokeColor"]}
Hue | layer.addTextAnimator {"properties":["strokeHue"]}
Saturation | layer.addTextAnimator {"properties":["strokeSaturation"]}
Brightness | layer.addTextAnimator {"properties":["strokeBrightness"]}
Opacity | layer.addTextAnimator {"properties":["strokeOpacity"]}
Stroke Width | layer.addTextAnimator {"properties":["strokeWidth"]}
---
Tracking | layer.addTextAnimator {"properties":["tracking"]}
Line Anchor | layer.addTextAnimator {"properties":["lineAnchor"]}
Line Spacing | layer.addTextAnimator {"properties":["lineSpacing"]}
---
Character Offset | layer.addTextAnimator {"properties":["characterOffset"]}
Character Value | layer.addTextAnimator {"properties":["characterValue"]}
---
Blur | layer.addTextAnimator {"properties":["blur"]}
Variable Font Axes | text.animatorFontAxes
Add Text Selector
Range | text.addSelector {"kind":"range"}
Wiggly | text.addSelector {"kind":"wiggly"}
Expression | text.addSelector {"kind":"expression"}
Remove All Text Animators | text.removeAllAnimators
---
Add Expression | prop.setExpression
Separate Dimensions | prop.separateDimensions
Track Camera | track.camera
Warp Stabilizer VFX | track.warpStabilizer
Track Motion | track.motion
Track Mask | track.mask
Track this Property | track.property
---
Reveal Properties with Keyframes | anim.reveal {"kind":"keyframes"} | U
Reveal Properties with Animation | anim.reveal {"kind":"animation"}
Reveal All Modified Properties | anim.reveal {"kind":"modified"}
View
Split with New Locked Viewer | view.splitLockedViewer
---
Zoom In | view.zoomIn
Zoom Out | view.zoomOut
---
Resolution
Full | view.res.full
Half | view.res.half
Third | view.res.third
Quarter | view.res.quarter
Custom... | view.res.custom
---
Use Display Color Management | view.displayColorManagement
Simulate Output
No Output Simulation | view.simulateOutput {"profile":"none"}
HDTV (Rec. 709) | view.simulateOutput {"profile":"rec709"}
SDTV NTSC | view.simulateOutput {"profile":"ntsc"}
SDTV PAL | view.simulateOutput {"profile":"pal"}
Legacy Macintosh RGB (Gamma 1.8) | view.simulateOutput {"profile":"mac18"}
Internet Standard RGB (sRGB) | view.simulateOutput {"profile":"srgb"}
Custom... | view.simulateOutput {"profile":"custom"}
---
Show Rulers | view.rulers
Panel Background Color
Black | view.panelBackground {"color":"black"}
Dark Gray | view.panelBackground {"color":"darkGray"}
Medium Gray (Default) | view.panelBackground {"color":"mediumGray"}
Light Gray | view.panelBackground {"color":"lightGray"}
White | view.panelBackground {"color":"white"}
---
Custom | view.panelBackground {"color":"custom"}
Select Custom Background Color... | view.panelBackground {"pick":true}
---
Show Guides | view.guides
Snap to Guides | view.snapToGuides
Lock Guides | view.lockGuides
Add Guide... | view.addGuide
Clear Guides | view.clearGuides
Import Guides... | view.importGuides
Export Guides... | view.exportGuides
---
Show Grid | view.grid
Snap to Grid | view.snapToGrid
---
View Options... | view.options
Show Layer Controls | view.layerControls
---
Camera Settings... | layer.cameraSettings
Reset 3D View | view.reset3DView
Create Camera from 3D View | camera.fromView
Switch View Layout
1 View | view.layout {"views":1}
2 Views | view.layout {"views":2}
4 Views | view.layout {"views":4}
---
Share View Options | view.shareViewOptions
Switch 3D View
Active Camera | view.3d.activeCamera
Default | view.3d.default
---
Front | view.3d.front
Left | view.3d.left
Top | view.3d.top
Back | view.3d.back
Right | view.3d.right
Bottom | view.3d.bottom
---
Custom View 1 | view.3d.custom1
Custom View 2 | view.3d.custom2
Custom View 3 | view.3d.custom3
---
Reset 3D View | view.reset3DView
Switch to Last 3D View | view.3d.last | Escape
Look at Selected Layers | view.lookAtSelected
Look at All Layers | view.lookAtAll
---
Go to Time... | time.set
Enter Full Screen | view.fullScreen
Window
Workspace
All Panels | window.workspace {"name":"All Panels"}
Animation | window.workspace {"name":"Animation"}
Default | window.workspace {"name":"Default"} | Shift+F10
Effects | window.workspace {"name":"Effects"}
Minimal | window.workspace {"name":"Minimal"}
Motion Tracking | window.workspace {"name":"Motion Tracking"}
Paint | window.workspace {"name":"Paint"}
Small Screen | window.workspace {"name":"Small Screen"} | Shift+F12
Standard | window.workspace {"name":"Standard"} | Shift+F11
Text | window.workspace {"name":"Text"}
---
Reset to Saved Layout | window.resetWorkspace
Save Changes to this Workspace | window.saveWorkspace
Save as New Workspace... | window.saveWorkspaceAs
Edit Workspaces... | window.editWorkspaces
---
Align | window.panel {"panel":"align"}
Audio | window.panel {"panel":"audio"} | Cmd+4
Brushes | window.panel {"panel":"brushes"} | Cmd+9
Character | window.panel {"panel":"character"} | Cmd+6
Content-Aware Fill | window.unavailablePanel {"panel":"contentAwareFill"}
Effects & Presets | window.panel {"panel":"effectsPresets"} | Cmd+5
Info | window.panel {"panel":"info"} | Cmd+2
Lumetri Scopes | window.unavailablePanel {"panel":"lumetriScopes"}
Mask Interpolation | window.unavailablePanel {"panel":"maskInterpolation"}
Media Browser | window.unavailablePanel {"panel":"mediaBrowser"}
Metadata | window.unavailablePanel {"panel":"metadata"}
Motion Sketch | window.panel {"panel":"motionSketch"}
Paint | window.panel {"panel":"paint"} | Cmd+8
Paragraph | window.panel {"panel":"paragraph"} | Cmd+7
Preview | window.panel {"panel":"preview"} | Cmd+3
Progress | window.unavailablePanel {"panel":"progress"}
Properties | window.panel {"panel":"properties"}
Smoother | window.panel {"panel":"smoother"}
Tools | window.panel {"panel":"tools"} | Cmd+1
Tracker | window.panel {"panel":"tracker"}
Wiggler | window.panel {"panel":"wiggler"}
---
Composition | window.panel {"panel":"composition"}
Effect Controls | window.panel {"panel":"effectControls"} | F3
Flowchart | window.panel {"panel":"flowchart"}
Footage | window.unavailablePanel {"panel":"footage"}
Layer | window.panel {"panel":"layer"}
Project | window.panel {"panel":"project"} | Cmd+0
Render Queue | window.panel {"panel":"renderQueue"} | Cmd+Alt+0
Timeline | window.panel {"panel":"timeline"}
Help
EffectCraft Help... | help.docs {"page":"help"} | F1
Scripting Help... | help.docs {"page":"scripting"}
Expression Reference... | help.docs {"page":"expressions"}
Effect Reference... | help.docs {"page":"effects"}
Animation Presets... | anim.browsePresets
Keyboard Shortcuts... | app.keyboardShortcuts
---
Join the ArtCraft Discord... | help.discord
Provide Feedback... | help.reportIssue
---
ArtCraft Website | help.website
EffectCraft Home Page | help.appPage
EffectCraft on GitHub | help.github
Open Demo Project | file.openDemoProject
[!mac] ---
[!mac] About EffectCraft... | app.about
"#;
#[cfg(test)]
mod tests;