Commit Graph

446 Commits

Author SHA1 Message Date
Brandon Thomas 6304956628 Merge pull request #835 from pravdomil/claude/cmd-arrow-frame-nav-3a2fc1
Shortcuts: Cmd+Left / Cmd+Right Arrow step a frame
2026-10-12 03:23:12 -04:00
Brandon Thomas 30f7ce8ae5 Merge pull request #826 from luniiya/feat/theme-favorites
UI: favorite themes and appearance shortcuts
2026-10-12 03:23:06 -04:00
Brandon Thomas d1e88ee52b Merge pull request #807 from cdinhoo/fix/nonblocking-expression-diagnostics
Performance: reduce UI stalls while dragging layers
2026-10-12 03:23:02 -04:00
Brandon Thomas e8e896e1d6 Merge pull request #799 from MarekSpodBiedry/issue-749
Automation: a layer or comp id sent as a string resolves as an id (#749)
2026-10-12 03:23:00 -04:00
Brandon Thomas 2cf28e5e24 Merge pull request #798 from MarekSpodBiedry/issue-795
Keep footage dropped on the timeline at its full length (#795)

# Conflicts:
#	crates/engine/src/tests_timeline.rs
2026-10-12 03:22:58 -04:00
Brandon Thomas 58b09391bd Merge pull request #789 from vangel76/gap-project-size-limits
#788: hold opened projects and size commands to the 30,000 px limits

# Conflicts:
#	crates/engine/src/commands/layer.rs
2026-10-12 03:22:54 -04:00
Brandon Thomas 909572dffd Merge pull request #785 from vangel76/gap-fuzz-project-open
#779: fuzz File > Open; refuse deeply nested XML projects
2026-10-12 03:22:51 -04:00
Brandon Thomas 24f98a0242 Merge pull request #778 from vangel76/gap-uncompressed-mov
#372: uncompressed QuickTime (raw RGB / ARGB) output module format

# Conflicts:
#	docs/gaps.md
2026-10-12 03:20:53 -04:00
Brandon Thomas 9ebabccbec Merge pull request #775 from vangel76/script-766-mouse-events
#766: ScriptUI mouse events reach script listeners

# Conflicts:
#	crates/engine/src/scriptui.rs
#	crates/script/src/lib.rs
#	docs/scripting-parity.md
2026-10-12 03:20:52 -04:00
Brandon Thomas be1d9928e0 Merge pull request #774 from vangel76/gap-proportional-scrubbing
#770: Proportional Scrubbing of Timeline values (Ctrl+Alt / Cmd+Opt)

# Conflicts:
#	docs/ui-parity.md
2026-10-12 03:20:51 -04:00
Brandon Thomas 3d02e65f2b Merge pull request #765 from vangel76/gap-dpx-import
#756: import DPX and Cineon stills and sequences

# Conflicts:
#	crates/media/tests/footage.rs
#	crates/ui-egui/src/menus.rs
#	docs/file-format-parity.md
#	docs/gaps.md
2026-10-12 03:20:04 -04:00
Brandon Thomas 261a5b125b Merge pull request #754 from vangel76/gap-surface-colors
#315: Front / Bevel / Side / Back Color for extruded shapes and text

# Conflicts:
#	docs/gaps.md
2026-10-12 03:20:01 -04:00
Brandon Thomas d57459a7d0 Merge pull request #742 from vangel76/gap-fbx-import
#736: import FBX 3D models (binary and ASCII)

# Conflicts:
#	crates/engine/src/commands/app_more.rs
#	crates/engine/src/commands/model3d.rs
#	crates/engine/src/media_browser.rs
#	crates/media/src/lib.rs
#	crates/media/src/probe.rs
#	crates/model/Cargo.toml
#	crates/model/src/lib.rs
#	crates/ui-egui/src/menus.rs
#	docs/file-format-parity.md
#	docs/gaps.md
2026-10-12 03:20:00 -04:00
Brandon Thomas d9dddb90b7 Merge pull request #781 from vangel76/script-773-repeating-tasks
#773: run repeating app.scheduleTask tasks until cancelTask
2026-10-12 03:03:45 -04:00
Brandon Thomas bcb9ee1fa5 Merge pull request #764 from vangel76/gap-transition-presets
#757: Transitions animation presets in Effects & Presets
2026-10-12 03:03:31 -04:00
Brandon Thomas c72e47b7dc Merge pull request #761 from vangel76/gap-variable-font-filter
#741: font menu filters to variable fonts
2026-10-12 03:03:28 -04:00
Brandon Thomas b14096c335 Merge pull request #744 from vangel76/fix-732-toolbar-fill-type
#732: toolbar Fill/Stroke Options change the selected shape layers' kind
2026-10-12 03:03:20 -04:00
Brandon Thomas 8176c86043 Merge pull request #739 from vangel76
# Conflicts:
#	docs/gaps.md
2026-10-12 03:03:10 -04:00
Brandon Thomas e98788690c Merge pull request #737 from vangel76/gap-copy-frame-clipboard
#730: Composition ▸ Copy Frame to Clipboard
2026-10-12 03:02:59 -04:00
Brandon Thomas e5ff011296 Merge pull request #735 from vangel76/gap-stl-import
#729: import STL 3D models (binary and ASCII)
2026-10-12 03:02:58 -04:00
Brandon Thomas d7fe6d2e56 Merge pull request #705 from tylerjenningsw/enhance/custom-ui-scale
Settings: allow custom UI scale percentages (#682)
2026-10-12 03:02:46 -04:00
Brandon Thomas 4482ad9634 Merge pull request #703 from didpublishing/fix/windows-path-tests
Tests: six engine tests no longer assume the working directory's drive on Windows
2026-10-12 03:02:44 -04:00
Brandon Thomas 162a2e2e01 Merge pull request #699 from didpublishing/fix/596-gpu-information
#596: GPU Information names the adapter, or why the GPU compositor is off
2026-10-12 03:02:43 -04:00
MarekSpodBiedry 5df37babc3 Keep Footage panel overlay/insert edits at full length past the comp end (#795) 2026-10-12 08:05:22 +02:00
pravdomil 88bf89ea5d Shortcuts: Cmd+Left / Cmd+Right Arrow step a frame
Previous / Next Frame also answer to Cmd+Left / Cmd+Right Arrow, and Back / Forward
10 Frames to Cmd+Shift+Left / Right, next to the Page keys (After Effects' second
default keys). A command spec can now list several default shortcuts
(`alt_shortcuts`, `cmd!(…, ["PageDown", "Cmd+ArrowRight"], …)`); menus and tooltips
keep showing the first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 16:26:27 +02:00
Luniiya e1bcaab0a0 UI: favorite themes and appearance shortcuts
Port of storytold/photocraft#2445. Each theme in the appearance picker gets a
star at the right edge of its row. Starred themes are saved with the
preferences and listed in a right-click menu on the header's appearance button,
next to Appearance Settings, so switching between favorites takes one click.
2026-10-11 15:46:18 +02:00
cdinhoo be6134e8ad Performance: reduce UI stalls while dragging layers 2026-10-11 15:49:21 +03:00
MarekSpodBiedry bee798c190 Automation: a layer or comp id sent as a string resolves as an id (#749) 2026-10-11 13:54:43 +02:00
MarekSpodBiedry db11dffa18 Keep footage dropped on the timeline at its full length (#795) 2026-10-11 13:53:52 +02:00
vangel76 765ba157be #788: hold opened projects and size commands to the 30,000 px limits
Problem: comp.new/comp.settings clamp sizes to 30,000 px, frame rates to
0.001-1000 fps and motion-blur samples to 2-64, but reading a project
(.ecproj and .ecprojx, templates, recovery, offload) accepted any value:
a 4-billion-px comp, solid or footage frame, a 0 or 10^18 fps rate
(zero-length frames), a negative duration or 4 billion blur samples all
reached the renderer. New Solid, Layer Settings, Import Solid/Placeholder,
Crop Comp and PSD import set sizes without the upper bound, and comp
sizes past 2^32 wrapped.

Fix: one set of limits in effectcraft_project::limits. Project::from_json
brings every item (and proxy) within them; from_json_repaired also says
what it changed, which file.open returns as `repaired` and shows in a
toast, so the project still opens. Project::add_item applies the same
limits to every new item, and the size-setting commands clamp with the
shared helpers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:22:59 +02:00
vangel76 b747ffc986 #779: fuzz File > Open; refuse deeply nested XML projects
Problem: nothing fuzzed project files. The hand-written .ecprojx XML reader
recursed once per nested <arr>/<obj> with no limit, so a deeply nested file
overflowed the stack and aborted the app instead of failing to open.

Fix: a dependency-free, deterministic, seeded mutation fuzzer for File > Open
(JSON and XML seeds from the demo, byte and JSON-structure mutations, the real
file.open path, first queries and a thumbnail render; each input under
catch_unwind with a deadline; bounded for cargo test, longer runs via env
vars). The XML reader now stops at the JSON reader's 128-level limit, with a
regression test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 13:02:40 +02:00
vangel76 7085816c98 #773: run repeating app.scheduleTask tasks until cancelTask
Problem: app.scheduleTask(code, delay, true) queued the task and then
skipped it (eval_with_tasks: `if repeat { continue; }`), so repeating
tasks - what ScriptUI panels use for polling and refreshing - never ran.
One-shot tasks scheduled by handlers or in the Script Console never ran
either.

Fix: tasks live per script context (crates/script/src/tasks.rs) with a
due time. Frontends run the due ones every frame through the new
ScriptUi::run_tasks hook (the egui app does; agents and headless hosts
use the `script.runTasks` command). Script-host threads get a Tick
message, inline hosts (Script Console, web) run in their kept context.
A repeating task runs every `delay` ms until app.cancelTask, until it
throws (reported once, then cancelled), or until its owner goes: the
script's last window closing, or - for scripts without windows and the
console - the project being closed or replaced (ScriptUi::project_epoch).
Bounds: at least 50 ms between runs, at most 64 pending tasks per
script, one run per pass (no catch-up bursts), task code nested too
deeply is refused, the engine's loop and recursion limits apply. Ticks
only reach the hosts of the session that scheduled them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:56:09 +02:00
vangel76 575642a5ed #372: uncompressed QuickTime (raw RGB / ARGB) output module format
Problem: the Render Queue had no single-file uncompressed output, so
handing a render to an external encoder meant a lossy codec, ProRes,
or an image sequence plus a separate WAV.

Fix: add the "QuickTime (Uncompressed)" Output Module format. Frames
are stored as QuickTime 'raw ' samples: 8-bit RGB (depth 24, rows
padded to even bytes) or ARGB with RGB + Alpha (depth 32), written
through FilmCraft's existing QuickTime muxer with PCM audio
(16/24-bit or float) like ProRes. Format name `uncompressed` on the
control channel; disk-space estimate and Sample Format menu included.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:43:33 +02:00
vangel76 1c06a793a5 #766: deliver ScriptUI mouse events to script listeners
Problem: ScriptUI controls never sent mouse events to scripts.
addEventListener("mousedown" | "mouseup" | "mousemove" | "mouseover" |
"mouseout" | "click") listeners never ran, so custom-drawn controls
(onDraw + mouse, as DuScriptUI builds them) could not react to the pointer.
docs/plugins.md listed this as missing.

Fix: scriptui.js builds a ScriptUI MouseEvent (clientX/clientY relative to
the target, button, detail = click count, screenX/Y, modifier keys,
getModifierState, stopPropagation, preventDefault) and delivers it in the
capture, target and bubble phases. addEventListener takes the capture flag.
The parent walk is bounded, so a script-made parent cycle cannot hang it.
Each published control lists the mouse events a listener wants
(Widget::mouse, inherited from containers). The new scriptui.mouse
command dispatches one event with clamped, finite values and rejects
unknown types and disabled controls. The egui ScriptUI view hit-tests the
pointer over script windows and docked panels, skipping covered layers and
windows blocked by a modal dialog. It sends mouseover/mouseout,
mousemove, mousedown/mouseup with click counts, and a click carrying the
pointer details in place of the plain click, so a checkbox still toggles
once. Handlers run under the existing script limits; a throwing listener
reports an error and the window keeps working.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:39:23 +02:00
vangel76 ea0414e280 #770: Proportional Scrubbing of Timeline values (Ctrl+Alt / Cmd+Opt)
Problem: After Effects 26.2 spreads a Ctrl+Alt (Cmd+Opt) hot-text scrub
over the selected layers by selection order (first unchanged, last the
whole change, the ones between even shares). Our multi-layer scrub
(#491) always moved every selected layer by the same change.

Fix: prop.set takes `proportional` (with `selected` + `offset`); each
participating layer, the scrubbed one included, gets index/(n-1) of the
change in selection order, in one undo step; the first layer is left
alone (no key added). The Timeline value editor sends it while Ctrl+Alt
(Cmd+Opt) is held; that chord no longer fine-scrubs or unlinks Scale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:39:06 +02:00
vangel76 557c0bdbba #756: import DPX and Cineon stills and sequences
Problem: the Media Browser listed .dpx files and After Effects imports
DPX and Cineon scans (stills and numbered sequences), but import
refused them: nothing could decode either format, and .cin was not even
an image to the browser, so its runs never grouped into sequences.

Fix: a small pure-Rust reader written from the public SMPTE 268M and
Kodak Cineon specifications (crates/media/src/dpx.rs): luma, RGB, RGBA
and ABGR at 8/10/12/16 bits, 10-bit filled method A/B, 12-bit in 16-bit
words, both byte orders, lines padded to 32-bit words, unrotated
orientations and the pixel aspect ratio; the codec names the transfer
(log, linear, ...) and code values are shown as stored. RLE, signed,
float, Y'CbCr, packed 10/12-bit and transposed files are refused with a
clear error; every header field is bounds-checked and the data length
verified before allocating. Probe and frame decode use it first;
is_importable, the Media Browser kinds and the import dialog list
.dpx/.cin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:24:16 +02:00
vangel76 81bec748ec #757: Transitions animation presets in Effects & Presets
Problem: EffectCraft shipped only 8 original text-animator presets against
After Effects' 621; there was no Transitions category (ranked gap #7).

Fix: an original preset library, crates/engine/presets/transitions.json
(23 presets on our own Transition effects and layer opacity: fades,
directional and diagonal wipes, clock and fan reveals, blinds and slats,
pixel/mosaic dissolves, card flips, CC grid, radial scale, line sweep and
jaws wipes; In and Out). New commands anim.applyBuiltinPreset (adds the
effects with values and keyframes from the current time, one undo step,
reports any path that doesn't fit) and anim.builtinPresets. Effects &
Presets lists them under * Animation Presets > Transitions; double-click
applies. Sidecar and ATTRIBUTION.md entry; gaps/effects-parity updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:24:10 +02:00
vangel76 7afe723260 #741: font menu filters to variable fonts
Problem: After Effects 26.3's font menu can show only variable fonts.
EffectCraft's font menus (Character panel, Properties ▸ Text) had no such
filter, and text.fonts / MCP list_fonts didn't say which families are
variable.

Fix: the font scan marks a face variable when its table directory has an
fvar table declaring axes (header read only, no extra file loading). The
font menu gets a "Variable fonts only" checkbox above the results; it
combines with the typed search, keeps the keyboard in the search field and
stays set while the app runs. text.fonts reports `variable` per family and
takes `{variable: true}`; list_fonts passes it through. Catalog rows for
ja, zh-Hans, zh-Hant and uk.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:14:50 +02:00
vangel76 fb5a5e9ac0 #315: Front / Bevel / Side / Back Color for extruded shapes and text
Problem: In Advanced 3D, an extruded shape or text layer was one mesh in
its paint colour. After Effects colours the front, bevel, side and back
of an extrusion separately (shape Contents ▸ Add ▸ Front/Bevel/Side/Back,
text Animate ▸ Front/Bevel/Side/Back), and EffectCraft had none of it.

Fix: The extruder can split its mesh by surface (front cap, bevel rings,
walls, back cap). Shape layers get Front / Bevel / Side / Back contents
items (layer.addShapeItem kinds frontColor…backColor, one item per
group, colouring every paint of the group and its nested groups; the
innermost wins). Text animators get Front / Bevel / Side / Back Color,
blended over the character's fill or stroke by the selection. Paints
with a surface colour render as one mesh per surface. Menus, the
Contents Add: pop-up and the menu translations list them. The other
surface material properties (Ambient, Diffuse, Specular, Metal,
Reflection, ...) remain.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 12:05:36 +02:00
vangel76 0ae960102c #732: toolbar Fill/Stroke Options change the selected shape layers' kind
Problem: with shape layers selected, the Tools bar's Fill Options / Stroke
Options (None, Solid Color, Linear Gradient, Radial Gradient, and the
swatch's Alt-click cycle) only changed the defaults for new shapes.
shape.fillStroke had no kind, so a selected layer's fill or stroke could
not be removed or turned into a gradient from the toolbar; it had to be
edited in the Timeline. The swatch also kept showing the tool's kind, not
the layer's.

Fix: shape.fillStroke takes fillType / strokeType. None switches the
layers' Fills and Gradient Fills (or Strokes) off; another kind switches
them on and turns them into that kind, keeping the uid and the
properties both kinds share (blend mode, opacity, width, dashes...), and
gives a group with paths and no such item one (gradients span the
group's paths). It reports the first layer's fillType / strokeType, and
the toolbar shows them. The kind picks in the options popup and the
Alt-click cycle apply to the selected shape layers, one undo step each.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:49:48 +02:00
vangel76 977532f135 #736: import FBX 3D models (binary and ASCII)
Problem: After Effects 26 imports FBX models into Advanced 3D comps; File > Import rejected
.fbx, so FBX product shots and scenes could not be used.

Fix: a clean-room FBX 7.x reader in effectcraft-model (binary node records with 32/64-bit
offsets and zlib arrays, ASCII syntax) and a scene builder: meshes with normals, UVs and
per-polygon materials, the Model hierarchy with the SDK transform (pivots, offsets, pre/post
rotation, rotation order, geometric transform), Lambert/Phong materials onto
metallic-roughness with embedded or relative textures, and GlobalSettings axes turned to
Y-up. Wired into import, probing, the media browser and the file dialog. Hostile input
(truncation, corruption, huge arrays, deep nesting) fails with an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:46:43 +02:00
vangel76 d58253cc5a #738: Edit ▸ Paste pastes SVG markup as a shape layer
Problem: After Effects 26.3 pastes copied vector artwork as shape layers. Edit ▸ Paste only
knew the app's own clipboard, so SVG markup on the system clipboard was ignored; turning SVG
into shapes needed an imported footage file and Create Shapes from Vector Layer.

Fix: a new `edit.pasteSvg {svg, name?, comp?}` command builds a shape layer from SVG text with
the same conversion as Create Shapes from Vector Layer (paths, fills, strokes, gradients, clip
masks), above the selected layer with the SVG canvas centred in the comp, as one undo step;
oversized, non-SVG or shapeless text is rejected. In the UI, Ctrl/Cmd+V (and the native Edit ▸
Paste) run it when the system clipboard holds SVG. Copying in the app already replaces the
system clipboard with a note, so SVG there is newer than the app's clipboard.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:43:18 +02:00
vangel76 ddcca91d50 #730: Composition ▸ Copy Frame to Clipboard
Problem: After Effects 26.3 copies the current composition frame to the
system clipboard; EffectCraft could only write it to a file (Save Frame As).

Fix: new command comp.copyFrame {comp?, time?, scale?} renders the frame as
Save Frame As ▸ File does and emits Event::CopyImage (pixels not
serialized); the egui frontend puts it on the clipboard after checking the
buffer size. In the Composition menu (translated) and the viewer's context
menu; drivable by id over the control channel / MCP.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:33:45 +02:00
vangel76 5661d5111c #729: import STL 3D models (binary and ASCII)
Problem: After Effects 26 imports STL models besides glTF and OBJ; we
refused .stl files, and the Media Browser and import dialog did not list
them as models.

Fix: a pure-Rust STL reader in effectcraft-model (binary and ASCII,
detected from the contents; several ASCII solids and polygon loops;
VisCAM/SolidView and Materialise Magics facet colours as plain
materials; Z-up turned to the model's Y-up; faceted normals from the
winding). Truncated, empty or non-finite input is an error, never a
crash. .stl is now a model extension for import, probing, the Media
Browser, the import dialog and the system report.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 11:32:11 +02:00
tylerjenningsw 42d8120147 Settings: allow custom UI scale percentages (#682)
Settings > Appearance > UI Scale offered only 75, 100, 125, 150, 175 and
200 %, so a size between 75 and 100 wasn't possible. It is now the shared
numeric field (Kind::Int(75, 200, "%")): type or drag any whole
percentage. The engine already stored the scale as a clamped integer and
the UI applies it with set_zoom_factor, so the range, the default, live
preview, Cancel and saved preferences are unchanged; the preset list
goes.

Numeric settings now apply a typed number once it is entered (Enter or
leaving the field), not at every keystroke: typing 85 used to apply the
partial 8, clamped to 75, and resize the whole interface before the 5.
Dragging still applies as it moves.

Tests: the UI Scale test types 85 a key at a time (nothing applies and
the field keeps the keyboard until Enter), Cancel, 50 and 300 clamp to
75 and 200, 90 with OK, and a new app with the saved preferences starts
at 90 %. The GPU Information transaction test types 125.
2026-10-10 23:47:27 -04:00
Dennis M. Francis fe5c2b29b0 Tests: six engine tests no longer assume the working directory's drive on Windows
Six effectcraft-engine tests failed on a Windows machine whose working directory
and home folder are on different drives:

- rq_tests::add_configure_render and tests_stubs::{add_output_module_encodes_twice,
  pre_render_imports_and_replaces, post_render_actions_import_and_set_proxy,
  log_notify_and_overflow_settings} used "/tmp/..." output paths. On Windows that
  has no drive letter, so the render queue resolves it against the home folder's
  drive (C:) while std::path::absolute, which the tests compared with, uses the
  working directory's (D:). They only passed where both are on one drive, as on CI.
  They now build their outputs with tests_stubs::tmp_out, a path under the OS temp
  directory that is absolute on every platform and unchanged on Unix.
- footage_check::relative_footage_is_found_next_to_the_project compared strings:
  Windows joins the stored "media/clip.png" onto the project's folder and leaves
  mixed separators. It now compares the paths by components.

Test-only: the render queue stores the path it writes to, so the product is
consistent. effectcraft-engine --lib: 497 passed (6 failed before).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-10 20:26:20 -07:00
Dennis M. Francis a6945de659 #596: GPU Information names the adapter, or why the GPU compositor is off
Settings > Previews > GPU Information was fixed text ("Compositing: CPU, N
threads... Display: egui on wgpu."): it never named a GPU, even while the GPU
compositor was running, so a card looked undetected. The System Report's GPU
line only said whether the compositor started.

sysinfo::gpu_summary builds the graphics lines from the session: the compositor's
adapter, or the reason compositing is on the CPU, and the adapter the window
itself got. The dialog uses it.

This does not explain why a particular card isn't used for compositing; that
needs the reporter's System Report (Help > System Report).

Tests: engine gpu_summary_names_the_adapter_or_says_why_there_is_none and UI
gpu_information_names_the_adapter_and_why_the_gpu_is_off (fails with the old
text). The six engine tests that fail on this Windows machine (relative footage
and render-queue paths) fail on main too.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-10 19:33:06 -07:00
tylerjenningsw ec1bc8cc99 Shapes: edit gradient color and opacity stops (#612)
The Timeline's "Edit Gradient…" on a gradient property (Gradient Fill /
Gradient Stroke ▸ Colors, Gradient Overlay) only painted its label. It now
opens a Gradient Editor (prop.editGradient): opacity stops above a preview
bar and colour stops below. Click to add a stop (it takes that track's
own value there), drag or type a Location to move it, set its opacity or
colour (egui's colour picker), Delete removes it (never the last of a
track, never while typing). The tracks stay in order while editing and the
selection follows its stop.

The editor works on a draft. OK sets the property once with prop.set at
the composition time it opened, snapped to the frame, with
timeBase: "comp" (offset and stretched layers get the key where it was
opened): one undo step, and nothing at all when the draft is unchanged, so
OK adds no keyframe. Cancel and Escape change nothing; Escape with the
picker open closes only the picker. On an animated gradient only the
keyframe at that time changes; when its stop counts no longer match a
neighbouring keyframe's (gradients only blend with equal counts) the
editor says so. The draft is saved in the typed form, so one colour stop
and each stop's RGBA survive.

prop.editGradient checks its target is a gradient property before the UI
opens anything, so agents get an error for a wrong one. New Gradient
color_at / opacity_at sample one track each (sample() multiplies opacity
in, which would apply it twice to a new stop).

Tests: ui_gradient_value (fails on main: the value had no automation id),
ui_gradient_editor (11), gradient_editor and keyframe unit tests.
2026-10-10 22:22:13 -04:00
skylar taylor-barrick 83ae6218ef UI.9: retain shared docking across upstream workspace changes 2026-10-10 19:25:49 -06:00
Claude 8e14c00eaf
Project: footage saved with a relative path is found next to the project file (#652)
file.import stores a path as given, so footage imported by the CLI or an
agent as `clip.mp4` was saved relative, and opening the project looked it
up from the working directory. The CLI, started in the project's folder,
found it; the desktop app, started from the Finder, Explorer or a launcher
in another folder, showed and rendered neither its picture nor its sound,
while the Project panel still listed the saved duration.

On open, a relative footage path that doesn't exist from the working
directory but does next to the project file is resolved there (After
Effects also looks for footage relative to the project). Paths that exist
as they are, and absolute ones, are kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 23:41:43 +00:00