The Ease Presets panel (#254) is modelled on a third-party panel, so,
as with third-party tools in After Effects, it isn't core any more: it
is `extensions/scriptui-panels/Ease Presets.jsx`, a ScriptUI panel
bundled as an optional extension (Window ▸ Ease Presets.jsx), written
only against the public scripting API. Clicking a preset eases every
pair of neighbouring selected keyframes in one undo step (per
dimension, along the motion path for spatial properties, the keys'
other sides as they play); the fields edit the working curve, which the
panel draws; From Keys, Save, Rename and Delete as before. The twelve
built-in curves are unchanged.
Scripting API fixes it needed (After Effects parity):
- `app.settings` lasts between runs (`script_settings.json` in the
settings folder; `script.settings.get` / `script.settings.save`).
- Per-key methods (`setTemporalEaseAtKey`, `setInterpolationTypeAtKey`,
`removeKey`, the continuous / auto-Bezier / roving setters) no longer
change the user's key selection (they selected the key they edited).
- `keyInTemporalEase` / `keyOutTemporalEase` report linear and
auto-Bezier sides as they play, `keyIn/OutSpatialTangent` the
tangents the motion path plays.
- `selectedProperties` includes the properties of selected keyframes.
- `isInterpolationTypeValid` is false for Linear / Bezier on values that
only hold.
Removed from core: Window ▸ Ease Presets, the `keys.easePreset.*`
commands, `engine::ease_presets` and the `ease_presets.json` store,
`keyframe::EaseCurve` (nothing else used it) and the egui panel. User
presets saved by the v0.6.0 panel move into the panel's app.settings
section the first time settings load (the old file is left as is).
Docs: plugins.md (bundled extensions), parity.md, gaps.md G8, agents.md.
Test: app_settings_last_between_runs,
per_key_methods_keep_the_key_selection_and_report_eases_as_they_play,
spatial_tangents_are_reported_as_the_path_plays (each fails before),
the_ease_presets_panel_eases_every_selected_pair_in_one_undo_step,
a_typed_curve_scales_to_each_dimension_and_follows_motion_paths,
user_presets_save_rename_delete_and_persist,
presets_saved_by_the_old_core_panel_carry_over (script, through the
panel's controls), ease_presets_panel_eases_the_selected_keys_from_its_list
(UI: the panel docks and a click on a preset eases the keys).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EffectCraft's extension points compared with After Effects' (public
user guide and Scripting Guide) are now in docs/plugins.md, which
becomes "Plug-ins and extensions": scripts, ScriptUI panels and effect
plug-ins, with a short guide and an example panel for writing your own.
ScriptUI panels already came from `Scripts/ScriptUI Panels/` in the
settings folder, were listed at the bottom of the Window menu and
docked as tabs. What was missing: After Effects reopens the panels
that were open when it quit. Now the open panels are remembered in
`scriptui_panels.json` in the settings folder (a panel is added when
it opens and dropped when its tab or its script closes it; quitting
leaves the list), and `window.restoreScriptPanels` opens them again;
the desktop and web apps run it at start-up. A panel whose script is
gone or fails is reported and forgotten, and the others still open.
Where a panel was docked isn't kept, as workspace layouts aren't saved
between launches.
Gaps recorded in plugins.md and gaps.md G8: the Startup and Shutdown
script folders, keyboard shortcuts for scripts, ExtendScript's
`#include` / `#target` directives and `.jsxbin`.
Test: open_panels_open_again_at_the_next_launch (fails before: no
restore command), a_panel_dropped_in_the_folder_is_listed_and_opens_as_a_panel
(script), panels_open_at_quit_dock_again_at_launch (UI: the restored
panel docks with its controls; closing the tab forgets it).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Tools bar's Snapping options menu (#265) offered per-feature
toggles (Edges, Corners, Centers, Anchor Points, Mask and Shape Path
Points) that After Effects' Snapping menu doesn't have. It now has the
two After Effects has:
- Snap Edges Extended (as before).
- Snap to Features in Collapsed Compositions and Text Layers: the
visible layers inside a precomp layer with Collapse Transformations
on are snap targets too, mapped into the outer comp (points always,
edge lines while the precomp layer is unrotated); a snap highlights
the precomp layer. Nested collapsed precomps are followed eight deep.
Every layer feature snaps again (corners, edge midpoints, centre,
anchor point, mask and shape path points); the snapping engine itself
is unchanged. `view.snappingOptions` takes `edgesExtended` and
`collapsedFeatures` (and `toggle` of either); the old keys are refused.
Test: collapsed_precomp_features_snap (fails before: no such option),
snapping_options_command_sets_and_toggles (the per-feature keys are
refused), snapping_options_pick_the_targets (AE behaviour kept: edges
extended or not, hidden layers, guides only), and the Snapping menu UI
test (only the two options are listed).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
After Effects' Interpret Footage has Start Timecode: Use Source File
Timecode, or Override Start, which changes the timecode the footage
shows and leaves its frames alone. The Start Frame field that trimmed or
padded a sequence's head isn't in After Effects, so it is replaced.
Footage.start_timecode (Override Start, in frames at the footage's
rate; None = Use Source, 0 as file timecode isn't read) relabels source
time: the Footage panel's current time and footage.info's `timecode`
count from it. file.interpretFootage takes `overrideStart` and
`startTimecode` (timecode, read at the footage's rate after any frame
rate change, or a frame number, 0 to 1e9). It applies to every footage
with time, not only sequences, and Reload keeps it. A sequence's frames
always run from its first file's number to its last.
Test: start_timecode_only_relabels_source_time (project),
interpret_footage_start_timecode_relabels_source_time (engine: the
reply, the Footage panel's timecode, frame numbers, Use Source, the
footage's rate, bad values, undo), Reload keeps it, and the Interpret
Footage fields in ui_drag_drop.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Effects: effect points on shape layers start at the layer centre (#227)
# Conflicts:
# crates/ui-egui/src/panels/puppet_tool.rs
# crates/ui-egui/src/panels/viewer.rs
#365 rejects arguments missing from a tool's schema; both tools read `id`
as an alias of `command`, so declare it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Checked every submenu of Adobe After Effects 26.5.0 (Build 89), Simplified Chinese, against
the catalog and took the wording the application shows. 81 of the 618 labels change; keys,
order, count and the Traditional Chinese catalog are untouched.
Three groups:
- 61 labels where #309 and this branch disagreed and the application matches this branch -
including the five blend modes whose Chinese names differ between After Effects and
Photoshop (屏幕, 发光度, 较深的颜色, 较浅的颜色, 纯色混合), 导入参考... / 导出参考...
(the application omits the 线), and 类型... for the Type settings page.
- 11 labels where both catalogs were wrong and this takes the application's wording:
向基本图形添加属性, 移除所有的文本动画器, 显示关键帧的属性, 显示动画的属性,
将合成裁剪到工作区, 裁切合成至选定图层边界, 基于 3D 视图生成摄像机,
创建立体 3D 设备, 使用摄像机控制光线, 使用显示色彩管理, 淡灰色.
- 9 labels of spelling and punctuation: 沿路径进行空值追踪 (a stray '1'), 启用逐字 3D 化,
裁剪合成到目标区域, 中灰(默认) with full-width parentheses.
Left for the maintainers: Material (the application says 素材, but it also calls Footage 素材),
the 导出 prefix on the Premiere export row (the application repeats it but drops 'Pro'), and the
verb in Reset 3D View (the application is inconsistent between 重设 and 重置).
Two things are one level deeper than the first pass at this review:
- Nine of the call sites that pass the resolved language are inside test functions, where `app`
is owned, so `crate::i18n::language(app)` needs `&app`. `cargo check --all-targets` (which
compiles the test modules as well) is what reported them; a check without it does not, which is
how they survived the first round.
- `clippy -D warnings` rejects the explicit `'static` on `const KANA` / `const HAN`: a const
already has one.
On this branch `cargo fmt --all --check`, `cargo check -p effectcraft-ui-egui --all-targets`,
`cargo clippy -p effectcraft-ui-egui --all-targets -- -D warnings` and
`cargo test -p effectcraft-ui-egui` (40 targets, 113 lib tests) are clean.
The QA test's time-remapped precomp no longer matches the precomp rendered
directly (crates/automation/tests/qa/kinetic.rs:78); FreeBSD CI has been red
since. Reverting until the PR is reworked against that test.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
After Effects shows a missing frame of an image sequence as a
placeholder (colour bars) and has no Hold Previous Frame or Skip choice
in Interpret Footage, so EffectCraft doesn't either.
Interpret Footage loses its Missing Frames field, file.interpretFootage
its missingFrames parameter, and Footage its missing_frames setting. A
gap in a numbered sequence's numbering always shows colour bars. A
sequence imported with Force Alphabetical Order still plays its files
one after another (its numbering doesn't count), as in After Effects;
that is now an internal Footage.alphabetical flag, set on import and
kept by Reload. The Interpret Footage note lists the missing frames.
Projects saved before this have no such field and open with the After
Effects behaviour: a sequence with gaps shows placeholders where it
used to close them up.
Test: project sequence unit tests (placeholders, alphabetical order,
projects saved without the flag), tests_sequence (Start Frame instead
of Hold / Skip), sequence_gaps_show_placeholders (media) and the
Interpret Footage fields in ui_drag_drop.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The footage item's context menu from #383 builds its entries with
menu_nodes, which #391 gave the menu bar's keyboard navigator and
depth; like the other context menus it passes a fresh one.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Settings > Appearance > UI Scale (appearance.uiScale, 75-200 %, default 100)
scales the whole interface on top of the display's own scaling, live as it is
chosen; Cancel puts it back, OK saves it and a saved scale applies from the
first frame. The Settings dialog's choice rows show a number setting with
choices as its text. Automation id: settings.appearance.uiScale.
egui's own Ctrl+= / Ctrl+- / Ctrl+0 zoom is off now: Ctrl+0 (Window > Project)
also reset the zoom behind the app's back, and the scale is a setting.
Test: engine tests_settings ui_scale_is_a_percentage_from_75_to_200; ui_panels
ui_scale_setting_sizes_the_interface.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The page column had a fixed 590 pt width, a few points more than the dialog had
beside the page list, so the whole Settings dialog scrolled sideways a little.
It takes the rest of the dialog's width now.
Test: ui_scrolling settings_dialog_does_not_scroll_sideways.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
As in After Effects, the right end of the Timeline's time ruler has a
composition marker bin (an original icon drawn in code). Dragging out of it
shows a marker following the pointer along the ruler's marker strip, snapping
like a dragged marker (current time, work area, other markers); releasing over
the time ruler's width adds a composition marker there with comp.addMarker (one
"Add Marker" undo step). Dropped back on the bin, nothing is added. The time
graph ends 18 px before the panel edge to leave the bin a column above the
layers' scroll bar. Automation id: timeline.markerBin.
Numpad * (or * typed with Shift+8), the keymap's Add Marker, now works in the
window too: egui has no key for it, so it comes as text.
The marker glyph and colour are shared helpers now (markers, nested markers,
the dragged marker).
Test: ui_timeline_zoom dragging_out_of_the_marker_bin_adds_a_comp_marker.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The in-window menu bar now works from the keyboard as After Effects' does on
Windows: Alt pressed and released on its own focuses the bar, Left / Right move
between its menus and Down, Enter or Space opens one. In an open menu Up / Down
move the highlight (skipping disabled entries, wrapping around), Right opens the
highlighted submenu and Left closes it (on a plain entry they open the next /
previous menu), Enter or Space chooses the entry and Escape closes the menus.
Hovering an entry moves the highlight there, and a highlighted entry scrolls
into view in long menus.
While a menu, context menu or popup list is open, or the bar has the keyboard,
keys go to the menus only: the app's shortcuts don't run (Space started the
preview behind an open menu) and the panels don't see the keys (Enter on an
entry no longer reaches the Project panel's rename).
Tall menus already scroll inside the window since #277 (ui_scrolling's
a_menu_taller_than_the_window_scrolls). F10 stays View > Switch 3D View > Front,
as in After Effects.
Test: ui_menus (shortcuts_wait_while_a_menu_is_open,
arrow_keys_drive_the_menu_bar, enter_chooses_the_highlighted_entry).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Review follow-ups on #312:
- `crates/ui-egui/src/theme.rs`'s `japanese_font_tests` still called `install` with two
arguments, so `cargo test` did not compile. Every call site now passes a language.
- The call sites passed the stored `general.language` preference. With the default `system`
that is `""`, which handed the CJK choice back to the environment locale — one macOS and
Windows usually do not set. They now pass `crate::i18n::language(app)`, the resolved
language, so Match System draws Chinese on a Chinese system.
- Changing Settings ▸ General ▸ Language only reached the fonts on the first frame.
`EffectcraftApp` now remembers the language its fonts were installed for and installs them
again when it changes, so the switch applies without a restart.
- Documented that `fonts::set_cjk_locale` is process-global: the Han preference therefore
applies to composition text as well as the interface chrome (docs/preferences.md and the
function's own comment).
Rebased onto main after #309 landed — `header.rs` gained an `install` call site in the
meantime, and the catalogs now resolve through `i18n::language`.
- `effectcraft_text::fonts::set_cjk_locale` lets the host say whose face should draw Han
ideographs instead of leaving it to the locale environment, and drops the fallback cache when
it changes. `han_order` reads that request first; the tag → order rule moves into the pure
`han_order_for`, so it can be tested without touching process state.
- `theme::install` now takes the stored `general.language` value and registers one CJK system
fallback per script in every font family, the interface's own script first: kana for Japanese,
a Han character for `zh-hans` / `zh-hant`, then the other script so names in it still render.
A face is registered at most once, and a machine with no CJK font installs nothing (the web
build installs nothing, as before).
- Before this the probe was always `あ`, so Han text was drawn by the Japanese face whenever the
operating system's locale was not Chinese: a Chinese interface, and Chinese file, layer or
template names on any locale, both got Japanese glyph forms.
- Tests: the tag → order rule (`zh`, `zh_Hans`, `zh-Hant-TW`, `ko`, `ja`, `en`, empty), and the
registration order plus once-only registration for a Chinese, a Japanese and an English
interface.
Image sequences were found only by the desktop media prober: one picked
numbered still brought its whole run, with no way to import just that
frame, picking every frame made one sequence item per frame, the browser
couldn't make sequences at all, and a gap in the numbering closed up
silently.
The engine now groups numbered stills itself (engine/src/sequence.rs):
one picked file brings its run from its folder, several picked files of
one run import as that range, a picked or dropped folder imports its
files with each run as a sequence, and Force Alphabetical Order takes
every image of that type in the folder by name. `file.import` takes
`sequence` (default on), `alphabetical` and `frameRate`; Replace Footage,
Reload, proxies and render outputs probe the same way. The media layer
probes one file; the browser lists its file table for the run and
registers every frame with its media pool.
File > Import > File... asks, like After Effects' "PNG Sequence"
checkbox, whether picked numbered stills import as a sequence, with
Force Alphabetical Order and the frame rate. Sequences are named
`shot_[0001-0250].png`.
Interpret Footage gains Missing Frames (Show Placeholder, colour bars as
in After Effects, the new default; Hold Previous Frame; Skip) and Start
Frame (the frame number at the footage's first frame: trims or pads the
head). Saved projects keep Skip, so their sequences play as before.
Form notes keep a readable width and the form fits its rows, so OK stays
in view. docs/footage.md explains importing footage and sequences.
Test: tests_sequence (engine: run, picked range, every frame, Sequence
off, folder, alphabetical, Interpret Footage Missing Frames / Start
Frame, Replace Footage and Reload), project sequence unit tests,
sequence_gaps_show_placeholders_hold_or_close_up (media),
import_dialog_offers_the_image_sequence_option and
dropped_frames_import_as_one_sequence_with_sequence_interpretation (UI).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Fixes#385, Fixes#386.
crop now maps the square's corners to the output corners, reads the source position as a
pixel-centre index, rounds to a whole 0-255 value, and takes a Border (Zero for the detector,
Replicate for the mesh), matching MediaPipe's CPU ImageToTensor. The frame size product is
checked, so an absurd size gives an lo-filled tensor instead of an overflow panic.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
EXtractoR's Red, Green, Blue and Alpha are text parameters naming
channels, and Effect Controls drew nothing for text parameters: there
was no way to choose a layer or channel. And a multi-layer OpenEXR file
whose colour is only in named layers (`diffuse.R`, Blender's
`ViewLayer.Combined.R`) didn't import at all: the image decoder wants an
unnamed RGB layer ("does not contain non-deep rgb channels").
- Effect Controls: EXtractoR gets a Layer popup listing the footage's
OpenEXR layers. Picking one sets red, green, blue and alpha to its
channels in one undo step, as After Effects' EXtractoR dialog does (a
one-channel layer such as depth goes in all three colours). Red /
Green / Blue / Alpha are popups of the layer's own R, G, B, A, every
channel of the file, and (none). The lists are read only while open,
so playing an EXR sequence doesn't parse files on the UI thread.
- `layer.channels {layer}` lists a layer's EXR layers and channels and
what each layer shows in red, green, blue and alpha, for agents.
- Media: an EXR without an unnamed RGB layer imports and shows its
colour layer (one named combined, beauty or rgba first); its channels
stay available to EXtractoR and the 3D Channel effects.
- Raster: AuxChannels::layers / layer_rgba and split_channel.
Test: extractor_shows_the_chosen_layer_and_channels_of_a_layered_exr
(host: a layered EXR written with the exr crate imports, layer.channels,
EXtractoR renders spec and depth; it failed to import before),
extractor_offers_the_exr_layers_and_channels (UI: Layer popup, one undo
step, channel popup), layered_exr_without_an_unnamed_rgb_layer_imports
(media).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The work area bar had no context menu. Right-clicking it now offers the
work area's commands from the menu bar: Trim Comp to Work Area, Lift
Work Area and Extract Work Area (After Effects).
Test: work_area_context_menu_trims_the_comp (ui_timeline_zoom).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Right-clicking imported footage offered no way to change its
interpretation. As in After Effects, right-clicking an item now selects
it, and footage items' menu has the File menu's footage commands:
Interpret Footage (Main, Proxy, Remember / Apply Interpretation),
Replace Footage, Reload Footage and Reveal in Explorer / Finder, with
the menu bar's labels, shortcuts and enabled state
(menus::submenu_at / entry_for). The panel's Interpret Footage button,
which only ever said to select footage, opens the dialog for selected
footage.
Test: footage_context_menu_interprets_the_footage (ui_project_rows).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The label menus listed the labels' names only, so a label couldn't be
picked by its colour. Every place a label is chosen now shows its
colour swatch before its name, as After Effects does: the Timeline's
label swatch (which now opens its menu on a click too, not only a
right-click), the Project panel's label swatch and a new Label submenu
in its item menu, Edit > Label, keyframe Label menus and the marker
dialog. One shared widget (widgets::label_entry / label_popup) draws
them; keyframe labels now show the names set in Settings > Labels.
Tests: label_swatch_opens_the_label_menu (ui_project_rows),
the_label_swatch_menu_shows_the_label_colours (ui_timeline_switches),
edit_label_menu_shows_the_label_colours (ui_panels).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Double-clicking a mask or shape path's point (or segment) put its points
in a free-transform box but left only the clicked point selected, so
once the box was gone a drag moved that one point. As in After Effects,
the double-click now selects all the path's points too: dragging inside
the box, or afterwards any of the points, moves the whole mask.
The test also draws a mask with the Pen, moves a point and undoes: each
undo takes back one action (the move, the closing, the last point).
Test: pen_mask_undoes_step_by_step_and_double_click_moves_the_whole_mask
(ui_viewer).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Position motion path drew a key's spatial handles only where its
tangent wasn't zero, and auto-Bezier keys have none at the path's ends,
so a two-key path (the usual start) had no handles to pull it into a
curve. Auto-Bezier keys now show a handle on such a side too, pointing
where the path leaves the key, a third of the way to the next key;
dragging it makes the key Bezier with that tangent. Linear keys still
show none.
Keyframe Interpolation > Spatial Bezier copied those zero tangents, so
the keys stayed straight with no handles (and read back as Linear); it
now gives them the same handles. The path keeps its shape and timing.
The handles come from the new keyframe::spatial_handles.
Tests: spatial_bezier_gives_a_straight_path_handles (engine),
a_straight_motion_path_has_handles_to_curve_it (ui_viewer).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The arrow keys did nothing to a selected layer, so it couldn't be lined
up precisely. As in After Effects, over the Composition panel or the
Timeline an arrow key moves the selected layers 1 pixel at the viewer's
magnification (half a comp pixel at 200 %, so sub-pixel steps come from
zooming in) and Shift+arrow 10, one undo step per press; holding the
key repeats. Not while typing or with mask points selected.
The move is the new engine command layer.nudge {x, y (comp pixels),
layers?}: through each layer's parents into Position (or X/Y Position),
a key at the current time when Position is animated, locked layers left
alone. It shares the Align panel's move.
Tests: nudge_moves_layers_by_comp_pixels (engine),
arrow_keys_nudge_the_selected_layer_at_the_viewer_magnification
(ui_viewer).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A footage or precomp layer dragged out past its source's end holds its
last frame, but its bar looked the same all the way, so the source's
end couldn't be seen. That part of the bar (and any part before the
source's start) is now darkened and striped in the label colour, with
an automation id (timeline.layer.<id>.bar.afterSource / beforeSource);
with time remapping on, the remap decides what shows there, so it isn't.
Enable Time Remapping on such a layer put its end key at the extended
out point; it now ends at the source's end, where the last frame starts
to hold (After Effects). Trimmed and reversed layers keep their keys.
Tests: time_remap_on_an_extended_layer_ends_at_the_source_end (engine),
the_bar_past_the_source_end_is_striped (ui_timeline_switches).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Selecting Time Remap in the Timeline (its name, which selects all its
keys) and pressing Delete removed the keys, and a second Delete then
deleted the whole layer. As in After Effects, Edit > Clear with Time
Remap selected now turns time remapping off (Layer > Time > Enable Time
Remapping off), in one undo step, and the layer stays; one selected
Time Remap key on its own is still deleted as a key.
Test: deleting_time_remap_turns_time_remapping_off (engine).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
While a mask was being drawn with the Pen, its open path was filled as
if closed, so the layer went black except inside the points placed so
far, which made rotoscoping by eye impossible. As in After Effects, an
open mask makes no transparency: the layer shows whole until the path
is closed. Effects that take a mask as a path (Stroke and the others)
still get open masks.
Test: open_masks_make_no_transparency (render).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Dragging an expression's pick whip always replaced the whole
expression, also while it was being edited (and the editor then wrote
its old text back over the reference). As in After Effects, a pick
whip pressed while the expression is being edited now puts the
reference at the cursor, replacing the selected text, and the editing
goes on with the cursor after the reference. Without an edit in
progress it replaces the expression as before.
`prop.pickWhip` takes the text being edited (`expression`) and the
selection in it (`range`, in characters) to insert there.
Tests: the_expression_pick_whip_inserts_at_the_cursor_while_editing
(ui_inline_edit), pick_whip_inserts_into_the_expression_being_edited
(engine).
Co-Authored-By: Claude Opus 5.5 <[email protected]>