Keeping OK and Cancel in view took away the outer scroll area that also
scrolled the dialog sideways, and the controls only scrolled up and
down. In a narrow window (960x600 at 200 % UI Scale leaves the controls
about 190 points next to the 200-point list) they ran past the dialog's
right edge with no way to reach them.
The controls now scroll both ways in the room left under their heading,
with no minimum scrolled size, so a small budget can't hide the end of
them; the list scrolls in its height the same way. Each control also
registers dialog.layerStyle.value.<style>/<path> next to its label's id.
Tests:
- layer_style_controls_stay_reachable_in_a_small_window: in a 960x600
window at 200 %, and opened large then narrowed, Noise is scrolled to,
painted whole and dragged; fails before (it lies past the window).
- layer_style_buttons_stay_in_view now checks both buttons at 100 % and
200 %, each case in a fresh app with a real edit: OK keeps it, Cancel
puts the project and its undo history back as before the dialog.
The Layer Style dialog's OK and Cancel came after the style list and
the style's controls in one scroll area, and at its usual size they
were laid out past the dialog's bottom edge: clipped, and a click on
them did nothing until the dialog was scrolled.
The dialog now uses above_footer: OK and Cancel sit at its bottom right
and the list and the controls scroll above them, also at 200 % UI
Scale.
Test: layer_style_buttons_stay_in_view (both buttons painted whole and
clicked, at 100 % and 200 %; fails before: Cancel isn't painted).
The layer bar Quick Offset was followed and ended in its row's code, and
rows out of view aren't drawn. Released with its row scrolled away, it
stayed active: the next drag of that bar, even a plain one, ran as a
Quick Offset from the old origin, into the old undo step.
Like the key drag, it now follows the pointer below the rows and ends
when the button is released, wherever the bar's row is.
Test: a_quick_offset_released_off_screen_ends (release with the row
scrolled away; a plain drag after it moves both layers together as its
own undo step; fails before).
In After Effects a marquee selection staggers in the comp's layer order,
not in the direction the box was drawn. A Shift-click range of layer
bars and a box of keys already come out top layer first; this test makes
both with the pointer (the range from the bottom layer up, the box from
the bottom right to the top left) and checks the Quick Offset that
follows: the top layer stays and the bottom one moves the whole delta.
Ctrl/Cmd+Alt dragging a selected layer bar or a selected key now runs a
Quick Offset (layer.quickOffset / keys.quickOffset with fromStart, one
undo step). The mode is chosen when the drag starts and needs at least
two unlocked layers; the last selected layer moves by whole frames with
the pointer. A readout by the pointer shows the Total Offset and the
offset Per Layer, translated.
A plain drag still moves the selection together, Alt alone still scales
a key group in time (it now leaves Ctrl/Cmd+Alt to Quick Offset), and
Ctrl/Cmd+Alt+click still toggles Hold. The key drag's readout is drawn
by a shared drag_tip. Keys also register timeline.keyAt.<uid>.<ticks>,
exact where keys on subframes can share a frame.
Tests (ui_quick_offset): staggering layer bars in selection order and
keys per layer, one undo each; plain drags and Alt scaling unchanged; the
readout while dragging.
The layer context menu gains an Effects submenu between Blending Mode and Arrange, mirroring the After Effects layer menu: the effect registry grouped by category (sorted), synced with the Effect menu; picking one applies it to the selected layers.
A colour pick clicked in the Composition panel before the viewer had a frame
(still rendering on a background thread, or a readback pending) sampled nothing
but was used up, leaving the colour unchanged. The click now leaves the
eyedropper armed, as the text colour eyedropper already does.
timeline_colour_eyedropper_samples_the_comp clicked without waiting for the
frame and hit this in about one run in four; it now waits for the frame.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The floating group's title strip used the same id as its Area's own
background widget, and the Area was shifted back into the window while
the group's rect kept moving, so the two no longer overlapped and egui
painted "Double use of widget ID" over the panel. The strip has its own
id and the group is drawn where its rect says.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The attach point's 12 px grab area sat on top of the feature region and is tested first. Zoomed out, a default feature region is smaller than that on screen, so every press inside it moved the attach point instead of the point. Inside the feature region the attach point now grabs only the region's inner half.
Problem: the built-in animation preset library had no Backgrounds category
(gap 7: 31 presets against After Effects' 621).
Fix: 23 original Backgrounds presets in crates/engine/presets/backgrounds.json,
built from our own generator, noise, colour and stylize effects: Soft Gradient
Drift, Aurora Veil, Slow Fog, Cloud Bank, Dark Smoke, Ember Haze, Magma Flow,
Ice Flow, Pastel Plasma, Cell Drift, Crystal Facets, Hex Mosaic, Mirrored Silk,
Water Caustics, Blueprint Grid, Checker Scroll, Scrolling Stripes, Pulse Rings,
Bokeh Drift, Spotlight Floor, Dawn Sky, Sweeping Light, Grainy Wash.
The preset format gains `expr` (an expression per property, so backgrounds keep
moving for the whole layer) and point parameters written as fractions of the
layer size. Effects & Presets lists them under * Animation Presets > Backgrounds.
Tests: catalogue uniqueness and categories, apply/undo/redo of every preset
(engine), render of every preset with the expression engine: finite,
non-flat, deterministic, moving (host), panel listing and apply (ui-egui).
Sidecar, ATTRIBUTION.md, gaps.md row 7 and effects-parity.md updated.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Undoes the 800 px Project Settings width from #980: the form is back at the
shared 480 px.
A form's choice popup keeps its 240 px column and ends a long choice (a GPU
adapter's name) in an ellipsis instead of widening the grid past the dialog.
Only the fields scroll, and only vertically; OK and Cancel sit under the
scroll area, so they stay in view.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shift+Spacebar set Skip to 1, so it played every other frame out of the box. It now plays every
frame, like Spacebar. Skip still works when it is set.
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
First in the menu, as in After Effects, and for the right-clicked comp rather than the one in the viewer: the dialog keeps the comp it edits and OK passes it to comp.settings. comp.settings with only {"comp": id} opens the dialog on that comp.
Zoom to Fit Comp remembers the span it left and goes back to it when pressed
again from the whole comp.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Cmd-click (or Alt-click) a twirl arrow now takes the whole tree with it in
three more places: Audio's Waveform row in the Timeline, an effect or effect
group in Effect Controls, and a folder in the Project panel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The engine's layer.create has shapesFromText and masksFromText, but the
timeline and Composition context menus only offered Create Shapes from Vector
Layer, so text could not be converted to editable outlines from the UI. Gate
the two text conversions on a text layer (LayerMenuCaps replaces the lone
vector flag), beside the vector one.
Cmd-click (or Alt-click) the arrow of Effects, Contents, Masks or any other
group in the Timeline twirls every group under it open, or closed, with it,
as the layer's own arrow already did.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Shift-click adds a vertex to the selection and removes it again, a plain
click collapses to one, and a marquee from empty viewer area selects the
enclosed vertices (additive with Shift) without moving the layer. These
behaviors regressed or were never covered; the tests drive them through the
Composition viewer like a user.
The Properties panel kept its own ephemeral per-layer link toggle, so
unlinking Scale there left the Timeline linked and vice versa, and its edit
math differed from the Timeline's. Use the shared per-property unlinked set
and widgets::constrained, with the stable match id instead of the display
name, plus Alt-edits-one-axis like the Timeline.