Adding functional controls.
This commit is contained in:
parent
282a4b671d
commit
93cc25adf5
@ -1,12 +1,14 @@
|
||||
import * as React from 'react';
|
||||
import {HOME_BUTTON_IMAGE} from '/constants';
|
||||
import {sendKeyDown} from '/packet';
|
||||
|
||||
export interface OverlayControlsProps {
|
||||
firstMenuElement: React.RefObject<HTMLAnchorElement>,
|
||||
setHiddenMenu: (c: boolean) => void,
|
||||
webSocket: WebSocket | null;
|
||||
};
|
||||
|
||||
export default function OverlayControls({firstMenuElement, setHiddenMenu}: OverlayControlsProps) {
|
||||
export default function OverlayControls({firstMenuElement, setHiddenMenu, webSocket}: OverlayControlsProps) {
|
||||
function showOverlayMenu() {
|
||||
setHiddenMenu(false);
|
||||
setTimeout(() => {
|
||||
@ -16,20 +18,103 @@ export default function OverlayControls({firstMenuElement, setHiddenMenu}: Overl
|
||||
firstMenuElement.current.focus();
|
||||
}, 100);
|
||||
}
|
||||
const [onGoingTouches, setOnGoingTouches] = React.useState<{[id: string]: number}>({});
|
||||
function mouseDown(e: React.MouseEvent<HTMLAnchorElement> | React.TouchEvent<HTMLAnchorElement>, key: number) {
|
||||
e.preventDefault();
|
||||
if (webSocket == null) {
|
||||
console.log('There is not websocket');
|
||||
return;
|
||||
}
|
||||
sendKeyDown(webSocket, true, key);
|
||||
}
|
||||
function touchStart(e: React.TouchEvent<HTMLAnchorElement>, key: number) {
|
||||
e.preventDefault();
|
||||
if (webSocket == null) {
|
||||
console.log('There is not websocket');
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < e.changedTouches.length; i++) {
|
||||
const touch = e.changedTouches[i];
|
||||
const idx = touch.identifier;
|
||||
onGoingTouches[idx] = key;
|
||||
sendKeyDown(webSocket, true, key);
|
||||
}
|
||||
}
|
||||
function touchEnd(e: React.TouchEvent<HTMLAnchorElement|HTMLDivElement>) {
|
||||
e.preventDefault();
|
||||
if (webSocket == null) {
|
||||
console.log('There is not websocket');
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < e.changedTouches.length; i++) {
|
||||
const touch = e.changedTouches[i];
|
||||
const idx = touch.identifier;
|
||||
const key = onGoingTouches[idx];
|
||||
delete onGoingTouches[idx];
|
||||
sendKeyDown(webSocket, false, key);
|
||||
}
|
||||
}
|
||||
|
||||
function mouseUp(e: React.MouseEvent<HTMLAnchorElement> | React.TouchEvent<HTMLAnchorElement>, key: number) {
|
||||
e.preventDefault();
|
||||
if (webSocket == null) {
|
||||
console.log('There is not websocket');
|
||||
return;
|
||||
}
|
||||
sendKeyDown(webSocket, false, key);
|
||||
}
|
||||
|
||||
document.onselectstart = () => false;
|
||||
return (
|
||||
<div className="overlay">
|
||||
<div className="overlay" onTouchEnd={touchEnd}>
|
||||
<div className="vertical-padding">
|
||||
</div>
|
||||
<div className="controls">
|
||||
<a href="#" className="gear control" onClick={showOverlayMenu}>
|
||||
<a tabIndex={-1} className="gear control" onClick={showOverlayMenu} onTouchStart={showOverlayMenu}>
|
||||
<img src={HOME_BUTTON_IMAGE} alt="Go to menu. (House icon)"/>
|
||||
</a>
|
||||
<a href="#" className="control-a control control-button">A</a>
|
||||
<a href="#" className="control-b control control-button">B</a>
|
||||
<a href="#" className="control-up control control-pad-button">^</a>
|
||||
<a href="#" className="control-down control control-pad-button">v</a>
|
||||
<a href="#" className="control-left control control-pad-button"><</a>
|
||||
<a href="#" className="control-right control control-pad-button">></a>
|
||||
<a tabIndex={-1}
|
||||
onMouseDown={(e: React.MouseEvent<HTMLAnchorElement>) => mouseDown(e, 0)}
|
||||
onMouseUp={(e: React.MouseEvent<HTMLAnchorElement>) => mouseUp(e, 0)}
|
||||
onTouchStart={(e: React.TouchEvent<HTMLAnchorElement>) => touchStart(e, 0)}
|
||||
onTouchEnd={(e: React.TouchEvent<HTMLAnchorElement>) => touchEnd(e)}
|
||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => e.preventDefault()}
|
||||
className="control-a control control-button">A</a>
|
||||
<a tabIndex={-1}
|
||||
onMouseDown={(e: React.MouseEvent<HTMLAnchorElement>) => mouseDown(e, 1)}
|
||||
onMouseUp={(e: React.MouseEvent<HTMLAnchorElement>) => mouseUp(e, 1)}
|
||||
onTouchStart={(e: React.TouchEvent<HTMLAnchorElement>) => touchStart(e, 1)}
|
||||
onTouchEnd={(e: React.TouchEvent<HTMLAnchorElement>) => touchEnd(e)}
|
||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => e.preventDefault()}
|
||||
className="control-b control control-button">B</a>
|
||||
<a tabIndex={-1}
|
||||
onMouseDown={(e: React.MouseEvent<HTMLAnchorElement>) => mouseDown(e, 6)}
|
||||
onMouseUp={(e: React.MouseEvent<HTMLAnchorElement>) => mouseUp(e, 6)}
|
||||
onTouchStart={(e: React.TouchEvent<HTMLAnchorElement>) => touchStart(e, 6)}
|
||||
onTouchEnd={(e: React.TouchEvent<HTMLAnchorElement>) => touchEnd(e)}
|
||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => e.preventDefault()}
|
||||
className="control-up control control-pad-button">^</a>
|
||||
<a tabIndex={-1}
|
||||
onMouseDown={(e: React.MouseEvent<HTMLAnchorElement>) => mouseDown(e, 7)}
|
||||
onMouseUp={(e: React.MouseEvent<HTMLAnchorElement>) => mouseUp(e, 7)}
|
||||
onTouchStart={(e: React.TouchEvent<HTMLAnchorElement>) => touchStart(e, 7)}
|
||||
onTouchEnd={(e: React.TouchEvent<HTMLAnchorElement>) => touchEnd(e)}
|
||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => e.preventDefault()}
|
||||
className="control-down control control-pad-button">v</a>
|
||||
<a tabIndex={-1}
|
||||
onMouseDown={(e: React.MouseEvent<HTMLAnchorElement>) => mouseDown(e, 8)}
|
||||
onMouseUp={(e: React.MouseEvent<HTMLAnchorElement>) => mouseUp(e, 8)}
|
||||
onTouchStart={(e: React.TouchEvent<HTMLAnchorElement>) => touchStart(e, 8)}
|
||||
onTouchEnd={(e: React.TouchEvent<HTMLAnchorElement>) => touchEnd(e)}
|
||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => e.preventDefault()}
|
||||
className="control-left control control-pad-button"><</a>
|
||||
<a tabIndex={-1}
|
||||
onMouseDown={(e: React.MouseEvent<HTMLAnchorElement>) => mouseDown(e, 9)}
|
||||
onMouseUp={(e: React.MouseEvent<HTMLAnchorElement>) => mouseUp(e, 9)}
|
||||
onTouchStart={(e: React.TouchEvent<HTMLAnchorElement>) => touchStart(e, 9)}
|
||||
onTouchEnd={(e: React.TouchEvent<HTMLAnchorElement>) => touchEnd(e)}
|
||||
onClick={(e: React.MouseEvent<HTMLAnchorElement>) => e.preventDefault()}
|
||||
className="control-right control control-pad-button">></a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -7,28 +7,28 @@ import OverlayControls from '/components/overlay-controls';
|
||||
import OverlayMenu from '/components/overlay-menu';
|
||||
import OverlaySelectFiles from '/components/overlay-select-files';
|
||||
import CloseButton from '/components/close-button';
|
||||
import {sendHello, handleSendFrame} from '/packet';
|
||||
|
||||
import Endian from '/endian';
|
||||
|
||||
import {MIN_WIDTH, MIN_HEIGHT, PACKET_ID_HELLO, PACKET_ID_SEND_FRAME} from '/constants';
|
||||
|
||||
type setBooleanCallback = (c: boolean) => boolean;
|
||||
|
||||
export interface handleClickStartEmulationButtonObjectArgs {
|
||||
e: React.MouseEvent<HTMLInputElement>;
|
||||
inputRom: HTMLInputElement | null;
|
||||
inputSaveState: HTMLInputElement | null;
|
||||
canvas: HTMLCanvasElement | null;
|
||||
printingFrame: boolean;
|
||||
setPrintingFrame: (c: setBooleanCallback) => void;
|
||||
setPrintingFrame: (c: boolean) => void;
|
||||
setEmulationStarted: (c: boolean) => void,
|
||||
setHiddenMenu: (c: boolean) => void;
|
||||
setHiddenFormSelectFiles: (c: boolean) => void;
|
||||
setWebSocket: (c: WebSocket) => void;
|
||||
};
|
||||
|
||||
function handleClickStartEmulationButton({e, inputRom, inputSaveState, canvas, printingFrame,
|
||||
setPrintingFrame, setEmulationStarted, setHiddenMenu,
|
||||
setHiddenFormSelectFiles}: handleClickStartEmulationButtonObjectArgs) {
|
||||
setHiddenFormSelectFiles, setWebSocket}: handleClickStartEmulationButtonObjectArgs) {
|
||||
e.preventDefault();
|
||||
if (canvas == null) {
|
||||
alert('Canvas does not exists?');
|
||||
@ -57,60 +57,29 @@ function handleClickStartEmulationButton({e, inputRom, inputSaveState, canvas, p
|
||||
savestate_file.arrayBuffer().then((savestate_buffer) => {
|
||||
const rom_array = new Uint8Array(rom_buffer);
|
||||
const savestate_array = new Uint8Array(savestate_buffer);
|
||||
const websocket = new WebSocket(`ws://${window.location.host}/ws`);
|
||||
websocket.binaryType = 'arraybuffer';
|
||||
websocket.onclose = (message) => {
|
||||
const webSocket = new WebSocket(`ws://${window.location.host}/ws`);
|
||||
setWebSocket(webSocket);
|
||||
webSocket.binaryType = 'arraybuffer';
|
||||
webSocket.onclose = (message) => {
|
||||
setEmulationStarted(false);
|
||||
console.log('Closing websocket.');
|
||||
}
|
||||
websocket.onopen = () => {
|
||||
webSocket.onopen = () => {
|
||||
console.log('Opened websocket.');
|
||||
setEmulationStarted(true);
|
||||
sendHello(websocket, rom_array, savestate_array);
|
||||
sendHello(webSocket, rom_array, savestate_array);
|
||||
setHiddenMenu(true);
|
||||
setHiddenFormSelectFiles(true);
|
||||
};
|
||||
setPrintingFrame(c => false);
|
||||
websocket.addEventListener('message', (event) => {
|
||||
setPrintingFrame(false);
|
||||
webSocket.addEventListener('message', (event) => {
|
||||
onWebSocketPacket(event, canvas, ctx, printingFrame, setPrintingFrame)
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function concatU8Array(array1: Uint8Array, array2: Uint8Array) {
|
||||
const final_array = new Uint8Array(array1.length + array2.length);
|
||||
final_array.set(array1);
|
||||
final_array.set(array2, array1.length);
|
||||
return final_array;
|
||||
}
|
||||
|
||||
function sendPacket(websocket: WebSocket, id: bigint, raw_data: Uint8Array) {
|
||||
const packet_u8 = concatU8Array(
|
||||
concatU8Array(Endian.u64ToByteArrayBigEndian(id), Endian.u64ToByteArrayBigEndian(BigInt(raw_data.length))),
|
||||
raw_data
|
||||
);
|
||||
const packet_buffer = packet_u8.buffer;
|
||||
console.log('Sending packet');
|
||||
websocket.send(packet_buffer);
|
||||
}
|
||||
|
||||
function sendHello(websocket: WebSocket, rom_array: Uint8Array, savestate_array: Uint8Array) {
|
||||
console.log('Sending hello.');
|
||||
const length_rom = BigInt(rom_array.length);
|
||||
const length_savestate = BigInt(savestate_array.length);
|
||||
const raw_data =
|
||||
concatU8Array(
|
||||
concatU8Array(
|
||||
concatU8Array(Endian.u64ToByteArrayBigEndian(length_rom), rom_array),
|
||||
Endian.u64ToByteArrayBigEndian(length_savestate)
|
||||
),
|
||||
savestate_array
|
||||
);
|
||||
sendPacket(websocket, PACKET_ID_HELLO, raw_data);
|
||||
}
|
||||
|
||||
function onWebSocketPacket(event: MessageEvent, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, printingFrame: boolean, setPrintingFrame: (c: setBooleanCallback) => void) {
|
||||
function onWebSocketPacket(event: MessageEvent, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, printingFrame: boolean, setPrintingFrame: (c: boolean) => void) {
|
||||
const buffer = event.data;
|
||||
let packet_u8: Uint8Array | null = new Uint8Array(buffer);
|
||||
const id = Endian.byteArrayToU64BigEndian(packet_u8.slice(0, 8));
|
||||
@ -127,34 +96,6 @@ function onWebSocketPacket(event: MessageEvent, canvas: HTMLCanvasElement, ctx:
|
||||
}
|
||||
}
|
||||
|
||||
function handleSendFrame(raw_data: Uint8Array, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, printingFrame: boolean, setPrintingFrame: (c: setBooleanCallback) => void) {
|
||||
if (printingFrame) {
|
||||
return;
|
||||
}
|
||||
setPrintingFrame(c => true);
|
||||
let data: Uint8Array | null = raw_data;
|
||||
const stride = Endian.byteArrayToU32BigEndian(data.slice(0, 4));
|
||||
data = data.slice(4, data.length);
|
||||
const output_buffer_size = Endian.byteArrayToU64BigEndian(data.slice(0, 8));
|
||||
data = data.slice(8, data.length);
|
||||
const img_data = ctx.createImageData(MIN_WIDTH, MIN_HEIGHT);
|
||||
const img_data_u8 = new Uint8Array(img_data.data.buffer);
|
||||
for (let i = 0; i<data.length; i++) {
|
||||
if (i % 4 == 3) {
|
||||
img_data_u8[i] = 255;
|
||||
continue;
|
||||
}
|
||||
img_data_u8[i] = data[i];
|
||||
}
|
||||
data = null;
|
||||
createImageBitmap(img_data).then((bitmap) => drawBitmap(bitmap, canvas, ctx, printingFrame));
|
||||
}
|
||||
|
||||
function drawBitmap(bitmap: ImageBitmap, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, printingFrame: boolean) {
|
||||
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
|
||||
printingFrame = false;
|
||||
}
|
||||
|
||||
export default function Page() {
|
||||
const screenDimensions = useScreenDimensions();
|
||||
const emulatorDimensions = calculateSizeEmulator(screenDimensions);
|
||||
@ -182,6 +123,7 @@ export default function Page() {
|
||||
const refInputSaveState = React.useRef<HTMLInputElement | null>(null);
|
||||
const [emulationStarted, setEmulationStarted] = React.useState<boolean>(false);
|
||||
const [hiddenMenu, setHiddenMenu] = React.useState<boolean>(true);
|
||||
const [webSocket, setWebSocket] = React.useState<WebSocket | null>(null);
|
||||
const onStartEmulation = (e: React.MouseEvent<HTMLInputElement>) => {
|
||||
handleClickStartEmulationButton({
|
||||
e: e,
|
||||
@ -193,6 +135,8 @@ export default function Page() {
|
||||
printingFrame: printingFrame,
|
||||
setHiddenMenu: setHiddenMenu,
|
||||
setHiddenFormSelectFiles: setHiddenFormSelectFiles,
|
||||
setWebSocket: setWebSocket,
|
||||
|
||||
});
|
||||
};
|
||||
const firstMenuElement = React.useRef<HTMLAnchorElement>(null);
|
||||
@ -201,7 +145,7 @@ export default function Page() {
|
||||
return (
|
||||
<div ref={screenRef}>
|
||||
<OverlayControls firstMenuElement={firstMenuElement}
|
||||
setHiddenMenu={setHiddenMenu}/>
|
||||
setHiddenMenu={setHiddenMenu} webSocket={webSocket}/>
|
||||
<OverlayMenu hiddenMenu={hiddenMenu}
|
||||
setHiddenMenu={setHiddenMenu} emulationStarted={emulationStarted}
|
||||
setHiddenFormSelectFiles={setHiddenFormSelectFiles} screenRef={screenRef}
|
||||
|
@ -2,6 +2,7 @@ export const MIN_WIDTH = 240;
|
||||
export const MIN_HEIGHT = 160;
|
||||
export const PACKET_ID_HELLO = 0n;
|
||||
export const PACKET_ID_SEND_FRAME = 1n;
|
||||
export const PACKET_ID_KEY_DOWN = 2n;
|
||||
export const CLOSE_BUTTON_IMAGE: string = "/img/close.png";
|
||||
export const HOME_BUTTON_IMAGE: string = "/img/home.png";
|
||||
|
||||
|
@ -37,4 +37,15 @@ export default class Endian {
|
||||
}
|
||||
return buffer8;
|
||||
}
|
||||
|
||||
static u32ToByteArrayBigEndian(inputNumber: number) {
|
||||
const buffer = new ArrayBuffer(4);
|
||||
const buffer8 = new Uint8Array(buffer);
|
||||
const buffer32 = new Uint32Array(buffer);
|
||||
buffer32[0] = inputNumber;
|
||||
if (Endian.isLittleEndian()) {
|
||||
buffer8.reverse();
|
||||
}
|
||||
return buffer8;
|
||||
}
|
||||
}
|
||||
|
72
js-src/packet.ts
Normal file
72
js-src/packet.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import Endian from '/endian';
|
||||
import {MIN_WIDTH, MIN_HEIGHT, PACKET_ID_HELLO, PACKET_ID_SEND_FRAME, PACKET_ID_KEY_DOWN} from '/constants';
|
||||
|
||||
function concatU8Array(array1: Uint8Array, array2: Uint8Array) {
|
||||
const final_array = new Uint8Array(array1.length + array2.length);
|
||||
final_array.set(array1);
|
||||
final_array.set(array2, array1.length);
|
||||
return final_array;
|
||||
}
|
||||
|
||||
export function sendHello(websocket: WebSocket, rom_array: Uint8Array, savestate_array: Uint8Array) {
|
||||
console.log('Sending hello.');
|
||||
const length_rom = BigInt(rom_array.length);
|
||||
const length_savestate = BigInt(savestate_array.length);
|
||||
const raw_data =
|
||||
concatU8Array(
|
||||
concatU8Array(
|
||||
concatU8Array(Endian.u64ToByteArrayBigEndian(length_rom), rom_array),
|
||||
Endian.u64ToByteArrayBigEndian(length_savestate)
|
||||
),
|
||||
savestate_array
|
||||
);
|
||||
sendPacket(websocket, PACKET_ID_HELLO, raw_data);
|
||||
}
|
||||
|
||||
export function sendKeyDown(websocket: WebSocket, isDown: boolean, key: number) {
|
||||
console.log('Sending keyDown.');
|
||||
console.log(PACKET_ID_KEY_DOWN);
|
||||
const isDownArray = new Uint8Array(1);
|
||||
isDownArray[0] = isDown ? 1: 0;
|
||||
const rawData = concatU8Array(isDownArray, Endian.u32ToByteArrayBigEndian(key));
|
||||
console.log(rawData.length);
|
||||
sendPacket(websocket, PACKET_ID_KEY_DOWN, rawData);
|
||||
}
|
||||
|
||||
export function sendPacket(websocket: WebSocket, id: bigint, raw_data: Uint8Array) {
|
||||
const packet_u8 = concatU8Array(
|
||||
concatU8Array(Endian.u64ToByteArrayBigEndian(id), Endian.u64ToByteArrayBigEndian(BigInt(raw_data.length))),
|
||||
raw_data
|
||||
);
|
||||
const packet_buffer = packet_u8.buffer;
|
||||
console.log('Sending packet');
|
||||
websocket.send(packet_buffer);
|
||||
}
|
||||
|
||||
export function handleSendFrame(raw_data: Uint8Array, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, printingFrame: boolean, setPrintingFrame: (c: boolean) => void) {
|
||||
if (printingFrame) {
|
||||
return;
|
||||
}
|
||||
setPrintingFrame(true);
|
||||
let data: Uint8Array | null = raw_data;
|
||||
const stride = Endian.byteArrayToU32BigEndian(data.slice(0, 4));
|
||||
data = data.slice(4, data.length);
|
||||
const output_buffer_size = Endian.byteArrayToU64BigEndian(data.slice(0, 8));
|
||||
data = data.slice(8, data.length);
|
||||
const img_data = ctx.createImageData(MIN_WIDTH, MIN_HEIGHT);
|
||||
const img_data_u8 = new Uint8Array(img_data.data.buffer);
|
||||
for (let i = 0; i<data.length; i++) {
|
||||
if (i % 4 == 3) {
|
||||
img_data_u8[i] = 255;
|
||||
continue;
|
||||
}
|
||||
img_data_u8[i] = data[i];
|
||||
}
|
||||
data = null;
|
||||
createImageBitmap(img_data).then((bitmap) => drawBitmap(bitmap, canvas, ctx, printingFrame));
|
||||
}
|
||||
|
||||
function drawBitmap(bitmap: ImageBitmap, canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D, printingFrame: boolean) {
|
||||
ctx.drawImage(bitmap, 0, 0, canvas.width, canvas.height);
|
||||
printingFrame = false;
|
||||
}
|
@ -1,3 +1,7 @@
|
||||
*:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #0E0E10;
|
||||
color: #F5F5F5;
|
||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user