feat: add scale highlighting to Piano Roll grid

This commit is contained in:
Xiaohan-Tian
2025-12-09 23:50:03 -08:00
parent 98ed8a60ce
commit 9e0c0cc1f5
8 changed files with 168 additions and 24 deletions
+15 -3
View File
@@ -1,8 +1,10 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useMemo } from 'react';
import type { MutableRefObject } from 'react';
import { Playhead } from '../common';
import SelectionBox from './SelectionBox';
import { isModifierKeyPressed } from '../../util/osUtil';
import { generatePianoGridBackground } from '../../util/scaleUtil';
import type { KeySignature } from '../../core/KGProject';
interface PianoGridProps {
gridRef: MutableRefObject<HTMLDivElement | null>;
@@ -18,6 +20,8 @@ interface PianoGridProps {
endY: number;
};
regionStartBeat?: number;
selectedMode: string;
keySignature: KeySignature;
}
interface CursorPosition {
@@ -35,11 +39,18 @@ const PianoGrid: React.FC<PianoGridProps> = ({
onMouseDown,
isBoxSelecting,
selectionBox,
regionStartBeat = 0
regionStartBeat = 0,
selectedMode,
keySignature
}) => {
const [cursorPosition, setCursorPosition] = useState<CursorPosition | null>(null);
const [isModifierPressed, setIsModifierPressed] = useState(false);
// Generate background with scale highlighting - only regenerate when mode or key changes
const backgroundImage = useMemo(() => {
return generatePianoGridBackground(selectedMode, keySignature);
}, [selectedMode, keySignature]);
// Track modifier key state
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
@@ -116,9 +127,10 @@ const PianoGrid: React.FC<PianoGridProps> = ({
return (
<div className="piano-grid-container">
<div
<div
className={`piano-grid ${isModifierPressed ? 'pencil-cursor' : ''}`}
ref={gridRef}
style={{ backgroundImage }}
onDoubleClick={onDoubleClick}
onClick={onClick}
onMouseDown={(e) => onMouseDown(e)}
+3 -1
View File
@@ -27,7 +27,7 @@ const PianoRoll: React.FC<PianoRollProps> = ({
initialPosition,
initialSize
}) => {
const { maxBars, tracks, updateTrack, timeSignature, showChatBox, showInstrumentSelection } = useProjectStore();
const { maxBars, tracks, updateTrack, timeSignature, showChatBox, showInstrumentSelection, keySignature } = useProjectStore();
// Tool state for piano roll
const [activeTool, setActiveTool] = useState<'pointer' | 'pencil'>('pointer');
@@ -792,6 +792,8 @@ const PianoRoll: React.FC<PianoRollProps> = ({
tracks={tracks}
onSetNoteUpdateTrigger={handleSetNoteUpdateTrigger}
onSetDeleteNotesTrigger={handleSetDeleteNotesTrigger}
selectedMode={selectedMode}
keySignature={keySignature}
/>
<div
@@ -10,6 +10,7 @@ import PianoGridHeader from './PianoGridHeader';
import PianoGrid from './PianoGrid';
import { useNoteOperations } from '../../hooks/useNoteOperations';
import { useNoteSelection } from '../../hooks/useNoteSelection';
import type { KeySignature } from '../../core/KGProject';
interface PianoRollContentProps {
contentRef: React.MutableRefObject<HTMLDivElement | null>;
@@ -21,6 +22,8 @@ interface PianoRollContentProps {
tracks: KGTrack[];
onSetNoteUpdateTrigger?: (setNoteFn: React.Dispatch<React.SetStateAction<number>>) => void;
onSetDeleteNotesTrigger?: (deleteFn: () => boolean) => void;
selectedMode: string;
keySignature: KeySignature;
}
const PianoRollContent: React.FC<PianoRollContentProps> = ({
@@ -32,7 +35,9 @@ const PianoRollContent: React.FC<PianoRollContentProps> = ({
updateTrack,
tracks,
onSetNoteUpdateTrigger,
onSetDeleteNotesTrigger
onSetDeleteNotesTrigger,
selectedMode,
keySignature
}) => {
// Get KGCore instance
const core = KGCore.instance();
@@ -227,6 +232,8 @@ const PianoRollContent: React.FC<PianoRollContentProps> = ({
isBoxSelecting={isBoxSelectingRef.current}
selectionBox={selectionBoxRef.current}
regionStartBeat={activeRegion?.getStartFromBeat() || 0}
selectedMode={selectedMode}
keySignature={keySignature}
>
{memoizedNotes}
</PianoGrid>
@@ -33,7 +33,7 @@ const PianoRollToolbar: React.FC<PianoRollToolbarProps> = ({
<div className="toolbar-left">
{/* Left section with mode dropdown */}
<KGDropdown
options={KGPianoRollState.MODE_OPTIONS}
options={KGPianoRollState.MODE_DATA.map(m => ({ label: m.name, value: m.id }))}
value={selectedMode}
onChange={(value) => onModeChange(value)}
label="Mode"