2024-03-31 12:13:27 -07:00
|
|
|
import { css } from "@emotion/css";
|
|
|
|
import { PicoCart, PicoPlayerHandle, makePicoConsole } from "./renderCart";
|
|
|
|
import { ForwardedRef, forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from "react";
|
|
|
|
|
|
|
|
type Pico8ConsoleImperatives = {
|
|
|
|
getPicoConsoleHandle(): PicoPlayerHandle | null;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const Pico8Console = forwardRef((props: { carts: PicoCart[] }, forwardedRef: ForwardedRef<Pico8ConsoleImperatives>) => {
|
|
|
|
const {carts} = props;
|
2024-03-31 20:37:18 -07:00
|
|
|
const [playing, setPlaying] = useState(false);
|
2024-03-31 21:25:54 -07:00
|
|
|
const playingRef = useRef(false);
|
2024-03-31 12:13:27 -07:00
|
|
|
const ref = useRef<HTMLDivElement>(null);
|
2024-03-31 21:25:54 -07:00
|
|
|
const listeners = useRef<Partial<{
|
|
|
|
keydown: null | ((ev: KeyboardEvent) => void)
|
|
|
|
}>>({});
|
2024-03-31 12:13:27 -07:00
|
|
|
const [handle, setHandle] = useState<PicoPlayerHandle | null>(null);
|
|
|
|
const attachConsole = useCallback(async () => {
|
|
|
|
const picoConsole = await makePicoConsole({
|
|
|
|
carts,
|
|
|
|
});
|
2024-03-31 21:25:54 -07:00
|
|
|
const codoTextarea: HTMLTextAreaElement = (picoConsole.rawModule as any).codo_textarea;
|
|
|
|
picoConsole.canvas.addEventListener('click', () => {
|
|
|
|
codoTextarea.focus();
|
|
|
|
codoTextarea.select();
|
|
|
|
})
|
2024-03-31 12:13:27 -07:00
|
|
|
if (ref.current) {
|
|
|
|
ref.current.appendChild(picoConsole.canvas);
|
2024-03-31 21:25:54 -07:00
|
|
|
ref.current.appendChild(codoTextarea);
|
2024-03-31 12:13:27 -07:00
|
|
|
}
|
|
|
|
setHandle(picoConsole);
|
2024-03-31 21:25:54 -07:00
|
|
|
listeners.current.keydown = function (event) {
|
|
|
|
console.log("keydown", event.key);
|
|
|
|
if (!playingRef.current) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
console.log(picoConsole.state);
|
|
|
|
if (picoConsole.state.hasFocus) {
|
|
|
|
console.log("hasFocus");
|
|
|
|
// block only cursors, M R P, tab
|
|
|
|
if (["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight", "M", "R", "P", "m", "r", "p", "Tab"].includes(event.key)) {
|
|
|
|
console.log("preventDefault");
|
|
|
|
event.preventDefault();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
document.addEventListener('keydown',
|
|
|
|
listeners.current.keydown,
|
|
|
|
{passive: false}
|
|
|
|
);
|
2024-03-31 12:13:27 -07:00
|
|
|
}, [carts]);
|
|
|
|
useImperativeHandle(forwardedRef, () => ({
|
|
|
|
getPicoConsoleHandle() {
|
|
|
|
return handle;
|
|
|
|
}
|
|
|
|
}), [handle]);
|
|
|
|
useEffect(() => {
|
2024-03-31 21:25:54 -07:00
|
|
|
if (playing && playingRef.current) {
|
2024-03-31 20:37:18 -07:00
|
|
|
attachConsole();
|
|
|
|
return () => {
|
2024-03-31 21:25:54 -07:00
|
|
|
playingRef.current = false;
|
2024-03-31 20:37:18 -07:00
|
|
|
if (ref.current) {
|
|
|
|
ref.current.innerHTML = "";
|
|
|
|
}
|
2024-03-31 21:25:54 -07:00
|
|
|
if (listeners.current.keydown) {
|
|
|
|
document.removeEventListener("keydown", listeners.current.keydown);
|
|
|
|
listeners.current.keydown = null;
|
|
|
|
}
|
2024-03-31 20:37:18 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [playing, attachConsole]);
|
|
|
|
if (!playing) {
|
|
|
|
return (
|
|
|
|
<div
|
|
|
|
ref={ref}
|
|
|
|
className={css`
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
aspect-ratio: 1;
|
|
|
|
background-color: black;
|
|
|
|
color: white;
|
|
|
|
cursor: pointer;
|
|
|
|
`}
|
2024-03-31 21:25:54 -07:00
|
|
|
onClick={() => {playingRef.current = true; setPlaying(true)}}
|
2024-03-31 20:37:18 -07:00
|
|
|
>
|
|
|
|
Play!
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|
2024-03-31 12:13:27 -07:00
|
|
|
return (
|
|
|
|
<div ref={ref} className={css`
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
justify-content: center;
|
|
|
|
|
|
|
|
& > canvas {
|
|
|
|
width: 100%;
|
|
|
|
height: 100%;
|
|
|
|
}
|
|
|
|
`}></div>
|
|
|
|
);
|
|
|
|
});
|