diff --git a/cafe/.env b/cafe/.env
index 1809f91e..ee96ce7b 100644
--- a/cafe/.env
+++ b/cafe/.env
@@ -7,5 +7,7 @@ ZSS_SHOW_CODE=false
ZSS_TRACE_CODE=
ZSS_LOG_DEBUG=false
ZSS_FORCE_CRT_OFF=false
+ZSS_FORCE_LOW_REZ=false
+ZSS_FORCE_TOUCH_UI=false
ZSS_HMR_ONLY=false
ZSS_ANALYZER=false
diff --git a/package.json b/package.json
index 2a677245..f24c63db 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "zed-software-system",
"private": true,
- "version": "0.21.11",
+ "version": "0.21.13",
"type": "module",
"scripts": {
"sloc": "npx sloc zss",
@@ -67,7 +67,6 @@
"msgpackr": "^1.11.2",
"nanoid": "^5.0.3",
"nanoid-dictionary": "^4.3.0",
- "nipplejs": "^0.10.2",
"path-browserify": "^1.0.0",
"peerjs": "^1.5.4",
"postprocessing": "^6.36.4",
diff --git a/yarn.lock b/yarn.lock
index 332c0b18..2c45ce98 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4463,11 +4463,6 @@ natural-compare@^1.4.0:
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
-nipplejs@^0.10.2:
- version "0.10.2"
- resolved "https://registry.npmjs.org/nipplejs/-/nipplejs-0.10.2.tgz#0e8f5346bd60f7a0fe0d44c856bc1654cde2b70c"
- integrity sha512-XGxFY8C2DOtobf1fK+MXINTzkkXJLjZDDpfQhOUZf4TSytbc9s4bmA0lB9eKKM8iDivdr9NQkO7DpIQfsST+9g==
-
node-gyp-build-optional-packages@5.2.2:
version "5.2.2"
resolved "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.2.2.tgz#522f50c2d53134d7f3a76cd7255de4ab6c96a3a4"
diff --git a/zss/config.ts b/zss/config.ts
index 0f0654e9..31757aae 100644
--- a/zss/config.ts
+++ b/zss/config.ts
@@ -8,6 +8,8 @@ const SHOW_CODE = !!JSON.parse(import.meta.env.ZSS_SHOW_CODE)
const TRACE_CODE = `${import.meta.env.ZSS_TRACE_CODE}`
const LOG_DEBUG = !!JSON.parse(import.meta.env.ZSS_LOG_DEBUG)
const FORCE_CRT_OFF = !!JSON.parse(import.meta.env.ZSS_FORCE_CRT_OFF)
+const FORCE_LOW_REZ = !!JSON.parse(import.meta.env.ZSS_FORCE_LOW_REZ)
+const FORCE_TOUCH_UI = !!JSON.parse(import.meta.env.ZSS_FORCE_TOUCH_UI)
// runtime config
export const RUNTIME = {
@@ -31,4 +33,6 @@ export {
TRACE_CODE,
LOG_DEBUG,
FORCE_CRT_OFF,
+ FORCE_LOW_REZ,
+ FORCE_TOUCH_UI,
}
diff --git a/zss/device/api.ts b/zss/device/api.ts
index c6604ce8..a565e118 100644
--- a/zss/device/api.ts
+++ b/zss/device/api.ts
@@ -187,6 +187,18 @@ export function tape_editor_close(sender: string, player: string) {
hub.emit('tape:editor:close', sender, undefined, player)
}
+export function userinput_up(sender: string, input: INPUT, player: string) {
+ hub.emit('userinput:up', sender, input, player)
+}
+
+export function userinput_down(sender: string, input: INPUT, player: string) {
+ hub.emit('userinput:down', sender, input, player)
+}
+
+export function userinput_update(sender: string, player: string) {
+ hub.emit('userinput:update', sender, undefined, player)
+}
+
export function vm_books(
sender: string,
books: string,
diff --git a/zss/device/vm.ts b/zss/device/vm.ts
index cee5236f..9c65768a 100644
--- a/zss/device/vm.ts
+++ b/zss/device/vm.ts
@@ -131,7 +131,6 @@ const vm = createdevice('vm', ['init', 'tick', 'second'], (message) => {
break
case 'input':
if (message.player) {
- console.info(message)
// player input
const flags = memoryreadflags(message.player)
const [input = INPUT.NONE, mods = 0] = message.data ?? [INPUT.NONE, 0]
diff --git a/zss/gadget/terminal.tsx b/zss/gadget/terminal.tsx
index 82ed4608..291b7ccc 100644
--- a/zss/gadget/terminal.tsx
+++ b/zss/gadget/terminal.tsx
@@ -5,7 +5,13 @@ import { deviceType, primaryInput } from 'detect-it'
import { useEffect, useState } from 'react'
import Stats from 'stats.js'
import { NearestFilter, OrthographicCamera } from 'three'
-import { FORCE_CRT_OFF, RUNTIME, STATS_DEV } from 'zss/config'
+import {
+ FORCE_CRT_OFF,
+ FORCE_LOW_REZ,
+ FORCE_TOUCH_UI,
+ RUNTIME,
+ STATS_DEV,
+} from 'zss/config'
import { CRTShape } from 'zss/gadget/fx/crt'
import decoimageurl from 'zss/gadget/fx/scratches.gif'
import { useTexture } from 'zss/gadget/usetexture'
@@ -54,12 +60,13 @@ export function Terminal() {
// config DRAW_CHAR_SCALE
const minrez = Math.min(viewwidth, viewheight)
- const islowrez = minrez < 600
+ const islowrez = minrez < 600 || FORCE_LOW_REZ
RUNTIME.DRAW_CHAR_SCALE = islowrez ? 1 : 2
// config LAYOUT
const islandscape = viewwidth > viewheight
- const showtouchcontrols = deviceType === 'hybrid' || primaryInput === 'touch'
+ const showtouchcontrols =
+ FORCE_TOUCH_UI || deviceType === 'hybrid' || primaryInput === 'touch'
// grit texture
const splat = useTexture(decoimageurl)
diff --git a/zss/gadget/touchui/component.tsx b/zss/gadget/touchui/component.tsx
index c798fe5c..ca1d0e32 100644
--- a/zss/gadget/touchui/component.tsx
+++ b/zss/gadget/touchui/component.tsx
@@ -1,10 +1,16 @@
/* eslint-disable react/no-unknown-property */
-import nipplejs from 'nipplejs'
-import { useEffect, useRef } from 'react'
+import { radToDeg } from 'maath/misc'
+import { useState } from 'react'
+import { Vector2, Vector3 } from 'three'
import { RUNTIME } from 'zss/config'
-import { tape_terminal_open, vm_input } from 'zss/device/api'
+import {
+ tape_terminal_open,
+ userinput_down,
+ userinput_up,
+ vm_input,
+} from 'zss/device/api'
import { registerreadplayer } from 'zss/device/register'
-import { ispresent } from 'zss/mapping/types'
+import { snap } from 'zss/mapping/number'
import {
WRITE_TEXT_CONTEXT,
createwritetextcontext,
@@ -13,29 +19,149 @@ import {
} from 'zss/words/textformat'
import { COLOR } from 'zss/words/types'
-import { Clickable } from '../clickable'
import { INPUT } from '../data/types'
import { ShadeBoxDither } from '../framed/dither'
import { useTiles } from '../hooks'
+import { Rect } from '../rect'
import { useScreenSize } from '../userscreen'
import { TilesData, TilesRender } from '../usetiles'
+const motion = new Vector2()
+const corner = new Vector3()
+
export type TouchUIProps = {
width: number
height: number
}
+function ptwithin(
+ x: number,
+ y: number,
+ top: number,
+ right: number,
+ bottom: number,
+ left: number,
+) {
+ return x >= left && x <= right && y >= top && y <= bottom
+}
+
export function TouchUI({ width, height }: TouchUIProps) {
const screensize = useScreenSize()
- const sticksref = useRef(
- nipplejs.create({
- zone: document.getElementById('frame') ?? undefined,
- color: '#00A',
- mode: 'dynamic',
- dataOnly: true,
- }),
- )
const player = registerreadplayer()
+ const [movestick] = useState({
+ startx: -1,
+ starty: -1,
+ tipx: -1,
+ tipy: -1,
+ pointerId: -1 as any,
+ })
+ // const [drawstick, setdrawstick] = useState(-1)
+
+ function clearmovestick(cx: number, cy: number) {
+ if (movestick.tipx === -1) {
+ // check touch targets
+ if (ptwithin(cx, cy, 3, 6, 6, 1)) {
+ // top-left button
+ tape_terminal_open('touchui', player)
+ console.info('top-left')
+ }
+ if (ptwithin(cx, cy, 3, width - 2, 6, width - 6)) {
+ // top-right button
+ vm_input('touchui', INPUT.MENU_BUTTON, 0, player)
+ console.info('top-right')
+ }
+ if (ptwithin(cx, cy, height - 5, 6, height - 2, 1)) {
+ // bottom-left button
+ vm_input('touchui', INPUT.OK_BUTTON, 0, player)
+ console.info('bottom-left')
+ }
+ if (ptwithin(cx, cy, height - 5, width - 2, height - 2, width - 6)) {
+ // bottom-right button
+ vm_input('touchui', INPUT.CANCEL_BUTTON, 0, player)
+ console.info('bottom-right')
+ }
+ } else {
+ // reset input
+ userinput_up('touchui', INPUT.MOVE_UP, player)
+ userinput_up('touchui', INPUT.MOVE_DOWN, player)
+ userinput_up('touchui', INPUT.MOVE_LEFT, player)
+ userinput_up('touchui', INPUT.MOVE_RIGHT, player)
+ }
+ // reset
+ movestick.startx = -1
+ movestick.starty = -1
+ movestick.tipx = -1
+ movestick.tipy = -1
+ movestick.pointerId = -1
+ }
+
+ function handlestickdir(snapdir: number) {
+ switch (snapdir) {
+ case 0:
+ // left
+ userinput_down('touchui', INPUT.MOVE_LEFT, player)
+ userinput_up('touchui', INPUT.MOVE_RIGHT, player)
+ userinput_up('touchui', INPUT.MOVE_UP, player)
+ userinput_up('touchui', INPUT.MOVE_DOWN, player)
+ break
+ case 45:
+ // left up
+ userinput_down('touchui', INPUT.MOVE_UP, player)
+ userinput_down('touchui', INPUT.MOVE_LEFT, player)
+ userinput_up('touchui', INPUT.MOVE_RIGHT, player)
+ userinput_up('touchui', INPUT.MOVE_DOWN, player)
+ break
+ case 90:
+ // up
+ userinput_down('touchui', INPUT.MOVE_UP, player)
+ userinput_up('touchui', INPUT.MOVE_DOWN, player)
+ userinput_up('touchui', INPUT.MOVE_LEFT, player)
+ userinput_up('touchui', INPUT.MOVE_RIGHT, player)
+ break
+ case 135:
+ // up right
+ userinput_down('touchui', INPUT.MOVE_UP, player)
+ userinput_down('touchui', INPUT.MOVE_RIGHT, player)
+ userinput_up('touchui', INPUT.MOVE_LEFT, player)
+ userinput_up('touchui', INPUT.MOVE_DOWN, player)
+ break
+ case 180:
+ // right
+ userinput_down('touchui', INPUT.MOVE_RIGHT, player)
+ userinput_up('touchui', INPUT.MOVE_LEFT, player)
+ userinput_up('touchui', INPUT.MOVE_UP, player)
+ userinput_up('touchui', INPUT.MOVE_DOWN, player)
+ break
+ case 225:
+ // right down
+ userinput_down('touchui', INPUT.MOVE_DOWN, player)
+ userinput_down('touchui', INPUT.MOVE_RIGHT, player)
+ userinput_up('touchui', INPUT.MOVE_LEFT, player)
+ userinput_up('touchui', INPUT.MOVE_DOWN, player)
+ break
+ case 270:
+ // down
+ userinput_down('touchui', INPUT.MOVE_DOWN, player)
+ userinput_up('touchui', INPUT.MOVE_UP, player)
+ userinput_up('touchui', INPUT.MOVE_LEFT, player)
+ userinput_up('touchui', INPUT.MOVE_RIGHT, player)
+ break
+ case 315:
+ // down left
+ userinput_down('touchui', INPUT.MOVE_DOWN, player)
+ userinput_down('touchui', INPUT.MOVE_LEFT, player)
+ userinput_up('touchui', INPUT.MOVE_RIGHT, player)
+ userinput_up('touchui', INPUT.MOVE_UP, player)
+ break
+ case 360:
+ // left
+ userinput_down('touchui', INPUT.MOVE_LEFT, player)
+ userinput_up('touchui', INPUT.MOVE_RIGHT, player)
+ userinput_up('touchui', INPUT.MOVE_UP, player)
+ userinput_up('touchui', INPUT.MOVE_DOWN, player)
+ break
+ }
+ }
const FG = COLOR.PURPLE
const BG = COLOR.ONCLEAR
@@ -45,46 +171,6 @@ export function TouchUI({ width, height }: TouchUIProps) {
...store.getState(),
}
- const BUTTON_WIDTH = 5
- const BUTTON_HEIGHT = 3
-
- useEffect(() => {
- const { current } = sticksref
- if (!ispresent(current)) {
- return
- }
-
- function handledirevt(evt: any) {
- switch (evt.type) {
- case 'removed':
- break
- case 'dir:up':
- vm_input('touchui', INPUT.MOVE_UP, 0, player)
- break
- case 'dir:down':
- vm_input('touchui', INPUT.MOVE_DOWN, 0, player)
- break
- case 'dir:left':
- vm_input('touchui', INPUT.MOVE_LEFT, 0, player)
- break
- case 'dir:right':
- vm_input('touchui', INPUT.MOVE_RIGHT, 0, player)
- break
- }
- }
-
- current.on('dir:up', handledirevt)
- current.on('dir:down', handledirevt)
- current.on('dir:left', handledirevt)
- current.on('dir:right', handledirevt)
- return () => {
- current.off('dir:up', handledirevt)
- current.off('dir:down', handledirevt)
- current.off('dir:left', handledirevt)
- current.off('dir:right', handledirevt)
- }
- }, [player])
-
// bail on odd states
if (screensize.cols < 10 || screensize.rows < 10) {
return null
@@ -93,28 +179,73 @@ export function TouchUI({ width, height }: TouchUIProps) {
// render ui
textformatedges(1, 1, width - 2, height - 2, context)
- // action button targets
+ // draw action button targets
context.y = 3
for (let i = 0; i < 3; ++i) {
context.x = context.active.leftedge = 1
tokenizeandwritetextformat(`$BLUE$177$177$177$177$177`, context, false)
context.x = context.active.leftedge = width - 7
- tokenizeandwritetextformat(`$PURPLE$177$177$177$177$177`, context, false)
- ++context.y
+ tokenizeandwritetextformat(`$PURPLE$177$177$177$177$177\n`, context, false)
}
-
context.y = height - 5
for (let i = 0; i < 3; ++i) {
context.x = context.active.leftedge = 1
tokenizeandwritetextformat(`$GREEN$177$177$177$177$177`, context, false)
context.x = context.active.leftedge = width - 7
- tokenizeandwritetextformat(`$RED$177$177$177$177$177`, context, false)
- ++context.y
+ tokenizeandwritetextformat(`$RED$177$177$177$177$177\n`, context, false)
}
return (
+ {
+ if (movestick.startx === -1) {
+ movestick.startx = e.x
+ movestick.starty = e.y
+ movestick.tipx = -1
+ movestick.tipy = -1
+ movestick.pointerId = e.pointerId
+ } else {
+ // flag as shooting now
+ userinput_down('touchui', INPUT.SHIFT, player)
+ }
+ }}
+ onPointerMove={(e) => {
+ if (e.pointerId === movestick.pointerId) {
+ // calc angle
+ motion.set(movestick.startx - e.x, movestick.starty - e.y)
+ if (motion.length() > 42) {
+ const snapdir = snap(radToDeg(motion.angle()), 45)
+ // track for visuals
+ movestick.tipx = e.x
+ movestick.tipy = e.y
+ // invoke input directions
+ handlestickdir(snapdir)
+ }
+ }
+ }}
+ onPointerUp={(e) => {
+ if (e.pointerId === movestick.pointerId) {
+ corner.copy(e.intersections[0].point)
+ e.intersections[0].object.worldToLocal(corner)
+ const dx =
+ Math.floor(width * 0.5) +
+ Math.floor(corner.x / RUNTIME.DRAW_CHAR_WIDTH())
+ const dy =
+ Math.floor(height * 0.5) +
+ Math.floor(corner.y / RUNTIME.DRAW_CHAR_HEIGHT())
+ clearmovestick(dx, dy)
+ } else {
+ // flag off shift
+ userinput_up('touchui', INPUT.SHIFT, player)
+ }
+ }}
+ />
-
- {
- // top-left button
- tape_terminal_open('touchui', player)
- }}
- />
-
-
- {
- // top-right button
- vm_input('touchui', INPUT.MENU_BUTTON, 0, player)
- }}
- />
-
-
- {
- // bottom-left button
- vm_input('touchui', INPUT.OK_BUTTON, 0, player)
- }}
- />
-
-
- {
- // bottom-right button
- vm_input('touchui', INPUT.CANCEL_BUTTON, 0, player)
- }}
- />
-
)
diff --git a/zss/gadget/userinput.tsx b/zss/gadget/userinput.tsx
index 297e5630..5df7d9fa 100644
--- a/zss/gadget/userinput.tsx
+++ b/zss/gadget/userinput.tsx
@@ -9,9 +9,11 @@ import {
useEffect,
useState,
} from 'react'
+import { createdevice } from 'zss/device'
import { vm_cli } from 'zss/device/api'
import { registerreadplayer } from 'zss/device/register'
import { INPUT } from 'zss/gadget/data/types'
+import { isnumber } from 'zss/mapping/types'
import { ismac } from 'zss/words/system'
import { NAME } from 'zss/words/types'
@@ -254,6 +256,54 @@ document.addEventListener(
{ capture: true },
)
+createdevice('userinput', ['tock'], (message) => {
+ switch (message.target) {
+ case 'up':
+ if (isnumber(message.data)) {
+ inputup(message.data)
+ }
+ break
+ case 'down':
+ if (isnumber(message.data)) {
+ inputdown(message.data)
+ }
+ break
+ case 'update': {
+ const now = performance.now()
+ const delta = now - previous
+
+ acc += delta
+ if (acc >= INPUT_RATE) {
+ acc %= INPUT_RATE
+ // signal input state
+ const mods: UserInputMods = {
+ alt: !!inputstate[INPUT.ALT],
+ ctrl: !!inputstate[INPUT.CTRL],
+ shift: !!inputstate[INPUT.SHIFT],
+ }
+ const inputs = [
+ INPUT.MOVE_UP,
+ INPUT.MOVE_DOWN,
+ INPUT.MOVE_LEFT,
+ INPUT.MOVE_RIGHT,
+ INPUT.OK_BUTTON,
+ INPUT.CANCEL_BUTTON,
+ INPUT.MENU_BUTTON,
+ ]
+ inputs.forEach((input) => {
+ if (inputstate[input]) {
+ userinputinvoke(input, mods)
+ }
+ })
+ }
+
+ previous = now
+ GamepadHelper.update()
+ break
+ }
+ }
+})
+
// gamepad input
const BUTTON_A = 0
@@ -280,9 +330,9 @@ const buttonlookup: Record = {
[BUTTON_X]: INPUT.OK_BUTTON,
[BUTTON_Y]: INPUT.CANCEL_BUTTON,
[BUTTON_LEFT_SHOULDER]: INPUT.ALT,
- [BUTTON_RIGHT_SHOULDER]: INPUT.ALT,
- [BUTTON_LEFT_TRIGGER]: INPUT.CTRL,
- [BUTTON_RIGHT_TRIGGER]: INPUT.CTRL,
+ [BUTTON_RIGHT_SHOULDER]: INPUT.CTRL,
+ [BUTTON_LEFT_TRIGGER]: INPUT.SHIFT,
+ [BUTTON_RIGHT_TRIGGER]: INPUT.SHIFT,
[BUTTON_MENU]: INPUT.MENU_BUTTON,
[BUTTON_UP]: INPUT.MOVE_UP,
[BUTTON_DOWN]: INPUT.MOVE_DOWN,
@@ -302,41 +352,6 @@ document.addEventListener('gamepadbuttonup', (event: GamepadEvent) => {
inputup(buttonlookup[event.detail.button])
})
-function inputpoll() {
- const now = performance.now()
- const delta = now - previous
-
- acc += delta
- if (acc >= INPUT_RATE) {
- acc %= INPUT_RATE
- // signal input state
- const mods: UserInputMods = {
- alt: !!inputstate[INPUT.ALT],
- ctrl: !!inputstate[INPUT.CTRL],
- shift: !!inputstate[INPUT.SHIFT],
- }
- const inputs = [
- INPUT.MOVE_UP,
- INPUT.MOVE_DOWN,
- INPUT.MOVE_LEFT,
- INPUT.MOVE_RIGHT,
- INPUT.OK_BUTTON,
- INPUT.CANCEL_BUTTON,
- INPUT.MENU_BUTTON,
- ]
- inputs.forEach((input) => {
- if (inputstate[input]) {
- userinputinvoke(input, mods)
- }
- })
- }
-
- previous = now
- GamepadHelper.update()
- setTimeout(inputpoll, 1)
-}
-inputpoll()
-
// mouse && touch input - used to activate :tap labels
// components
diff --git a/zss/userspace.ts b/zss/userspace.ts
index f09270ba..2f764651 100644
--- a/zss/userspace.ts
+++ b/zss/userspace.ts
@@ -2,6 +2,15 @@
import './device/peer'
import './device/gadgetclient'
import './device/modem'
-import './device/register'
import './device/synth'
import './device/tape'
+import { userinput_update } from './device/api'
+import { registerreadplayer } from './device/register'
+
+function inputpolling() {
+ const player = registerreadplayer()
+ userinput_update('userspace', player)
+ setTimeout(inputpolling, 10)
+}
+
+inputpolling()