947 lines
38 KiB
Rust
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;
|