jacdac-multitool/ui.ts

156 строки
5.0 KiB
TypeScript

namespace menu {
export type Callback = (opts: Options) => void
export class Item {
public onRight: Callback
public onLeft: Callback
constructor(public name: string, public cb: Callback) { }
}
export function item(name: string, cb: Callback, onRight?: Callback, onLeft?: Callback) {
const item = new Item(name, cb)
item.onRight = onRight
item.onLeft = onLeft
return item
}
export interface Options {
title: string
footer?: string
elements?: Item[]
onB?: (opts: Options) => void
update?: (opts: Options) => void
highlightColor?: number
active?: boolean
}
export function exit(opts: Options) {
game.popScene()
opts.active = false
}
export function show(opts: Options) {
let cursor = 0;
let offset = 0;
let blinkOut = 0;
const move = (dx: number) => {
let nc = cursor + dx
if (nc < 0) nc = 0
else if (nc >= opts.elements.length)
nc = opts.elements.length - 1
if (nc - offset < 2) offset = nc - 2
if (nc - offset > 6) offset = nc - 6
if (offset < 0) offset = 0
cursor = nc
}
function showMenu() {
cursor = 0
offset = 0
controller.down.onEvent(ControllerButtonEvent.Pressed, () => move(1))
controller.down.onEvent(ControllerButtonEvent.Repeated, () => move(1))
controller.up.onEvent(ControllerButtonEvent.Pressed, () => move(-1))
controller.up.onEvent(ControllerButtonEvent.Repeated, () => move(-1))
controller.A.onEvent(ControllerButtonEvent.Pressed, () => {
blinkOut = control.millis() + 100
const e = opts.elements[cursor]
if (e && e.cb)
control.runInBackground(() => e.cb(opts))
})
controller.B.onEvent(ControllerButtonEvent.Pressed, () => {
if (opts.onB) {
blinkOut = control.millis() + 100
control.runInBackground(() => opts.onB(opts))
} else {
exit(opts)
}
})
controller.left.onEvent(ControllerButtonEvent.Pressed, () => {
const e = opts.elements[cursor]
if (e && e.onLeft) {
blinkOut = control.millis() + 100
control.runInBackground(() => e.onLeft(opts))
}
})
controller.right.onEvent(ControllerButtonEvent.Pressed, () => {
const e = opts.elements[cursor]
if (e && e.onRight) {
blinkOut = control.millis() + 100
control.runInBackground(() => e.onRight(opts))
}
})
game.onPaint(function () {
if (game.consoleOverlay.isVisible())
return
const x = 10
if (opts.elements.length == 0) {
screen.print("Nothing here", x, 60, 4, image.font5)
screen.print("press B to go back", x, 80, 4, image.font5)
return
}
// if elements changed, cursor could be off limits - move(0) will limit it
if (cursor >= opts.elements.length) move(0)
screen.fillRect(0, 0, 160, 12, 12)
screen.print(opts.title, x - 1, 2, 4, image.font8)
let hl = opts.highlightColor || 5
if (blinkOut) {
if (blinkOut < control.millis()) blinkOut = 0
else hl = 6
}
for (let i = 0; i < 9; ++i) {
const e = opts.elements[i + offset];
const ename = e ? e.name : ""
let y = 15 + i * 11
if (i + offset == cursor) {
screen.fillRect(0, y - 2, 160, 11, hl)
screen.print(ename, x, y, 15)
}
else
screen.print(ename, x, y, 1)
}
if (opts.footer)
screen.print(opts.footer, x, 120 - 6, 4, image.font5)
})
}
game.pushScene()
opts.active = true
if (opts.update) opts.update(opts)
showMenu()
let cnt = 0
while (opts.active) {
if (cnt++ > 20 && opts.update) {
opts.update(opts)
cnt = 0
}
pause(20)
}
}
export function wait(ms: number, msg: string) {
game.pushScene();
const dialog = new game.SplashDialog(screen.width, 35);
dialog.setText(msg);
dialog.cursor = img`.`
const s = sprites.create(dialog.image, -1);
game.onUpdate(() => {
dialog.update();
})
pause(ms)
game.popScene()
}
}