Skip to content

feat: fill frame for fixed aspect ratios and full-width camera - #2382

Open
YoavLevavi wants to merge 2 commits into
CapSoftware:mainfrom
YoavLevavi:feat/fill-frame
Open

YoavLevavi wants to merge 2 commits into
CapSoftware:mainfrom
YoavLevavi:feat/fill-frame

Conversation

@YoavLevavi

@YoavLevavi YoavLevavi commented Sep 29, 2026 •

Copy link
Copy Markdown

What

A Fill Frame mode for fixed aspect ratios, aimed at vertical (9:16) edits of landscape screen recordings, similar to Screen Studio's vertical mode.

  • Fill Frame (Background settings, shown when an aspect ratio is set): the recording is scaled to cover the output instead of fitting inside it with large bars.
  • Follow Cursor (on by default): at rest, the visible part follows a smoothed cursor. The live pointer is kept at least 15% in from the output edges, so fast moves never leave it off-screen.
  • Screen Position: with Follow Cursor off, a position pad picks the fixed point of the recording that stays centered.
  • Per-scene position: a Default or Hide Camera scene can pin its own Screen Position while Fill Frame is on, for example to show a sidebar for one section. The pin is eased over the scene's transition in/out. "Follow the default behavior" removes it.
  • Zooms still work: an engaged zoom blends from the resting framing to the zoom's own proportional framing, with the zoom amount applied on top of the cover scale.
  • Full-width camera: the camera size limit rises from 80% to 100%, so on a vertical output the camera can span the full width. Camera bubbles are now also clamped to stay inside the output.

While Fill Frame is on, the screen can't be dragged in the preview; the tooltip explains why and offers to turn Fill Frame off. The notch restore is skipped in Fill Frame, because the display no longer maps 1:1 to the capture edges.

Implementation

  • BackgroundConfiguration gains fill_frame, fill_frame_follow_cursor (default true) and fill_frame_position (default center). All use serde defaults, so existing projects render exactly as before.
  • Renderer (crates/rendering/src/lib.rs):
    • fill_frame_bounds computes the display rect: cover scale × zoom amount, resting focus, zoom blend, and the cursor margin.
    • fill_frame_cursor_focus averages the cursor over a trailing 1.2 s window. It depends only on time, not on frame-to-frame state, so preview and export frame identically.
    • fill_frame_scene_override provides the per-scene pin with smoothstep easing.
    • The previous-frame bounds are computed the same way, so motion blur stays consistent.
  • Per-scene positions reuse the scene's existing splitLayout.screenPosition, so no new timeline schema is needed.

Testing

  • cap-rendering: new tests for:
    • covering a vertical output and following the focus to both edges
    • zoomed framing
    • the live-cursor margin
    • per-scene pinning with eased edges
    • the full-width camera touching both sides
  • cap-project: legacy configs default to Follow Cursor and a centered position.
  • cargo clippy -p cap-project -p cap-rendering -p cap-desktop --all-targets -D warnings is clean; cap-rendering and cap-project tests pass. Desktop Vitest suite passes; Biome and tsc --noEmit are clean.
  • Used for vertical edits of 16:9 screen recordings with and without zooms, in preview and export.

🤖 Generated with Claude Code

RetriggerConfidence Score: 2/5

The PR is not ready to merge because Fill Frame can appear enabled without rendering, and removing a scene pin can discard saved layout adjustments.

Findings

  1. P1 Fill Frame silently stays off ▶
  2. P1 Removing pin deletes layout settings ▶
  3. P1 Scene pin starts late ▶
  4. P2 Position pad shows wrong focus ▶
Fix with agent prompt
### Issue 1
crates/rendering/src/lib.rs:2545
When a fixed-aspect project uses a macOS, Windows, Browser, or MacBook frame, the editor offers Fill Frame and locks screen dragging, but this condition prevents the renderer from applying Fill Frame. The recording remains fitted instead of filling the output, and the user cannot reposition it by dragging.

### Issue 2
apps/desktop/src/routes/editor/ConfigSidebar.tsx:5256-5263
If a user changes a configured split or floating scene to Default or Hide Camera, then clicks “Follow the default behavior,” this action deletes the entire `splitLayout`, not just its screen-position pin. Switching back loses the saved screen zoom, camera zoom, and camera position.

### Issue 3
crates/rendering/src/lib.rs:2593-2597
A scene’s transition-in begins before `scene.start`, but this lookup does not select its pin until `scene.start`. The screen stays at its default framing through the first part of the transition, then continues panning after the scene has started.

### Issue 4
apps/desktop/src/routes/editor/ConfigSidebar.tsx:5249-5251
Before a scene has its own pin, this pad shows the split-layout default of center. When Follow Cursor is off and the Background screen position is elsewhere, the pad misrepresents the scene’s current framing; using it can then create a pin at a different position.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

This PR adds fixed-aspect Fill Frame framing with cursor following and per-scene pins, raises the camera size limit, and updates the desktop controls and project bindings.

  • The renderer and editor disagree about Fill Frame availability when a decorative frame is selected.
  • Scene pin removal can discard unrelated split-layout settings, and pin transitions start late.
  • The scene position pad can misrepresent an inherited fixed position.

Reviews (1) · Last reviewed commit: "feat: fill frame for fixed aspect ratios..."

With a fixed aspect ratio (e.g. 9:16), Fill Frame scales the recording to
cover the whole output instead of letterboxing it. The screen follows a
smoothed cursor (kept inside an edge margin) or rests at a fixed Screen
Position, and zooms still pan to their own focus. Default / Hide Camera
scenes can pin their own screen position, eased over the scene's
transitions. The camera size limit rises to 100% so a camera can span the
full output width, and camera bubbles stay inside the output.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
fn fill_frame_active(project: &ProjectConfiguration) -> bool {
project.background.fill_frame
&& project.aspect_ratio.is_some()
&& FrameConfiguration::active_style(project.background.frame.as_ref()) == FrameStyle::None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Fill Frame silently stays off When a fixed-aspect project uses a macOS, Windows, Browser, or MacBook frame, the editor offers Fill Frame and locks screen dragging, but this condition prevents the renderer from applying Fill Frame. The recording remains fitted instead of filling the output, and the user cannot reposition it by dragging.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/rendering/src/lib.rs
Line: 2545

Comment:
**Fill Frame silently stays off** When a fixed-aspect project uses a macOS, Windows, Browser, or MacBook frame, the editor offers Fill Frame and locks screen dragging, but this condition prevents the renderer from applying Fill Frame. The recording remains fitted instead of filling the output, and the user cannot reposition it by dragging.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f64f45f. Fill Frame is deliberately skipped with a decorative frame, because the window/MacBook chrome needs the fitted display. The editor now matches that: the Fill Frame toggle, Follow Cursor, the Screen Position pads and the screen-drag lock only appear when no frame style is active. Adding a frame shows the normal fitted, draggable layout.

Comment on lines +5256 to +5263
onClick={() =>
setProject(
"timeline",
"sceneSegments",
props.segmentIndex,
"splitLayout",
null,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Removing pin deletes layout settings If a user changes a configured split or floating scene to Default or Hide Camera, then clicks “Follow the default behavior,” this action deletes the entire splitLayout, not just its screen-position pin. Switching back loses the saved screen zoom, camera zoom, and camera position.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/routes/editor/ConfigSidebar.tsx
Line: 5256-5263

Comment:
**Removing pin deletes layout settings** If a user changes a configured split or floating scene to Default or Hide Camera, then clicks “Follow the default behavior,” this action deletes the entire `splitLayout`, not just its screen-position pin. Switching back loses the saved screen zoom, camera zoom, and camera position.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f64f45f. The per-scene pin now has its own optional field, SceneSegment.fillFramePosition (serde default, skipped when None). It no longer reuses splitLayout.screenPosition. "Follow the default behavior" only clears that field, so a scene's split or floating zoom and positions survive switching modes. The renderer test now sets a split layout alongside the pin to cover this.

Comment on lines +2593 to +2597
let scene = scenes.iter().find(|scene| {
matches!(scene.mode, SceneMode::Default | SceneMode::HideCamera)
&& time >= scene.start
&& time < scene.end
})?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Scene pin starts late A scene’s transition-in begins before scene.start, but this lookup does not select its pin until scene.start. The screen stays at its default framing through the first part of the transition, then continues panning after the scene has started.

Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/rendering/src/lib.rs
Line: 2593-2597

Comment:
**Scene pin starts late** A scene’s transition-in begins before `scene.start`, but this lookup does not select its pin until `scene.start`. The screen stays at its default framing through the first part of the transition, then continues panning after the scene has started.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f64f45f. The pin is selected over [start - transition_in, end + transition_out), the same window scene transitions use elsewhere, and eases in before start and out after end. It reaches full weight exactly at start. The test checks partial weight at 1.75 s (entering), full at 2.0 s, and partial at 6.25 s (leaving).

Comment on lines +5249 to +5251
<PositionPad
value={() => split().screenPosition}
onChange={(pos) => updateSplit({ screenPosition: pos })}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Position pad shows wrong focus Before a scene has its own pin, this pad shows the split-layout default of center. When Follow Cursor is off and the Background screen position is elsewhere, the pad misrepresents the scene’s current framing; using it can then create a pin at a different position.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/desktop/src/routes/editor/ConfigSidebar.tsx
Line: 5249-5251

Comment:
**Position pad shows wrong focus** Before a scene has its own pin, this pad shows the split-layout default of center. When Follow Cursor is off and the Background screen position is elsewhere, the pad misrepresents the scene’s current framing; using it can then create a pin at a different position.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in f64f45f. Before a scene has its own pin, the pad shows the scene's effective framing: the Background Screen Position when Follow Cursor is off, otherwise center. Dragging it starts from what's on screen.

- hide Fill Frame while a decorative frame is active, where it can't apply
- store the per-scene pin in its own fillFramePosition field so removing it
  keeps the scene's split layout
- ease the pin across the full transition window around the scene edges
- show the scene's effective framing in its position pad

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant