Commit Graph
290 Commits
Author SHA1 Message Date
bflatasticandClaude Opus 5.5 976784b0a6 Timeline: Shift-clicking layer bars selects the range (#284)
Shift-clicking a layer's name selected the layers from the last one
clicked to it, but Shift-clicking a layer bar in the time graph only
added that layer. Bars now select the same range (shared with the
name column: `shift_range`), and a click on a bar becomes where the next
range starts, as a click on a name does.

Test: timeline_shift_click_on_layer_bars_selects_the_range
(ui_workflow_selection).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:59:53 +07:00
bflatasticandClaude Opus 5.5 2419605cc4 Timeline: a press in the time ruler moves the current time (#284)
The ruler moved the current-time indicator only once the pointer had
dragged past egui's drag threshold, or on release for a click. As in
After Effects, the time now jumps to the pointer as soon as the button
goes down in the ruler, then follows the drag.

Test: a_press_in_the_ruler_moves_the_current_time (ui_timeline_zoom).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 16:59:53 +07:00
Nguyen Thanh Cong b5da7a5754 UI: Commit text edit session when switching away from Type tool 2026-10-09 12:17:13 +07:00
Brandon ThomasandClaude Opus 5.5 e4d1a4494f G3: audio scrub test waits on the clock, not a step count (flaky under load)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 00:17:53 -04:00
BFlat d60cb71e29 Merge pull request #210 from jonassneu/fix/blank-panel-context-menus
G2: add context menus to blank Project and Timeline areas
2026-10-08 22:11:11 +07:00
bflatasticandClaude Opus 5.5 3318cb0da1 Import: dropped and picked files show an Importing card (#270)
Files dropped on the window or picked in the Import dialog were probed
one after the other on the UI thread: the app froze while a video was
read, nothing said what was being imported or how many files were
left, and only the last new item was selected. People ended up
importing the same file again and again.

file.import takes `background`: the files are probed in a background
job (Window > Progress, jobs.list / jobs.cancel) that names the file
being read and how many there are ("b.png (2 of 3)"); then they are
added in one undo step, all selected (the Project panel scrolls to
them, a file already in the project arrives again as in After
Effects) and a toast reports the count ("Imported 3 items", with the
first error). Drops and the Import dialog use it, and an Importing
card at the bottom left shows the job with its progress bar and
Cancel. Agents and scripts still import at once by default. The
toast registers an automation id.

Test: background_imports_report_progress_and_select_what_arrived
(engine), dropped_files_show_import_progress_then_the_items_they_made
(ui_drag_drop).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:05:07 +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 02c75fcaf1 Workspaces: leaving Learn closes its tutorials (#272)
The Learn workspace opens the Home screen on its Learn tab, but no other workspace closed it, so
after Learn the tutorials stayed over Default, Review and the rest until a project was created or
Home was toggled. It wasn't about clicking quickly: any switch away from Learn kept them. Leaving
Learn for another workspace now closes the Home screen it opened.

Test: ui_panels leaving_the_learn_workspace_closes_its_tutorials (clicks through the workspace
tabs: the Home screen and the tutorials show only on Learn).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:40:05 +07:00
bflatasticandClaude Opus 5.5 11f3736025 Panels: every scrolling panel has a scroll bar to drag (#271)
Most panels lay out their own rows and scrolled only with the mouse wheel: Effect Controls,
Effects & Presets, the Media Browser, Ease Presets and the Timeline's layer rows had no scroll
bar, Properties and Preview a thin indicator that couldn't be dragged, and Render Queue, History,
Markers, Character, Paragraph and Info didn't scroll at all, so a panel shorter than its content
cut it off. Without a wheel or a working touchpad there was no way down. The Project panel had
the only real scroll bars, hand-rolled, and its vertical one was hit-tested under the rows, so it
only dragged where no row was.

- widgets::scroll_bar: the Project panel's bars generalised (horizontal or vertical, drag the
  thumb or press the track to move it there, automation id with "scroll/max"); the Project
  panel's two bars and the Timeline's column bar use it, drawn over the rows.
- widgets::PanelScroll: the wheel offset, its clamping and the bar for a panel's own rows, used by
  Effect Controls, Properties, Effects & Presets, Media Browser, Ease Presets, Preview, Render
  Queue, History (which still follows the current state), Markers, Character, Paragraph and Info.
  The Timeline's layer rows get a bar on the right edge.
- Effect Controls: an animated vector's values (a puppet pin's Position) ran under the keyframe
  navigator in a narrow panel. They now end before it, moving left over the name, which is cut
  short.

Test: ui_scrolling panels_taller_than_their_space_have_a_scroll_bar_to_drag (eleven panels in a
short window: a press on the track and a thumb drag scroll them),
effect_values_stay_clear_of_the_keyframe_navigator.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:40:05 +07:00
bflatasticandClaude Opus 5.5 6d2ef5a830 Menus: a long dropdown scrolls instead of running off the window (#269)
The menu bar's own menus (Edit has 30 entries) had no scroll area, only their submenus did, so
on a short screen Edit ran past the bottom of the window. The font menu was capped at 420
points and never moved up, the Preview panel's and the viewer's popup lists neither scrolled nor
moved, and the Timeline's Blending Mode and Expression Language submenus didn't scroll. egui's
scroll bars also only appeared while the pointer was over the list, so a list could only be
scrolled with a wheel or touchpad.

- widgets::popup_list: the one popup list behind popup_menu, the font menu, the Preview panel's
  dropdowns and the viewer's popups: as tall as the window allows, scrolling beyond, moved up to
  fit the window.
- widgets::menu_scroll: the same for menus and submenus (the menu bar's, Blending Mode, the
  Expression Language menu, the viewer's Select menu).
- Scroll bars always show (solid scroll style), so they can be dragged.
- A first window that doesn't fit the screen is maximized: the 1680 x 1020 default on a smaller
  screen is only shrunk to the monitor's size, so with its title bar it ran under the taskbar and
  cut menus off at the bottom of the screen even when they fitted the window.

Test: ui_scrolling a_menu_taller_than_the_window_scrolls (Edit in a 520-point window scrolls
its last entry into view and it takes the click); overflows_screen unit test.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 18:40:05 +07:00
bflatasticandClaude Opus 5.5 1c22615553 UI: Window > Ease Presets panel draws, edits and applies ease curves (#254)
A dockable panel (our own design) for the keys.easePreset commands. A
value graph shows the working curve with its two handles, which drag
to edit it; Apply eases the selected keyframes with it and From Keys
loads the curve between the first selected pair. Every preset is a
thumbnail drawn in code; a click loads it and applies it to the
selected keys. Save Current keeps the working curve under the name in
the field, and Rename and Delete act on the selected user preset.
Wide panels put the presets beside the graph; narrow ones below it,
the graph shrinking to leave a row of presets, scrolled with the wheel.

The panel state (working curve, selection, name) is serde in the UI
state, and every control registers an automation id (easePresets.*).
widgets::text_field is the search field without its magnifier.

Test: clicking_an_ease_preset_eases_the_selected_keys_and_handles_edit_the_curve
(ui_keyframes); every_panel_registers_automation_ids_for_its_widgets
covers the new panel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:25:00 +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 0ac5f9fb9b Timeline: Shift-dragging the work area snaps to the current time (#252)
Shift-dragging the work area's begin or end handle now snaps it to the
current-time indicator, keyframes, layer in and out points and markers
within 8 px, with the snap candidates the CTI and keyframe drags use.
The work area bar itself drags too, as in After Effects: it moves the
work area and keeps its length, and with Shift the nearer end snaps.

Test: shift_dragging_the_work_area_snaps_to_the_current_time.

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 8d5e4ae0ef Timeline: a selected key selects its property and layer (#252)
Clicking (or marquee-selecting) keyframes in the Timeline selected only
the keys. Their property and layer stayed unselected, so the Graph
Editor, which shows the selected properties or else the animated
properties of the selected layers, came up empty until the property
was selected separately. As in After Effects, the Timeline's key
selection now selects the keys' properties and layers too (Shift adds
to them). keys.select takes this as `selectProperties`; the Graph
Editor's own key clicks leave what it shows alone.

Test: a_key_clicked_in_the_timeline_shows_in_the_graph_editor
(ui_keyframes).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 16:19:53 +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 0048569682 Shapes: Duplicate duplicates the selected shape items, not the layer (#227)
Edit > Duplicate (Ctrl+D) with a shape group or other shape item
selected in the Timeline duplicated the whole shape layer. As in After
Effects it now duplicates the selected items inside their group, each
copy above its original and numbered on ("Rectangle 1" gives
"Rectangle 2"), and selects the copies. With only layers selected the
layers are duplicated as before.

Test: duplicate_with_shape_items_selected_duplicates_them_in_place,
ctrl_d_duplicates_the_selected_shape_group_in_its_layer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 13:03:38 +07:00
bflatasticandClaude Opus 5.5 95bb346c89 Shapes: Copy, Cut and Paste shape items between shape layers (#227)
Edit > Copy with a shape group (or a path, fill, stroke or path
operation) selected in a shape layer's Contents copied the whole layer,
and Paste made a new layer. As in After Effects, Copy now copies the
selected items, Paste puts them into each selected shape layer's
Contents (above its selected item, in that item's group, else on top,
with names kept unique there: "Rectangle 2"), and Cut removes them, so
Cut + Paste moves them. Pasting with no shape layer selected is an
error rather than a new layer. Copying anything else clears the shape
item clipboard, as it does the other clipboards.

Test: shape_contents_copy_cut_and_paste_between_shape_layers,
ctrl_c_and_ctrl_v_copy_a_shape_group_into_another_shape_layer.

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 c4c28682eb Timeline: dragging over layer switches sets them all (#227)
Each layer switch toggled on its own click only, so hiding or locking
many layers took a click per layer. As in After Effects, pressing a
switch in the A/V Features or Switches column now sets it at once, and
dragging up or down gives the same switch of every layer the pointer
passes the state the first one got, whatever their own state was
(rows passed in a quick drag included). The whole drag is one undo
step: layer.setSwitch takes a `merge` key like other gesture edits.
A single click still changes only that layer, so clicking the eye of
one selected layer doesn't change the others (Layer > Switches still
sets all selected layers).

Test: dragging_over_layer_switches_sets_them_all.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 12:59:40 +07:00
bflatasticandClaude Opus 5.5 6b95b7a182 Project panel: items dropped on New Composition make a comp (#227)
Dragging Project items onto the Create a new Composition button at the
bottom of the Project panel did nothing. As in After Effects, the
button now takes the drop and runs File > New Comp from Selection with
the dragged items (the selection when the dragged item is in it): one
item becomes a comp of its size, frame rate and duration holding it,
and several open the New Composition from Selection dialog. The button
is outlined while items are dragged over it.

Test: project_items_dropped_on_new_comp_make_a_composition.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 12:59:40 +07:00
bflatasticandClaude Opus 5.5 fab6dedd7c Effect Controls: right-click shows the Effect menu (#227)
Right-clicking the Effect Controls panel did nothing outside an
effect's header. As in After Effects, a right-click anywhere a control
has no menu of its own (the empty area, property names, buttons such
as Reset) now opens the Effect menu: Effect Controls, Last Effect,
Remove All, Manage Effects and every effect category, applying the
chosen effect to the selected layers. It is the menu bar's own Effect
menu tree and drawing code; an effect header keeps its own menu.

Test: right_click_in_effect_controls_shows_the_effect_menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 12:59:40 +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
bflatasticandClaude Opus 5.5 7612b220ef Project panel: a panel too short for the scroll bar draws (#231)
The Project panel's vertical scroll bar clamped its thumb between 16
points and the track's height. When the track was shorter than 16
points (an empty project in a 1280 x 800 browser window at device pixel
ratio 2, or any short panel), that clamp panicked on the first frame:
the browser canvas went blank and the app stopped responding. The
thumb is now at most the track's length, and both scroll bars share
the calculation.

Test: a_short_project_panel_draws (ui_project_rows).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 12:38:10 +07:00
Jonas Neubauer 8af8a11500 G2: merge main and preserve blank panel menus and selection 2026-10-08 02:06:56 +02:00
Claude b4d2d58eeb Masks and Project panel: selecting a mask selects its points; linked Mask Feather; selection box (#203)
- Selecting a mask in the Timeline (its row or Mask Path), or a shape's
  Path, selects all its points, so dragging one in the viewer moves the
  whole mask, as in After Effects. Delete still removes the mask, and
  selecting another property deselects the points.
- Mask Feather has the chain link (Constrain Proportions, on by
  default, Alt edits one value) and no longer takes negative values;
  Scale's link shows as a toggle too.
- Dragging in the Project panel's empty area draws a selection box
  (Shift / Cmd adds); a click there deselects.

Tests: selecting_a_mask_selects_its_points,
mask_feather_values_are_linked_and_not_negative,
dragging_in_the_empty_area_box_selects_items.

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
Claude b26f55a775 Shapes and timeline: toolbar Fill / Stroke edit selected shape layers; Alt+Shift+P reveals; click below layers deselects (#205)
- With a shape layer selected, the toolbar shows its Fill, Stroke and
  Stroke Width (with any tool) and editing them changes the layer's
  fills and strokes, as in After Effects (new shape.fillStroke, one
  undo step per picker or drag). Shapes drawn without a stroke get one
  when a stroke colour or width is set, so changing the stroke now works.
- Alt+Shift+P (A, S, R, T) reveals the property it keyed on each layer.
- A click in the time graph below the last layer deselects the layers
  (it only deselected keyframes), so a shape tool draws a new shape
  layer instead of a mask on the selected image.

Tests: toolbar_fill_and_stroke_paint_selected_shape_layers,
toolbar_stroke_edits_the_selected_shape_layer,
add_position_key_reveals_position,
clicking_below_the_layers_deselects_them.

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
Claude b8b13b1cec Timeline: shape layers get the Contents "Add:" menu; Classic 3D shows Change Renderer (#206)
Trim Paths, Merge Paths, Repeater and the other path operations
existed (layer.addShapeItem) but no menu added them to a shape layer.
Like After Effects, a shape layer's Contents row now has "Add:" with
Group, Rectangle, Ellipse, Polystar, Path, Fill, Stroke, Gradient Fill,
Gradient Stroke and the path operations; with a shape group selected
they go into it. layer.addShapeItem gains the empty Path and Gradient
Stroke kinds.

A 3D text or shape layer in a Classic 3D comp now shows Geometry
Options with "Change Renderer…", which opens Composition Settings on
its 3D Renderer tab (Advanced 3D comps show the extrusion options).

Popup menus are as tall as the window allows and open upward near its
bottom: the 20-entry Add menu was cut at 420 px, hiding Trim Paths.

Tests: shape_contents_add_menu_adds_path_operations,
classic_3d_shape_layers_offer_change_renderer.

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
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
Claude 6e31677718 Preview: sound starts once rendering keeps real time (#208)
Since #103, a preview with audio stayed silent until every frame from
the playhead to the end of the preview (or what fits in the RAM
preview) was cached. The prefetch only renders a few frames ahead, so
on a normal work area the sound came near the end or on the second
loop, even when the comp rendered in real time.

The held sound now also starts once the silent preview has kept real
time for a quarter second with the prefetch window ahead cached. A
comp that can't render in real time still waits for the cache, as #103
intended; a stall clears the measured rate so the sound doesn't cut in
and out.

Test: preview_with_audio_sounds_once_rendering_keeps_up (a 30 s comp
sounds within 2 s, without skipping a frame first).

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
Jonas Neubauer a3de99c5e5 G2: add context menus to blank Project and Timeline areas 2026-10-07 21:41:00 +02:00
BFlatandClaude Opus 5.5 f2b2fa811c Render Queue: clicking the Output To file name opens a save dialog (#192)
Clicking an item's Output To file name opened an inline editor on the
name template (`[compName].[fileExtension]`), so there was no way to
pick a folder from there. Choose File… in the menu beside it did open
a dialog, but the project save dialog: filtered to .ecproj and opening
wherever the OS chose.

As After Effects' Output Movie To, clicking the file name now opens a
save dialog at the current output (its folder and name) for the output
format's extension, and the chosen file becomes the output. Choose
File… opens the same dialog. Alt-click still edits the template in
place, and hosts without a save dialog (the web) keep the inline
editor. The desktop `pick_save_file` hook opens in the folder of a
default path.

Test: clicking_the_output_file_name_opens_the_save_dialog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:29:29 +07:00
BFlatandClaude Opus 5.5 1db3698719 Window: saved workspaces are listed in Window ▸ Workspace (#191)
Save as New Workspace stored the layout and `window.workspace` could
switch to it, but the Workspace submenu was a fixed list of the
built-in workspaces, so a saved one never appeared and could not be
chosen from the menu.

The submenu now has a `savedWorkspaces` dynamic node after the
built-ins (DynCtx carries the saved names), in the in-window and native
menus, with a check mark on the current one as the built-ins have.

The in-window submenu was also too short to show it. egui lays out a
new submenu in a default 400 pt area, and the scroll area inside took
that height and kept it, so Window ▸ Workspace (514 pt) opened with its
last rows (Undocked Panels and below, where saved workspaces go)
scrolled out of view. Submenus now open at their full height and scroll
only when taller than the window.

Tests: saved_workspace_is_listed_in_the_workspace_menu (picks it from
the menu bar), saved_workspaces_are_in_the_native_workspace_menu.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:17:28 +07:00
bflatasticandClaude Opus 5.5 7387049ed4 Dock: drag the gaps between stacked panels to resize them (#171)
Cause: a `DockNode::Stack` laid its panels out from fixed heights (Preview
46 pt) with the rest shared, but only split gutters were interactive, so
the right column's panels could be expanded or collapsed and never resized.

Fix: the gap below a stacked panel drags the nearest open panels above and
below it against each other, neither going below 40 pt. The new sizes are
stored as the entries' heights, so they persist in the layout like split
sizes. The last panel without a height keeps sharing the rest, and when no
panel takes the rest, or space runs short, the heights scale so the stack
fills its column. Split and stack gaps share one `gutter` helper.
Automation ids: `dock.gutter.<path>s<i>` for the gap below stacked entry i.

Test: ui_tabs `stacked_panel_gaps_resize_the_panels` (drag, persistence,
collapse/expand, minimum); dock unit tests `stacked_panels_fill_their_column`
and `dragging_a_stack_gap_trades_height_between_its_neighbours`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 14:23:18 +07:00
bflatasticandClaude Opus 5.5 bcf5f32d9b UI: clicking into a value or inline text field selects all of it (#170)
Cause: the click-to-edit fields (hot numbers and revolutions, the Timeline
time display and layer comments, Project rename/comment, the Render Queue
output path, Essential Graphics control names) opened a text edit with
egui's default caret at the end of the text, so typing appended to the old
value. The Timeline time display, layer comment and Essential Graphics name
editors also re-requested focus before checking `lost_focus()`, which hid
the loss, so Enter, Escape and clicking away never closed them.

Fix: a shared `widgets::keep_focus` keeps an open inline editor focused and
selects its whole text on the frame it opens (`widgets::select_all`, which
the layer rename now reuses). Those editors check for a lost focus first,
and Escape now cancels in the time display and the Essential Graphics
name editor.

Test: ui_angles `typing_replaces_the_clicked_value` (degrees and revolutions
in Effect Controls, Timeline and Properties, typed without Ctrl+A);
ui_inline_edit `project_rename_replaces_the_name` and
`timecode_field_replaces_commits_and_cancels`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 14:23:18 +07:00
bflatasticandClaude Opus 5.5 254e895316 Viewer: a locked viewer keeps its comp when the Project panel opens another (#156)
The Project panel (like other panels) runs comp.open while it draws, so the
new comp became the active comp mid-frame. Later in the same frame the
active, locked viewer drew and recorded the active comp as its own. On the
next frame on_open_comp compared that record with the opened comp, found
them equal and skipped the lock, so the locked viewer switched comps.

The active viewer no longer records its comp while an OpenComp event is
pending (it asks for a repaint instead); on_open_comp then sees the comp
the locked viewer really shows and routes the new one to an unlocked or
new viewer.

The viewers test's click helper now releases a frame after the press, as
a hand does: a press in a tab strip focuses the shown panel first, and a
same-frame press and release refocused it after the tab's own click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:44:46 +07: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
bflatasticandClaude Opus 5.5 472521954d Project: folder twirls and label swatches take clicks (#152)
Each Project panel row registered its click / drag area after the twirl,
proxy indicator, label swatch and comment cell drawn on it, so the row sat
on top and took their clicks: a folder's arrow selected the folder instead
of opening it, and the swatch never opened the label menu.

The row's area now goes in first and those widgets sit on top of it. A
single click on the arrow twirls the folder; a click on the swatch opens
the label menu (project.setLabel). Clicks, double-clicks and drags on the
rest of the row work as before; a single click on the comment cell still
selects the row, and a double-click there now edits the comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:32:40 +07:00
bflatasticandClaude Opus 5.5 72e70f5360 Tests: ui_angles reveals Rotation through the per-layer filter (#160)
Its setup wrote the Timeline's old global reveal field, which no longer
filters on its own since reveals are kept per layer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:21:08 +07:00
bflatasticandClaude Opus 5.5 bfd1e974ea Timeline: the Time Navigator's ends drag to zoom; ; toggles frame level (#159)
The navigator drew handles on both ends but the whole bar was one drag
area that scrolled. Each end is now a handle (timeline.navigator.start /
.end) that moves that side of the visible span while the other stays:
pulled in, the ruler zooms in; stretched to the track's ends, it shows
the whole comp again. The middle still scrolls at the current zoom.

; (timeline.zoomFrameToggle) zooms in to frame level around the current
time and, pressed again, back out to the whole comp. timeline.zoomFit
stays as the plain fit command (no shortcut). Both go through set_zoom.

Tests: navigator_ends_drag_to_zoom and
semicolon_toggles_frame_level_and_the_whole_comp (real pointer drags).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:13:26 +07:00
bflatasticandClaude Opus 5.5 f936b1ff9c Timeline: Alt+wheel zooms out to the whole comp (#158)
Alt+wheel's zoom took its lower limit from the current zoom rather than
the comp's fit and kept the ruler's start where the pointer had put it,
so on long comps it stopped zoomed out on a slice from the middle of the
comp (0:05:02–0:10:27 of a 10-minute comp). The = / - zoom used a
different limit again.

Both now go through set_zoom: the limit is the zoom that fits the comp
(fit_pps, the same formula the ruler and the zoom slider use), and
reaching it switches the ruler back to fitting the whole comp from 0.

Test: alt_wheel_zooms_out_until_the_whole_comp_shows (real Alt+wheel
events on a 10-minute comp); it failed before (Some(1.135) px/s, start
still offset).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 13:09:18 +07:00
Jonas Neubauer a6685a1eb7 G2: cancel numeric edits on Escape and reject non-finite values 2026-10-07 01:43:57 +02:00
bflatasticandClaude Opus 5.5 63a7483a48 Comments: describe the new drop, angle and cache code without product names
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 02:34:14 +07:00
bflatasticandClaude Opus 5.5 8ef1a28bc4 Viewer: drop Project items, files and effects onto the comp (#85, #88)
The Composition viewer ignored drags from other panels. Now, as in After
Effects:

- A Project item dropped on the viewer becomes a layer centred where it
  lands (above the selected layer, like any new layer); while dragging, the
  viewer is outlined and the layer's frame follows the pointer.
- Media Browser files do the same; files dropped from the OS file manager
  do too when the platform reports where the pointer is during the drag
  (otherwise they import as before).
- An effect from Effects & Presets goes on the layer under the pointer,
  which is outlined while dragging.

layer.addItem and layer.newModel take `position` ([x, y] comp px).
file.import takes `addToComp` with `time`, `index` and `position`;
mediaBrowser.import shares it (file::add_to_comp): layered files go in as
their comps, the comp they were dropped in stays open, and items that
can't be layers are reported in `errors`. viewer::layer_at is the hit
test pick() and the drop share.

Tests: ui_drag_drop.rs (project item and effect dropped on the viewer)
and imported_files_go_into_the_comp_where_they_were_dropped (engine).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 02:31:13 +07:00
bflatasticandClaude Opus 5.5 1a0940d50a Timeline: Project items land where they are dropped (#89)
A Project item (or Media Browser file) dropped in the Timeline always
went above the selected layer, starting where new layers start.

Now, as in After Effects, a line shows where it goes in the stack (above
the first layer row whose middle is below the pointer, else below the
last), and over the time graph a marker with a timecode tag shows its In
point, which follows the pointer (frame-snapped) or, with Shift, the
current time. The layer reorder drag shares the insertion logic.

layer.addItem and layer.newModel take `index` (1-based stack position);
mediaBrowser.import passes `time` and `index` on, one layer under the
other.

Tests: project_items_land_where_they_are_dropped_in_the_timeline
(ui_drag_drop.rs, real pointer drags: outline, time graph, Shift, below
the last layer) and add_item_at_a_stack_index_and_time (engine).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 02:18:38 +07:00
bflatasticandClaude Opus 5.5 93856382e7 Preview: Audio, Lock and Shy keep the cache; playback with audio waits for frames (#103)
Toggling a layer's Audio, Lock or Shy switch (or Hide Shy Layers) threw
away every cached frame: the RAM preview identity hashes each comp's
address, which any edit replaces, and the layer and disk cache keys
hashed every switch. None of these change pixels.

- Switches::pixels, Layer::same_pixels and Comp::same_pixels (exhaustive
  field lists, so a new field must be considered) and Comp::pixel_form.
- Layer cache keys hash Switches::pixels; the disk cache's comp content
  key hashes Comp::pixel_form.
- Frames::identity: when a new revision's comps draw the same pixels as
  those of the comp's last new identity, that identity (and the project
  holding its comps' addresses) is kept, so the frames stay, also when the
  switch is toggled back.

With audio on, playback skipped frames that weren't rendered yet. Now the
sound waits: until the frames ahead (to the end of the preview, or what
fits in the RAM preview) are cached, every frame shows as it renders,
silently, like a preview without audio; then the sound starts in sync.
Reaching an uncached frame stops the sound instead of skipping frames.

Tests: audio_lock_and_shy_switches_keep_the_frames (frames.rs, nested
comps and toggling back), audio_lock_and_shy_keep_cached_layers (engine),
the disk key in tests_disk_cache, and
preview_with_audio_shows_every_frame_and_sounds_once_cached.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:46:35 +07:00
bflatasticandClaude Opus 5.5 27b0eec216 Angles: the revolutions ("0x") scrub and take typing (#93)
An angle's revolutions were painted text: only the degrees could be
dragged or typed, so full turns needed the dial or typing past 360.

fx_widgets::angle_field draws `0x+45.0°` as two hot numbers: the
revolutions (widgets::hot_int_at, ten pixels a turn, fractions adding up
between frames so slow drags still step; a typed value is truncated) and
the degrees, each keeping the other. Effect Controls, the Timeline and
the Properties panel use it, so they read and edit angles alike (the
Timeline showed `-1x-40.0°` and Properties had its own split).

Automation ids: timeline.prop.<uid>.revolutions,
effectControls.prop.<uid>.revolutions, properties.prop.<uid>.revolutions.

Regression test: angle_revolutions_scrub_and_take_typing drags and types
the revolutions in all three panels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:29:03 +07:00
bflatasticandClaude Opus 5.5 8a415e03ca Render Queue: Render Settings templates apply from the menu (#117)
Choosing Draft Settings (or any entry) in a Render Queue item's Render
Settings menu did nothing when the menu had been moved up to fit the
window. egui hit-tests a fixed-position Area where it was asked to go,
not where it was moved, so the popup's "pressed outside" check was true
over its own entries and the menu closed on the press, before the click.

widgets::pressed_outside tests the press against the rect the popup was
drawn at. Every popup with the same check uses it: popup_menu (Render
Queue, Effect Controls, dropdowns), the colour popup, the Preview and
viewer popups, the panel menu, the Flowchart, OpenType and font menus.

Regression test: ui_render_queue.rs picks Draft Settings from a menu
moved up to fit a 1000 px window; it failed before the fix.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 01:19:25 +07:00