fix: doc-level ruler drag, min 16bars playback, selection overlay, edge/center handles

This commit is contained in:
2026-07-25 21:28:26 +07:00
parent 9bfcd1870c
commit 1a5c1eff43
+100 -25
View File
@@ -5739,29 +5739,32 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
}
return;
}
rulerDragRef.current = { startX: e.clientX, startBeat: clickBeat, scrollLeft: e.currentTarget.scrollLeft };
},
onMouseMove: e => {
if (!rulerDragRef.current) return;
const rect = e.currentTarget.getBoundingClientRect();
const x = e.clientX - rect.left + rulerDragRef.current.scrollLeft;
const beat = Math.max(0, x / pixelsPerBeat);
if (Math.abs(e.clientX - rulerDragRef.current.startX) > 5) {
const sBeat = Math.max(0, Math.min(rulerDragRef.current.startBeat, beat));
const eBeat = Math.max(sBeat + 1, Math.max(rulerDragRef.current.startBeat, beat));
setLoopStartBeat(sBeat);
setLoopEndBeat(eBeat);
setIsLooping(true);
setSubTabs(prev => prev.map(s => s.id === st.id ? {
...s,
selectionStart: sBeat * beatSec,
selectionEnd: eBeat * beatSec,
isLooping: true
} : s));
}
},
onMouseUp: () => { rulerDragRef.current = null; },
onMouseLeave: () => { rulerDragRef.current = null; }
const startData = { startX: e.clientX, startBeat: clickBeat, scrollLeft: e.currentTarget.scrollLeft };
rulerDragRef.current = startData;
const onMove = (ev) => {
const r = rulerScrollRef.current;
if (!r || !rulerDragRef.current) return;
const rRect = r.getBoundingClientRect();
const bx = ev.clientX - rRect.left + rulerDragRef.current.scrollLeft;
const beat = Math.max(0, bx / pixelsPerBeat);
if (Math.abs(ev.clientX - rulerDragRef.current.startX) > 5) {
const sBeat = Math.max(0, Math.min(rulerDragRef.current.startBeat, beat));
const eBeat = Math.max(sBeat + 1, Math.max(rulerDragRef.current.startBeat, beat));
setLoopStartBeat(sBeat);
setLoopEndBeat(eBeat);
setIsLooping(true);
setSubTabs(prev => prev.map(s => s.id === st.id ? {
...s,
selectionStart: sBeat * beatSec,
selectionEnd: eBeat * beatSec,
isLooping: true
} : s));
}
};
const onUp = () => { rulerDragRef.current = null; document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}
}, /*#__PURE__*/React.createElement("div", {
style: {
width: `${viewWidth}px`,
@@ -5774,7 +5777,65 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
width: `${(loopEndBeat - loopStartBeat) * pixelsPerBeat}px`,
top: 0, bottom: 0
},
className: "absolute bg-emerald-500/15 border-l border-r border-emerald-400 pointer-events-none"
className: "absolute bg-emerald-500/15 border-l border-r border-emerald-400"
}, /*#__PURE__*/React.createElement("div", {
style: { position: 'absolute', left: 0, top: 0, bottom: 0, width: '4px', cursor: 'ew-resize' },
onMouseDown: (e) => {
e.stopPropagation();
const startBeat = loopStartBeat;
const onMove = (ev) => {
const r = rulerScrollRef.current;
if (!r) return;
const rRect = r.getBoundingClientRect();
const bx = ev.clientX - rRect.left + r.scrollLeft;
const nBeat = Math.max(0, Math.min(loopEndBeat - 1, Math.round(bx / pixelsPerBeat / 4) * 4));
setLoopStartBeat(nBeat);
setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, selectionStart: nBeat * beatSec } : s));
};
const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}
}), /*#__PURE__*/React.createElement("div", {
style: { position: 'absolute', right: 0, top: 0, bottom: 0, width: '4px', cursor: 'ew-resize' },
onMouseDown: (e) => {
e.stopPropagation();
const onMove = (ev) => {
const r = rulerScrollRef.current;
if (!r) return;
const rRect = r.getBoundingClientRect();
const bx = ev.clientX - rRect.left + r.scrollLeft;
const nBeat = Math.max(loopStartBeat + 1, Math.round(bx / pixelsPerBeat / 4) * 4);
setLoopEndBeat(nBeat);
setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, selectionEnd: nBeat * beatSec } : s));
};
const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}
}), /*#__PURE__*/React.createElement("div", {
style: { position: 'absolute', left: '4px', right: '4px', top: 0, bottom: 0, cursor: 'grab' },
onMouseDown: (e) => {
e.stopPropagation();
const startBeat = loopStartBeat;
const range = loopEndBeat - loopStartBeat;
const offsetBeat = startBeat + range / 2;
const onMove = (ev) => {
const r = rulerScrollRef.current;
if (!r) return;
const rRect = r.getBoundingClientRect();
const bx = ev.clientX - rRect.left + r.scrollLeft;
const centerBeat = Math.round(bx / pixelsPerBeat / 4) * 4;
const halfRange = range / 2;
const newStart = Math.max(0, centerBeat - halfRange);
setLoopStartBeat(newStart);
setLoopEndBeat(newStart + range);
setSubTabs(prev => prev.map(s => s.id === st.id ? { ...s, selectionStart: newStart * beatSec, selectionEnd: (newStart + range) * beatSec } : s));
};
const onUp = () => { document.removeEventListener('mousemove', onMove); document.removeEventListener('mouseup', onUp); };
document.addEventListener('mousemove', onMove);
document.addEventListener('mouseup', onUp);
}
})))), /*#__PURE__*/React.createElement("div", {
className: "flex-1 flex overflow-hidden min-h-0 relative"
}, /*#__PURE__*/React.createElement("div", {
@@ -5803,6 +5864,13 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
onMouseLeave: handleGridMouseUp,
onContextMenu: handleContextMenu,
className: "absolute inset-0 cursor-crosshair"
}), loopStartBeat !== null && loopEndBeat !== null && loopEndBeat > loopStartBeat && /*#__PURE__*/React.createElement("div", {
style: {
left: `${loopStartBeat * pixelsPerBeat}px`,
width: `${(loopEndBeat - loopStartBeat) * pixelsPerBeat}px`,
top: 0, bottom: 0
},
className: "absolute bg-emerald-500/10 border-l border-r border-emerald-400/50 pointer-events-none"
})))), showCC && /*#__PURE__*/React.createElement("div", {
style: { height: `${ccHeight}px` },
className: "bg-[#161616] border-t border-zinc-900 flex shrink-0 relative"
@@ -5835,6 +5903,13 @@ const PianoRollTabEditor = ({ st, zoom, bpm, viewportWidth, onClose, onUpdateNot
onMouseUp: () => { ccDragRef.current = null; },
onMouseLeave: () => { ccDragRef.current = null; },
className: "absolute inset-0"
}), loopStartBeat !== null && loopEndBeat !== null && loopEndBeat > loopStartBeat && /*#__PURE__*/React.createElement("div", {
style: {
left: `${loopStartBeat * pixelsPerBeat}px`,
width: `${(loopEndBeat - loopStartBeat) * pixelsPerBeat}px`,
top: 0, bottom: 0
},
className: "absolute bg-emerald-500/10 border-l border-r border-emerald-400/50 pointer-events-none"
})))), scaleMenuPos && renderScaleContextMenu());
};
@@ -9304,7 +9379,7 @@ const App = () => {
const end = (n.start_beat || 0) + (n.duration_beats || 1);
if (end > maxEnd) maxEnd = end;
});
return maxEnd * beatSec + 1.0;
return Math.max(maxEnd * beatSec, 16 * beatSec * 4) + 1.0;
})() : st.buffer.duration;
if (bufferPos >= effectiveDuration) {
stopAllPlayback();