feat: add intuitive drag-to-select on waveforms
Improved selection UX to match professional DAWs: - Drag directly on waveform to create selections (no modifier keys needed) - Click without dragging seeks playhead and clears selection - 3-pixel drag threshold prevents accidental selections - Fixed variable name conflict with existing file drag-and-drop feature Users can now naturally drag across waveforms to select regions for editing, providing a more intuitive workflow. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -68,6 +68,8 @@ export function Track({
|
||||
// Selection state
|
||||
const [isSelecting, setIsSelecting] = React.useState(false);
|
||||
const [selectionStart, setSelectionStart] = React.useState<number | null>(null);
|
||||
const [isSelectingByDrag, setIsSelectingByDrag] = React.useState(false);
|
||||
const [dragStartPos, setDragStartPos] = React.useState<{ x: number; y: number } | null>(null);
|
||||
|
||||
const handleNameClick = () => {
|
||||
setIsEditingName(true);
|
||||
@@ -224,41 +226,66 @@ export function Track({
|
||||
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
const clickTime = (x / rect.width) * duration;
|
||||
|
||||
// Shift+drag to create selection
|
||||
if (e.shiftKey) {
|
||||
setIsSelecting(true);
|
||||
setSelectionStart(clickTime);
|
||||
onSelectionChange?.({ start: clickTime, end: clickTime });
|
||||
} else {
|
||||
// Regular click clears selection and seeks
|
||||
onSelectionChange?.(null);
|
||||
if (onSeek) {
|
||||
onSeek(clickTime);
|
||||
}
|
||||
}
|
||||
// Store drag start position
|
||||
setDragStartPos({ x: e.clientX, y: e.clientY });
|
||||
setIsSelectingByDrag(false);
|
||||
|
||||
// Start selection immediately (will be used if user drags)
|
||||
setIsSelecting(true);
|
||||
setSelectionStart(clickTime);
|
||||
};
|
||||
|
||||
const handleCanvasMouseMove = (e: React.MouseEvent<HTMLCanvasElement>) => {
|
||||
if (!isSelecting || selectionStart === null || !duration) return;
|
||||
if (!isSelecting || selectionStart === null || !duration || !dragStartPos) return;
|
||||
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const currentTime = (x / rect.width) * duration;
|
||||
|
||||
// Clamp to valid time range
|
||||
const clampedTime = Math.max(0, Math.min(duration, currentTime));
|
||||
// Check if user has moved enough to be considered dragging (threshold: 3 pixels)
|
||||
const dragDistance = Math.sqrt(
|
||||
Math.pow(e.clientX - dragStartPos.x, 2) + Math.pow(e.clientY - dragStartPos.y, 2)
|
||||
);
|
||||
|
||||
// Update selection (ensure start < end)
|
||||
const start = Math.min(selectionStart, clampedTime);
|
||||
const end = Math.max(selectionStart, clampedTime);
|
||||
if (dragDistance > 3) {
|
||||
setIsSelectingByDrag(true);
|
||||
}
|
||||
|
||||
onSelectionChange?.({ start, end });
|
||||
// If dragging, update selection
|
||||
if (isSelectingByDrag || dragDistance > 3) {
|
||||
// Clamp to valid time range
|
||||
const clampedTime = Math.max(0, Math.min(duration, currentTime));
|
||||
|
||||
// Update selection (ensure start < end)
|
||||
const start = Math.min(selectionStart, clampedTime);
|
||||
const end = Math.max(selectionStart, clampedTime);
|
||||
|
||||
onSelectionChange?.({ start, end });
|
||||
}
|
||||
};
|
||||
|
||||
const handleCanvasMouseUp = () => {
|
||||
const handleCanvasMouseUp = (e: React.MouseEvent<HTMLCanvasElement>) => {
|
||||
if (!duration) return;
|
||||
|
||||
const rect = e.currentTarget.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const clickTime = (x / rect.width) * duration;
|
||||
|
||||
// If user didn't drag (just clicked), clear selection and seek
|
||||
if (!isSelectingByDrag) {
|
||||
onSelectionChange?.(null);
|
||||
if (onSeek) {
|
||||
onSeek(clickTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset drag state
|
||||
setIsSelecting(false);
|
||||
setIsSelectingByDrag(false);
|
||||
setDragStartPos(null);
|
||||
};
|
||||
|
||||
// Handle mouse leaving canvas during selection
|
||||
@@ -266,6 +293,8 @@ export function Track({
|
||||
const handleGlobalMouseUp = () => {
|
||||
if (isSelecting) {
|
||||
setIsSelecting(false);
|
||||
setIsSelectingByDrag(false);
|
||||
setDragStartPos(null);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user