feat: let playhead automatically centered
This commit is contained in:
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user