tick button can be held for ticks
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
import type Decimal from 'break_eternity.js';
|
import type Decimal from 'break_eternity.js';
|
||||||
|
import { useEffect, useRef } from 'react';
|
||||||
|
import type { PointerEvent } from 'react';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
time: Decimal;
|
time: Decimal;
|
||||||
@@ -7,7 +9,47 @@ interface Props {
|
|||||||
onInitialize: () => void;
|
onInitialize: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HOLD_TICK_INTERVAL_MS = 1000;
|
||||||
|
const MIN_TICK_INTERVAL_MS = 50;
|
||||||
|
|
||||||
export default function Topbar({ time, onTick, onReset, onInitialize }: Props) {
|
export default function Topbar({ time, onTick, onReset, onInitialize }: Props) {
|
||||||
|
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
// Mutable, not React state: doTick reschedules itself via setTimeout, so it must always read
|
||||||
|
// and write the CURRENT speed directly — a value captured in a render's closure (e.g. via
|
||||||
|
// useState) would stay frozen for the lifetime of that closure, since nothing ever forces
|
||||||
|
// doTick to be re-created from a fresh render while a hold is in progress.
|
||||||
|
const speedRef = useRef(HOLD_TICK_INTERVAL_MS);
|
||||||
|
|
||||||
|
function stopHolding() {
|
||||||
|
if (timeoutRef.current !== null) {
|
||||||
|
clearTimeout(timeoutRef.current);
|
||||||
|
timeoutRef.current = null;
|
||||||
|
}
|
||||||
|
speedRef.current = HOLD_TICK_INTERVAL_MS;
|
||||||
|
}
|
||||||
|
|
||||||
|
function doTick() {
|
||||||
|
onTick();
|
||||||
|
speedRef.current = Math.max(MIN_TICK_INTERVAL_MS, Math.pow(speedRef.current, 0.97));
|
||||||
|
timeoutRef.current = setTimeout(doTick, speedRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
function startHolding(e: PointerEvent<HTMLButtonElement>) {
|
||||||
|
if (e.button !== 0)
|
||||||
|
return;
|
||||||
|
e.currentTarget.setPointerCapture(e.pointerId);
|
||||||
|
stopHolding();
|
||||||
|
doTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener('blur', stopHolding);
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('blur', stopHolding);
|
||||||
|
stopHolding();
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="topbar">
|
<div className="topbar">
|
||||||
<h1 className="topbar__title">
|
<h1 className="topbar__title">
|
||||||
@@ -24,7 +66,12 @@ export default function Topbar({ time, onTick, onReset, onInitialize }: Props) {
|
|||||||
<button className="btn" onClick={onReset}>
|
<button className="btn" onClick={onReset}>
|
||||||
Reset
|
Reset
|
||||||
</button>
|
</button>
|
||||||
<button className="btn btn--primary" onClick={onTick}>
|
<button
|
||||||
|
className="btn btn--primary"
|
||||||
|
onPointerDown={startHolding}
|
||||||
|
onPointerUp={stopHolding}
|
||||||
|
onPointerCancel={stopHolding}
|
||||||
|
>
|
||||||
Tick ▶
|
Tick ▶
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user