Commit Graph
63 Commits
Author SHA1 Message Date
Brandon Thomas 010065e290 Merge pull request #1679 2026-10-18 09:35:34 -04:00
Brandon Thomas d4dd1491f0 Merge pull request #1649 2026-10-18 09:35:34 -04:00
Brandon Thomas 6648c5befe Merge pull request #1269 2026-10-18 09:35:33 -04:00
webdevtodayjason f368aa0df3 Viewer: keep unselected null outlines visible
Fixes #1642
2026-10-16 14:09:40 -05:00
Marco Arnulfo 34f1ebfe55 Masks: the arrow keys nudge the selected points (part of #1288) 2026-10-16 14:42:03 +02:00
BFlat fa5eff7210 Merge pull request #1332 from Themolx/null-visibility-switch
Null layers: the Video switch hides them in the Composition viewer, as in After Effects
2026-10-16 19:02:24 +07:00
BFlat 5bb76bf50c Merge pull request #1561 from AyberkA0/pen-shift-45
fix(viewer): Shift-click with the Pen draws a straight segment (#1544)
2026-10-16 19:01:27 +07:00
Ayberk A. a8b25043a9 fix(viewer): Shift-click with the Pen draws a straight segment (#1544)
`pen_press` placed each new vertex where it was clicked (after snapping)
whatever the modifiers, so Shift did nothing and a vertical, horizontal or
45° segment had to be lined up by eye. With Shift held, the new vertex now
moves onto the nearest line at a multiple of 45° through the vertex before
it, as far along that line as the click lies (`constrain_45`). Masks and
shape paths both go through this path.
2026-10-15 14:52:58 +03:00
ThemolxandClaude Opus 5.5 03b7a958e5 Solo: exclude other layers even when the soloed layer is outside its time range
In After Effects solo is not time-dependent: while any AV layer is soloed,
non-soloed layers are excluded at every time. The renderer, nested/collapsed
3D paths, engine viewer helpers and viewer hit tests only counted solo layers
active at the current time, so outside the soloed layer's in-out range the
whole comp reappeared. Drop the time condition (audio already ignored it).

Update the #66 viewer regression test to the AE behaviour and add a render
test for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:30:26 +02:00
ThemolxandClaude Opus 5.5 a15a7a4818 Null layers: the Video switch hides them in the Composition viewer, as in After Effects
Every layer gets the Video switch (eye) in the Timeline (nulls, cameras and lights
were excluded by an is_av() check). A selected layer whose eye is off no longer
draws its box, handles, anchor or paths in the viewer. Alt-click on an eye and
Layer > Switches > Hide Other Video now take nulls along.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 20:04:34 +02:00
Jannik Wappler dbc595b0d6 #1220: Hide layer bounds during path editing 2026-10-13 15:38:06 +03:00
Brandon Thomas e248c172e8 Merge pull request #1044 from pravdomil/claude/timeline-chevron-expand-tree-93231f 2026-10-13 04:29:02 -04:00
Brandon Thomas 9396e39ae5 Merge pull request #1035 from vbasky/fix/1004-mask-multiselect
# Conflicts:
#	crates/ui-egui/tests/ui_viewer.rs
2026-10-13 04:28:49 -04:00
pravdomilandClaude Opus 5.5 f96663b1f5 Timeline: Cmd/Alt-click a group twirl opens its whole tree
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>
2026-10-12 16:40:35 +02:00
Vikram Bhaskaran d33e82a571 Cover mask vertex multi-select: Shift-click toggle and marquee (#1004)
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.
2026-10-13 01:01:11 +11:00
ThemolxandClaude Opus 5.5 e9c8d703a7 Rotation tool: keep counting past a full turn, as in After Effects
The viewer's Rotation tool wrote start rotation + (atan2 angle now - atan2 angle at
press), so the value jumped a full revolution back whenever the pointer crossed
atan2's ±180° seam (left of the anchor) and could never pass 360°. Sum the
per-frame angle change, wrapped to (-180°, 180°], instead: the value keeps
growing across revolutions in both directions (1x +0.0°, -1x -90.0°…), Shift
snaps the accumulated value to 45°, other selected layers follow, and one drag
is still one undo step. Non-finite angles turn by nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:12:12 +02:00
Brandon Thomas 2ea7220e49 Merge pull request #916 from pravdomil/claude/composition-maximize-zoom-fit-86627e
Viewer: Fit up to 100% follows the panel size

# Conflicts:
#	crates/ui-egui/src/panels/viewer_tools.rs
#	crates/ui-egui/tests/ui_viewer.rs
2026-10-12 04:22:53 -04:00
Brandon Thomas 617412b555 Merge pull request #888 from vangel76/fix-882-viewer-pixel-exact
#882: pixel-exact Composition viewer at 100 % and above

# Conflicts:
#	crates/ui-egui/src/menus.rs
2026-10-12 04:22:01 -04:00
Brandon Thomas b2d05582e0 Merge pull request #462 from storytold/fix/417-viewer-resolution
Viewer: each composition keeps its Resolution in the project, as in After Effects (#417)

# Conflicts:
#	crates/ui-egui/src/control.rs
#	crates/ui-egui/src/menus.rs
#	crates/ui-egui/src/panels/viewer_tools.rs
2026-10-12 04:21:14 -04:00
pravdomilandClaude Opus 5.5 a78961a346 Viewer: Fit up to 100% follows the panel size
Magnification ▸ Fit up to 100% stored the magnification of the moment it
was picked, so the comp kept that size when the panel was maximized or
resized. It is now a fit mode like Fit, capped at 100%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 22:21:52 +02:00
vangel76andClaude Opus 5.5 8ac8612ace #882: pixel-exact Composition viewer at 100 % and above
Problem: the viewer blurred hard edges even at Full resolution and 100 %
(reported on Discord). The frame rect was centred without snapping to
physical pixels, so bilinear sampling blended neighbouring pixels at 1:1
(worst at 125 %/150 % display scaling); More Accurate also magnified
bilinearly, so 200 %+ and lower resolutions were smoothed; and Zoom In /
Zoom Out stepped in points, so at 125 % scaling they never reached 100 %.

Fix: snap the frame origin (and the ViewerMap overlays use) to the
physical pixel grid; magnify with nearest in both Zoom Quality modes
(CPU texture options and the GPU frame sampler), keeping the averaged
minification below 100 % (#417); the snapshot texture follows the same
options; Zoom In / Out step through screen-pixel magnifications.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 18:50:53 +02:00
vangel76andClaude Opus 5.5 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
Claude 1ac09c7fa3 Issue fixes: #591 GPU allocation and device loss, #553 motion-path vertex tools, #488 rename focus, #511 macOS sheet dialogs, #500 MCP file confinement, #343 desktop shortcut 2026-10-10 23:06:04 +00:00
Claude 6a3d5c6703 Viewer: the Pen, Add, Delete and Convert Vertex tools edit motion paths, with pen pointers (#553)
Motion-path hits were only handled for the Selection tool, so the Pen slot's
tools did nothing on a layer's motion path, and every Pen slot tool showed the
same crosshair. As in After Effects, on a motion path:
- Add Vertex (and the Pen) add a Position keyframe at the clicked frame dot,
  with the path's value there (prop.addKey), so the path keeps its shape;
- Delete Vertex removes the clicked keyframe (keys.select + keys.delete);
- Convert Vertex switches it between Linear and Auto Bezier spatial
  interpolation (keys.interpolation {spatial}).
motion_path now reports its frame dots for the hit test. Over the viewer the Pen
slot tools draw their own pen glyph (Pen, Add, Delete, Convert, Mask Feather) at
the pointer in place of the system cursor, and the Pen shows Add Vertex's pen over
a mask, shape or motion path it would add a point to.

Test: ui_viewer clicks a two-key motion path with each tool and checks the added
key, its spatial interpolation both ways, its removal, and the pointer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 22:54:56 +00:00
Claude 0b91b465df Merge pull request #650 from KattKnight/auto-bezier-spatial-tangents
# Conflicts:
#	crates/expr/src/tests.rs
2026-10-10 22:28:40 +00:00
Claude 657f34d40b Layers: with several layers selected, value edits, stopwatches and viewer Scale / Rotate change them all (#355, #491)
With several layers selected, After Effects applies a property edit to the same property of
every selected layer; we changed only the layer whose row was edited (the Properties panel
showed only the first selected layer), so a stopwatch switched on one layer and later moves
of the others had nothing to keyframe (#355), and scaling or rotating a selection changed
one layer (#491).

- Engine: `prop.set`, `prop.toggleAnimation` and `prop.toggleKey` take `selected: true`:
  the change also goes to the same property (by match path, `transform/scale`,
  `effects/<effect>#n/<param>`) of every other selected, unlocked layer whose value has the
  same kind, in one undo step (a scrub keeps folding into it through its `merge` key). With
  `offset: true` (a scrub) each layer changes by the same amount from its own value; a typed
  value sets them all. Position with Separate Dimensions is handled per layer.
- The Timeline, Properties and Effect Controls panels pass `selected` (and `offset` while a
  value is dragged). The Properties panel heads "(N) Selected Objects" (zh-CN / zh-TW / ja /
  uk rows).
- Viewer: a bounding-box handle drag scales every selected layer by the same ratio about its
  own anchor point; the Rotation tool turns them all by the same angle (one undo step).

Tests: engine test (stopwatch, scrub offset, typed value, keyframe button, one undo step,
unselected layers untouched); kittests: the Properties panel with three layers selected
(header, stopwatch on all three undone in one step, a scrub moving all three by the same
amount) and a handle drag scaling two selected layers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 21:23:36 +00:00
KattandClaude Opus 5.5 6240ed646e Keyframes: auto-Bezier spatial tangents follow After Effects
Measured against After Effects 26.3 with the original
examples/ae-spatial-tangents.jsx (three paths, ten keys, even and uneven
timing): an auto-Bezier key's out tangent is (next - previous) / 6 and
the in tangent its mirror, whatever the key times; an end key uses its
one neighbour, (neighbour - key) / 6. EffectCraft gave end keys none and
scaled middle tangents by segment length, so paths with auto-Bezier keys
ran up to 28 px from AE's. All ten tangents now match; path samples are
within 0.05 px.

Tests that relied on end keys having no tangent now expect AE's: Spatial
Bezier keeps auto keys' handles (linear keys still get a third), the
motion-path handle is drawn at a sixth, and a straight loopOut path is
compared within 1e-9. docs/fidelity records the method and scores.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 02:44:49 +05:30
Claude 1bea11c9f1 Roto Brush: Cmd/Ctrl-drag resizes the brush, and the tool options show its Diameter (#509)
Cause: the Roto Brush and Refine Edge diameters live in the editor state and
the roto.options command sets them, but the UI never did: the Layer panel
only painted strokes and the bottom bar only set the view mode, so the brush
size could not be changed.

Fix, as in After Effects: Cmd-drag (Ctrl-drag on Windows and Linux) in the
Layer panel resizes the active brush (Roto Brush or Refine Edge) without
painting. The circle stays at the press point, its edge follows the pointer,
and the size is shown while dragging. The tools bar shows a Diameter field
(header.roto.diameter) while a Roto Brush tool is active. Both go through
roto.options, and the next stroke uses the new radius. Diameter is in all
four interface catalogs.

Test: roto_brush_ctrl_drag_resizes_the_brush (a 20 px Cmd/Ctrl-drag makes a
40 px brush and paints nothing, the next stroke's radius is half of it, and
the Diameter field is registered).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
(cherry picked from commit b4183c28eea34ba097d7a16153b84fcf7aeea045)
2026-10-10 19:48:25 +00:00
Claude e4916e5ca9 #513: clicking off a mask's path, even on its layer, deselects its points
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 19:16:03 +00:00
Claude 0ce5063d9f Viewer: Ctrl/Cmd draws a shape from its centre, as in After Effects (#443 follow-up)
Also keeps #442's newest-completed-revision lookup for the live drag, so
Frames has one way to pick the frame shown while an edit renders.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QiAJeFttZp83Fh7YuzAR5f
2026-10-10 12:51:15 +00:00
Aavirash 603337a061 Viewer: a layer drag moves the picture while the button is down
Every pointer move replaces the comp's content identity, so the frame on
screen takes a new cache key at every step while its render is still in
flight; the viewer only ever showed an exact key, so the picture froze on
the last completed frame until the drag ended. While a drag or a property
scrub is running, show the newest frame of the same comp, frame, scale,
view and render options that is ready — a render behind, as After Effects
does — and go back to exact frames when it stops.
2026-10-10 12:13:55 +02:00
Aavirash 0d2264dbed Viewer: the shape tools show the tool's own shape while dragging
The drag preview was a 1 px axis-aligned rectangle for every shape tool,
which read as no preview at all against most comps. Draw the tool's own
path instead (rectangle, rounded rectangle, ellipse, polygon, star), from
the engine's own geometry, filled with the Tools bar's Fill and outlined
in its Stroke, with a caption saying where the shape lands — a mask on
the selected layer, into the selected shape layer, or a new shape layer.

The ghost and the released shape go through one normalisation, so the
drag shows exactly what it commits: Alt (Option) draws from the press
point and Shift squares the box, both live. The drag also starts at the
press point now, not where the 6 px drag threshold was crossed, so the
shape is drawn from where the button went down, as in After Effects.
2026-10-10 12:13:21 +02:00
bflatasticandClaude Opus 5.5 67305fbcd1 Viewer: each composition keeps its Resolution in the project, as in After Effects (#417)
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 <noreply@anthropic.com>
2026-10-10 00:57:00 +07:00
bflatasticandClaude Opus 5.5 fcf3bb5dca Viewer: resize cursors follow each handle and the layer's rotation (#393)
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 <noreply@anthropic.com>
2026-10-09 22:52:56 +07:00
bflatasticandClaude Opus 5.5 6ce58040fd Viewer: the Snapping menu has After Effects' two options
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 <noreply@anthropic.com>
2026-10-09 21:03:12 +07:00
bflatasticandClaude Opus 5.5 3174652ce4 Viewer: double-clicking a mask point selects the whole mask (#290)
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 <noreply@anthropic.com>
2026-10-09 17:20:43 +07:00
bflatasticandClaude Opus 5.5 ed5ad56eb5 Viewer: a straight motion path has Bezier handles to curve it (#290)
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 <noreply@anthropic.com>
2026-10-09 17:20:43 +07:00
bflatasticandClaude Opus 5.5 5b05a5ae80 Viewer: arrow keys nudge the selected layers 1 pixel at the current magnification (#290)
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 <noreply@anthropic.com>
2026-10-09 17:20:43 +07:00
bflatasticandClaude Opus 5.5 02bc2b8f9a Puppet: the tools stay on the rigged layer and pins go on the deformed mesh (#284)
With a Puppet tool, a click off the pins picked the topmost layer under
the pointer by its own bounds, and selected it. So a click where the
deformation had stretched the mesh past the layer's bounds went to the
layer behind (selecting it and giving it a Puppet effect), and any
click on another layer switched to it.

While selected layers have a Puppet effect, a click (or a press that
might start a pin marquee) now works only on them, as in After
Effects: on the layer whose mesh, as deformed now, is under the
pointer (the pin lands on the deformed shape), else the one whose own
art is (a new mesh); elsewhere nothing happens and the selection
stays. Without a rigged layer selected, the tools pick and rig the
layer under the pointer as before.

`puppet::contains` tests a point against a deformed mesh; `puppet.addPin`
uses it too (its own test only held for one triangle winding).

Test: puppet_pins_go_on_the_deformed_mesh_and_the_tool_stays_on_the_rigged_layer
(ui_viewer).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:59:53 +07:00
bflatasticandClaude Opus 5.5 285297ed93 Timeline: Puppet pin keyframes show in the Timeline (#273)
Moving a puppet pin keys its Position, but the Timeline didn't show
those keys where people look for them. U revealed every pin's Position
as one more flat "Position" row under the layer, indistinguishable from
the layer's own Position and from each other, and the reveal was a
snapshot: a pin placed or moved after U (or with the layer twirled open
and Effects closed) got keys that no row showed.

As in After Effects, revealed properties inside an effect now show
under the effect and its groups (Puppet > Mesh 1 > Deform > Puppet
Pin 1 > Position), twirled open by the reveal and closable like any
group; Transform and mask properties stay as they were. Placing, moving
or recording a pin in the viewer keeps its Position in view: on a
twirled-open layer its groups open, and a U reveal takes it in. A
collapsed layer stays collapsed. The keys select, move and delete like
any others.

Test: puppet_pin_keys_show_under_their_pins_in_the_timeline,
placing_a_pin_opens_its_groups_on_a_twirled_open_layer (ui_viewer).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:05:07 +07:00
bflatasticandClaude Opus 5.5 631c069557 Viewer: scale handles snap to the comp's and other layers' edges (#252)
Scaling a layer by a bounding-box handle didn't snap, so it couldn't
be scaled exactly to the comp. With snapping on (Cmd/Ctrl toggles it
during the drag), the dragged handle now snaps like a dragged layer's
feature: to the comp's edges, corners and centre and to other layers'
edges, corners, centres and anchor points, as the Snapping options
allow. A corner snaps both axes, an edge its own axis; with Shift the
corner slides along its line from the anchor point to the nearer
snapped edge, so the proportions stay and the layer lands on, say, the
comp's size.

Test: handle_drags_snap_to_the_comp_edges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:14:05 +07:00
bflatasticandClaude Opus 5.5 41be200749 Viewer: Snapping options pick which layer features snap (#253)
Dragging a layer already snapped its feature nearest the pointer to
other layers' edges, corners, centres, anchor points and path points
and to the comp's edges and centre, and Cmd/Ctrl held during the drag
already turned snapping on or off against the checkbox. What was
missing were After Effects' Snapping options beside the Tools bar's
checkbox. The new menu turns Snap Edges Extended off (a layer's edges
then snap only along the layer, not across the comp) and each feature
family (edges, corners, centres, anchor points, mask and shape path
points) on or off, for the dragged layer's snap features and for the
targets alike. Agents set them with view.snappingOptions.

Layers whose eye is off (or that a solo hides) are no longer snap
targets, mask and shape path points of the dragged layer snap too, and
while snapping is on the feature that snaps is boxed during the drag.

Test: layer_drag_snaps_to_other_layers_edges_ctrl_toggles_and_options_apply
(UI), snapping_options_pick_the_targets and
snapping_options_command_sets_and_toggles (engine).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:14:05 +07:00
bflatasticandClaude Opus 5.5 89e075d1b1 Shapes: Tools bar Tool Creates Shape / Mask and Fill / Stroke Options (#227)
The Tools bar had Fill and Stroke swatches and a Stroke Width, kept in
the interface only, so agents couldn't set them and new shapes could
only be a solid fill with a solid stroke. As in After Effects:

- With a shape layer selected and a shape tool or the Pen active, Tool
  Creates Shape / Tool Creates Mask choose whether drawing adds shapes
  to the layer or masks on it (Fill and Stroke hide while drawing
  masks).
- Clicking the word "Fill" or "Stroke" opens Fill Options / Stroke
  Options: None, Solid Color, Linear Gradient or Radial Gradient, the
  blend mode and the opacity. The swatches show the kind; Alt-click
  steps through the kinds.
- New shapes and Pen paths are painted with them: a Gradient Fill or
  Gradient Stroke across the shape for the gradient kinds.

The options are engine state (EditorState.shape_tool) set with the new
shape.toolOptions command; shape.newShape and shape.newPath use them
unless given paint parameters (which now include fillType, fillBlend,
fillOpacity and their stroke twins; layer.newShape takes them too). A
Pen path without paint parameters now takes the Tools bar's stroke
(none by default) instead of a fixed 2 px white one. layer.addMask also
draws rounded rectangle, polygon and star masks, so every shape tool
makes a mask on a selected footage or solid layer (rounded rectangles,
polygons and stars made a new shape layer).

Test: shape_tool_options_paint_new_shapes,
masks_of_every_shape_tool_kind,
tool_creates_mask_draws_a_mask_on_the_selected_shape_layer,
fill_options_paint_the_next_shape.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:03:38 +07:00
bflatasticandClaude Opus 5.5 9968950b71 Shapes: drawing with a shape tool adds to the selected shape layer (#227)
With a shape layer selected, the Rectangle, Rounded Rectangle, Ellipse,
Polygon and Star tools made a new shape layer for every shape. After
Effects draws into the selected shape layer instead: the shape becomes a
new group on top of its Contents ("Rectangle 2", ...), placed by the
group's Transform. The new shape.newShape command does that (in the
layer's space, so a moved or scaled layer still gets the shape where it
was drawn), and makes a new shape layer only when no unlocked shape
layer is selected. The viewer's shape tools use it; masks on other
selected layers are unchanged. layer.newShape and the Pen's
shape.newPath share its layer and Contents helpers.

Test: shape_tools_draw_into_the_selected_shape_layer,
shape_tool_draws_into_the_selected_shape_layer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:03:38 +07:00
bflatasticandClaude Opus 5.5 e008085a93 Spacebar: a tap previews on release, held it is the Hand tool (#227)
Spacebar started or stopped the preview on its press, so holding it to
pan the Composition panel (Space+drag already panned there) also
toggled playback. As in After Effects, plain Spacebar now runs its
shortcut when it is released, and only if no mouse button went down
while it was held and the press didn't go to a text field; key
auto-repeat toggles nothing more. Shift+Spacebar and the numpad
preview shortcuts still run on the press.

Holding Spacebar over the Timeline's ruler and time graph is the Hand
tool there too: dragging scrolls the time graph (when zoomed in, never
before the comp's start or past its end) instead of moving bars, keys
or markers.

Test: spacebar_taps_preview_and_held_spacebar_pans_the_viewer,
spacebar_drag_scrolls_the_time_graph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 12:59:40 +07:00
bflatasticandClaude Opus 5.5 2c7eca7ef2 Viewer: a press on a selected layer behind others acts on it (#230)
A press in the Composition panel always acted on the topmost layer
under the pointer, so a selected layer could not be dragged where
another layer covered it: the press selected the layer in front. As in
After Effects, a selected layer under the pointer now comes first: it
stays selected and a drag moves it. With no selected layer there the
topmost layer is picked as before, and Shift still toggles the topmost
one. The context menu's list of layers under the pointer shares the
same hit test.

Test: viewer_press_prefers_a_selected_layer_under_the_pointer
(ui_viewer).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 12:47:30 +07:00
Claude c357a53e5e Viewer: frames wider than the GPU's texture limit are shown (#201)
An 11000 x 2200 comp at Full resolution renders on the CPU, and the
frame went to egui as one texture over the device's 8192 px limit:
the upload failed every frame (a panic in debug builds) and the window
stopped drawing. Creating a comp that size at Full did the same.

Frames, snapshots, the channel / exposure view, other viewers, the
Layer and Footage panels and the Roto Brush overlay now go up averaged
down by the smallest whole factor that fits. The full frame stays
readable for pixel readouts.

Tests: frames_over_the_texture_limit_are_averaged_down and
full_resolution_frames_wider_than_the_texture_limit_fit (panicked
before).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018gnTnH3Jmk5zr4tMq1Mc9f
2026-10-07 22:13:19 +00:00
bflatasticandClaude Opus 5.5 72b039c6d5 Viewer: Pan Behind Alt-drag, Shift-drag and Ctrl+double-click to center (#162)
The Pan Behind gesture ignored modifiers other than Ctrl (snapping): it
always moved Anchor Point and Position together and never constrained the
move, and the tool's button had no double-click action.

- Alt held as the drag begins moves the Anchor Point alone (Position is
  left as it is, so the layer shifts).
- Shift keeps the move to the axis dragged along most; Move and Pan
  Behind now share that rule (one_axis).
- Ctrl+double-clicking the Pan Behind tool's button runs
  layer.centerAnchor (Center Anchor Point in Layer Content).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:44:46 +07:00
bflatasticandClaude Opus 5.5 83f17dda37 Viewer: Pan Behind snaps the anchor to its own layer (#147)
The Pan Behind drag passed its layer to the snapper's exclude list, which
dropped all of that layer's targets: its corners, edge midpoints and centre
as well as the anchor being dragged. The layer's box doesn't move in comp
space while the anchor pans (Position compensates), so those points are
valid targets.

The engine's per-layer snap targets are now one function, layer_targets,
which can leave out the anchor point. The gesture records its layer's
targets without the anchor as the drag begins and passes them to the
snapper (snap_with) as extra targets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:44:46 +07:00
Brandon Thomas a46ebf4dc7 Puppet: selected pins are filled and ringed, unselected hollow (#96)
A selected pin was drawn only 1 px bigger and slightly brighter. Now, like
selected and unselected mask vertices in the viewer, a selected pin is
filled with its kind colour and ringed in white, and an unselected pin is
hollow (dark centre, coloured outline). Drawing only; hit-testing is
unchanged.

Regression test: selected_puppet_pins_are_filled_and_unselected_hollow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 103b5c41e9e8ea84008593e05af50b01a2800b3e)
2026-10-06 11:09:41 -04:00