clipper/frontend/src/Waveform/Overview.tsx
2021-09-30 16:16:51 +02:00

157 lines
3.8 KiB
TypeScript

import { useEffect, useState, useRef, MouseEvent } from 'react';
import { Canvas as WaveformCanvas } from './Canvas';
import { CanvasLogicalWidth, CanvasLogicalHeight, Selection } from './Waveform';
import { mouseEventToCanvasX } from './Helpers';
interface Props {
peaks: number[][] | null;
numFrames: number;
style: React.CSSProperties;
onSelectionStart: (x1: number) => void;
onSelectionChange: (selection: Selection) => void;
}
enum Mode {
Normal,
Selecting,
}
// TODO: render position marker during playback
export const Waveform: React.FC<Props> = (props: Props) => {
const hudCanvasRef = useRef<HTMLCanvasElement>(null);
const [mode, setMode] = useState(Mode.Normal);
const defaultSelection: Selection = { x1: 0, x2: 0 };
// selection is the current selection in canvas coordinates:
const [selection, setSelection] = useState(defaultSelection);
// newSelection is a new selection in the process of being drawn by the user.
// It is only useful if Mode.Selecting is active.
const [newSelection, setNewSelection] = useState(defaultSelection);
// effects
// draw the overview waveform
useEffect(() => {
(async function () {
const canvas = hudCanvasRef.current;
if (canvas == null) {
console.error('no hud canvas ref available');
return;
}
const ctx = canvas.getContext('2d');
if (ctx == null) {
console.error('no hud 2d context available');
return;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
let currentSelection: Selection;
if (mode == Mode.Selecting) {
currentSelection = newSelection;
} else {
currentSelection = selection;
}
if (currentSelection.x1 >= currentSelection.x2) {
return;
}
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.lineWidth = 2;
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.rect(
currentSelection.x1,
2,
currentSelection.x2 - currentSelection.x1,
canvas.height - 8
);
ctx.fill();
ctx.stroke();
})();
});
// handlers
const handleMouseDown = (evt: MouseEvent<HTMLCanvasElement>) => {
if (mode != Mode.Normal) {
return;
}
setMode(Mode.Selecting);
const x = mouseEventToCanvasX(evt);
setNewSelection({ x1: x, x2: x });
props.onSelectionStart(x);
};
const handleMouseMove = (evt: MouseEvent<HTMLCanvasElement>) => {
if (mode != Mode.Selecting) {
return;
}
const x = mouseEventToCanvasX(evt);
if (x == newSelection.x2) {
return;
}
setNewSelection({ ...newSelection, x2: x });
};
const handleMouseUp = () => {
if (mode != Mode.Selecting) {
return;
}
setMode(Mode.Normal);
// TODO: better shallow equality check?
if (selection.x1 !== newSelection.x1 || selection.x2 !== newSelection.x2) {
setSelection(newSelection);
props.onSelectionChange(newSelection);
}
};
// render component
const canvasStyles = {
width: '100%',
height: '100%',
margin: '0 auto',
display: 'block',
} as React.CSSProperties;
const hudCanvasStyles = {
width: '100%',
height: '100%',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
zIndex: 1,
} as React.CSSProperties;
return (
<>
<div style={props.style}>
<WaveformCanvas
peaks={props.peaks}
fillStyle="grey"
strokeStyle="black"
style={canvasStyles}
></WaveformCanvas>
<canvas
ref={hudCanvasRef}
style={hudCanvasStyles}
width={CanvasLogicalWidth}
height={CanvasLogicalHeight}
onMouseDown={handleMouseDown}
onMouseMove={handleMouseMove}
onMouseUp={handleMouseUp}
></canvas>
</div>
</>
);
};