fix: stabilize the preview canvas inside of the region during resizing

This commit is contained in:
Xiaohan-Tian
2026-05-19 17:57:21 -07:00
parent 4b5b5c8d76
commit b865064d65
7 changed files with 251 additions and 24 deletions
+37 -21
View File
@@ -9,6 +9,7 @@ import { KGAudioRegion } from '../../core/region/KGAudioRegion';
import { useProjectStore } from '../../stores/projectStore';
import { KGMainContentState } from '../../core/state/KGMainContentState';
import type { AudioRecordingPeak } from '../../core/audio-interface/KGAudioRecorder';
import type { RegionPreviewContentStyle } from '../interfaces';
const DRAG_START_THRESHOLD_PX = 4;
@@ -46,6 +47,7 @@ interface RegionItemProps {
previewWaveformPeaks?: AudioRecordingPeak[];
isPreview?: boolean;
isAudioRegion?: boolean;
previewContentStyle?: RegionPreviewContentStyle;
}
const RegionItem: React.FC<RegionItemProps> = ({
@@ -73,6 +75,7 @@ const RegionItem: React.FC<RegionItemProps> = ({
previewWaveformPeaks,
isPreview = false,
isAudioRegion = false,
previewContentStyle,
}) => {
// Get selection state and time signature from store
const { selectedRegionIds, timeSignature, bpm } = useProjectStore();
@@ -98,24 +101,26 @@ const RegionItem: React.FC<RegionItemProps> = ({
// Canvas ref for note visualization
const canvasRef = useRef<HTMLCanvasElement | null>(null);
const regionContentRef = useRef<HTMLDivElement | null>(null);
const previewContentRef = useRef<HTMLDivElement | null>(null);
// Function to render notes on canvas
const renderNotesOnCanvas = () => {
if (!canvasRef.current || !regionContentRef.current || !midiRegion) return;
if (!canvasRef.current || !previewContentRef.current || !midiRegion) return;
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Get the current dimensions of the region content
const contentRect = regionContentRef.current.getBoundingClientRect();
const width = contentRect.width;
const height = contentRect.height;
const contentRect = previewContentRef.current.getBoundingClientRect();
const width = Math.max(1, Math.round(contentRect.width));
const height = Math.max(1, Math.round(contentRect.height));
// Set canvas size to match the region content
canvas.width = width;
canvas.height = height;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
// Clear the canvas
ctx.clearRect(0, 0, width, height);
@@ -242,18 +247,20 @@ const RegionItem: React.FC<RegionItemProps> = ({
// Function to render audio waveform on canvas
const renderWaveformOnCanvas = () => {
if (!canvasRef.current || !regionContentRef.current || !audioBuffer) return;
if (!canvasRef.current || !previewContentRef.current || !audioBuffer) return;
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const contentRect = regionContentRef.current.getBoundingClientRect();
const width = contentRect.width;
const height = contentRect.height;
const contentRect = previewContentRef.current.getBoundingClientRect();
const width = Math.max(1, Math.round(contentRect.width));
const height = Math.max(1, Math.round(contentRect.height));
canvas.width = width;
canvas.height = height;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
ctx.clearRect(0, 0, width, height);
@@ -311,18 +318,20 @@ const RegionItem: React.FC<RegionItemProps> = ({
};
const renderPreviewWaveformOnCanvas = () => {
if (!canvasRef.current || !regionContentRef.current || !previewWaveformPeaks || previewWaveformPeaks.length === 0) return;
if (!canvasRef.current || !previewContentRef.current || !previewWaveformPeaks || previewWaveformPeaks.length === 0) return;
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
if (!ctx) return;
const contentRect = regionContentRef.current.getBoundingClientRect();
const width = contentRect.width;
const height = contentRect.height;
const contentRect = previewContentRef.current.getBoundingClientRect();
const width = Math.max(1, Math.round(contentRect.width));
const height = Math.max(1, Math.round(contentRect.height));
canvas.width = width;
canvas.height = height;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
ctx.clearRect(0, 0, width, height);
const centerY = height / 2;
@@ -374,11 +383,11 @@ const RegionItem: React.FC<RegionItemProps> = ({
} else {
renderNotesOnCanvas();
}
}, [midiRegion, audioRegion, audioBuffer, previewWaveformPeaks, timeSignature, bpm, id, noteUpdateTrigger, barNumber, length]);
}, [midiRegion, audioRegion, audioBuffer, previewWaveformPeaks, timeSignature, bpm, id, noteUpdateTrigger, barNumber, length, previewContentStyle?.width]);
// Re-render canvas when region content size changes
useEffect(() => {
if (!regionContentRef.current) return;
if (!previewContentRef.current) return;
const resizeObserver = new ResizeObserver(() => {
if (previewWaveformPeaks && previewWaveformPeaks.length > 0) {
@@ -390,14 +399,14 @@ const RegionItem: React.FC<RegionItemProps> = ({
}
});
resizeObserver.observe(regionContentRef.current);
resizeObserver.observe(previewContentRef.current);
return () => {
if (regionContentRef.current) {
resizeObserver.unobserve(regionContentRef.current);
if (previewContentRef.current) {
resizeObserver.unobserve(previewContentRef.current);
}
};
}, [midiRegion, audioRegion, audioBuffer, previewWaveformPeaks, timeSignature, bpm]);
}, [midiRegion, audioRegion, audioBuffer, previewWaveformPeaks, timeSignature, bpm, previewContentStyle?.width]);
// Handle mouse movement to detect edge proximity
const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
@@ -668,7 +677,7 @@ const RegionItem: React.FC<RegionItemProps> = ({
<div className="region-header">
{name}
</div>
<div className={`region-content${(audioRegion || isAudioRegion) ? ' audio-region-content' : ''}`} ref={regionContentRef}>
<div className={`region-content${(audioRegion || isAudioRegion) ? ' audio-region-content' : ''}`}>
{!isPreview && <div className="region-left-buttons">
{!audioRegion && (
<button
@@ -771,7 +780,14 @@ const RegionItem: React.FC<RegionItemProps> = ({
)}
</div>
</div>}
<canvas ref={canvasRef} />
<div
className="region-preview-content"
ref={previewContentRef}
style={previewContentStyle}
data-preview-content-active={previewContentStyle ? 'true' : 'false'}
>
<canvas ref={canvasRef} />
</div>
</div>
</div>
);