feat: add snap-to-grid toggle for track grid regions and playhead

This commit is contained in:
Xiaohan-Tian
2026-04-10 20:57:34 -07:00
parent 1670679c61
commit d94b53dcf5
6 changed files with 54 additions and 18 deletions
+1 -1
View File
@@ -307,7 +307,7 @@ const RegionItem: React.FC<RegionItemProps> = ({
} else {
renderNotesOnCanvas();
}
}, [midiRegion, audioRegion, audioBuffer, timeSignature, bpm, id, noteUpdateTrigger]);
}, [midiRegion, audioRegion, audioBuffer, timeSignature, bpm, id, noteUpdateTrigger, barNumber, length]);
// Re-render canvas when region content size changes
useEffect(() => {
+13 -9
View File
@@ -268,20 +268,23 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
// If the mouse was moved and we have current values, calculate the new values
if (mouseMoved.current && currentResizeWidth.current !== null && currentResizeLeft.current !== null) {
const snap = KGMainContentState.instance().isSnappingEnabled();
if (resizeAction === 'end') {
// End resize: round length to nearest bar
newLength = Math.max(REGION_CONSTANTS.MIN_REGION_LENGTH, Math.round(currentResizeWidth.current / barWidth));
// End resize: snap length to nearest bar, or use raw value
const rawLength = currentResizeWidth.current / barWidth;
newLength = Math.max(REGION_CONSTANTS.MIN_REGION_LENGTH, snap ? Math.round(rawLength) : rawLength);
} else if (resizeAction === 'start') {
// Start resize: round bar number and adjust length accordingly
// Start resize: snap bar number, or use raw value
const rawBarNumber = currentResizeLeft.current / barWidth + 1;
newBarNumber = Math.max(1, Math.round(rawBarNumber));
newBarNumber = Math.max(1, snap ? Math.round(rawBarNumber) : rawBarNumber);
// Calculate the difference from the initial position
const barDiff = initialBarNumberRef.current! - newBarNumber;
// Adjust length to maintain the end position
newLength = initialLengthRef.current! + barDiff;
// Ensure minimum length
if (newLength < REGION_CONSTANTS.MIN_REGION_LENGTH) {
newLength = REGION_CONSTANTS.MIN_REGION_LENGTH;
@@ -434,9 +437,10 @@ const TrackGridItem: React.FC<TrackGridItemProps> = ({
// If the mouse was moved, calculate the final position
if (mouseMoved.current && currentDragLeft.current !== null && currentDragTop.current !== null) {
// Calculate the new bar number and round to nearest integer
// Calculate the new bar number; snap to nearest integer when snapping is on
const snap = KGMainContentState.instance().isSnappingEnabled();
const rawBarNumber = (currentDragLeft.current / barWidth) + 1;
finalBarNumber = Math.max(1, Math.round(rawBarNumber));
finalBarNumber = Math.max(1, snap ? Math.round(rawBarNumber) : rawBarNumber);
// Calculate the closest track based on vertical position
if (allTracks && allTracks.length > 0 && gridContainerRef.current) {
+5 -2
View File
@@ -193,6 +193,7 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
const secondsPerBeat = 60 / bpm;
const clipOffset = coreRegion.getClipStartOffsetSeconds();
const audioDuration = coreRegion.getAudioDurationSeconds();
const snap = KGMainContentState.instance().isSnappingEnabled();
// Left edge changed — calculate new clip offset
if (clampedBarNumber !== oldBarNumber) {
@@ -205,7 +206,9 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
// Dragged past audio start — snap to earliest allowed position
const maxLeftExtensionBeats = clipOffset / secondsPerBeat;
const minStartBeat = oldStartBeat - maxLeftExtensionBeats;
clampedBarNumber = Math.ceil(minStartBeat / beatsPerBar) + 1;
clampedBarNumber = snap
? Math.ceil(minStartBeat / beatsPerBar) + 1
: (minStartBeat / beatsPerBar) + 1;
const oldEndBarNumber = oldBarNumber + (coreRegion.getLength() / beatsPerBar);
clampedLength = oldEndBarNumber - clampedBarNumber;
newClipStartOffsetSeconds = 0;
@@ -219,7 +222,7 @@ const TrackGridPanel: React.FC<TrackGridPanelProps> = ({
const maxDurationSeconds = audioDuration - effectiveClipOffset;
const maxLengthBars = (maxDurationSeconds / secondsPerBeat) / beatsPerBar;
if (clampedLength > maxLengthBars) {
clampedLength = Math.floor(maxLengthBars);
clampedLength = snap ? Math.floor(maxLengthBars) : maxLengthBars;
if (clampedLength < REGION_CONSTANTS.MIN_REGION_LENGTH) {
clampedLength = REGION_CONSTANTS.MIN_REGION_LENGTH;
}