Gameboy: add a fullscreen button
This commit is contained in:
parent
48db472af5
commit
4d840e0290
1 changed files with 38 additions and 7 deletions
|
@ -3,9 +3,11 @@ import { WasmBoy } from '@soapbox.pub/wasmboy';
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
||||||
|
|
||||||
|
import { exitFullscreen, isFullscreen, requestFullscreen } from 'soapbox/features/ui/util/fullscreen';
|
||||||
|
|
||||||
import { IconButton } from './ui';
|
import { IconButton } from './ui';
|
||||||
|
|
||||||
interface IGameboy extends Pick<React.CanvasHTMLAttributes<HTMLCanvasElement>, 'onFocus' | 'onBlur'> {
|
interface IGameboy extends Pick<React.HTMLAttributes<HTMLDivElement>, 'onFocus' | 'onBlur'> {
|
||||||
/** Classname of the outer `<div>`. */
|
/** Classname of the outer `<div>`. */
|
||||||
className?: string;
|
className?: string;
|
||||||
/** URL to the ROM. */
|
/** URL to the ROM. */
|
||||||
|
@ -16,10 +18,12 @@ interface IGameboy extends Pick<React.CanvasHTMLAttributes<HTMLCanvasElement>, '
|
||||||
|
|
||||||
/** Component to display a playable Gameboy emulator. */
|
/** Component to display a playable Gameboy emulator. */
|
||||||
const Gameboy: React.FC<IGameboy> = ({ className, src, aspect = 'normal', onFocus, onBlur, ...rest }) => {
|
const Gameboy: React.FC<IGameboy> = ({ className, src, aspect = 'normal', onFocus, onBlur, ...rest }) => {
|
||||||
|
const node = useRef<HTMLDivElement>(null);
|
||||||
const canvas = useRef<HTMLCanvasElement>(null);
|
const canvas = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
const [paused, setPaused] = useState(false);
|
const [paused, setPaused] = useState(false);
|
||||||
const [muted, setMuted] = useState(true);
|
const [muted, setMuted] = useState(true);
|
||||||
|
const [fullscreen, setFullscreen] = useState(false);
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
await WasmBoy.config(WasmBoyOptions, canvas.current!);
|
await WasmBoy.config(WasmBoyOptions, canvas.current!);
|
||||||
|
@ -33,14 +37,18 @@ const Gameboy: React.FC<IGameboy> = ({ className, src, aspect = 'normal', onFocu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleFocus: React.FocusEventHandler<HTMLCanvasElement> = useCallback(() => {
|
const handleFocus: React.FocusEventHandler<HTMLDivElement> = useCallback(() => {
|
||||||
WasmBoy.enableDefaultJoypad();
|
WasmBoy.enableDefaultJoypad();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleBlur: React.FocusEventHandler<HTMLCanvasElement> = useCallback(() => {
|
const handleBlur: React.FocusEventHandler<HTMLDivElement> = useCallback(() => {
|
||||||
WasmBoy.disableDefaultJoypad();
|
WasmBoy.disableDefaultJoypad();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleFullscreenChange = useCallback(() => {
|
||||||
|
setFullscreen(isFullscreen());
|
||||||
|
}, []);
|
||||||
|
|
||||||
const pause = async () => {
|
const pause = async () => {
|
||||||
await WasmBoy.pause();
|
await WasmBoy.pause();
|
||||||
setPaused(true);
|
setPaused(true);
|
||||||
|
@ -58,6 +66,14 @@ const Gameboy: React.FC<IGameboy> = ({ className, src, aspect = 'normal', onFocu
|
||||||
setMuted(false);
|
setMuted(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const toggleFullscreen = () => {
|
||||||
|
if (isFullscreen()) {
|
||||||
|
exitFullscreen();
|
||||||
|
} else if (node.current) {
|
||||||
|
requestFullscreen(node.current);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
init();
|
init();
|
||||||
|
|
||||||
|
@ -67,17 +83,27 @@ const Gameboy: React.FC<IGameboy> = ({ className, src, aspect = 'normal', onFocu
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.addEventListener('fullscreenchange', handleFullscreenChange, true);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('fullscreenchange', handleFullscreenChange, true);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={clsx(className, 'relative')}>
|
<div
|
||||||
|
ref={node}
|
||||||
|
tabIndex={0}
|
||||||
|
className={clsx(className, 'relative')}
|
||||||
|
onFocus={onFocus ?? handleFocus}
|
||||||
|
onBlur={onBlur ?? handleBlur}
|
||||||
|
>
|
||||||
<canvas
|
<canvas
|
||||||
ref={canvas}
|
ref={canvas}
|
||||||
tabIndex={0}
|
|
||||||
className={clsx('h-full w-full bg-black ', {
|
className={clsx('h-full w-full bg-black ', {
|
||||||
'object-contain': aspect === 'normal',
|
'object-contain': aspect === 'normal',
|
||||||
'object-cover': aspect === 'stretched',
|
'object-cover': aspect === 'stretched',
|
||||||
})}
|
})}
|
||||||
onFocus={onFocus ?? handleFocus}
|
|
||||||
onBlur={onBlur ?? handleBlur}
|
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -92,6 +118,11 @@ const Gameboy: React.FC<IGameboy> = ({ className, src, aspect = 'normal', onFocu
|
||||||
onClick={unmute}
|
onClick={unmute}
|
||||||
src={muted ? require('@tabler/icons/volume-3.svg') : require('@tabler/icons/volume.svg')}
|
src={muted ? require('@tabler/icons/volume-3.svg') : require('@tabler/icons/volume.svg')}
|
||||||
/>
|
/>
|
||||||
|
<IconButton
|
||||||
|
className='ml-auto text-white'
|
||||||
|
onClick={toggleFullscreen}
|
||||||
|
src={fullscreen ? require('@tabler/icons/arrows-minimize.svg') : require('@tabler/icons/arrows-maximize.svg')}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
Loading…
Reference in a new issue