diff options
-rw-r--r-- | components/controls.tsx | 161 | ||||
-rw-r--r-- | pages/present.tsx | 59 |
2 files changed, 188 insertions, 32 deletions
diff --git a/components/controls.tsx b/components/controls.tsx new file mode 100644 index 0000000..b7df330 --- /dev/null +++ b/components/controls.tsx @@ -0,0 +1,161 @@ +import { useEffect, useRef } from 'react'; + +interface controlsPropsType { + next: () => void; + previous: () => void; + menu: () => void; +} + +export function FullScreenControls({ next, previous, menu }: controlsPropsType) { + return <div className='fullscreenControls posabs a0'> + <div className='control previous' onClick={previous} /> + <div className='control menu' onClick={menu} /> + <div className='control next' onClick={next} /> + </div>; +} + +export function MenuBarControls({ next, previous, menu }: controlsPropsType) { + var canvasRef = useRef(null); + + var options = { + margin: 100, // screen margin + friction: 0.0, // friction + edgeForce: 1.0, // force outside margin (inwards to edges) + centerForce: 0.6, // force inside margin (outwards to edges) + maxForce: 50, // limit force to not go over this value + doneTolerance: 0.5, // if movement per frame goes below this value the animation is considered done + }; + + useEffect(() => { + var canvas = canvasRef.current as HTMLCanvasElement; + var ctx = canvas.getContext('2d'); + + function line([x1, y1]: [number, number], [x2, y2]: [number, number]) { + ctx.beginPath(); + ctx.moveTo(x1, y1); + ctx.lineTo(x2, y2); + ctx.stroke(); + } + + var mouseX = 0; + var mouseY = 0; + + function draw() { + var box = { + outer: { + x: 0, + y: 0, + width: canvas.width, + height: canvas.height, + }, + inner: { + x: options.margin, + y: options.margin, + width: Math.max(0, canvas.width - 2 * options.margin), + height: Math.max(0, canvas.height - 2 * options.margin), + }, + }; + + function findCenter(x: number, y: number): [number, number] { + var ratio = canvas.width / canvas.height; + var coolZone = (Math.abs(ratio - 1) * canvas.height) / 2; + var hw = canvas.width / 2; + var hh = canvas.height / 2; + + if (ratio > 1) { + if (x < hw - coolZone) { + return [hw - coolZone, hh]; + } else if (x > hw + coolZone) { + return [hw + coolZone, hh]; + } else { + return [x, hh]; + } + } else if (ratio < 1) { + if (y < hh - coolZone) { + return [hw, hh - coolZone]; + } else if (y > hh + coolZone) { + return [hw, hh + coolZone]; + } else { + return [hw, y]; + } + } else { + return [hw, hh]; + } + } + + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.lineWidth = 2; + + ctx.strokeStyle = '#ff00ff'; + + var center = findCenter(mouseX, mouseY); + var mouse = [mouseX, mouseY]; + // line(center, mouse as [ number, number ]); + + var slope = (mouse[1] - center[1]) / (mouse[0] - center[0]); + if (Math.abs(slope) < 1) { + var right = mouse[0] > center[0]; + if (right) { + var x = box.inner.x + box.inner.width; + var y = (x - mouseX) * slope + mouseY; + } else { + var x = box.inner.x; + var y = (mouseX - x) * -slope + mouseY; + } + } else { + var slope = (mouse[0] - center[0]) / (mouse[1] - center[1]); + var bottom = mouse[1] > center[1]; + if (bottom) { + var y = box.inner.y + box.inner.height; + var x = (y - mouseY) * slope + mouseX; + } else { + var y = box.inner.y; + var x = (mouseY - y) * -slope + mouseX; + } + } + var offset = [mouse[0] - x, mouse[1] - y]; + var outside = mouse[0] < box.inner.x + || mouse[0] > box.inner.x + box.inner.width + || mouse[1] < box.inner.y + || mouse[1] > box.inner.y + box.inner.height; + offset = offset.map(o => o * options[outside ? 'edgeForce' : 'centerForce']); + var distance = Math.sqrt(offset[0] ** 2 + offset[1] ** 2); + offset = offset.map(o => o * -Math.min(1, options.maxForce / distance)); + + ctx.strokeStyle = '#ff0000'; + line([mouseX + offset[0], mouseY + offset[1]], [mouseX, mouseY]); + + ctx.strokeStyle = '#00ffff'; + + line([box.inner.x, box.inner.y], [box.inner.x + box.inner.width, box.inner.y]); + line([box.inner.x + box.inner.width, box.inner.y + box.inner.height], [ + box.inner.x + box.inner.width, + box.inner.y, + ]); + line([box.inner.x, box.inner.y + box.inner.height], [ + box.inner.x + box.inner.width, + box.inner.y + box.inner.height, + ]); + line([box.inner.x, box.inner.y], [box.inner.x, box.inner.y + box.inner.height]); + + requestAnimationFrame(draw); + } + draw(); + + function onresize() { + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + } + onresize(); + window.addEventListener('resize', onresize); + canvas.addEventListener('mousemove', e => { + mouseX = e.clientX; + mouseY = e.clientY; + }); + }, []); + return <div className='fullscreenControls posabs a0'> + <canvas ref={canvasRef} /> + <div className='menuBar'> + </div> + </div>; +} diff --git a/pages/present.tsx b/pages/present.tsx index 0468d48..2cb8e98 100644 --- a/pages/present.tsx +++ b/pages/present.tsx @@ -1,6 +1,7 @@ import Button from '@material-ui/core/Button'; import { useEffect, useState } from 'react'; import Timecode from 'timecode-boss'; +import { FullScreenControls, MenuBarControls } from '../components/controls'; import Project, { arrayBufferToBase64 } from '../project'; import timeline, { delaySlide, loopSlide, slide, speedChangeSlide } from '../timeline'; @@ -203,10 +204,18 @@ export default function Present() { var [project, _setProject] = useState(new Project()); player.project = project; + var controlType = project.settings?.controls?.ControlType; + var [menu, setMenu] = useState(true); + + var Controls = { + 'FullScreen': FullScreenControls, + 'MenuBar': MenuBarControls, + }[controlType] || (() => null); + + var [time, setTime] = useState(''); + useEffect(() => { - setInterval(() => { - document.getElementById('time').innerText = new Date().toLocaleTimeString(); - }, 500); + setInterval(() => setTime(new Date().toLocaleTimeString()), 500); }, []); useEffect(() => { @@ -222,40 +231,26 @@ export default function Present() { </div> </div> </div> - <div className='fullscreenControls posabs a0'> - <div - className='control previous' - onClick={() => { - player.previous(); - rerender(); - }} - /> - <div - className='control menu' - onClick={() => { - document.getElementById('menu').classList.add('active'); - rerender(); - }} - /> - <div - className='control next' - onClick={() => { - player.next(); - player.player.play(); - rerender(); - }} - /> - </div> - <div className='menu posabs a0' id='menu'> + <MenuBarControls + next={() => { + player.next(); + player.player.play(); + rerender(); + }} + previous={() => { + player.previous(); + rerender(); + }} + menu={() => setMenu(true)} + /> + <div className={'menu posabs a0 ' + (menu ? 'active ' : '')} id='menu'> <div className='background posabs a0' - onClick={() => { - document.getElementById('menu').classList.remove('active'); - }} + onClick={() => setMenu(false)} /> <div className='info sidebyside posabs h0 b0'> <div className='timetitle floatb'> - <h3 className='time numbers nobr' id='time'>14:00:41</h3> + <h3 className='time numbers nobr'>{time}</h3> <h1 className='title nobr'>{player.project?.name || '???'}</h1> </div> <div className='buttons floatb'> |