Dragging an integer setting applied every step, so UI Scale resized the
interface under the pointer mid-drag. A dragged value now shows in its
field and applies when the button is released (a typed one already
waited for Enter).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The test opened its project before the window existed, so its 'GPU' frames could be the ones the CPU composited while the compositor was built. It now waits for the compositor (ui_gpu::wait_for_compositor, made pub(crate)) and then opens the project, as the PR does for ui_gpu.rs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A docked ScriptUI panel only ever saw its preferredSize: the host drew
it in a scroll area at max(dock, preferred) and the script's window never
learned the dock. scriptui.resize now gives a window a size: the script's
window takes it, lays itself out again and runs onResizing / onResize.
The dock host sends it whenever the panel's dock size changes, so
window.size is the dock's, as in After Effects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Clicking an effect, Layer Style, mask or shape item row in the Timeline
always replaced the selection, so one animation preset couldn't hold
several effects and styles. Shift or Cmd/Ctrl now adds the group to the
selection, or takes a selected one out, as property rows and Effect
Controls headers already do.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add Per-Corner Roundness to Rectangle Path: a checkbox (off by default) and four Pixels properties (top-left, top-right, bottom-right, bottom-left). New rectangles initialise the four values from their Roundness, so ticking the box keeps the shape; the radii are animatable and expression-driven.\n\nrect_radii() builds the path clockwise from the top-left with kappa quarter-circle arcs. Overlapping radii scale down like CSS border-radius; negative and non-finite radii are square; rect() delegates to it, so uniform roundness clamps to half the shorter side as before.\n\nLottie rc has one r: four equal static radii export as that value; mixed or animated radii export roundness 0 with a warning. Import puts r into the four per-corner values so ticking the box on an imported rectangle keeps the shape.\n\nTests: path geometry and clamping, rendered per-corner pixels, Lottie equal/mixed/checkbox-off export.
A drag that started on a script control stopped at the window edge:
moves outside were dropped and a release outside never reached the
control. While a button is held, the control it was pressed on now gets
the moves (with screen and control-relative coordinates) and the
release, wherever the pointer is, as in After Effects. Every MouseEvent
also carries buttons (held: 1 left, 2 right, 4 middle), so a handler can
tell a drag from a hover; scriptui.mouse accepts it too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With the compositor built on a worker thread, the parity test's first
frame could be composited on the CPU while the build ran. Frame keys
don't name the compositor (by design: the disk cache serves frames from
either), so after the hand-over the viewer could keep showing that CPU
frame, and the GPU-vs-CPU comparison would pass comparing the CPU with
itself. The test now waits for the compositor, then opens the project
and sets the time, so every frame it compares was requested with the
compositor attached.
(`viewer_on_gpu()` can't tell here: on the desktop GPU frames are read
back into CPU images, and only the browser draws them from their texture.)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The status line is the toast that clears itself after 5 s, so the
preparing notice went blank while the viewer was still on the CPU for the
rest of a cold-cache build (tens of seconds), though `init_gpu` says the
status bar tells the user so. draw_toast now leaves that one status up
while `gpu_initializing()`; `finish_gpu_build` clears it when the
compositor lands, and the worker's repaint brings that frame, so the
toast doesn't tick meanwhile. A click still dismisses it; every other
status clears as before.
Test: with the build held on its gate, 30 more steps (7.5 s) leave the
status as it was; before this change it was empty by then.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`init_gpu` built the compositor on the UI thread in the first frame, and eframe shows the window
only after that frame has painted. The compositor's ~235 compute pipelines take tens of seconds to
compile on a cold Metal shader cache (first launch, after an OS update, after an update that touched
a shader): about 40 s of no window at all, measured 39–41 s on an M2 Max.
On the desktop the compositor is now built on a worker thread (`gpu-compositor`), inside the same
`catch_unwind`, and attached when its result arrives; the worker requests a repaint, so nothing
polls. Until then the viewer composites on the CPU, as it already does without a GPU, and the
status line and `help.systemInfo` say "Preparing the GPU compositor…". A device failure during the
build discards the result; a thread that can't be spawned, and the browser, build in place as
before. `Hooks::before_gpu_build` lets tests hold or fail the build; `gpu_initializing()` exposes
the pending state.
Measured (release, cold cache, A/B interleaved): time to the window 39.0/40.9 s → 1.9/0.5 s, and
the control channel answers right after; with a heavy project opened at launch 38.3 s → 0.64 s
(first frame 88 ms after the app was created). Warm: 776 → 355 ms. The compile itself is not
faster; #1705 (effect kernels on first use) is the complementary half.
Tests: the interface draws, composites a CPU frame and reports the preparing state while the build
is held on a gate, then attaches the compositor; a build that panics leaves the viewer on the CPU
with the reason; the existing GPU/CPU parity test waits for the asynchronous hand-over.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Dragging a mask or shape vertex, a tangent or a Pen point against the
edge of the Composition panel now pans the view toward that edge, faster
the further out the pointer is, and the point keeps moving with it, so
zoomed-in rotoscoping no longer stops at the panel edge (as in After
Effects).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Dragging an effect's (or mask's, shape item's, text animator's) row in
the Timeline drew a marquee. The row now drags: a line shows where it
lands among its siblings and the drop runs prop.moveGroup (one undo
step); Escape cancels, as the layer reorder drag does.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Text strokes were always drawn with round joins. The document gets a
Line Join (`lineJoin: miter|round|bevel` in layer.setText / newText),
Miter for new text as in After Effects, Round for documents saved before
it so their look is kept. The 2D and Advanced 3D (extruded) text strokes
use it, expressions' style copies carry it, and the Character panel has
Miter / Round / Bevel buttons under the stroke width.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>