fix: sync TempoTrackLane leadIn with ruler, time labels to top

- Added leadInMargin prop to TempoTrackLane so time-0 aligns with ruler
- Added CLIP_BUFFER to TempoTrackLane grid rendering
- Moved TimelineRuler time labels from bottom (y=40) to top (y=11)
This commit is contained in:
2026-07-25 10:41:13 +07:00
parent 70d1abaa7a
commit f299b93372
3 changed files with 21 additions and 11 deletions
+12 -8
View File
@@ -1302,7 +1302,7 @@ const TimelineRuler = ({
ctx.fillStyle = 'rgba(255, 180, 100, 0.55)';
ctx.font = 'bold 8px monospace';
ctx.textAlign = 'left';
ctx.fillText(formatTimeSimple(t), localX + 4, height - 8);
ctx.fillText(formatTimeSimple(t), localX + 4, 11);
}
ctx.fillStyle = 'rgba(255, 255, 255, 0.4)';
@@ -1337,7 +1337,8 @@ const TempoTrackLane = ({
snapValue,
onRulerMouseDown,
scrollLeft,
canvasRedrawCount
canvasRedrawCount,
leadInMargin: propLeadIn
}) => {
const canvasRef = useRef(null);
const drawWidth = Math.min(timelineWidth, viewportWidth);
@@ -1358,15 +1359,17 @@ const TempoTrackLane = ({
ctx.fillRect(0, 0, drawWidth, height);
const beatDuration = 60 / bpm;
const barDuration = beatDuration * 4;
const leadIn = propLeadIn !== undefined ? propLeadIn : barDuration;
const CLIP_BUFFER = Math.max(400, barDuration * zoom + 200);
const PADDING_LEFT = barDuration; // 1-bar margin on left
const tStart = scrollLeftVal / zoom - PADDING_LEFT;
const tEnd = (scrollLeftVal + drawWidth) / zoom + PADDING_LEFT;
const tStart = (scrollLeftVal - leadIn * zoom) / zoom - PADDING_LEFT;
const tEnd = (scrollLeftVal + drawWidth - leadIn * zoom) / zoom + PADDING_LEFT;
const firstBeat = Math.floor(tStart / beatDuration) * beatDuration;
for (let t = firstBeat; t <= tEnd; t += beatDuration) {
const beatNum = Math.floor(t / beatDuration) + 1;
const isBar = beatNum % 4 === 1;
const localX = (t - tStart) * zoom;
if (localX < -200 || localX > drawWidth + 200) continue;
if (localX < -CLIP_BUFFER || localX > drawWidth + CLIP_BUFFER) continue;
if (isBar) {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
ctx.lineWidth = 1.5;
@@ -1423,7 +1426,7 @@ const TempoTrackLane = ({
const firstSec = Math.floor(tStart / secInterval) * secInterval;
for (let t = firstSec; t <= tEnd; t += secInterval) {
const localX = (t - tStart) * zoom;
if (localX < -200 || localX > drawWidth + 200) continue;
if (localX < -CLIP_BUFFER || localX > drawWidth + CLIP_BUFFER) continue;
ctx.strokeStyle = 'rgba(255, 180, 100, 0.15)';
ctx.lineWidth = 0.8;
ctx.beginPath();
@@ -1440,7 +1443,7 @@ const TempoTrackLane = ({
ctx.font = 'bold 10px Inter, sans-serif';
ctx.textAlign = 'right';
ctx.fillText(`${bpm} BPM`, drawWidth - 6, 12);
}, [bpm, zoom, timelineWidth, viewportWidth, snapValue, scrollLeft, canvasRedrawCount]);
}, [bpm, zoom, timelineWidth, viewportWidth, snapValue, scrollLeft, canvasRedrawCount, propLeadIn]);
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("div", {
key: "virtual-spacer-tempo",
style: {
@@ -1459,7 +1462,8 @@ const TempoTrackLane = ({
onMouseDown: e => {
const rect = canvasRef.current.getBoundingClientRect();
const x = e.clientX - rect.left + (scrollLeft || 0);
const time = Math.max(0, x / zoom);
const leadIn = (60 / bpm) * 4;
const time = Math.max(0, x / zoom - leadIn);
if (e.shiftKey) {
e.preventDefault();
e.stopPropagation();