From c07ddcee27729c52f3e41dd78e2b02cf310807db Mon Sep 17 00:00:00 2001 From: goldbuick Date: Tue, 31 Dec 2024 21:04:38 -0500 Subject: [PATCH 1/4] working on touch ui feedback --- zss/device/api.ts | 4 + zss/device/tape.ts | 8 ++ zss/gadget/console/input.tsx | 6 - zss/gadget/terminal.tsx | 1 - zss/gadget/touchui/component.tsx | 229 ++++--------------------------- zss/gadget/touchui/controls.tsx | 73 ++++++++++ zss/gadget/touchui/inputs.ts | 71 ++++++++++ zss/gadget/touchui/surface.tsx | 152 ++++++++++++++++++++ zss/gadget/userscreen.tsx | 3 +- zss/mapping/2d.ts | 11 ++ 10 files changed, 344 insertions(+), 214 deletions(-) create mode 100644 zss/gadget/touchui/controls.tsx create mode 100644 zss/gadget/touchui/inputs.ts create mode 100644 zss/gadget/touchui/surface.tsx diff --git a/zss/device/api.ts b/zss/device/api.ts index a565e118..ca81df4d 100644 --- a/zss/device/api.ts +++ b/zss/device/api.ts @@ -160,6 +160,10 @@ export function tape_terminal_close(sender: string, player: string) { hub.emit('tape:terminal:close', sender, undefined, player) } +export function tape_terminal_toggle(sender: string, player: string) { + hub.emit('tape:terminal:toggle', sender, undefined, player) +} + export function tape_terminal_inclayout( sender: string, inc: boolean, diff --git a/zss/device/tape.ts b/zss/device/tape.ts index 66488726..3f8740ae 100644 --- a/zss/device/tape.ts +++ b/zss/device/tape.ts @@ -91,6 +91,14 @@ createdevice('tape', [], (message) => { }, })) break + case 'terminal:toggle': + useTape.setState((state) => ({ + terminal: { + ...state.terminal, + open: !state.terminal.open, + }, + })) + break case 'terminal:inclayout': if (isboolean(message.data)) { terminalinclayout(message.data) diff --git a/zss/gadget/console/input.tsx b/zss/gadget/console/input.tsx index 11509d4a..d00eaa85 100644 --- a/zss/gadget/console/input.tsx +++ b/zss/gadget/console/input.tsx @@ -209,12 +209,6 @@ export function ConsoleInput({ }} /> { - tape_terminal_close('tape', player) - }} - MENU_BUTTON={() => { - tape_terminal_inclayout('tape', true, player) - }} keydown={(event) => { const { key } = event const lkey = NAME(key) diff --git a/zss/gadget/terminal.tsx b/zss/gadget/terminal.tsx index 291b7ccc..63009ad9 100644 --- a/zss/gadget/terminal.tsx +++ b/zss/gadget/terminal.tsx @@ -86,7 +86,6 @@ export function Terminal() { <> diff --git a/zss/gadget/touchui/component.tsx b/zss/gadget/touchui/component.tsx index ca1d0e32..8201c6e1 100644 --- a/zss/gadget/touchui/component.tsx +++ b/zss/gadget/touchui/component.tsx @@ -1,173 +1,41 @@ -/* eslint-disable react/no-unknown-property */ -import { radToDeg } from 'maath/misc' import { useState } from 'react' -import { Vector2, Vector3 } from 'three' -import { RUNTIME } from 'zss/config' -import { - tape_terminal_open, - userinput_down, - userinput_up, - vm_input, -} from 'zss/device/api' import { registerreadplayer } from 'zss/device/register' -import { snap } from 'zss/mapping/number' import { WRITE_TEXT_CONTEXT, createwritetextcontext, textformatedges, - tokenizeandwritetextformat, } from 'zss/words/textformat' import { COLOR } from 'zss/words/types' -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() +import { Controls } from './controls' +import { Surface } from './surface' export type TouchUIProps = { width: number height: number + islandscape: boolean } -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) { +export function TouchUI({ width, height, islandscape }: TouchUIProps) { const screensize = useScreenSize() const player = registerreadplayer() - const [movestick] = useState({ + + const [drawstick, setdrawstick] = 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 - const store = useTiles(width, height, 0, FG, BG) + // setup text writing + const store = useTiles(width, height, 0, COLOR.WHITE, COLOR.ONCLEAR) const context: WRITE_TEXT_CONTEXT = { - ...createwritetextcontext(width, height, FG, BG), + ...createwritetextcontext(width, height, COLOR.WHITE, COLOR.ONCLEAR), ...store.getState(), } @@ -179,80 +47,31 @@ export function TouchUI({ width, height }: TouchUIProps) { // render ui textformatedges(1, 1, width - 2, height - 2, context) - // 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\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\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) - } - }} + islandscape={islandscape} + drawstick={drawstick} + /> + + setdrawstick({ startx, starty, tipx, tipy }) + } /> diff --git a/zss/gadget/touchui/controls.tsx b/zss/gadget/touchui/controls.tsx new file mode 100644 index 00000000..b99339fd --- /dev/null +++ b/zss/gadget/touchui/controls.tsx @@ -0,0 +1,73 @@ +import { + tokenizeandwritetextformat, + WRITE_TEXT_CONTEXT, +} from 'zss/words/textformat' + +type ControlsProps = { + context: WRITE_TEXT_CONTEXT + width: number + height: number + islandscape: boolean + drawstick: { + startx: number + starty: number + tipx: number + tipy: number + } +} + +export function Controls({ + context, + width, + height, + islandscape, + drawstick, +}: ControlsProps) { + // draw action button targets + context.y = 3 + for (let i = 0; i < 3; ++i) { + context.x = context.active.leftedge = 1 + tokenizeandwritetextformat(`$YELLOW$176$176$176$176$176`, context, false) + context.x = context.active.leftedge = width - 7 + tokenizeandwritetextformat(`$WHITE$176$176$176$176$176\n`, context, false) + } + context.y = height - 5 + for (let i = 0; i < 3; ++i) { + context.x = context.active.leftedge = 1 + tokenizeandwritetextformat(`$GREEN$176$176$176$176$176`, context, false) + context.x = context.active.leftedge = width - 7 + tokenizeandwritetextformat(`$RED$176$176$176$176$176\n`, context, false) + } + + // draw active stick + if (drawstick.startx !== -1) { + console.info(drawstick) + context.active.leftedge = 0 + context.x = context.active.leftedge = 3 // drawstick.startx - 4 + context.y = 3 //drawstick.starty - 4 + for (let i = 0; i < 5; ++i) { + tokenizeandwritetextformat( + `$BLUE$176$176$176$176$176$176$176$176\n`, + context, + false, + ) + } + } + + // draw labels in portrait + if (!islandscape) { + context.active.leftedge = 1 + context.y = 4 + context.x = 2 + tokenizeandwritetextformat(`$YELLOW?`, context, false) + context.x = width - 7 + tokenizeandwritetextformat(`$WHITEMENU`, context, false) + context.y = height - 4 + context.x = 2 + tokenizeandwritetextformat(`$GREENOKAY`, context, false) + context.x = width - 9 + tokenizeandwritetextformat(`$REDCANCEL`, context, false) + } + + return null +} diff --git a/zss/gadget/touchui/inputs.ts b/zss/gadget/touchui/inputs.ts new file mode 100644 index 00000000..30296c88 --- /dev/null +++ b/zss/gadget/touchui/inputs.ts @@ -0,0 +1,71 @@ +import { userinput_down, userinput_up } from 'zss/device/api' + +import { INPUT } from '../data/types' + +export function handlestickdir(snapdir: number, player: string) { + 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 + } +} diff --git a/zss/gadget/touchui/surface.tsx b/zss/gadget/touchui/surface.tsx new file mode 100644 index 00000000..387c7e99 --- /dev/null +++ b/zss/gadget/touchui/surface.tsx @@ -0,0 +1,152 @@ +import { radToDeg } from 'maath/misc' +import { useState } from 'react' +import { Vector2, Vector3 } from 'three' +import { RUNTIME } from 'zss/config' +import { + tape_terminal_toggle, + userinput_down, + userinput_up, + vm_input, +} from 'zss/device/api' +import { ptwithin } from 'zss/mapping/2d' +import { snap } from 'zss/mapping/number' + +import { INPUT } from '../data/types' +import { Rect } from '../rect' + +import { handlestickdir } from './inputs' + +type SurfaceProps = { + width: number + height: number + player: string + onDrawStick: ( + startx: number, + starty: number, + tipx: number, + tipy: number, + ) => void +} + +const motion = new Vector2() +const point = new Vector3() + +function coords(width: number, height: number) { + return { + cx: + Math.floor(width * 0.5) + Math.floor(point.x / RUNTIME.DRAW_CHAR_WIDTH()), + cy: + Math.floor(height * 0.5) + + Math.floor(point.y / RUNTIME.DRAW_CHAR_HEIGHT()), + } +} + +export function Surface({ width, height, player, onDrawStick }: SurfaceProps) { + const [movestick] = useState({ + startx: -1, + starty: -1, + tipx: -1, + tipy: -1, + pointerId: -1 as any, + }) + + 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_toggle('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 + } + + return ( + { + if (movestick.startx === -1) { + e.intersections[0].object.worldToLocal( + point.copy(e.intersections[0].point), + ) + const { cx, cy } = coords(width, height) + movestick.startx = cx + movestick.starty = cy + 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) { + e.intersections[0].object.worldToLocal( + point.copy(e.intersections[0].point), + ) + const { cx, cy } = coords(width, height) + // calc angle + motion.set(movestick.startx - cx, movestick.starty - cy) + if (motion.length() > 6) { + const snapdir = snap(radToDeg(motion.angle()), 45) + // track for visuals + movestick.tipx = cx + movestick.tipy = cy + // invoke input directions + handlestickdir(snapdir, player) + // update visuals + onDrawStick( + movestick.startx, + movestick.starty, + movestick.tipx, + movestick.tipy, + ) + } + } + }} + onPointerUp={(e) => { + if (e.pointerId === movestick.pointerId) { + e.intersections[0].object.worldToLocal( + point.copy(e.intersections[0].point), + ) + const { cx, cy } = coords(width, height) + clearmovestick(cx, cy) + } else { + // flag off shift + userinput_up('touchui', INPUT.SHIFT, player) + } + }} + /> + ) +} diff --git a/zss/gadget/userscreen.tsx b/zss/gadget/userscreen.tsx index 7b0d496c..42735ad0 100644 --- a/zss/gadget/userscreen.tsx +++ b/zss/gadget/userscreen.tsx @@ -21,13 +21,11 @@ export function useScreenSize() { } type UserScreenProps = PropsWithChildren<{ - islowrez: boolean islandscape: boolean showtouchcontrols: boolean }> export function UserScreen({ - islowrez, islandscape, showtouchcontrols, children, @@ -100,6 +98,7 @@ export function UserScreen({ key={insetcols * insetrows} width={insetcols} height={insetrows} + islandscape={islandscape} /> )} diff --git a/zss/mapping/2d.ts b/zss/mapping/2d.ts index 1848486c..387f1477 100644 --- a/zss/mapping/2d.ts +++ b/zss/mapping/2d.ts @@ -18,3 +18,14 @@ export function indextopt(index: number, width: number): PT { export function pttoindex(pt: PT, width: number) { return pt.x + pt.y * width } + +export function ptwithin( + x: number, + y: number, + top: number, + right: number, + bottom: number, + left: number, +) { + return x >= left && x <= right && y >= top && y <= bottom +} From 9f3cc32693c31014875b9942e0b7e3ddd91549a2 Mon Sep 17 00:00:00 2001 From: Automated Version Bump Date: Wed, 1 Jan 2025 02:05:16 +0000 Subject: [PATCH 2/4] ci: version bump to v0.21.14 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f24c63db..d911da81 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zed-software-system", "private": true, - "version": "0.21.13", + "version": "0.21.14", "type": "module", "scripts": { "sloc": "npx sloc zss", From a1f02081c11d4c652858e828e1bab97dac9738bf Mon Sep 17 00:00:00 2001 From: goldbuick Date: Tue, 31 Dec 2024 22:02:02 -0500 Subject: [PATCH 3/4] better feedback --- zss/gadget/touchui/component.tsx | 4 ++-- zss/gadget/touchui/controls.tsx | 40 +++++++++++++++++++++++++++++--- zss/gadget/touchui/surface.tsx | 20 +++++++++++----- 3 files changed, 53 insertions(+), 11 deletions(-) diff --git a/zss/gadget/touchui/component.tsx b/zss/gadget/touchui/component.tsx index 8201c6e1..0b799a14 100644 --- a/zss/gadget/touchui/component.tsx +++ b/zss/gadget/touchui/component.tsx @@ -68,7 +68,7 @@ export function TouchUI({ width, height, islandscape }: TouchUIProps) { 6) { + if (movestick.tipx !== -1 || motion.length() > 3) { const snapdir = snap(radToDeg(motion.angle()), 45) // track for visuals movestick.tipx = cx From 527bcc92a3d03ffde28a094526a3501b3e71fc34 Mon Sep 17 00:00:00 2001 From: Automated Version Bump Date: Wed, 1 Jan 2025 03:04:03 +0000 Subject: [PATCH 4/4] ci: version bump to v0.21.15 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d911da81..b95228ab 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "zed-software-system", "private": true, - "version": "0.21.14", + "version": "0.21.15", "type": "module", "scripts": { "sloc": "npx sloc zss",