What: the Composition panel's Resolution (Auto, Full, Half, Third, Quarter, Custom) is now a
property of the composition (`Comp::resolution`, moved with its `Resolution` type from the UI
into the project crate) instead of one global viewer setting. Switching to another comp shows
that comp's resolution, switching back shows the first one's again, and saving and reopening the
project keeps them. `view.res.full|half|third|quarter|custom` and the new engine command
`view.res.auto` set it (headless too, with an optional `comp`); `ui.set {viewer: {res}}` sets
the active comp's and `ui.inspect` still reports `ui.viewer.res`; `comp.info` reports
`resolution` and `resolutionFactor`; View ▸ Resolution's check marks come from the engine.
Why: After Effects saves each composition's Resolution with the project (CompItem.resolutionFactor
is a per-comp attribute). As there, changing it marks the project modified but adds no undo step:
undo, redo and History jumps carry the current values over to the restored state. It is not part
of the comp's pixels (`Comp::same_pixels` ignores it and `Comp::pixel_form` normalises it), so
changing it keeps the comp's RAM and disk cache keys. Auto is not written (serde default and
skip_serializing_if), so projects saved before open with Auto and older versions read new files.
The magnification is not saved per comp: Adobe's documentation doesn't say it is.
Tests: project `comp_resolution_is_saved_and_old_projects_load_with_auto`,
`resolution_factors_and_scales`; engine
`each_comp_keeps_its_resolution_outside_the_undo_history_and_in_the_file` (switching comps,
no undo step, undo/redo keep it, save/open round trip, menu check marks, Custom dialog);
ui-egui `each_comp_keeps_its_viewer_resolution` (the viewer popup on comp A, comp B through
`ui.set`, back to A, unchanged frame cache identity, save/open). They don't build before the
change (no `Comp::resolution`, no `view.res.auto` engine command).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cause: Auto dropped to Half below 75 % magnification, so text between
50 % and 75 % was rendered with fewer pixels than the screen shows and
looked soft. Below 100 % the frame texture was minified with a plain
bilinear sampler and no mip levels (Full at 46 % skipped most frame
pixels), so edges stair-stepped.
Fix:
- Auto renders the pixels the magnification needs, as in After Effects:
the coarsest whole factor that still gives every screen pixel a
rendered one (Full above 50 %, Half down to 33.3 %, Third down to
25 %, Quarter below).
- GPU frames: the display texture carries a full mip chain, each level
the 2 x 2 mean of the one above (display.wgsl `mip`, same bind group
layout as `display`), sampled trilinearly with Viewer Zoom Quality
More Accurate (Faster keeps the nearest pixel of the full-size frame).
- CPU frames (egui-wgpu textures have no mips): uploaded averaged down
by the whole factor that leaves about one texel per screen pixel, the
same box average as the texture-limit fit (#201), re-uploaded when the
magnification calls for another factor; drawn with texture coordinates
that keep each texel over its pixels when the size isn't a multiple.
Show Channel / exposure textures follow.
Tests: gpu tests::display_frames_are_mipmapped (level count and level 1
= 2 x 2 means of level 0), viewer_tools
auto_resolution_follows_the_magnification (60 % was Half) and
minified_frames_keep_their_texels_in_place.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Text is vector in After Effects and stays sharp at any Scale. Here a
text layer was drawn at its font size and the bitmap resampled by Scale
unless Continuously Rasterize was on, so text grown with Scale went soft.
This reworks #200 (reverted in 29c4d3a for kinetic_typography).
Why kinetic_typography failed: its time remap key at 1.9 s sits on
frame 23 (1.917 s), so main-comp 0.3 s shows the precomp at 1.6026 s,
but the test compared it with the precomp rendered at 1.6 s. The moving,
motion-blurred title already differed by 0.89 of the 1.0 mean-difference
budget on main (0.987 in all); with continuous rasterisation the "Sub"
line, whose expression scale is 84.00 % at 1.6 s and 84.105 % at
1.6026 s, falls on either side of the 2^-1/4 quarter-octave step and was
drawn at 0.84 at one time and at 1.0 at the other, pushing the total to
1.075.
Fix: raster_scale treats text layers as always continuously rasterised
(shape layers and vector footage still follow the switch), and rounds
the quarter-octave step up past rounding noise only: an unscaled layer
at a fractional position measured its scale as 1 + 1e-15 and was
rasterised at 2^1/4, softening unscaled text. The QA test now compares
the remapped precomp with the precomp at the time the remap really
shows (read back from the property): difference 0.63, all of it the
title's motion blur, the Sub line identical.
Tests: tests_collapse::text_scaled_up_stays_sharp_without_the_switch
(from #200: 10 px text at 800 % matches 80 px text),
tests_collapse::unscaled_text_rasterises_at_the_output_scale (fails
without the noise tolerance), automation qa kinetic_typography.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cause: the interior styles were blended over the layer body with the
content's coverage folded into their opacity. On an edge pixel at 50 %
coverage a Gradient or Color Overlay went over the half-transparent body
as a half-transparent layer, so the result kept a quarter of the layer's
own fill and its alpha rose to 0.75: a light rim (the white fill
showing through) on styled text.
Fix: blend_clipped composites the interior styles (Gradient Overlay,
Color Overlay, Satin, Inner Glow, Inner Shadow and the inside of
Stroke) onto the layer within its shape: the body is divided by the
content coverage, blended with the style at its own opacity, and
multiplied back, so the alpha is kept and an edge pixel mixes the style
with the layer's colour inside the shape only, as a clipped layer does.
Fill Opacity and Blend Interior Styles as Group behave as before.
Tests: tests_styles::overlays_leave_antialiased_edges_clean (a masked
square with half-covered edge columns; Color Overlay, Gradient Overlay
and an inside Stroke now match a plain layer of the overlay colour;
Color Overlay's edge was 0.41 red against 0.23 before).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cause: each glyph's fill was rasterised and composited on its own, so
where two glyphs share an edge (the crossbars of "ff") two half-covered
pixels composited to 0.75 coverage instead of 1: a thin light seam,
where After Effects draws the line as one outline.
Fix: consecutive fill passes of the same opaque colour, without Blur,
under Normal Inter-Character Blending are rasterised as one coverage
mask (the glyphs' outlines in layer space, wound the same way so
overlaps add up under NonZero). Characters of other colours, translucent
or blurred ones, strokes and the other blending modes are drawn one by
one as before.
Tests: text::tests::touching_glyphs_join_without_a_seam (two glyphs
sharing an edge mid-pixel: 0.75 before, 1.0 now; opposite windings and
overlaps stay filled; other colours and Multiply still composite one by
one).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Right-clicking a property's name in the Timeline did nothing, so
Position's dimensions could be separated only from the Graph Editor.
As in After Effects, it now opens the property's menu: Reset, Edit
Value…, Separate Dimensions (Position and, once separated, X / Y / Z
Position, checked), Add / Remove Expression and Add Property to
Essential Graphics. Only Position separates, as in After Effects
(Scale stays one property).
`prop.reset` without `default` used to leave the value as it was: it
now gives the property its own default (an effect parameter's, or a
Transform property's, shared with Layer ▸ Transform ▸ Reset), which
also makes the Properties panel's Reset reset the values. `prop.set
{layer, prop}` without `value` opens the value dialog, like
`keys.set`.
Tests: timeline_property_names_have_the_property_menu
(ui_blank_context_menus), reset_gives_a_property_its_default (engine).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Tab in a value being typed (hot text) dropped the keyboard: egui moved
the focus to the next widget, and the inline editors asked for the
focus again every frame, which also reset what keys a field keeps for
itself.
As in After Effects, Tab now commits the value and types in the next
value field of the same panel (the next dimension, then the next
property's), Shift+Tab the previous one. The hot-text fields of each
panel register in drawing order; the dock tags each panel's Ui so the
order stays within it. `keep_focus` asks for the focus only when the
field doesn't have it.
Tests: tab_commits_a_value_and_moves_to_the_next (ui_inline_edit:
Timeline and Effect Controls).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The inline expression field took its height from the saved
expression, so lines typed into it ran over the rows below until the
expression was committed.
Its height now comes from the text being typed (up to 8 lines, as
before), and its bottom edge drags it to any height in whole lines, as
in After Effects; the dragged height is kept per property in the
Timeline state (`expr_lines`).
Tests: the_expression_field_grows_while_editing_and_drags_taller
(ui_inline_edit).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Dropping the expression pick whip on one of a property's values (the X
of Position) referenced the whole property. As in After Effects, a
drop on one value now picks that dimension: `transform.position[0]`;
a drop on the name still picks the whole property.
`prop.pickWhip` takes `target.dimension`.
The rest of #363 is unchanged: inserting at the cursor landed with
#284, and After Effects' property pick whip in the Parent & Link
column is left for later.
Tests: pick_whip_picks_one_dimension_of_the_target (engine),
the_expression_pick_whip_picks_the_dimension_it_is_dropped_on
(ui_inline_edit).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A pick whip dragged to the top or bottom of the Timeline's layers left
them where they were, so a property out of view couldn't be picked.
While a pick whip (expression or parent) is held within a row of the
top or bottom edge of the layers, or past it, the layers scroll that
way, faster the deeper, as in After Effects.
Tests: the_pick_whip_scrolls_the_timeline_at_its_edges
(ui_inline_edit).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
The Time Display Style could only be changed in Project Settings. As in
After Effects, Ctrl-click (Cmd-click on macOS) on the current-time
display of the Timeline or the Composition panel now toggles it between
Timecode and Frames (Feet + Frames goes to Timecode). It runs
`file.projectSettings {timeDisplay}`, so it is one undo step.
Under the Timeline's display the other style shows now (frames under
timecode, timecode under frames), as in After Effects; it always showed
frames.
Tests: ctrl_click_on_the_time_display_toggles_timecode_and_frames
(ui_inline_edit).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Every bounding-box handle in the Composition viewer showed the
north-west / south-east resize cursor, so two corners and all four
edges showed the wrong direction.
Each handle now takes the cursor along the direction it scales, worked
out from the on-screen box: a corner points between its two edges (the
box's diagonal whatever its proportions), an edge handle across its
edge. The cursors turn with the layer's rotation, as in After Effects.
Tests: handle_cursors_follow_each_handle_and_the_layer_rotation
(ui_viewer: a wide box at 0°, 90° and 45°).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cause: pipeline::Cx rendered with Backend::Auto, which picks the CPU or the
GPU compositor for each top-level frame from measured frame times
(render::AutoPick: warm-up on both sides, a re-probe every 48 frames). The
two compositors differ slightly, so frames came out differently depending
on which side the timings favoured in that run, and two GPU exports of the
same frames differed.
Fix: the job renders every frame on the project's renderer, as After
Effects renders a job with one renderer: Backend::Gpu with Mercury GPU
Acceleration (frames the GPU declines still render on the CPU, as they do
in every GPU render), Backend::Cpu with Mercury Software Only. The viewer
and previews keep Auto. Docs (architecture, parity, web) say so.
Tests: render_settings::a_job_renders_on_one_compositor exports ten frames
with a stand-in GPU compositor that keeps Auto history and draws solid
green: with Mercury GPU Acceleration every frame is green and two runs are
identical (with Auto the warm-up sent frames to the CPU: it fails), with
Software Only the stand-in is never asked. The web worker GPU job test's
comment no longer describes Auto.
Diagnosis by @pat-the-wizard.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Cause: pipeline::Cx::render_at built the job's Renderer without a layer
cache, so every layer of every frame rendered from scratch (source, masks,
effects, styles), and the GPU compositor, whose uploads are keyed by the
cached buffers, uploaded every layer again on every frame.
Fix: each job (Cx) owns a LayerCache, the cache type the viewer uses, with
its default budget (1 GiB, the default Settings > Memory & CPU layer cache),
shared by the job's parallel frames and dropped with the job. With deferred
GPU readbacks it holds the accelerator's miss gate, as the job workers'
caches do. Cx::renderer builds the job's renderer (options, accelerator,
cache) for render_at and the test.
Tests: pipeline::tests::layers_render_once_per_job exports ten frames of a
solid animated only in Transform: the cache misses of the first frame are
the only ones (it fails without the cache: no layer is cached), and every
frame equals the frame rendered without the cache.
Diagnosis by @pat-the-wizard.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
#402 made Effect Controls' rows compact and aligned, as After Effects
draws them, and also added thin separator lines between parameter rows
in Effect Controls and the Timeline, with a Settings > Appearance > Show
Parameter Row Separators preference. After Effects has neither, so the
lines, the preference and the Tokens field go; the compact rows, the
alignment and the automation ids stay.
Saved settings with the old key load as before (unknown keys are
ignored).
Test: ui_parameter_spacing keeps the row geometry, hit target and
nesting tests; the separator tests go with the feature.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
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]>
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]>
The menu bar has had a catalog since #229 (keyed by `(command, English label)`); everything below
it - panel labels, buttons, field names, tooltips, the sentences a panel draws itself - was written
straight into the code and stayed English in every language (#333).
This adds `crates/ui-egui/src/i18n/ui.rs`:
- Rows are keyed by the English source string, so a panel wraps a literal it already has instead of
inventing an id, and a row that is missing falls back to English rather than to an empty label.
- `tr("Rename")` translates a plain string; `tr_args("Proxy Use: {}", &[&quality])` fills the values
of a row that carries them. `tr_args` writes into its own buffer, so it cannot panic on a mismatch,
and a test keeps every row's placeholder count equal to its English source.
- The language in force is set once per frame from the resolved interface language - the same value
the CJK fonts are installed for - and kept in a thread-local, so the panels do not have to thread
it through every function. The render threads never draw interface text.
- Simplified and Traditional Chinese are complete (384 strings); the Japanese rows are still open,
and the catalog takes them without any other change.
Tests: the two Chinese catalogs must list the same source strings, no empty or duplicated rows, every
row must be asked for by a converted panel, and a converted panel that gains a hard-coded string in a
common call shape fails the build.
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]>
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]>
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`.
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]>
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]>
Keep serving during long renders, recover failed workers, and remove only files opened by the interrupted export. Cover progress, cancellation, EOF and output ownership with regression tests.
Co-Authored-By: OpenAI Codex <[email protected]>
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
- New TRADITIONAL_CHINESE catalog: the same (command, English label) keys as JAPANESE and
SIMPLIFIED_CHINESE, one entry per fixed menu row (618 rows). The wording follows the
Traditional Chinese catalogs PhotoCraft, PdfCraft, VectorCraft and LightCraft already ship
(`檔案`, `開啟`, `儲存`, `圖層`, `遮色片`, `影格`, `預設集`, `快速鍵`, …).
- Match System maps a Traditional Chinese locale (`zh-TW`, `zh-HK`, `zh-Hant`, `zh_MO`) to
`zh-hant` instead of English, now that there is a catalog for it.
- `simplified()` and `traditional()` replace the Simplified-only `chinese()`; the macOS Services
item is 服务 in Simplified and 服務 in Traditional.
- `general.language` gains 繁體中文; `docs/preferences.md` documents the locale mapping.
- Tests: all three catalogs must cover the same fixed TREE entries; the shape check covers both
Chinese catalogs; label/undo/panel-title/submenu/native-menu behaviour in zh-hant with file and
layer names kept verbatim; the Traditional locale mapping; the preference round-trips.