Commit Graph
2 Commits
Author SHA1 Message Date
Carlos Alcaraz GregorandCarlos Alcaraz 83c29faaf9 fix(studio): auto-enable loop when work-area markers are set (#859)
Setting an in or out point now turns on loopEnabled so the playhead
respects the marker instead of running past the out-point. Closes the
last open sub-bug of #834.

Background: PR #811 wired the work-area RAF loop to read inPoint/outPoint
but kept the loop branch gated behind loopEnabled. Default for that flag
is false, so users who set markers without first toggling the loop button
saw playback sail past the out-point (or, with the L shuttle, overshoot
by a few frames before pausing). The original spec for the feature in
issue #807 described markers as logic that "constrains the playback
engine"; the actual UX did not match that until the toggle was on.

Fix: setInPoint and setOutPoint flip loopEnabled to true when given a
non-null value. This sits next to the existing "smart setter" behavior
already in the store (setting one marker past the other nullifies the
counterpart). Clearing a marker with null preserves the current
loopEnabled, so a user who manually toggles the loop button stays in
control after that point.

Tests: full coverage for setInPoint and setOutPoint (none existed
before), including overlap nullification, non-finite rejection,
auto-enable on set, and preserve-on-clear in both directions.

Closes #834

Co-authored-by: Carlos Alcaraz <193642530+calcarazgre646@users.noreply.github.com>
2026-05-15 06:42:32 +02:00
Carlos Alcaraz GregorandCarlos Alcaraz 0f2a705259 fix(studio): preserve playback state on Jump-to-in/out shortcuts (#842)
When the user has the timeline playing and presses A (Jump to in-point)
or E (Jump to out-point), the seek seeks to the marker as expected but
also pauses the playback. The reporter (and the natural UX) expects
playback to keep going from the marker.

Root cause sits in two layers:

1. The `seek` callback in `useTimelinePlayer.ts` unconditionally calls
   `setIsPlaying(false)` and `stopRAFLoop()` whenever the store reports
   playing. That path is shared with timeline clicks, LayersPanel
   navigation, and frame stepping — flipping the default would change
   behavior the rest of the app expects.

2. `wrapTimeline` (the GSAP-timeline-backed adapter) calls `tl.pause()`
   before `tl.seek(t)`, so even if the callback above stopped pausing,
   GSAP-driven compositions would still get paused inside the adapter.

The fix is opt-in at both layers:

- Extend `PlaybackAdapter.seek` with `options?: { keepPlaying?: boolean }`.
  Default is omitted/false, preserving existing behavior for every
  caller that doesn't pass the option.
- `wrapTimeline.seek` skips the implicit `tl.pause()` when keepPlaying
  is set. `createStaticSeekPlaybackAdapter` accepts the new signature
  but is a no-op for the flag (it never paused internally).
- `useTimelinePlayer` seek callback grows the same option and forwards
  it to adapter.seek(time, options). The reset block (stopRAFLoop,
  setIsPlaying(false), shuttle refs) is gated behind !options.keepPlaying.
- Reverse shuttle is always stopped on seek (the RAF reverse tick
  cannot survive a seek), so keepPlaying is overridden when the
  shuttle was running backward. Documented with an inline comment.
- usePlaybackKeyboard updates its seek param type to match and passes
  { keepPlaying: true } on the A and E handlers only. Frame stepping
  (Arrow keys, J/L with K held) keeps the default.

Tests (happy-dom):

- useTimelinePlayer.seek.test.ts covers the callback in three cases:
  default seek clears isPlaying, seek with keepPlaying preserves
  isPlaying=true, and the option from paused state stays paused.
- playbackAdapter.test.ts (new) covers wrapTimeline: default seek
  pauses the GSAP timeline, keepPlaying: true skips the pause,
  keepPlaying: false is the explicit default.

Closes part of #834 (sub-bug #2). Sub-bug #1 (playhead should loop
to in-point when exceeding out-point) lives in the RAF tick and is
left for a follow-up PR.

Co-authored-by: Carlos Alcaraz <193642530+calcarazgre646@users.noreply.github.com>
2026-05-14 22:37:32 +02:00