msgba-web/js-src/components/overlay-controls.tsx

260 lines
8.7 KiB
TypeScript

import * as React from 'react'
import { HOME_BUTTON_IMAGE } from '@msgba/constants'
import { sendKeyDown } from '@msgba/packet'
export interface OverlayControlsProps {
controlsRef: React.RefObject<HTMLDivElement>
setHiddenMenu: (c: boolean) => void
webSocket: WebSocket | null
};
interface ControlValue {
key: number
ref: React.RefObject<HTMLAnchorElement>
sym: string
classes: string
transformX?: number
transformY?: number
}
type ControlMap = Record<string, ControlValue>
export default function OverlayControls ({ setHiddenMenu, webSocket, controlsRef }: OverlayControlsProps): JSX.Element {
function showOverlayMenu (): void {
setHiddenMenu(false)
}
const onGoingTouches = new Map<number, number>()
function mouseDown (e: React.MouseEvent<HTMLAnchorElement>, key: number): void {
e.preventDefault()
if (webSocket == null) {
console.log('There is not websocket')
return
}
sendKeyDown(webSocket, true, key)
}
function mouseUp (e: React.MouseEvent<HTMLAnchorElement>, key: number): void {
e.preventDefault()
if (webSocket == null) {
console.log('There is not websocket')
return
}
sendKeyDown(webSocket, false, key)
}
const controls: ControlMap = {}
controls.a = {
ref: React.useRef<HTMLAnchorElement | null>(null),
key: 0,
sym: 'A',
classes: 'control-a control-button-a-b control control-button'
}
controls.b = {
ref: React.useRef<HTMLAnchorElement | null>(null),
key: 1,
sym: 'B',
transformX: 50,
classes: 'control-b control-button-a-b control control-button'
}
controls.l = {
key: 2,
sym: 'L',
classes: 'control-l control-button-l-r control',
ref: React.useRef<HTMLAnchorElement | null>(null)
}
controls.r = {
key: 3,
sym: 'R',
classes: 'control-r control-button-l-r control',
ref: React.useRef<HTMLAnchorElement | null>(null)
}
controls.start = {
key: 4,
sym: 'START',
classes: 'control-start control-button-start-select control',
transformX: 25,
ref: React.useRef<HTMLAnchorElement | null>(null)
}
controls.select = {
key: 5,
sym: 'SEL',
classes: 'control-select control-button-start-select control',
transformX: -25,
ref: React.useRef<HTMLAnchorElement | null>(null)
}
controls.up = {
key: 6,
sym: '^',
transformX: 100,
classes: 'control-up control control-pad-button',
ref: React.useRef<HTMLAnchorElement | null>(null)
}
controls.down = {
key: 7,
sym: 'v',
transformX: 100,
classes: 'control-down control control-pad-button',
ref: React.useRef<HTMLAnchorElement | null>(null)
}
controls.left = {
key: 8,
sym: '<',
classes: 'control-left control control-pad-button',
ref: React.useRef<HTMLAnchorElement | null>(null)
}
controls.right = {
key: 9,
sym: '>',
transformX: 200,
classes: 'control-right control control-pad-button',
ref: React.useRef<HTMLAnchorElement | null>(null)
}
function determineKey (e: React.TouchEvent<HTMLDivElement>, touch: React.Touch): number | null {
const x = touch.pageX
const y = touch.pageY
for (const control of Object.keys(controls)) {
const ref = controls[control].ref.current
if (ref == null) {
console.log('No ref found')
continue
}
const top = ref.getBoundingClientRect().top + document.documentElement.scrollTop
const currentControl = controls[control]
const transformX = currentControl.transformX
const transformY = currentControl.transformY
let offsetLeft = ref.offsetLeft
const offsetWidth = ref.offsetWidth
let offsetTop = top
const offsetHeight = ref.offsetHeight
if (transformX != null) {
offsetLeft += offsetWidth * (transformX / 100)
}
if (transformY != null) {
offsetTop += offsetHeight * (transformY / 100)
}
if (x >= offsetLeft && x <= offsetLeft + offsetWidth && y >= offsetTop && y <= offsetTop + offsetHeight) {
return controls[control].key
}
}
return null
}
function touchStartControls (e: React.TouchEvent<HTMLDivElement>): boolean {
e.preventDefault()
if (webSocket == null) {
console.log('There is not websocket')
return false
}
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i]
const key: number | null = determineKey(e, touch)
if (key == null) {
continue
}
const idx = touch.identifier
onGoingTouches.set(idx, key)
sendKeyDown(webSocket, true, key)
}
return false
}
function touchMoveControls (e: React.TouchEvent<HTMLDivElement>): boolean {
e.preventDefault()
if (webSocket == null) {
console.log('There is not websocket')
return false
}
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i]
const key: number | null = determineKey(e, touch)
const idx = touch.identifier
if (key == null) {
continue
}
const oldKey = onGoingTouches.get(idx)
if (oldKey != null) {
sendKeyDown(webSocket, false, oldKey)
onGoingTouches.delete(idx)
}
onGoingTouches.set(idx, key)
sendKeyDown(webSocket, true, key)
}
return false
}
function touchEndControls (e: React.TouchEvent<HTMLDivElement>): boolean {
e.preventDefault()
if (webSocket == null) {
console.log('There is not websocket')
return false
}
for (let i = 0; i < e.changedTouches.length; i++) {
const touch = e.changedTouches[i]
const idx = touch.identifier
const oldKey = onGoingTouches.get(idx)
if (oldKey == null) {
return false
}
sendKeyDown(webSocket, false, oldKey)
onGoingTouches.delete(idx)
}
return false
}
const keyMap: string[] = ['KeyZ', 'KeyX', 'KeyA', 'KeyS',
'Enter', 'Space', 'ArrowUp', 'ArrowDown',
'ArrowLeft', 'ArrowRight']
function onPressControl (e: React.KeyboardEvent<HTMLDivElement>): void {
if (e.code === 'Escape') {
e.preventDefault()
showOverlayMenu()
return
}
if (webSocket == null) {
console.log('There is not websocket')
return
}
const key = keyMap.findIndex((c: string) => c === e.code)
if (key !== -1) {
e.preventDefault()
sendKeyDown(webSocket, true, key)
}
}
function onUnpressControl (e: React.KeyboardEvent<HTMLDivElement>): void {
if (webSocket == null) {
console.log('There is not websocket')
return
}
const key = keyMap.findIndex((c: string) => c === e.code)
if (key !== -1) {
e.preventDefault()
sendKeyDown(webSocket, false, key)
}
}
document.onselectstart = () => false
return (
<div ref={controlsRef} tabIndex={-1} className="overlay" onKeyDown={onPressControl} onKeyUp={onUnpressControl}>
<div className="vertical-padding">
</div>
<div className="controls" onTouchStart={touchStartControls} onTouchMove={touchMoveControls} onTouchEnd={touchEndControls}>
<a tabIndex={-1} className="gear control" onClick={showOverlayMenu} onTouchStart={showOverlayMenu}>
<img src={HOME_BUTTON_IMAGE} alt="Go to menu. (House icon)"/>
</a>
{
Object.keys(controls).map((key) =>
<a tabIndex={-1}
className={controls[key].classes}
ref={controls[key].ref}
key={key}
onMouseDown={(e: React.MouseEvent<HTMLAnchorElement>) => { mouseDown(e, controls[key].key) }}
onMouseUp={(e: React.MouseEvent<HTMLAnchorElement>) => { mouseUp(e, controls[key].key) }}>
{controls[key].sym}
</a>
)
}
</div>
</div>
)
}