优化目录结构

This commit is contained in:
2026-05-25 20:37:36 +08:00
parent bbe7e71274
commit 40fd7f7e08
101 changed files with 108 additions and 110 deletions

View File

@@ -0,0 +1,26 @@
export const GamepadButton = {
DpadUp: 0,
DpadDown: 1,
DpadLeft: 2,
DpadRight: 3,
North: 4,
East: 5,
South: 6,
West: 7,
LeftStick: 8,
RightStick: 9,
LeftShoulder: 10,
RightShoulder: 11,
Start: 12,
Select: 13,
LeftTrigger: 32,
RightTrigger: 33,
X: 7, // West
Y: 4, // North
A: 6, // South,
B: 5, // East,
Cross: 6, // South,
Square: 7, // West,
Triangle: 4, //North,
Circle: 5 // East,
};

View File

@@ -0,0 +1,44 @@
export class GamepadHandler extends EventTarget {
constructor() {
super();
this._controllers = {};
window.requestAnimationFrame(this._updateStatus.bind(this));
}
/**
* @param {Gamepad} gamepad
*/
addGamepad(gamepad) {
this._controllers[gamepad.index] = gamepad;
}
/**
* @param {Gamepad} gamepad
*/
removeGamepad(gamepad) {
delete this._controllers[gamepad.index];
}
_updateStatus() {
this._scanGamepad();
for(let i in this._controllers) {
const controller = this._controllers[i];
// gamepadupdated event type is own definition
this.dispatchEvent(new GamepadEvent('gamepadupdated', {
gamepad: controller
}));
}
window.requestAnimationFrame(this._updateStatus.bind(this));
}
_scanGamepad() {
let gamepads = navigator.getGamepads();
for (let i = 0; i < gamepads.length; i++) {
if (gamepads[i] && (gamepads[i].index in this._controllers)) {
this._controllers[gamepads[i].index] = gamepads[i];
}
}
}
}

View File

@@ -0,0 +1,718 @@
import {
MemoryHelper,
} from "../utils/memoryhelper.js";
import { CharNumber } from "../utils/charnumber.js";
import { Keymap } from "./keymap.js";
import { MouseButton } from "./mousebutton.js";
import { GamepadButton } from "./gamepadbutton.js";
import { TouchPhase } from "./touchphase.js";
import { TouchFlags } from "./touchflags.js";
export class FourCC {
/**
* {Number} _code;
*/
/**
*
* @param {String} a
* @param {String} b
* @param {String} c
* @param {String} d
*/
constructor(a, b, c, d) {
this._code = (a.charCodeAt() << 24)
| (b.charCodeAt() << 16)
| (c.charCodeAt() << 8)
| d.charCodeAt();
}
/**
* @returns {Number}
*/
toInt32() {
return this._code;
}
}
export class InputDevice {
/**
*
* name;
* layout;
* deviceId;
* usages;
* description;
*
* _inputState;
*/
/**
*
* @param {Number} name
* @param {String} layout
* @param {Number} deviceId
* @param {String[]} usages
* @param {Object} description
*/
constructor(name, layout, deviceId, usages, description) {
this.name = name;
this.layout = layout;
this.deviceId = deviceId;
this.usages = usages;
this.description = description;
this._inputState = null;
}
/**
*
* @param {IInputState} state
*/
updateState(state) {
this._inputState = state;
}
queueEvent(event) {
throw new Error(`Please implement this method. event:${event}`);
}
/**
* @returns {IInputState}
*/
get currentState() {
return this._inputState;
}
}
export class Mouse extends InputDevice {
/**
* @param {(MouseEvent|WheelEvent)} event
*/
queueEvent(event) {
this.updateState(new MouseState(event));
}
}
export class Keyboard extends InputDevice {
static get keycount() { return 110; }
/**
*
* @param {KeyboardEvent} event
*/
queueEvent(event) {
this.updateState(new KeyboardState(event, this.currentState));
}
}
export class Touchscreen extends InputDevice {
/**
* @param {TouchScreenEvent} event
*/
queueEvent(event, time) {
this.updateState(new TouchscreenState(event, this.currentState, time));
}
}
export class Gamepad extends InputDevice {
/**
* @param {GamepadButtonEvent | GamepadAxisEvent} event
*/
queueEvent(event) {
this.updateState(new GamepadState(event));
}
}
export class InputEvent {
static get invalidEventId() { return 0; }
static get size() { return 20; }
/**
* field offset 0
* @member {Number} type;
*
* field offset 4
* @member {Number} sizeInBytes;
*
* field offset 6
* @member {Number} deviceId;
*
* field offset 8
* @member {Number} time;
*
* field offset 16
* @member {Number} eventId;
*/
/**
*
* @param {Number} type
* @param {Number} sizeInBytes
* @param {Number} deviceId
* @param {Number} time
*/
constructor(type, sizeInBytes, deviceId, time) {
this.type = type;
this.sizeInBytes = sizeInBytes;
this.deviceId = deviceId;
this.time = time;
this.eventId = InputEvent.invalidEventId;
}
/**
* @returns {ArrayBuffer}
*/
get buffer() {
let _buffer = new ArrayBuffer(InputEvent.size);
let view = new DataView(_buffer);
view.setInt32(0, this.type, true);
view.setInt16(4, this.sizeInBytes, true);
view.setInt16(6, this.deviceId, true);
view.setFloat64(8, this.time, true);
view.setInt16(16, this.sizeInBytes, true);
return _buffer;
}
}
export class IInputState {
/**
* @returns {ArrayBuffer}
*/
get buffer() {
throw new Error('Please implement this field');
}
/**
* @returns {Number}
*/
get format() {
throw new Error('Please implement this field');
}
}
export class MouseState extends IInputState {
static get size() { return 30; }
static get format() { return new FourCC('M', 'O', 'U', 'S').toInt32(); }
/**
* field offset 0
* @member {Array} position;
*
* field offset 8
* @member {Array} delta;
*
* field offset 16
* @member {Array} scroll;
*
* field offset 24
* @member {ArrayBuffer} buttons;
*
* field offset 26
* @member {Array} displayIndex;
*
* field offset 28
* @member {Array} clickCount;
*/
/**
* @param {MouseEvent | WheelEvent} event
*/
constructor(event) {
super();
this.position = [event.clientX, event.clientY];
this.delta = [event.movementX, -event.movementY];
this.scroll = [0, 0];
if(event.type === 'wheel') {
this.scroll = [event.deltaX, event.deltaY];
}
this.buttons = new ArrayBuffer(2);
const left = event.buttons & 1 << 0;
const right = event.buttons & 1 << 1;
const middle = event.buttons & 1 << 2;
const back = event.buttons & 1 << 3;
const forward = event.buttons & 1 << 4;
MemoryHelper.writeSingleBit(this.buttons, MouseButton.Left, left);
MemoryHelper.writeSingleBit(this.buttons, MouseButton.Right, right);
MemoryHelper.writeSingleBit(this.buttons, MouseButton.Middle, middle);
MemoryHelper.writeSingleBit(this.buttons, MouseButton.Forward, forward);
MemoryHelper.writeSingleBit(this.buttons, MouseButton.Back, back);
}
/**
* @returns {ArrayBuffer}
*/
get buffer() {
const size = MouseState.size;
const buttons = new Uint16Array(this.buttons)[0];
let _buffer = new ArrayBuffer(size);
let view = new DataView(_buffer);
view.setFloat32(0, this.position[0], true);
view.setFloat32(4, this.position[1], true);
view.setFloat32(8, this.delta[0], true);
view.setFloat32(12, this.delta[1], true);
view.setFloat32(16, this.scroll[0], true);
view.setFloat32(20, this.scroll[1], true);
view.setUint16(24, buttons, true);
view.setUint16(26, this.displayIndex, true);
view.setUint16(28, this.clickCount, true);
return _buffer;
}
/**
* @returns {Number}
*/
get format() {
return MouseState.format;
}
}
export class KeyboardState extends IInputState {
static get sizeInBits() { return Keyboard.keycount; }
static get sizeInBytes() { return (KeyboardState.sizeInBits + 7) >> 3; }
static get format() { return new FourCC('K', 'E', 'Y', 'S').toInt32(); }
/**
* field offset 0
* @number {ArrayBuffer} keys;
*/
/**
* @param {KeyboardEvent} event
*/
constructor(event, state) {
super();
if (state == null || state.keys == null) {
this.keys = new ArrayBuffer(KeyboardState.sizeInBytes);
} else {
this.keys = state.keys;
}
let value = false;
switch(event.type) {
case 'keydown':
value = true;
break;
case 'keyup':
value = false;
break;
default:
throw new Error(`unknown event type ${event.type})`);
}
const key = Keymap[event.code];
MemoryHelper.writeSingleBit(this.keys, key, value);
}
/**
* @returns {ArrayBuffer}
*/
get buffer() {
return this.keys;
}
/**
* @returns {Number}
*/
get format() {
return KeyboardState.format;
}
}
export class TouchState {
static get format() { return new FourCC('T', 'O', 'U', 'C').toInt32(); }
static get size() { return 56; }
static incrementTouchId() {
if(TouchState._currentTouchId === undefined) {
TouchState._currentTouchId = 0;
}
return ++TouchState._currentTouchId;
}
static prevTouches() {
if(TouchState._prevTouches === undefined) {
// max touch count is 10
TouchState._prevTouches = new Array(10);
}
return TouchState._prevTouches;
}
/**
* field offset 0
* @number {Number} touchId;
* field offset 4
* @number {Number[]} position;
* field offset 12
* @number {Number[]} delta;
* field offset 20
* @number {Number} pressure;
* field offset 24
* @number {Number[]} radius;
* field offset 32
* @number {Number} phase;
* field offset 33
* @number {Number} tapCount;
* field offset 34
* @number {Number} displayIndex;
* field offset 35
* @number {Number} flag;
* field offset 36
* @number {Number} padding;
* field offset 40
* @number {Number} startTime;
* field offset 48
* @number {Number[]} startPosition;
*/
/**
* @param {Touch} touchId
* @param {TouchState} prevState
* @param {Number[]} position
* @param {Number} pressure
* @param {Number[]} radius
* @param {TouchPhase} phaseId
* @param {Number} time
*/
constructor(touchId, prevState, position, pressure, radius, phaseId, time) {
this.touchId = touchId;
this.position = position != null ? position.slice() : null;
if(phaseId == TouchPhase.Moved) {
this.delta = [this.position[0] - prevState.position[0], this.position[1] - prevState.position[1]];
} else {
this.delta = [0, 0];
}
this.pressure = pressure;
this.radius = radius != null ? radius.slice(): null;
this.phaseId = phaseId;
this.tapCount = 0;
this.displayIndex = 0;
this.flags = 0;
this.padding = 0;
if(phaseId == TouchPhase.Began) {
this.startTime = time;
this.startPosition = this.position.slice();
} else {
this.startTime = prevState != null ? prevState.startTime : null;
this.startPosition = prevState != null ? prevState.startPosition.slice() : null;
}
}
copy() {
let state = new TouchState();
state.touchId = this.touchId;
state.position = this.position.slice();
state.delta = this.delta.slice();
state.pressure = this.pressure;
state.radius = this.radius.slice();
state.phaseId = this.phaseId;
state.tapCount = this.tapCount;
state.displayIndex = this.displayIndex;
state.flags = this.flags;
state.padding = this.padding;
state.startTime = this.startTime;
state.startPosition = this.startPosition.slice();
return state;
}
/**
* @returns {ArrayBuffer}
*/
get buffer() {
const size = TouchState.size; // todo
let _buffer = new ArrayBuffer(size);
let view = new DataView(_buffer);
view.setInt32(0, this.touchId, true);
view.setFloat32(4, this.position[0], true);
view.setFloat32(8, this.position[1], true);
view.setFloat32(12, this.delta[0], true);
view.setFloat32(16, this.delta[1], true);
view.setFloat32(20, this.pressure, true);
view.setFloat32(24, this.radius[0], true);
view.setFloat32(28, this.radius[1], true);
view.setInt8(32, this.phaseId, true);
view.setInt8(33, this.tapCount, true);
view.setInt8(34, this.displayIndex, true);
view.setInt8(35, this.flags, true);
view.setInt32(36, this.padding, true);
view.setFloat64(40, this.startTime, true);
view.setFloat32(48, this.startPosition[0], true);
view.setFloat32(52, this.startPosition[1], true);
return _buffer;
}
/**
* @returns {Number}
*/
get format() {
return TouchState.format;
}
}
export class TouchscreenState extends IInputState {
static get maxTouches() { return 10; }
static get format() { return new FourCC('T', 'S', 'C', 'R').toInt32(); }
static convertPhaseId(type) {
let phaseId = TouchPhase.Stationary;
switch(type) {
case 'touchstart':
phaseId = TouchPhase.Began; break;
case 'touchend':
phaseId = TouchPhase.Ended; break;
case 'touchmove':
phaseId = TouchPhase.Moved; break;
case 'touchcancel':
phaseId = TouchPhase.Canceled; break;
}
return phaseId;
}
/**
* @param {TouchEvent} event
* @param {TouchScreenState} state
* @param {Date} time
*/
constructor(event, state, time) {
super();
switch(event.type) {
// `click` event is called when releasing mouse button or finger on screen.
case 'click' : {
this.touchData = new Array(state.touchData.length);
for(let i = 0; i < state.touchData.length; i++) {
this.touchData[i] = state.touchData[i];
if(this.touchData[i].phaseId == TouchPhase.Ended) {
this.touchData[i].tapCount = 1;
this.touchData[i].flags |= TouchFlags.Tap;
}
}
break;
}
default: {
let touches = event.changedTouches;
this.touchData = new Array(touches.length);
for(let i = 0; i < touches.length; i++) {
const touch = touches[i];
const position = [touch.clientX, touch.clientY];
const phaseId = TouchscreenState.convertPhaseId(event.type);
const pressure = touch.force;
const radius = [touch.radiusX, touch.radiusY];
// `touchId` in InputSystem must be set uniquely.
// The numbers of `touch.identifier` in Web API are reused, so these are not unique.
const touchId = phaseId == TouchPhase.Began ? TouchState.incrementTouchId() : TouchState.prevTouches()[touch.identifier].touchId;
const prevState = phaseId != TouchPhase.Began ? TouchState.prevTouches()[touch.identifier] : null;
const touchData = new TouchState(touchId, prevState, position, pressure, radius, phaseId, time);
// cache state
TouchState.prevTouches()[touch.identifier] = touchData;
this.touchData[i] = touchData;
}
break;
}
}
}
/**
* @returns {ArrayBuffer}
*/
get buffer() {
const size = TouchState.size * this.touchData.length;
let _buffer = new ArrayBuffer(size);
let view = new Uint8Array(_buffer);
for(let i = 0; i < this.touchData.length; i++) {
view.set(new Uint8Array(this.touchData[i].buffer), TouchState.size * i);
}
return _buffer;
}
/**
* @returns {Number}
*/
get format() {
return TouchscreenState.format;
}
}
export class GamepadState extends IInputState {
static get size() { return 28; }
static get format() { return new FourCC('G', 'P', 'A', 'D').toInt32(); }
/**
* field offset 0
* @member buttons;
*
* field offset 4
* @member leftStick;
*
* field offset 12
* @member rightStick;
*
* field offset 20
* @member leftTrigger;
*
* field offset 24
* @member rightTrigger;
*/
/**
*
* @param {GamepadButtonEvent | GamepadAxisEvent} event
*/
constructor(event) {
super();
const gamepad = event.gamepad;
const buttons = event.gamepad.buttons;
this.buttons = new ArrayBuffer(4);
this.leftStick = [ gamepad.axes[0], -gamepad.axes[1] ];
this.rightStick = [ gamepad.axes[2], -gamepad.axes[3] ];
this.leftTrigger = buttons[6].value;
this.rightTrigger = buttons[7].value;
// see https://w3c.github.io/gamepad/#remapping
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.A, buttons[0].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.B, buttons[1].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.X, buttons[2].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.Y, buttons[3].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.LeftShoulder, buttons[4].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.RightShoulder, buttons[5].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.LeftTrigger, buttons[6].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.RightTrigger, buttons[7].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.Select, buttons[8].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.Start, buttons[9].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.LeftStick, buttons[10].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.RightStick, buttons[11].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.DpadUp, buttons[12].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.DpadDown, buttons[13].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.DpadLeft, buttons[14].pressed);
MemoryHelper.writeSingleBit(this.buttons, GamepadButton.DpadRight, buttons[15].pressed);
}
/**
* @returns {ArrayBuffer}
*/
get buffer() {
const size = GamepadState.size;
let _buffer = new ArrayBuffer(size);
let view = new DataView(_buffer);
view.setUint32(0, new Uint32Array(this.buttons)[0], true);
view.setFloat32(4, this.leftStick[0], true);
view.setFloat32(8, this.leftStick[1], true);
view.setFloat32(12, this.rightStick[0], true);
view.setFloat32(16, this.rightStick[1], true);
view.setFloat32(20, this.leftTrigger, true);
view.setFloat32(24, this.rightTrigger, true);
return _buffer;
}
/**
* @returns {Number}
*/
get format() {
return GamepadState.format;
}
}
export class TextEvent {
static get format() { return new FourCC('T', 'E', 'X', 'T').toInt32(); }
/**
* field offset 0
* @member {InputEvent} baseEvent;
*
* field offset 20
* @member {Number} character;
*/
/**
*
* @param {Number} deviceId
* @param {KeyboardEvent} event
* @param {Number} time
* @returns {TextEvent}
*/
static create(deviceId, event, time) {
const eventSize = InputEvent.size + MemoryHelper.sizeOfInt;
let textEvent = new TextEvent();
textEvent.baseEvent = new InputEvent(TextEvent.format, eventSize, deviceId, time);
textEvent.character = CharNumber[event.key];
return textEvent;
}
/**
* @returns {ArrayBuffer}
*/
get buffer() {
const size = InputEvent.size + MemoryHelper.sizeOfInt;
let _buffer = new ArrayBuffer(size);
let arrayView = new Uint8Array(_buffer);
let dataView = new DataView(_buffer);
arrayView.set(new Uint8Array(this.baseEvent.buffer), 0);
dataView.setInt32(InputEvent.size, this.character, true);
return _buffer;
}
}
export class StateEvent {
static get format() { return new FourCC('S', 'T', 'A', 'T').toInt32(); }
/**
* field offset 0
* @member {InputEvent} baseEvent;
*
* field offset 20
* @member {Number} stateFormat;
*
* field offset 24
* @member {ArrayBuffer} stateData;
*/
/**
*
* @param {InputDevice} device
* @param {Number} time
* @returns {StateEvent}
*/
static from(device, time) {
return StateEvent.fromState(device.currentState, device.deviceId, time);
}
/**
*
* @param {IInputState} state
* @param {Number} deviceId
* @param {Number} time
*/
static fromState(state, deviceId, time) {
const stateData = state.buffer;
const stateSize = stateData.byteLength;
const eventSize = InputEvent.size + MemoryHelper.sizeOfInt + stateSize;
let stateEvent = new StateEvent();
stateEvent.baseEvent = new InputEvent(StateEvent.format, eventSize, deviceId, time);
stateEvent.stateFormat = state.format;
stateEvent.stateData = stateData;
return stateEvent;
}
/**
* @returns {ArrayBuffer}
*/
get buffer() {
const stateSize = this.stateData.byteLength;
const size = InputEvent.size + MemoryHelper.sizeOfInt + stateSize;
let _buffer = new ArrayBuffer(size);
let uint8View = new Uint8Array(_buffer);
let dataView = new DataView(_buffer);
uint8View.set(new Uint8Array(this.baseEvent.buffer), 0);
dataView.setInt32(InputEvent.size, this.stateFormat, true);
uint8View.set(new Uint8Array(this.stateData), InputEvent.size+MemoryHelper.sizeOfInt);
return _buffer;
}
}

View File

@@ -0,0 +1,299 @@
import {
StateEvent,
} from "./inputdevice.js";
import {
MemoryHelper
} from "../utils/memoryhelper.js";
export class LocalInputManager {
constructor() {
this._onevent = new EventTarget();
}
/**
* event type 'event', 'changedeviceusage'
* @return {Event}
*/
get onEvent() {
return this._onevent;
}
/**
* @return {Event}
*/
get devices() {
throw new Error(`Please implement this method.`);
}
/**
* @return {Number} time (sec)
*/
get startTime() {
return this._startTime;
}
/**
* @return {Number} time (sec)
*/
get timeSinceStartup() {
return Date.now()/1000 - this.startTime;
}
/**
* @param {Number} time (sec)
*/
setStartTime(time) {
this._startTime = time;
}
}
export const InputDeviceChange = {
Added: 0,
Removed: 1,
Disconnected: 2,
Reconnected: 3,
Enabled: 4,
Disabled: 5,
UsageChanged: 6,
ConfigurationChanged: 7,
Destroyed: 8,
};
export class InputRemoting {
/**
* @param {LocalInputManager} manager
*/
constructor(manager) {
this._localManager = manager;
this._subscribers = new Array();
this._sending = false;
}
startSending() {
if(this._sending) {
return;
}
this._sending = true;
const onEvent = e => {
this._sendEvent(e.detail.event);
};
const onDeviceChange = e => {
this._sendDeviceChange(e.detail.device, e.detail.change);
};
this._localManager.setStartTime(Date.now()/1000);
this._localManager.onEvent.addEventListener("event", onEvent);
this._localManager.onEvent.addEventListener("changedeviceusage", onDeviceChange);
this._sendInitialMessages();
}
stopSending() {
if (!this._sending) {
return;
}
this._sending = false;
}
/**
*
* @param {Observer} observer
*/
subscribe(observer) {
this._subscribers.push(observer);
}
_sendInitialMessages() {
this._sendAllGeneratedLayouts();
this._sendAllDevices();
}
_sendAllGeneratedLayouts() {
// todo:
}
_sendAllDevices() {
var devices = this._localManager.devices;
if(devices == null)
return;
for (const device of devices) {
this._sendDevice(device);
}
}
_sendDevice(device) {
const newDeviceMessage = NewDeviceMsg.create(device);
this._send(newDeviceMessage);
// Send current state. We do this here in this case as the device
// may have been added some time ago and thus have already received events.
// todo:
// const stateEventMessage = NewEventsMsg.createStateEvent(device);
// this._send(stateEventMessage);
}
_sendEvent(event) {
const message = NewEventsMsg.create(event);
this._send(message);
}
_sendDeviceChange(device, change) {
if (this._subscribers == null)
return;
let msg = null;
switch (change) {
case InputDeviceChange.Added:
msg = NewDeviceMsg.Create(device);
break;
case InputDeviceChange.Removed:
msg = RemoveDeviceMsg.Create(device);
break;
case InputDeviceChange.UsageChanged:
msg = ChangeUsageMsg.Create(device);
break;
default:
return;
}
this._send(msg);
}
_send(message) {
for(let subscriber of this._subscribers) {
subscriber.onNext(message);
}
}
}
export const MessageType = {
Connect: 0,
Disconnect: 1,
NewLayout: 2,
NewDevice: 3,
NewEvents: 4,
RemoveDevice: 5,
RemoveLayout: 6,
ChangeUsages: 7,
StartSending: 8,
StopSending: 9,
};
export class Message {
/**
* field offset 0
* {Number} participant_id;
*
* field offset 4
* {Number} type;
*
* field offset 8
* {Number} length;
*
* field offset 12
* {ArrayBuffer} data;
*/
/**
*
* @param {number} participantId
* @param {MessageType} type
* @param {ArrayBuffer} data
*/
constructor(participantId, type, data) {
this.participant_id = participantId;
this.type = type;
this.length = data.byteLength;
this.data = data;
}
/**
*
* @returns {ArrayBuffer}
*/
get buffer() {
const totalSize =
MemoryHelper.sizeOfInt + // size of this.participant_id
MemoryHelper.sizeOfInt + // size of this.type
MemoryHelper.sizeOfInt + // size of this.length
this.data.byteLength; // size of this.data
let buffer = new ArrayBuffer(totalSize);
let dataView = new DataView(buffer);
let uint8view = new Uint8Array(buffer);
dataView.setUint32(0, this.participant_id, true);
dataView.setUint32(4, this.type, true);
dataView.setUint32(8, this.length, true);
uint8view.set(new Uint8Array(this.data), 12);
return buffer;
}
}
export class NewDeviceMsg {
/**
* @param {InputDevice} device
* @returns {Message}
*/
static create(device) {
const data = {
name: device.name,
layout: device.layout,
deviceId: device.deviceId,
variants: device.variants,
description: device.description
};
const json = JSON.stringify(data);
let buffer = new ArrayBuffer(json.length*2); // 2 bytes for each char
let view = new Uint8Array(buffer);
const length = json.length;
for (let i = 0; i < length; i++) {
view[i] = json.charCodeAt(i);
}
return new Message(0, MessageType.NewDevice, buffer);
}
}
export class NewEventsMsg {
/**
*
* @param {InputDevice} device
* @returns {Message}
*/
static createStateEvent(device) {
const events = StateEvent.from(device);
return NewEventsMsg.create(events);
}
/**
*
* @param {StateEvent} event
* @returns {Message}
*/
static create(event) {
return new Message(0, MessageType.NewEvents, event.buffer);
}
}
export class RemoveDeviceMsg {
/**
*
* @param {InputDevice} device
* @returns {Message}
*/
static create(device) {
let buffer = new ArrayBuffer(MemoryHelper.sizeOfInt);
let view = new DataView(buffer);
view.setInt32(device.deviceId);
return new Message(0, MessageType.RemoveDevice, buffer);
}
}
export class ChangeUsageMsg {
static create(device) {
// todo:
throw new Error(`ChangeUsageMsg class is not implemented. device=${device}`);
}
}

120
client/src/input/keymap.js Normal file
View File

@@ -0,0 +1,120 @@
export const Keymap = {
"Space": 1,
"Enter": 2,
"Tab": 3,
"Backquote": 4,
"Quote": 5,
"Semicolon": 6,
"Comma": 7,
"Period": 8,
"Slash": 9,
"Backslash": 10,
"BracketLeft": 11,
"BracketRight": 12,
"Minus": 13,
"Equal": 14,
"KeyA": 15,
"KeyB": 16,
"KeyC": 17,
"KeyD": 18,
"KeyE": 19,
"KeyF": 20,
"KeyG": 21,
"KeyH": 22,
"KeyI": 23,
"KeyJ": 24,
"KeyK": 25,
"KeyL": 26,
"KeyM": 27,
"KeyN": 28,
"KeyO": 29,
"KeyP": 30,
"KeyQ": 31,
"KeyR": 32,
"KeyS": 33,
"KeyT": 34,
"KeyU": 35,
"KeyV": 36,
"KeyW": 37,
"KeyX": 38,
"KeyY": 39,
"KeyZ": 40,
"Digit1": 41,
"Digit2": 42,
"Digit3": 43,
"Digit4": 44,
"Digit5": 45,
"Digit6": 46,
"Digit7": 47,
"Digit8": 48,
"Digit9": 49,
"Digit0": 50,
"ShiftLeft": 51,
"ShiftRight": 52,
"AltLeft": 53,
"AltRight": 54,
// "AltGr": 54,
"ControlLeft": 55,
"ControlRight": 56,
"MetaLeft": 57,
"MetaRight": 58,
// "LeftWindows": 57,
// "RightWindows": 58,
// "LeftApple": 57,
// "RightApple": 58,
// "LeftCommand": 57,
// "RightCommand": 58,
"ContextMenu": 59,
"Escape": 60,
"ArrowLeft": 61,
"ArrowRight": 62,
"ArrowUp": 63,
"ArrowDown": 64,
"Backspace": 65,
"PageDown": 66,
"PageUp": 67,
"Home": 68,
"End": 69,
"Insert": 70,
"Delete": 71,
"CapsLock": 72,
"NumLock": 73,
"PrintScreen": 74,
"ScrollLock": 75,
"Pause": 76,
"NumpadEnter": 77,
"NumpadDivide": 78,
"NumpadMultiply": 79,
"NumpadAdd": 80,
"NumpadSubtract": 81,
"NumpadDecimal": 82,
"NumpadEquals": 83,
"Numpad0": 84,
"Numpad1": 85,
"Numpad2": 86,
"Numpad3": 87,
"Numpad4": 88,
"Numpad5": 89,
"Numpad6": 90,
"Numpad7": 91,
"Numpad8": 92,
"Numpad9": 93,
"F1": 94,
"F2": 95,
"F3": 96,
"F4": 97,
"F5": 98,
"F6": 99,
"F7": 100,
"F8": 101,
"F9": 102,
"F10": 103,
"F11": 104,
"F12": 105,
// "OEM1": 106,
// "OEM2": 107,
// "OEM3": 108,
// "OEM4": 109,
// "OEM5": 110,
// "IMESelected": 111,
};

View File

@@ -0,0 +1,7 @@
export const MouseButton = {
Left: 0,
Right: 1,
Middle: 2,
Foaward: 3,
Back: 4,
};

View File

@@ -0,0 +1,124 @@
export const LetterBoxType = {
Vertical: 0,
Horizontal: 1
};
export class PointerCorrector {
/**
* @param {Number} videoWidth
* @param {Number} videoHeight
* @param {HTMLVideoElement} videoElem
*/
constructor(videoWidth, videoHeight, videoElem) {
this.reset(videoWidth, videoHeight, videoElem);
}
/**
* @param {Number[]} position MouseEvent.clientX, MouseEvent.clientY
* @returns {Number[]}
*/
map(position) {
var rect = this._videoElem.getBoundingClientRect();
const _position = new Array(2);
// (1) set origin point to zero
_position[0] = position[0] - rect.left;
_position[1] = position[1] - rect.top;
// (2) translate Unity coordinate system (reverse y-axis)
_position[1] = rect.height - _position[1];
// (3) add offset of letterbox
_position[0] -= this._contentRect.x;
_position[1] -= this._contentRect.y;
// (4) mapping element rectangle to video rectangle
_position[0] = _position[0] / this._contentRect.width * this._videoWidth;
_position[1] = _position[1] / this._contentRect.height * this._videoHeight;
return _position;
}
/**
* @param {Number} videoWidth
*/
setVideoWidth(videoWidth) {
this._videoWidth = videoWidth;
this._reset();
}
/**
* @param {Number} videoHeight
*/
setVideoHeight(videoHeight) {
this._videoHeight = videoHeight;
this._reset();
}
/**
* @param {HTMLVideoElement} videoElem
*/
setRect(videoElem) {
this._videoElem = videoElem;
this._reset();
}
/**
* @param {Number} videoWidth
* @param {Number} videoHeight
* @param {HTMLVideoElement} videoElem
*/
reset(videoWidth, videoHeight, videoElem) {
this._videoWidth = videoWidth;
this._videoHeight = videoHeight;
this._videoElem = videoElem;
this._reset();
}
get letterBoxType() {
const videoRatio = this._videoHeight / this._videoWidth;
var rect = this._videoElem.getBoundingClientRect();
const rectRatio = rect.height / rect.width;
return videoRatio > rectRatio ? LetterBoxType.Vertical : LetterBoxType.Horizontal;
}
get letterBoxSize() {
var rect = this._videoElem.getBoundingClientRect();
switch(this.letterBoxType) {
case LetterBoxType.Horizontal: {
const ratioWidth = rect.width / this._videoWidth;
const height = this._videoHeight * ratioWidth;
return (rect.height - height) * 0.5;
}
case LetterBoxType.Vertical: {
const ratioHeight = rect.height / this._videoHeight;
const width = this._videoWidth * ratioHeight;
return (rect.width - width) * 0.5;
}
}
throw 'invalid status';
}
/**
* Returns rectangle for displaying video with the origin at the left-top of the element.
* Not considered applying CSS like `object-fit`.
* @returns {Object}
*/
get contentRect() {
const letterBoxType = this.letterBoxType;
const letterBoxSize = this.letterBoxSize;
var rect = this._videoElem.getBoundingClientRect();
const x = letterBoxType == LetterBoxType.Vertical ? letterBoxSize : 0;
const y = letterBoxType == LetterBoxType.Horizontal ? letterBoxSize : 0;
const width = letterBoxType == LetterBoxType.Vertical ? rect.width - letterBoxSize * 2 : rect.width;
const height = letterBoxType == LetterBoxType.Horizontal ? rect.height - letterBoxSize * 2 : rect.height;
return {x: x, y: y, width: width, height: height};
}
_reset() {
this._contentRect = this.contentRect;
}
}

View File

@@ -0,0 +1,7 @@
export const TouchFlags =
{
IndirectTouch: 1 << 0,
PrimaryTouch: 1 << 4,
Tap: 1 << 5,
OrphanedPrimaryTouch: 1 << 6,
};

View File

@@ -0,0 +1,8 @@
export const TouchPhase = {
None: 0,
Began: 1,
Moved: 2,
Ended: 3,
Canceled: 4,
Stationary: 5
};