feat: add cross-component playhead scroll synchronization

This commit is contained in:
Xiaohan-Tian
2026-05-02 00:25:41 -07:00
parent 6b8a470022
commit da8b9496df
7 changed files with 94 additions and 11 deletions
+38 -3
View File
@@ -50,6 +50,8 @@ const MainContent: React.FC<MainContentProps> = ({
addAudioTrack,
projectName,
savedProjectName,
requestPianoRollScroll,
mainContentScrollRequest,
} = useProjectStore();
// State to store regions
@@ -136,8 +138,10 @@ const MainContent: React.FC<MainContentProps> = ({
) || 40;
const playheadPixel = barPosition * barWidth;
// Center the playhead in the visible grid area (excluding the 200px sticky info panel)
const infoWidth = 200;
// Center the playhead in the visible grid area (excluding the sticky info panel)
const infoWidth = parseInt(
getComputedStyle(document.documentElement).getPropertyValue('--track-info-panel-width')
) || 200;
const targetScrollLeft = playheadPixel - (container.clientWidth - infoWidth) / 2;
const clampedScrollLeft = Math.max(
0,
@@ -148,6 +152,36 @@ const MainContent: React.FC<MainContentProps> = ({
container.scrollLeft = clampedScrollLeft;
}, [playheadPosition, isPlaying, autoScrollEnabled, timeSignature]);
// Handle scroll requests from piano roll header clicks
useEffect(() => {
if (mainContentScrollRequest === null) return;
const container = mainContentRef.current;
if (!container) return;
const beatsPerBar = timeSignature.numerator;
const barPosition = mainContentScrollRequest / 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 sticky info panel)
const infoWidth = parseInt(
getComputedStyle(document.documentElement).getPropertyValue('--track-info-panel-width')
) || 200;
const targetScrollLeft = playheadPixel - (container.clientWidth - infoWidth) / 2;
const clampedScrollLeft = Math.max(
0,
Math.min(targetScrollLeft, container.scrollWidth - container.clientWidth)
);
container.scrollLeft = clampedScrollLeft;
// Clear the request after handling
useProjectStore.setState({ mainContentScrollRequest: null });
}, [mainContentScrollRequest, timeSignature]);
// Effect to verify track updates
useEffect(() => {
// Check for pending updates
@@ -774,6 +808,7 @@ const MainContent: React.FC<MainContentProps> = ({
const clickPosition = calculatePlayheadFromMouse(e.clientX);
if (clickPosition !== null) {
setPlayheadPosition(clickPosition);
requestPianoRollScroll(clickPosition);
if (DEBUG_MODE.MAIN_CONTENT) {
console.log(`Single click on bar numbers - Set playhead to: ${clickPosition}`);
@@ -799,7 +834,7 @@ const MainContent: React.FC<MainContentProps> = ({
document.removeEventListener('mousemove', handleMouseMove);
document.removeEventListener('mouseup', handleMouseUp);
};
}, [calculateBarIndexFromMouse, calculatePlayheadFromMouse, setPlayheadPosition]);
}, [calculateBarIndexFromMouse, calculatePlayheadFromMouse, setPlayheadPosition, requestPianoRollScroll]);
const { showInstrumentSelection, isLooping, loopingRange } = useProjectStore();