Skip to content

Commit

Permalink
Input processing rework
Browse files Browse the repository at this point in the history
  • Loading branch information
Namaneo committed Sep 11, 2023
1 parent 122947d commit 5275386
Show file tree
Hide file tree
Showing 5 changed files with 228 additions and 121 deletions.
32 changes: 32 additions & 0 deletions ui/sources/entities/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
export class Button {
/** @type {number} */
id = 0;

/** @type {DOMRect} */
rect = null;
}

export class Touch {
/** @type {string} */
type = null;

/** @type {number} */
id = 0;

/** @type {number} */
x = 0;

/** @type {number} */
y = 0;
}

export class InputMessage {
/** @type {number} */
device = 0;

/** @type {number} */
id = 0;

/** @type {number} */
value = 0;
}
120 changes: 32 additions & 88 deletions ui/sources/modals/core-modal.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { IonAccordion, IonAccordionGroup, IonButton, IonButtons, IonCheckbox, IonContent, IonHeader, IonIcon, IonItem, IonLabel, IonList, IonMenu, IonMenuButton, IonPage, IonSegment, IonSegmentButton, IonSelect, IonSelectOption, IonTitle, IonToolbar, useIonAlert } from '@ionic/react';
import { useEffect, useRef, useState } from 'react';
import { useEffect, useRef } from 'react';
import { checkmarkOutline } from 'ionicons/icons';
import { useSize } from '../hooks/size';
import { useCore } from '../hooks/core';
Expand All @@ -8,7 +8,7 @@ import { Game } from '../entities/game';
import { Variable } from '../entities/variable';
import { Settings } from '../entities/settings';
import { Cheat } from '../entities/cheat';
import Core from '../services/core';
import Input from '../services/input';

/**
* @param {Object} parameters
Expand Down Expand Up @@ -126,8 +126,6 @@ export const CoreModal = ({ system, game, close }) => {
const canvas = useRef(/** @type {HTMLCanvasElement} */ (null));

const [core, audio, speed, gamepad] = useCore(system.lib_name);
const [pointer, setPointer] = useState(false);
const [touches, setTouches] = useState({});
const [window_w, window_h] = useSize({ current: document.body });
const [canvas_w, canvas_h] = useSize(canvas);

Expand All @@ -150,79 +148,25 @@ export const CoreModal = ({ system, game, close }) => {
}
};


/** @param {HTMLElement} element @param {number} x @param {number} y @returns {number} */
const distance = (element, x, y) => {
const rect = element.getBoundingClientRect();
const center_x = rect.left + rect.width / 2;
const center_y = rect.top + rect.height / 2;
const dist_x = Math.max(Math.abs(x - center_x) - rect.width / 2, 0);
const dist_y = Math.max(Math.abs(y - center_y) - rect.height / 2, 0);
return Math.sqrt(Math.pow(dist_x, 2) + Math.pow(dist_y, 2));
}

/** @param {HTMLButtonElement[]} @param {number} identifier @param {number} x @param {number} y @param {string} type @returns {void} */
const send = (buttons, identifier, x, y, type) => {
const button = buttons.reduce((value, current) => {
const curr_dist = distance(current, x, y);
if (!value)
return curr_dist < 25 ? current : null;

const prev_dist = distance(value, x, y);
if (prev_dist > 25 && curr_dist > 25)
return null;

return curr_dist < prev_dist ? current : value;
}, null);

const touch = touches[identifier];
if (touch && touch?.id != button?.dataset.id)
core.current.send(Core.Device.JOYPAD, touch.id, false);

const start = !!['mousedown', 'touchstart'].find(t => t == type);
const move = !!['mousemove', 'touchmove'].find(t => t == type);
const pressed = start || (move && touch && touch.pressed);

if (button?.dataset.id)
core.current.send(Core.Device.JOYPAD, button?.dataset.id, pressed);

setTouches({ ...touches, [identifier]: { id: button?.dataset.id, pressed } });
}

/** @param {Event} event @returns {void} */
const touch = (event) => {
if (event.type == 'mousemove' && event.buttons == 0)
return;

if (gamepad.value) {
const buttons = [...event.target.children];

if (event.type.startsWith('mouse'))
send(buttons, 0, event.clientX, event.clientY, event.type);

if (event.type.startsWith('touch'))
for (const touch of event.changedTouches)
send(buttons, touch.identifier, touch.clientX, touch.clientY, event.type);

} else {
const x = event.clientX || (event.changedTouches && event.changedTouches[0].clientX);
const y = event.clientY || (event.changedTouches && event.changedTouches[0].clientY);

const rect = canvas.current.getBoundingClientRect();
const scaled_x = (x - rect.left) / (rect.right - rect.left) * canvas.current.width;
const scaled_y = (y - rect.top ) / (rect.bottom - rect.top ) * canvas.current.height;

const start = !!['mousedown', 'touchstart'].find(t => t == event.type);
const move = !!['mousemove', 'touchmove'].find(t => t == event.type);
const pressed = start || (move && pointer);

core.current.send(Core.Device.POINTER, Core.Pointer.X, scaled_x);
core.current.send(Core.Device.POINTER, Core.Pointer.Y, scaled_y);
core.current.send(Core.Device.POINTER, Core.Pointer.PRESSED, pressed);
core.current.send(Core.Device.POINTER, Core.Pointer.COUNT, 1);

setPointer(pressed);
}
const buttons = [...event.target.children].map(button => ({
id: button.dataset.id,
rect: button.getBoundingClientRect(),
}), []);

const map_touch = (touch) => ({
type: event.type,
id: touch.identifier ?? 0,
x: touch.clientX,
y: touch.clientY,
});
const touches =
event.type.startsWith('mouse') ? [map_touch(event)] :
event.type.startsWith('touch') ? [...event.changedTouches].map(map_touch):
[];

core.current.input(buttons, touches, gamepad.value, canvas.current.getBoundingClientRect(), canvas.current.width, canvas.current.height);
}

/** @returns {void} */
Expand Down Expand Up @@ -313,19 +257,19 @@ export const CoreModal = ({ system, game, close }) => {
<canvas ref={canvas} />

{gamepad.value && <div className="controls"><div>
<Control name="A" device={Core.Device.JOYPAD} id={Core.Joypad.A} type='generic' inset={{bottom: 30, right: 4 }} />
<Control name="B" device={Core.Device.JOYPAD} id={Core.Joypad.B} type='generic' inset={{bottom: 18, right: 16}} />
<Control name="X" device={Core.Device.JOYPAD} id={Core.Joypad.X} type='generic' inset={{bottom: 42, right: 16}} />
<Control name="Y" device={Core.Device.JOYPAD} id={Core.Joypad.Y} type='generic' inset={{bottom: 30, right: 28}} />
<Control name="R" device={Core.Device.JOYPAD} id={Core.Joypad.R} type='shoulder' inset={{bottom: 48, right: 34}} />
<Control name="&#x00B7;" device={Core.Device.JOYPAD} id={Core.Joypad.START} type='special' inset={{bottom: 18, right: 37}} />

<Control name="&#x140A;" device={Core.Device.JOYPAD} id={Core.Joypad.LEFT} type='arrow' inset={{bottom: 30, left: 4 }} />
<Control name="&#x1401;" device={Core.Device.JOYPAD} id={Core.Joypad.DOWN} type='arrow' inset={{bottom: 18, left: 16}} />
<Control name="&#x1403;" device={Core.Device.JOYPAD} id={Core.Joypad.UP} type='arrow' inset={{bottom: 42, left: 16}} />
<Control name="&#x1405;" device={Core.Device.JOYPAD} id={Core.Joypad.RIGHT} type='arrow' inset={{bottom: 30, left: 28}} />
<Control name="L" device={Core.Device.JOYPAD} id={Core.Joypad.L} type='shoulder' inset={{bottom: 48, left: 34}} />
<Control name="&#x00B7;" device={Core.Device.JOYPAD} id={Core.Joypad.SELECT} type='special' inset={{bottom: 18, left: 37}} />
<Control name="A" device={Input.Device.JOYPAD} id={Input.Joypad.A} type='generic' inset={{bottom: 30, right: 4 }} />
<Control name="B" device={Input.Device.JOYPAD} id={Input.Joypad.B} type='generic' inset={{bottom: 18, right: 16}} />
<Control name="X" device={Input.Device.JOYPAD} id={Input.Joypad.X} type='generic' inset={{bottom: 42, right: 16}} />
<Control name="Y" device={Input.Device.JOYPAD} id={Input.Joypad.Y} type='generic' inset={{bottom: 30, right: 28}} />
<Control name="R" device={Input.Device.JOYPAD} id={Input.Joypad.R} type='shoulder' inset={{bottom: 48, right: 34}} />
<Control name="&#x00B7;" device={Input.Device.JOYPAD} id={Input.Joypad.START} type='special' inset={{bottom: 18, right: 37}} />

<Control name="&#x140A;" device={Input.Device.JOYPAD} id={Input.Joypad.LEFT} type='arrow' inset={{bottom: 30, left: 4 }} />
<Control name="&#x1401;" device={Input.Device.JOYPAD} id={Input.Joypad.DOWN} type='arrow' inset={{bottom: 18, left: 16}} />
<Control name="&#x1403;" device={Input.Device.JOYPAD} id={Input.Joypad.UP} type='arrow' inset={{bottom: 42, left: 16}} />
<Control name="&#x1405;" device={Input.Device.JOYPAD} id={Input.Joypad.RIGHT} type='arrow' inset={{bottom: 30, left: 28}} />
<Control name="L" device={Input.Device.JOYPAD} id={Input.Joypad.L} type='shoulder' inset={{bottom: 48, left: 34}} />
<Control name="&#x00B7;" device={Input.Device.JOYPAD} id={Input.Joypad.SELECT} type='special' inset={{bottom: 18, left: 37}} />
</div></div>}
</IonContent>
</IonPage>
Expand Down
44 changes: 14 additions & 30 deletions ui/sources/services/core.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Button, Touch } from '../entities/input';
import { Cheat } from '../entities/cheat';
import { Settings } from '../entities/settings';
import { Variable } from '../entities/variable';
Expand Down Expand Up @@ -138,6 +139,19 @@ export default class Core {
this.#on_variables(variables);
}

/**
* @param {Button[]} buttons
* @param {Touch[]} touches
* @param {boolean} gamepad
* @param {DOMRect} canvas
* @param {number} width
* @param {number} height
* @returns {Promise<void>}
*/
async input(buttons, touches, gamepad, canvas, width, height) {
await this.#interop?.input(buttons, touches, gamepad, canvas, width, height);
}

/** @param {Settings} settings @returns {Promise<void>} */
async settings(settings) { await this.#interop?.variables(settings.variables); }

Expand All @@ -150,39 +164,9 @@ export default class Core {
/** @param {number} value @returns {Promise<void>} */
async speed(value) { await this.#interop?.speed(value); }

/** @param {number} device @param {number} id @param {number} value @returns {Promise<void>} */
async send(device, id, value) { await this.#interop?.send(device, id, value); }

/** @returns {Promise<void>} */
async save() { await this.#interop?.save(); }

/** @returns {Promise<void>} */
async restore() { await this.#interop?.restore(); }

static Device = class {
static get JOYPAD() { return 1; }
static get POINTER() { return 6; }
}

static Joypad = class {
static get B() { return 0; }
static get Y() { return 1; }
static get SELECT() { return 2; }
static get START() { return 3; }
static get UP() { return 4; }
static get DOWN() { return 5; }
static get LEFT() { return 6; }
static get RIGHT() { return 7; }
static get A() { return 8; }
static get X() { return 9; }
static get L() { return 10; }
static get R() { return 11; }
}

static Pointer = class {
static get X() { return 0; }
static get Y() { return 1; }
static get PRESSED() { return 2; }
static get COUNT() { return 3; }
}
}
131 changes: 131 additions & 0 deletions ui/sources/services/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { Button, InputMessage, Touch } from '../entities/input';

export default class Input {
/** @type {Touch[]} */
#touches = {};

/** @type {boolean} */
#pressed = {};

/**
* @param {DOMRect} rect
* @param {number} x
* @param {number} y
* @returns {number}
*/
#distance(rect, x, y) {
const center_x = rect.left + rect.width / 2;
const center_y = rect.top + rect.height / 2;
const dist_x = Math.max(Math.abs(x - center_x) - rect.width / 2, 0);
const dist_y = Math.max(Math.abs(y - center_y) - rect.height / 2, 0);
return Math.sqrt(Math.pow(dist_x, 2) + Math.pow(dist_y, 2));
}

/**
* @param {Button[]} buttons
* @param {Touch} touch
* @returns {InputMessage[]}
*/
#click(buttons, touch) {
const messages = [];

const button = buttons.reduce((value, current) => {
const curr_dist = this.#distance(current.rect, touch.x, touch.y);
if (!value)
return curr_dist < 25 ? current : null;

const prev_dist = this.#distance(value.rect, touch.x, touch.y);
if (prev_dist > 25 && curr_dist > 25)
return null;

return curr_dist < prev_dist ? current : value;
}, null);

const prev_touch = this.#touches[touch.identifier];
if (prev_touch && prev_touch.id != button?.id)
messages.push({ device: Input.Device.JOYPAD, id: prev_touch.id, value: false });

const start = !!['mousedown', 'touchstart'].find(type => type == touch.type);
const move = !!['mousemove', 'touchmove'].find(type => type == touch.type);
const pressed = start || (move && prev_touch && prev_touch.pressed);

if (button?.id)
messages.push({ device: Input.Device.JOYPAD, id: button.id, value: pressed });

this.#touches[touch.identifier] = { id: button?.id, pressed };

return messages;
}

/**
* @param {Touch} touch
* @param {DOMRect} canvas
* @param {number} width
* @param {number} height
* @returns {InputMessage[]}
*/
#touch(touch, canvas, width, height) {
const scaled_x = (touch.x - canvas.left) / (canvas.right - canvas.left) * width;
const scaled_y = (touch.y - canvas.top ) / (canvas.bottom - canvas.top ) * height;

const start = !!['mousedown', 'touchstart'].find(t => t == touch.type);
const move = !!['mousemove', 'touchmove'].find(t => t == touch.type);
const pressed = start || (move && this.#pressed);
this.#pressed = pressed;

return [
{ device: Input.Device.POINTER, id: Input.Pointer.X, value: scaled_x },
{ device: Input.Device.POINTER, id: Input.Pointer.Y, value: scaled_y },
{ device: Input.Device.POINTER, id: Input.Pointer.PRESSED, value: pressed },
{ device: Input.Device.POINTER, id: Input.Pointer.COUNT, value: 1 },
];
}

/**
* @param {Button[]} buttons
* @param {Touch[]} touches
* @param {boolean} gamepad
* @param {DOMRect} canvas
* @param {number} width
* @param {number} height
* @returns {InputMessage[]}
*/
process(buttons, touches, gamepad, canvas, width, height) {
if (gamepad) {
const messages = [];
for (const touch of touches)
messages.push(...this.#click(buttons, touch));
return messages;

} else {
return this.#touch(touches[0], canvas, width, height);
}
}

static Device = class {
static get JOYPAD() { return 1; }
static get POINTER() { return 6; }
}

static Joypad = class {
static get B() { return 0; }
static get Y() { return 1; }
static get SELECT() { return 2; }
static get START() { return 3; }
static get UP() { return 4; }
static get DOWN() { return 5; }
static get LEFT() { return 6; }
static get RIGHT() { return 7; }
static get A() { return 8; }
static get X() { return 9; }
static get L() { return 10; }
static get R() { return 11; }
}

static Pointer = class {
static get X() { return 0; }
static get Y() { return 1; }
static get PRESSED() { return 2; }
static get COUNT() { return 3; }
}
}
Loading

0 comments on commit 5275386

Please sign in to comment.