import { useRef, useEffect, useState, MouseEvent } from 'react'; import { mouseEventToCanvasPoint } from './Helpers'; interface Props { position: number; duration: number; offsetPixels: number; onPositionChanged: (posiiton: number) => void; } enum Mode { Normal, Dragging, } const LogicalWidth = 2000; const LogicalHeight = 100; const InnerMargin = 40; export const SeekBar: React.FC = ({ position, duration, offsetPixels, onPositionChanged, }: Props) => { const [mode, setMode] = useState(Mode.Normal); const [cursor, setCursor] = useState('cursor-auto'); const canvasRef = useRef(null); // render canvas useEffect(() => { const canvas = canvasRef.current; if (canvas == null) { console.error('no seekbar canvas ref available'); return; } const ctx = canvas.getContext('2d'); if (ctx == null) { console.error('no seekbar 2d context available'); return; } // Set aspect ratio. canvas.width = canvas.height * (canvas.clientWidth / canvas.clientHeight); // background ctx.fillStyle = 'transparent'; ctx.fillRect(0, 0, canvas.width, canvas.height); // seek bar const pixelRatio = canvas.width / canvas.clientWidth; const offset = offsetPixels * pixelRatio; const width = canvas.width - offset * 2; ctx.fillStyle = 'black'; ctx.fillRect(offset, InnerMargin, width, canvas.height - InnerMargin * 2); // pointer const positionRatio = position / duration; const x = offset + width * positionRatio; const y = canvas.height / 2; ctx.beginPath(); ctx.arc(x, y, 20, 0, 2 * Math.PI, false); ctx.fillStyle = 'green'; ctx.fill(); }); // helpers const emitPositionEvent = (evt: MouseEvent) => { const canvas = evt.currentTarget; const { x } = mouseEventToCanvasPoint(evt); const pixelRatio = canvas.width / canvas.clientWidth; const offset = offsetPixels * pixelRatio; const ratio = (x - offset) / (canvas.width - offset * 2); onPositionChanged(ratio * duration); }; // handlers const handleMouseDown = (evt: MouseEvent) => { if (mode != Mode.Normal) return; setMode(Mode.Dragging); emitPositionEvent(evt); }; const handleMouseUp = () => { if (mode != Mode.Dragging) return; setMode(Mode.Normal); }; const handleMouseMove = (evt: MouseEvent) => { const { y } = mouseEventToCanvasPoint(evt); // TODO: improve mouse detection around knob. if (y > InnerMargin && y < LogicalHeight - InnerMargin) { setCursor('cursor-pointer'); } else { setCursor('cursor-auto'); } if (mode == Mode.Normal) return; emitPositionEvent(evt); }; const handleMouseEnter = () => { if (mode != Mode.Dragging) return; setMode(Mode.Normal); }; // render component return ( <> ); };