feat: let playhead automatically centered

This commit is contained in:
Xiaohan-Tian
2026-04-16 20:46:28 -07:00
parent 1d5bb59ad7
commit 425aeab2d1
+59 -1
View File
@@ -31,6 +31,8 @@ const MainContent: React.FC<MainContentProps> = ({
updateTrackProperties, updateTrackProperties,
timeSignature, timeSignature,
setPlayheadPosition, setPlayheadPosition,
playheadPosition,
isPlaying,
clearAllSelections, clearAllSelections,
setSelectedTrack, setSelectedTrack,
showPianoRoll, showPianoRoll,
@@ -80,6 +82,12 @@ const MainContent: React.FC<MainContentProps> = ({
// Refs to track pending updates for verification // Refs to track pending updates for verification
const pendingUpdates = useRef<Map<string, { trackId: string, regionId: string, startBeat: number, length: number }>>(new Map()); const pendingUpdates = useRef<Map<string, { trackId: string, regionId: string, startBeat: number, length: number }>>(new Map());
// Refs for auto-scroll during playback
const mainContentRef = useRef<HTMLDivElement | null>(null);
const userManuallyScrolledRef = useRef(false);
const expectedScrollLeftRef = useRef<number>(-1);
const isPlayingRef = useRef(false);
// Refs for bar numbers and loop range drag functionality // Refs for bar numbers and loop range drag functionality
const barNumbersRef = useRef<HTMLDivElement | null>(null); const barNumbersRef = useRef<HTMLDivElement | null>(null);
const isLoopDraggingRef = useRef(false); const isLoopDraggingRef = useRef(false);
@@ -87,6 +95,56 @@ const MainContent: React.FC<MainContentProps> = ({
const loopDragStartXRef = useRef<number | null>(null); const loopDragStartXRef = useRef<number | null>(null);
const loopDragOriginalSettingsRef = useRef<{ isLooping: boolean; loopingRange: [number, number] } | null>(null); const loopDragOriginalSettingsRef = useRef<{ isLooping: boolean; loopingRange: [number, number] } | null>(null);
// Sync isPlayingRef and reset manual-scroll flag when playback starts
useEffect(() => {
if (isPlaying && !isPlayingRef.current) {
userManuallyScrolledRef.current = false;
}
isPlayingRef.current = isPlaying;
}, [isPlaying]);
// Detect manual horizontal scroll during playback
useEffect(() => {
const container = mainContentRef.current;
if (!container) return;
const handleScroll = () => {
if (!isPlayingRef.current) return;
// If scrollLeft matches what we programmatically set (within 1px), it's our scroll — ignore
if (Math.abs(container.scrollLeft - expectedScrollLeftRef.current) < 1) return;
userManuallyScrolledRef.current = true;
};
container.addEventListener('scroll', handleScroll);
return () => container.removeEventListener('scroll', handleScroll);
}, []);
// Auto-scroll to keep playhead centered during playback
useEffect(() => {
if (!isPlaying || userManuallyScrolledRef.current) return;
const container = mainContentRef.current;
if (!container) return;
const beatsPerBar = timeSignature.numerator;
const barPosition = playheadPosition / beatsPerBar;
const barWidth = parseInt(
getComputedStyle(document.documentElement).getPropertyValue('--track-grid-bar-width')
) || 40;
const playheadPixel = barPosition * barWidth;
// Center the playhead in the visible grid area (excluding the 200px sticky info panel)
const infoWidth = 200;
const targetScrollLeft = playheadPixel - (container.clientWidth - infoWidth) / 2;
const clampedScrollLeft = Math.max(
0,
Math.min(targetScrollLeft, container.scrollWidth - container.clientWidth)
);
expectedScrollLeftRef.current = clampedScrollLeft;
container.scrollLeft = clampedScrollLeft;
}, [playheadPosition, isPlaying, timeSignature]);
// Effect to verify track updates // Effect to verify track updates
useEffect(() => { useEffect(() => {
// Check for pending updates // Check for pending updates
@@ -722,7 +780,7 @@ const MainContent: React.FC<MainContentProps> = ({
}; };
return ( return (
<div className={`main-content${showInstrumentSelection ? ' has-left-instrument' : ''}`}> <div className={`main-content${showInstrumentSelection ? ' has-left-instrument' : ''}`} ref={mainContentRef}>
<div className="main-content-wrapper"> <div className="main-content-wrapper">
{/* Top-left spacer */} {/* Top-left spacer */}
<div className="top-left-spacer"> <div className="top-left-spacer">