silphius/app/react/components/dom/dialogue.jsx

119 lines
3.1 KiB
React
Raw Normal View History

2024-07-31 09:47:51 -05:00
import {useCallback, useEffect, useMemo, useRef, useState} from 'react';
2024-07-12 02:10:22 -05:00
2024-07-20 04:32:33 -05:00
import {useDomScale} from '@/react/context/dom-scale.js';
import {useRadians} from '@/react/context/radians.js';
2024-07-31 09:47:51 -05:00
import useAnimationFrame from '@/react/hooks/use-animation-frame.js';
2024-07-20 04:41:00 -05:00
import {RESOLUTION} from '@/util/constants.js';
import {render} from '@/util/dialogue.js';
2024-07-12 02:10:22 -05:00
2024-07-23 12:53:12 -05:00
import DialogueCaret from './dialogue-caret.jsx';
2024-07-12 02:10:22 -05:00
import styles from './dialogue.module.css';
export default function Dialogue({
camera,
dialogue,
scale,
}) {
const domScale = useDomScale();
const ref = useRef();
const [dimensions, setDimensions] = useState({h: 0, w: 0});
2024-07-13 03:02:55 -05:00
const [caret, setCaret] = useState(0);
2024-07-14 21:07:46 -05:00
const radians = useRadians();
2024-07-13 17:08:23 -05:00
useEffect(() => {
return dialogue.addSkipListener(() => {
if (caret >= dialogue.letters.length - 1) {
dialogue.onClose();
}
else {
setCaret(dialogue.letters.length - 1);
}
});
}, [caret, dialogue]);
2024-07-13 03:02:55 -05:00
useEffect(() => {
const {params} = dialogue.letters[caret];
let handle;
if (caret >= dialogue.letters.length - 1) {
2024-07-13 17:08:23 -05:00
const {linger} = dialogue;
2024-07-13 03:02:55 -05:00
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)
}
2024-07-12 02:10:22 -05:00
}
2024-07-13 03:02:55 -05:00
return () => {
clearTimeout(handle);
}
}, [caret, dialogue]);
2024-07-31 09:47:51 -05:00
const updateDimensions = useCallback(() => {
if (ref.current) {
const {height, width} = ref.current.getBoundingClientRect();
setDimensions({h: height / domScale, w: width / domScale});
2024-07-12 02:10:22 -05:00
}
2024-07-31 09:47:51 -05:00
}, [domScale]);
useAnimationFrame(updateDimensions);
2024-07-14 07:24:15 -05:00
const localRender = useMemo(
() => render(dialogue.letters, styles.letter),
[dialogue.letters],
);
2024-07-22 04:19:29 -05:00
let position = 'function' === typeof dialogue.position
? dialogue.position()
: dialogue.position || {x: 0, y: 0};
position = {
x: position.x + dialogue.offset.x,
y: position.y + dialogue.offset.y,
};
2024-07-12 02:10:22 -05:00
const bounds = {
x: dimensions.w / (2 * scale),
y: dimensions.h / (2 * scale),
};
const left = Math.max(
Math.min(
2024-07-30 14:05:27 -05:00
position.x * scale,
RESOLUTION.x - bounds.x * scale - 16 + camera.x,
2024-07-12 02:10:22 -05:00
),
2024-07-30 14:05:27 -05:00
bounds.x * scale + 16 + camera.x,
2024-07-12 02:10:22 -05:00
);
const top = Math.max(
Math.min(
2024-07-30 14:05:27 -05:00
position.y * scale - dimensions.h / 2,
RESOLUTION.y - bounds.y * scale - 16 + camera.y,
2024-07-12 02:10:22 -05:00
),
2024-07-30 14:05:27 -05:00
bounds.y * scale + 16 + camera.y,
2024-07-12 02:10:22 -05:00
);
return (
<div
2024-07-13 16:56:47 -05:00
className={styles.dialogue}
2024-07-12 02:10:22 -05:00
ref={ref}
style={{
2024-07-30 14:05:27 -05:00
translate: `
${left}px
${top}px
`,
2024-07-12 02:10:22 -05:00
}}
>
2024-07-23 12:53:12 -05:00
<DialogueCaret
camera={camera}
dialogue={dialogue}
dimensions={dimensions}
scale={scale}
/>
2024-07-13 16:56:47 -05:00
<p className={styles.letters}>
2024-07-14 07:24:15 -05:00
{localRender(caret, radians)}
2024-07-13 16:56:47 -05:00
</p>
2024-07-12 02:10:22 -05:00
</div>
);
}