import {useEffect, useMemo, useRef, useState} from 'react'; import {useDomScale} from '@/react/context/dom-scale.js'; import {useRadians} from '@/react/context/radians.js'; import {RESOLUTION} from '@/util/constants.js'; import {render} from '@/util/dialogue.js'; import styles from './dialogue.module.css'; const CARET_SIZE = 12; export default function Dialogue({ camera, dialogue, scale, }) { const domScale = useDomScale(); const ref = useRef(); const [dimensions, setDimensions] = useState({h: 0, w: 0}); const [caret, setCaret] = useState(0); const radians = useRadians(); useEffect(() => { return dialogue.addSkipListener(() => { if (caret >= dialogue.letters.length - 1) { dialogue.onClose(); } else { setCaret(dialogue.letters.length - 1); } }); }, [caret, dialogue]); useEffect(() => { const {params} = dialogue.letters[caret]; let handle; if (caret >= dialogue.letters.length - 1) { const {linger} = dialogue; if (!linger) { return; } handle = setTimeout(() => { dialogue.onClose(); }, linger * 1000); } else { let jump = caret; while (0 === dialogue.letters[jump].params.rate.frequency && jump < dialogue.letters.length - 1) { jump += 1; } setCaret(jump); if (jump < dialogue.letters.length - 1) { handle = setTimeout(() => { setCaret(caret + 1); }, params.rate.frequency * 1000) } } return () => { clearTimeout(handle); } }, [caret, dialogue]); useEffect(() => { let handle; function track() { if (ref.current) { const {height, width} = ref.current.getBoundingClientRect(); setDimensions({h: height / domScale, w: width / domScale}); } handle = requestAnimationFrame(track); } handle = requestAnimationFrame(track); return () => { cancelAnimationFrame(handle); }; }, [dialogue, domScale, ref]); const localRender = useMemo( () => render(dialogue.letters, styles.letter), [dialogue.letters], ); const origin = 'function' === typeof dialogue.origin ? dialogue.origin() : dialogue.origin || {x: 0, y: 0}; const bounds = { x: dimensions.w / (2 * scale), y: dimensions.h / (2 * scale), }; const left = Math.max( Math.min( dialogue.position.x * scale - camera.x, RESOLUTION.x - bounds.x * scale - 16, ), bounds.x * scale + 16, ); const top = Math.max( Math.min( dialogue.position.y * scale - camera.y, RESOLUTION.y - bounds.y * scale - 16, ), bounds.y * scale + 88, ); const offsetPosition = { x: ((dialogue.position.x * scale - camera.x) - left) / scale, y: ((dialogue.position.y * scale - camera.y) - top) / scale, }; const difference = { x: origin.x - dialogue.position.x + offsetPosition.x, y: origin.y - dialogue.position.y + offsetPosition.y, }; const within = { x: Math.abs(difference.x) < bounds.x, y: Math.abs(difference.y) < bounds.y, }; const caretPosition = { x: Math.max(-bounds.x, Math.min(origin.x - dialogue.position.x + offsetPosition.x, bounds.x)), y: Math.max(-bounds.y, Math.min(origin.y - dialogue.position.y + offsetPosition.y, bounds.y)), }; let caretRotation = Math.atan2( difference.y - caretPosition.y, difference.x - caretPosition.x, ); caretRotation += Math.PI * 1.5; if (within.x) { caretPosition.y = bounds.y * Math.sign(difference.y); if (within.y) { if (Math.sign(difference.y) > 0) { caretRotation = Math.PI; } else { caretRotation = 0; } } } else if (within.y) { caretPosition.x = bounds.x * Math.sign(difference.x); } caretPosition.x *= scale; caretPosition.y *= scale; caretPosition.x += -Math.sin(caretRotation) * (CARET_SIZE / 2); caretPosition.y += Math.cos(caretRotation) * (CARET_SIZE / 2); return (

{localRender(caret, radians)}

); }