Skip to content

Commit ec30986

Browse files
feat(studio): hide playhead while dragging a beat; default beat dots to music track
- Dragging a beat dot now hides the playhead guideline (new beatDragging store flag set on beat pointer down/up) so its line doesn't track the scrub and clutter the beat being moved. - Beat dots render on the selected track, falling back to the music track when nothing is selected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Miguel Ángel <miguel07alm@protonmail.com>
1 parent 8d4962b commit ec30986

3 files changed

Lines changed: 22 additions & 4 deletions

File tree

packages/studio/src/player/components/BeatStrip.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ export const BeatStrip = memo(function BeatStrip({
112112
e.currentTarget.setPointerCapture(e.pointerId);
113113
dragRef.current = { index: i, startX: e.clientX, origTime: t };
114114
setDrag({ index: i, dx: 0 });
115+
usePlayerStore.getState().setBeatDragging(true); // hide the playhead guideline
115116
usePlayerStore.getState().requestSeek(Math.max(0, t)); // scrub audio at beat
116117
}}
117118
onPointerMove={(e) => {
@@ -127,6 +128,7 @@ export const BeatStrip = memo(function BeatStrip({
127128
const d = dragRef.current;
128129
dragRef.current = null;
129130
setDrag(null);
131+
usePlayerStore.getState().setBeatDragging(false);
130132
if (e.currentTarget.hasPointerCapture?.(e.pointerId)) {
131133
e.currentTarget.releasePointerCapture(e.pointerId);
132134
}

packages/studio/src/player/components/TimelineCanvas.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import type { TrackVisualStyle } from "./timelineIcons";
2222
import { STUDIO_KEYFRAMES_ENABLED } from "../../components/editor/manualEditingAvailability";
2323
import { SPLIT_BOUNDARY_EPSILON_S } from "../../utils/timelineElementSplit";
2424
import { useTimelineEditContext } from "../../contexts/TimelineEditContext";
25+
import { isMusicTrack } from "../../utils/timelineInspector";
2526

2627
function ClipLabel({ element, color }: { element: TimelineElement; color: string }) {
2728
const lint = usePlayerStore((s) => s.lintFindingsByElement.get(element.key ?? element.id));
@@ -145,6 +146,7 @@ export const TimelineCanvas = memo(function TimelineCanvas({
145146
}: TimelineCanvasProps) {
146147
const { onResizeElement, onMoveElement, onRazorSplit, onRazorSplitAll } =
147148
useTimelineEditContext();
149+
const beatDragging = usePlayerStore((s) => s.beatDragging);
148150
const draggedElement = draggedClip?.element ?? null;
149151
const activeDraggedElement =
150152
draggedClip?.started === true && draggedElement
@@ -250,8 +252,11 @@ export const TimelineCanvas = memo(function TimelineCanvas({
250252
pps={pps}
251253
highlightTime={draggedClip?.started ? draggedClip.snapBeatTime : null}
252254
/>
253-
{/* Beat dots only on the active track (the one holding the selection). */}
254-
{els.some((e) => (e.key ?? e.id) === selectedElementId) && (
255+
{/* Beat dots on the active track (the one holding the selection),
256+
falling back to the music track when nothing is selected. */}
257+
{(selectedElementId
258+
? els.some((e) => (e.key ?? e.id) === selectedElementId)
259+
: els.some(isMusicTrack)) && (
255260
<BeatStrip
256261
beatTimes={beatAnalysis?.beatTimes}
257262
beatStrengths={beatAnalysis?.beatStrengths}
@@ -494,11 +499,16 @@ export const TimelineCanvas = memo(function TimelineCanvas({
494499
/>
495500
)}
496501

497-
{/* Playhead */}
502+
{/* Playhead — hidden while dragging a beat so its guideline doesn't
503+
track the scrub and clutter the beat being moved. */}
498504
<div
499505
ref={playheadRef}
500506
className="absolute top-0 bottom-0 pointer-events-none"
501-
style={{ left: `${GUTTER}px`, zIndex: 100 }}
507+
style={{
508+
left: `${GUTTER}px`,
509+
zIndex: 100,
510+
display: beatDragging ? "none" : undefined,
511+
}}
502512
>
503513
<PlayheadIndicator />
504514
</div>

packages/studio/src/player/store/playerStore.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ interface PlayerState {
6060
currentTime: number;
6161
duration: number;
6262
timelineReady: boolean;
63+
/** True while a beat dot is being dragged — hides the playhead guideline. */
64+
beatDragging: boolean;
6365
elements: TimelineElement[];
6466
selectedElementId: string | null;
6567
playbackRate: number;
@@ -103,6 +105,7 @@ interface PlayerState {
103105
setAudioMuted: (muted: boolean) => void;
104106
setLoopEnabled: (enabled: boolean) => void;
105107
setTimelineReady: (ready: boolean) => void;
108+
setBeatDragging: (dragging: boolean) => void;
106109
setElements: (elements: TimelineElement[]) => void;
107110
setSelectedElementId: (id: string | null) => void;
108111
updateElement: (
@@ -171,6 +174,7 @@ export const usePlayerStore = create<PlayerState>((set, get) => ({
171174
currentTime: 0,
172175
duration: 0,
173176
timelineReady: false,
177+
beatDragging: false,
174178
elements: [],
175179
selectedElementId: null,
176180
playbackRate: readStudioUiPreferences().playbackRate ?? 1,
@@ -307,6 +311,7 @@ export const usePlayerStore = create<PlayerState>((set, get) => ({
307311
setCurrentTime: (time) => set({ currentTime: Number.isFinite(time) ? time : 0 }),
308312
setDuration: (duration) => set({ duration: Number.isFinite(duration) ? duration : 0 }),
309313
setTimelineReady: (ready) => set({ timelineReady: ready }),
314+
setBeatDragging: (dragging) => set({ beatDragging: dragging }),
310315
setElements: (elements) => set({ elements }),
311316
setSelectedElementId: (id) => set({ selectedElementId: id }),
312317
updateElement: (elementId, updates) =>
@@ -324,6 +329,7 @@ export const usePlayerStore = create<PlayerState>((set, get) => ({
324329
currentTime: 0,
325330
duration: 0,
326331
timelineReady: false,
332+
beatDragging: false,
327333
elements: [],
328334
selectedElementId: null,
329335
inPoint: null,

0 commit comments

Comments
 (0)