zx-kit 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +319 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/tilemap.d.ts +133 -0
- package/dist/tilemap.d.ts.map +1 -0
- package/dist/tilemap.js +166 -0
- package/dist/tilemap.js.map +1 -0
- package/dist/ui.d.ts +231 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +210 -0
- package/dist/ui.js.map +1 -0
- package/package.json +7 -4
package/README.md
CHANGED
|
@@ -344,6 +344,319 @@ resetInput() // discard any queued keypresses from the previous phase
|
|
|
344
344
|
|
|
345
345
|
---
|
|
346
346
|
|
|
347
|
+
### `ui.ts` — ZX-style UI primitives
|
|
348
|
+
|
|
349
|
+
High-level drawing helpers and a stateful widget system for HUD elements.
|
|
350
|
+
All primitives operate in game pixels and enforce the Spectrum palette via `SpectrumColor`.
|
|
351
|
+
|
|
352
|
+
#### Types
|
|
353
|
+
|
|
354
|
+
**`BorderOptions`**
|
|
355
|
+
|
|
356
|
+
| Field | Type | Default | Description |
|
|
357
|
+
|-------|------|---------|-------------|
|
|
358
|
+
| `enabled` | `boolean` | `true` | Set to `false` to suppress the border without removing the object |
|
|
359
|
+
| `thickness` | `number` | `1` | Border thickness in pixels |
|
|
360
|
+
| `color` | `SpectrumColor` | same as ink/color | Overrides the parent function's foreground color |
|
|
361
|
+
| `style` | `'solid' \| 'dashed'` | `'solid'` | Solid = continuous lines; dashed = 2 px on / 2 px off |
|
|
362
|
+
|
|
363
|
+
**`DrawProgressBarOptions`**
|
|
364
|
+
|
|
365
|
+
| Field | Type | Default | Description |
|
|
366
|
+
|-------|------|---------|-------------|
|
|
367
|
+
| `id` | `string` | `"${x},${y}"` | Stable key for managed redraws |
|
|
368
|
+
| `x` | `number` | — | Left edge in game pixels |
|
|
369
|
+
| `y` | `number` | — | Top edge in game pixels |
|
|
370
|
+
| `width` | `number` | — | Total width in game pixels (multiples of `CELL = 8` recommended) |
|
|
371
|
+
| `value` | `number` | — | Current value |
|
|
372
|
+
| `min` | `number` | `0` | Value at the left (empty) edge |
|
|
373
|
+
| `max` | `number` | `1` | Value at the right (full) edge |
|
|
374
|
+
| `ink` | `SpectrumColor` | `C.B_WHITE` | Filled-block color |
|
|
375
|
+
| `paper` | `SpectrumColor` | `C.BLACK` | Empty-block background |
|
|
376
|
+
| `border` | `BorderOptions` | — | Optional border around the bar |
|
|
377
|
+
| `visibilityLength` | `number` | `500` | Ms to stay visible after last call; `0` = permanent |
|
|
378
|
+
|
|
379
|
+
#### Stateless primitives
|
|
380
|
+
|
|
381
|
+
##### `drawBox(ctx, options): void`
|
|
382
|
+
|
|
383
|
+
Fills a rectangle with `paper` and draws an optional border.
|
|
384
|
+
|
|
385
|
+
```ts
|
|
386
|
+
drawBox(ctx, {
|
|
387
|
+
x: 8, y: 8, width: 112, height: 40,
|
|
388
|
+
paper: C.BLACK, ink: C.B_WHITE,
|
|
389
|
+
border: { style: 'solid', thickness: 1 },
|
|
390
|
+
})
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
##### `drawFrame(ctx, options): void`
|
|
394
|
+
|
|
395
|
+
Draws a border only — no background fill.
|
|
396
|
+
|
|
397
|
+
```ts
|
|
398
|
+
drawFrame(ctx, { x: 0, y: 0, width: 256, height: 176, color: C.B_CYAN })
|
|
399
|
+
drawFrame(ctx, { x: 16, y: 16, width: 64, height: 32, color: C.B_RED,
|
|
400
|
+
border: { style: 'dashed' } })
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
##### `drawPanelTitle(ctx, options): void`
|
|
404
|
+
|
|
405
|
+
Renders a text strip (height = `CELL + padding * 2`) with optional background fill.
|
|
406
|
+
Does NOT draw the surrounding container — use `drawBox` / `drawFrame` separately.
|
|
407
|
+
|
|
408
|
+
```ts
|
|
409
|
+
drawBox(ctx, { x: 8, y: 24, width: 128, height: 56, paper: C.BLACK })
|
|
410
|
+
drawPanelTitle(ctx, {
|
|
411
|
+
text: 'OPTIONS', x: 8, y: 24,
|
|
412
|
+
ink: C.B_YELLOW, paper: C.BLACK,
|
|
413
|
+
centered: true, width: 128,
|
|
414
|
+
})
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
#### Stateful widget — progress bar
|
|
418
|
+
|
|
419
|
+
The progress bar is a managed widget: after a `drawProgressBar` call, the bar is
|
|
420
|
+
re-rendered automatically on subsequent frames by `renderUI` until `visibilityLength`
|
|
421
|
+
milliseconds have elapsed. Calling `drawProgressBar` again resets the timer.
|
|
422
|
+
|
|
423
|
+
##### `drawProgressBar(ctx, options): void`
|
|
424
|
+
|
|
425
|
+
Draws the bar immediately **and** registers it for managed redraws.
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
// On value change:
|
|
429
|
+
drawProgressBar(ctx, {
|
|
430
|
+
id: 'volume', x: 88, y: 88, width: 80,
|
|
431
|
+
value: getMasterVolume(),
|
|
432
|
+
ink: C.B_GREEN, paper: C.BLACK,
|
|
433
|
+
border: { style: 'solid' },
|
|
434
|
+
visibilityLength: 1500,
|
|
435
|
+
})
|
|
436
|
+
|
|
437
|
+
// Permanent HUD element:
|
|
438
|
+
drawProgressBar(ctx, {
|
|
439
|
+
id: 'health', x: 0, y: 184, width: 40,
|
|
440
|
+
value: lives, min: 0, max: 3,
|
|
441
|
+
ink: C.B_GREEN, paper: C.BLACK,
|
|
442
|
+
visibilityLength: 0,
|
|
443
|
+
})
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
##### `tickUI(dtMs): void`
|
|
447
|
+
|
|
448
|
+
Advances all managed bar timers. Expired bars are removed. Call once per frame.
|
|
449
|
+
|
|
450
|
+
##### `renderUI(ctx): void`
|
|
451
|
+
|
|
452
|
+
Redraws all currently visible bars. Call every frame **after** the game world render.
|
|
453
|
+
|
|
454
|
+
##### `resetUI(): void`
|
|
455
|
+
|
|
456
|
+
Clears all managed state. Call alongside `resetInput()` on phase transitions.
|
|
457
|
+
|
|
458
|
+
```ts
|
|
459
|
+
// Typical game loop:
|
|
460
|
+
renderFrame(ctx, state)
|
|
461
|
+
tickUI(dt)
|
|
462
|
+
renderUI(ctx)
|
|
463
|
+
|
|
464
|
+
// Phase transition:
|
|
465
|
+
resetInput()
|
|
466
|
+
resetUI()
|
|
467
|
+
appPhase = 'intro'
|
|
468
|
+
```
|
|
469
|
+
|
|
470
|
+
---
|
|
471
|
+
|
|
472
|
+
### `tilemap.ts` — Scrollable tile map
|
|
473
|
+
|
|
474
|
+
A scrollable, queryable `TileMap` backed by an O(1) id-index. Tiles use the same 8×8 sprite format as `drawSprite`. Supports seasonal background swapping, viewport-clipped rendering, collision queries, and fast id-based lookups.
|
|
475
|
+
|
|
476
|
+
#### Types
|
|
477
|
+
|
|
478
|
+
**`Tile`**
|
|
479
|
+
|
|
480
|
+
| Field | Type | Description |
|
|
481
|
+
|-------|------|-------------|
|
|
482
|
+
| `sprite` | `Uint8Array` | 8-byte bitmap — same format as `drawSprite()` |
|
|
483
|
+
| `ink` | `string` | Foreground colour (`C.*` palette value) |
|
|
484
|
+
| `paper` | `string` | Background colour (`C.*` palette value) |
|
|
485
|
+
| `solid` | `boolean` | `true` = blocks movement (walls, rocks, closed doors) |
|
|
486
|
+
| `id` | `string \| number` | Stable identifier for game logic and background swapping |
|
|
487
|
+
| `metadata?` | `Record<string, unknown>` | Optional game-specific payload (points, next level, …) |
|
|
488
|
+
|
|
489
|
+
**`Viewport`**
|
|
490
|
+
|
|
491
|
+
| Field | Type | Description |
|
|
492
|
+
|-------|------|-------------|
|
|
493
|
+
| `x` | `number` | First visible column (tile units) |
|
|
494
|
+
| `y` | `number` | First visible row (tile units) |
|
|
495
|
+
| `cols` | `number` | Number of columns to render |
|
|
496
|
+
| `rows` | `number` | Number of rows to render |
|
|
497
|
+
|
|
498
|
+
#### `createTileMap(cols, rows): TileMap`
|
|
499
|
+
|
|
500
|
+
Creates an empty map of `cols × rows` tiles — all cells start `null`. Returns a plain object implementing the `TileMap` interface (factory pattern, consistent with the rest of zx-kit).
|
|
501
|
+
|
|
502
|
+
#### Method reference
|
|
503
|
+
|
|
504
|
+
| Method | Description |
|
|
505
|
+
|--------|-------------|
|
|
506
|
+
| `setTile(x, y, tile)` | Store a shallow copy of `tile`. Out-of-bounds is a silent no-op. |
|
|
507
|
+
| `getTile(x, y)` | Return the tile at `(x, y)`, or `null`. Never throws. |
|
|
508
|
+
| `clearTile(x, y)` | Remove the tile (e.g. collect gem, break wall). Out-of-bounds is a no-op. |
|
|
509
|
+
| `fill(tile)` | Fill every cell with independent shallow copies of `tile`. |
|
|
510
|
+
| `fillRect(x, y, w, h, tile)` | Fill a rectangle; regions outside the map are silently clipped. |
|
|
511
|
+
| `setBackground(tile)` | Register or swap the background tile (see below). |
|
|
512
|
+
| `render(ctx, viewport?)` | Render the map or viewport via `drawSprite`. Empty cells are skipped. |
|
|
513
|
+
| `isSolid(x, y)` | `true` when the tile is solid, or when the position is out-of-bounds. |
|
|
514
|
+
| `findById(id)` | Return `{ x, y, tile }[]` for all tiles with the given `id` — O(1). |
|
|
515
|
+
|
|
516
|
+
#### Smart background swapping (`setBackground`)
|
|
517
|
+
|
|
518
|
+
`setBackground` has two modes depending on whether a background has been registered before:
|
|
519
|
+
|
|
520
|
+
- **First call** — registers the tile as the current background. The map is not modified; call `fill` or `fillRect` first to actually place the background tiles.
|
|
521
|
+
- **Subsequent calls (smart swap)** — replaces every cell whose `id` still matches the previous background with a fresh copy of the new tile. Cells with any other `id` (player, gems, rocks, modified terrain) are left completely untouched.
|
|
522
|
+
|
|
523
|
+
Comparison is by `id` value, so it works correctly after shallow copies.
|
|
524
|
+
|
|
525
|
+
```ts
|
|
526
|
+
map.fill(TILE_GRASS)
|
|
527
|
+
map.setBackground(TILE_GRASS) // register — map unchanged
|
|
528
|
+
|
|
529
|
+
map.setTile(5, 3, TILE_PLAYER) // player placed on grass
|
|
530
|
+
|
|
531
|
+
map.setBackground(TILE_SNOW) // TILE_GRASS → TILE_SNOW everywhere
|
|
532
|
+
// TILE_PLAYER at (5, 3) — untouched
|
|
533
|
+
|
|
534
|
+
map.setBackground(TILE_NIGHT) // TILE_SNOW → TILE_NIGHT
|
|
535
|
+
// TILE_PLAYER — still untouched
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
#### Boulder Dash-style level
|
|
541
|
+
|
|
542
|
+
A complete setup showing map construction, collision detection, item collection, and seasonal background swap — the typical usage pattern for a scrollable ZX Spectrum-style game.
|
|
543
|
+
|
|
544
|
+
```ts
|
|
545
|
+
import { createTileMap, C, CELL } from 'zx-kit'
|
|
546
|
+
import type { Tile, Viewport } from 'zx-kit'
|
|
547
|
+
|
|
548
|
+
// ── Tile definitions ──────────────────────────────────────────────────────────
|
|
549
|
+
|
|
550
|
+
const TILE_DIRT: Tile = {
|
|
551
|
+
sprite: new Uint8Array([0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA]),
|
|
552
|
+
ink: C.YELLOW, paper: C.BLACK,
|
|
553
|
+
solid: false, id: 'dirt',
|
|
554
|
+
}
|
|
555
|
+
const TILE_WALL: Tile = {
|
|
556
|
+
sprite: new Uint8Array([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]),
|
|
557
|
+
ink: C.WHITE, paper: C.BLACK,
|
|
558
|
+
solid: true, id: 'wall',
|
|
559
|
+
}
|
|
560
|
+
const TILE_ROCK: Tile = {
|
|
561
|
+
sprite: new Uint8Array([0x3C, 0x7E, 0xFF, 0xFF, 0xFF, 0xFF, 0x7E, 0x3C]),
|
|
562
|
+
ink: C.B_WHITE, paper: C.BLACK,
|
|
563
|
+
solid: true, id: 'rock',
|
|
564
|
+
}
|
|
565
|
+
const TILE_GEM: Tile = {
|
|
566
|
+
sprite: new Uint8Array([0x18, 0x3C, 0x7E, 0xFF, 0xFF, 0x7E, 0x3C, 0x18]),
|
|
567
|
+
ink: C.B_CYAN, paper: C.BLACK,
|
|
568
|
+
solid: false, id: 'gem',
|
|
569
|
+
metadata: { points: 10 },
|
|
570
|
+
}
|
|
571
|
+
const TILE_EXIT: Tile = {
|
|
572
|
+
sprite: new Uint8Array([0x3C, 0x42, 0x99, 0xA5, 0xA5, 0x99, 0x42, 0x3C]),
|
|
573
|
+
ink: C.B_YELLOW, paper: C.BLACK,
|
|
574
|
+
solid: false, id: 'exit',
|
|
575
|
+
metadata: { nextLevel: 2 },
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// ── Map setup ─────────────────────────────────────────────────────────────────
|
|
579
|
+
|
|
580
|
+
const COLS = 64
|
|
581
|
+
const ROWS = 32
|
|
582
|
+
const map = createTileMap(COLS, ROWS)
|
|
583
|
+
|
|
584
|
+
// Fill with dirt and register it as the seasonal background
|
|
585
|
+
map.fill(TILE_DIRT)
|
|
586
|
+
map.setBackground(TILE_DIRT)
|
|
587
|
+
|
|
588
|
+
// Perimeter walls
|
|
589
|
+
map.fillRect(0, 0, COLS, 1, TILE_WALL) // top
|
|
590
|
+
map.fillRect(0, ROWS - 1, COLS, 1, TILE_WALL) // bottom
|
|
591
|
+
map.fillRect(0, 0, 1, ROWS, TILE_WALL) // left
|
|
592
|
+
map.fillRect(COLS - 1, 0, 1, ROWS, TILE_WALL) // right
|
|
593
|
+
|
|
594
|
+
// Objects
|
|
595
|
+
map.setTile(10, 5, TILE_ROCK)
|
|
596
|
+
map.setTile(20, 14, TILE_GEM)
|
|
597
|
+
map.setTile(35, 10, TILE_GEM)
|
|
598
|
+
map.setTile(60, 15, TILE_EXIT)
|
|
599
|
+
|
|
600
|
+
// ── Seasonal swap ─────────────────────────────────────────────────────────────
|
|
601
|
+
|
|
602
|
+
const TILE_SNOW: Tile = {
|
|
603
|
+
sprite: new Uint8Array([0x00, 0x18, 0x3C, 0xFF, 0x3C, 0x18, 0x00, 0x00]),
|
|
604
|
+
ink: C.B_WHITE, paper: C.BLACK,
|
|
605
|
+
solid: false, id: 'snow',
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Winter: only dirt tiles become snow — walls, rocks, gems, exit are untouched
|
|
609
|
+
map.setBackground(TILE_SNOW)
|
|
610
|
+
|
|
611
|
+
// ── Game loop ─────────────────────────────────────────────────────────────────
|
|
612
|
+
|
|
613
|
+
const SCREEN_COLS = 32
|
|
614
|
+
const SCREEN_ROWS = 24
|
|
615
|
+
let playerX = 2
|
|
616
|
+
let playerY = 2
|
|
617
|
+
let score = 0
|
|
618
|
+
|
|
619
|
+
function gameLoop(ctx: CanvasRenderingContext2D) {
|
|
620
|
+
// Clamp camera so it doesn't scroll past map edges
|
|
621
|
+
const camX = Math.max(0, Math.min(playerX - Math.floor(SCREEN_COLS / 2), COLS - SCREEN_COLS))
|
|
622
|
+
const camY = Math.max(0, Math.min(playerY - Math.floor(SCREEN_ROWS / 2), ROWS - SCREEN_ROWS))
|
|
623
|
+
|
|
624
|
+
map.render(ctx, { x: camX, y: camY, cols: SCREEN_COLS, rows: SCREEN_ROWS })
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// ── Collision & interaction ───────────────────────────────────────────────────
|
|
628
|
+
|
|
629
|
+
function tryMove(dx: number, dy: number) {
|
|
630
|
+
const nx = playerX + dx
|
|
631
|
+
const ny = playerY + dy
|
|
632
|
+
|
|
633
|
+
if (map.isSolid(nx, ny)) return // wall, rock, or map boundary
|
|
634
|
+
|
|
635
|
+
const target = map.getTile(nx, ny)
|
|
636
|
+
if (target?.id === 'gem') {
|
|
637
|
+
score += target.metadata!['points'] as number
|
|
638
|
+
map.clearTile(nx, ny)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
playerX = nx
|
|
642
|
+
playerY = ny
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
// ── Level completion ──────────────────────────────────────────────────────────
|
|
646
|
+
|
|
647
|
+
const exits = map.findById('exit') // O(1) — no map scan
|
|
648
|
+
if (exits.some(e => e.x === playerX && e.y === playerY)) {
|
|
649
|
+
const next = exits[0].tile.metadata!['nextLevel'] as number
|
|
650
|
+
console.log(`Loading level ${next}`)
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Count remaining gems
|
|
654
|
+
const gemsLeft = map.findById('gem').length
|
|
655
|
+
console.log(`${gemsLeft} gems remaining`)
|
|
656
|
+
```
|
|
657
|
+
|
|
658
|
+
---
|
|
659
|
+
|
|
347
660
|
## File structure
|
|
348
661
|
|
|
349
662
|
```
|
|
@@ -361,8 +674,12 @@ zx-kit/
|
|
|
361
674
|
│ │ # getAudioContext, getMasterGain,
|
|
362
675
|
│ │ # getMasterVolume, setMasterVolume,
|
|
363
676
|
│ │ # increaseVolume, decreaseVolume
|
|
364
|
-
│
|
|
365
|
-
│
|
|
677
|
+
│ ├── input.ts # initInput, tickMovement, consumeFlag/Debug/Pause/AnyKey,
|
|
678
|
+
│ │ # isHeld, resetInput, Direction
|
|
679
|
+
│ ├── ui.ts # drawBox, drawFrame, drawPanelTitle,
|
|
680
|
+
│ │ # drawProgressBar, tickUI, renderUI, resetUI,
|
|
681
|
+
│ │ # BorderOptions, DrawProgressBarOptions
|
|
682
|
+
│ └── tilemap.ts # createTileMap, Tile, Viewport, TileMap
|
|
366
683
|
└── dist/ # compiled output (generated by npm run build)
|
|
367
684
|
├── index.js / .d.ts
|
|
368
685
|
└── ...
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA"}
|
package/dist/index.js
CHANGED
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAA;AAC5B,cAAc,WAAW,CAAA;AACzB,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,YAAY,CAAA;AAC1B,cAAc,SAAS,CAAA;AACvB,cAAc,cAAc,CAAA"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single 8×8 tile placed in a `TileMap`.
|
|
3
|
+
* Sprite format is identical to `drawSprite()` — one byte per row, bit 7 = leftmost pixel.
|
|
4
|
+
*/
|
|
5
|
+
export interface Tile {
|
|
6
|
+
/** 8-byte sprite bitmap (one byte per row, bit 7 = leftmost pixel). */
|
|
7
|
+
sprite: Uint8Array;
|
|
8
|
+
/** Foreground colour — a `C.*` palette value. */
|
|
9
|
+
ink: string;
|
|
10
|
+
/** Background colour — a `C.*` palette value. */
|
|
11
|
+
paper: string;
|
|
12
|
+
/** When `true` the tile blocks movement (walls, solid objects, map boundary). */
|
|
13
|
+
solid: boolean;
|
|
14
|
+
/** Stable identifier used for game logic and smart background swapping. */
|
|
15
|
+
id: string | number;
|
|
16
|
+
/** Arbitrary game-specific data attached to this tile instance. */
|
|
17
|
+
metadata?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
/** Defines the visible region of a map to render. All values are in tile units, not pixels. */
|
|
20
|
+
export interface Viewport {
|
|
21
|
+
/** First visible column (tile units). */
|
|
22
|
+
x: number;
|
|
23
|
+
/** First visible row (tile units). */
|
|
24
|
+
y: number;
|
|
25
|
+
/** Number of tile columns to render. */
|
|
26
|
+
cols: number;
|
|
27
|
+
/** Number of tile rows to render. */
|
|
28
|
+
rows: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A scrollable, queryable tile map with O(1) id-based lookup.
|
|
32
|
+
* Obtain an instance via `createTileMap(cols, rows)`.
|
|
33
|
+
*
|
|
34
|
+
* All mutating methods store **shallow copies** of tile objects — the caller
|
|
35
|
+
* may safely reuse or mutate tile definitions without affecting placed tiles.
|
|
36
|
+
*/
|
|
37
|
+
export interface TileMap {
|
|
38
|
+
/** Map width in tile columns. */
|
|
39
|
+
readonly cols: number;
|
|
40
|
+
/** Map height in tile rows. */
|
|
41
|
+
readonly rows: number;
|
|
42
|
+
/**
|
|
43
|
+
* Registers or swaps the background tile.
|
|
44
|
+
*
|
|
45
|
+
* - **First call:** stores `tile` as the current background; the map is left unchanged.
|
|
46
|
+
* - **Subsequent calls (smart swap):** every cell whose `id` still matches the previously
|
|
47
|
+
* registered background is replaced with a shallow copy of the new tile. Cells with
|
|
48
|
+
* any other `id` (player, objects, modified terrain) are not touched.
|
|
49
|
+
*
|
|
50
|
+
* Comparison is by `id` value, so it works correctly after shallow copies.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* map.fill(TILE_GRASS)
|
|
54
|
+
* map.setBackground(TILE_GRASS) // registers background; map unchanged
|
|
55
|
+
* map.setTile(3, 5, TILE_PLAYER)
|
|
56
|
+
* map.setBackground(TILE_SNOW) // grass → snow; TILE_PLAYER untouched
|
|
57
|
+
*/
|
|
58
|
+
setBackground(tile: Tile): void;
|
|
59
|
+
/**
|
|
60
|
+
* Stores a shallow copy of `tile` at `(x, y)`.
|
|
61
|
+
* Out-of-bounds coordinates are silently ignored.
|
|
62
|
+
*/
|
|
63
|
+
setTile(x: number, y: number, tile: Tile): void;
|
|
64
|
+
/**
|
|
65
|
+
* Returns the tile at `(x, y)`, or `null` if the cell is empty or out of bounds.
|
|
66
|
+
* Never throws.
|
|
67
|
+
*/
|
|
68
|
+
getTile(x: number, y: number): Tile | null;
|
|
69
|
+
/**
|
|
70
|
+
* Removes the tile at `(x, y)`, making the cell empty.
|
|
71
|
+
* Out-of-bounds coordinates are silently ignored.
|
|
72
|
+
*/
|
|
73
|
+
clearTile(x: number, y: number): void;
|
|
74
|
+
/** Fills every cell in the map with a separate shallow copy of `tile`. */
|
|
75
|
+
fill(tile: Tile): void;
|
|
76
|
+
/**
|
|
77
|
+
* Fills the rectangle `(x, y) – (x+w, y+h)` with shallow copies of `tile`.
|
|
78
|
+
* Areas outside the map boundary are silently clipped.
|
|
79
|
+
*/
|
|
80
|
+
fillRect(x: number, y: number, w: number, h: number, tile: Tile): void;
|
|
81
|
+
/**
|
|
82
|
+
* Renders the map (or the given `viewport`) to `ctx` via `drawSprite`.
|
|
83
|
+
* Empty cells are skipped. Tiles partially outside the viewport are not drawn.
|
|
84
|
+
* Canvas pixel position: `canvasX = (tileX - viewport.x) * CELL`.
|
|
85
|
+
*
|
|
86
|
+
* When `viewport` is omitted the entire map is rendered starting at canvas origin.
|
|
87
|
+
*/
|
|
88
|
+
render(ctx: CanvasRenderingContext2D, viewport?: Viewport): void;
|
|
89
|
+
/**
|
|
90
|
+
* Returns `true` when the tile at `(x, y)` has `solid === true`.
|
|
91
|
+
* Out-of-bounds coordinates return `true` — the map boundary is implicitly solid.
|
|
92
|
+
*/
|
|
93
|
+
isSolid(x: number, y: number): boolean;
|
|
94
|
+
/**
|
|
95
|
+
* Returns every cell whose tile has the given `id`.
|
|
96
|
+
* Uses an internal index — O(1) lookup, does not iterate the full map.
|
|
97
|
+
* Returns an empty array when no matching tile exists.
|
|
98
|
+
*/
|
|
99
|
+
findById(id: string | number): {
|
|
100
|
+
x: number;
|
|
101
|
+
y: number;
|
|
102
|
+
tile: Tile;
|
|
103
|
+
}[];
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Creates a `TileMap` of `cols × rows` tiles. All cells start empty.
|
|
107
|
+
* Use `fill` / `fillRect` / `setTile` to populate terrain, and `setBackground`
|
|
108
|
+
* to register a background tile for later smart swapping.
|
|
109
|
+
*
|
|
110
|
+
* @param cols - Map width in tiles
|
|
111
|
+
* @param rows - Map height in tiles
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* import { createTileMap, C } from 'zx-kit'
|
|
115
|
+
*
|
|
116
|
+
* const map = createTileMap(64, 32)
|
|
117
|
+
* map.fill(TILE_GRASS)
|
|
118
|
+
* map.setBackground(TILE_GRASS)
|
|
119
|
+
* map.fillRect(0, 0, 64, 1, TILE_WALL) // ceiling
|
|
120
|
+
* map.fillRect(0, 31, 64, 1, TILE_WALL) // floor
|
|
121
|
+
* map.setTile(60, 15, TILE_EXIT)
|
|
122
|
+
*
|
|
123
|
+
* // In game loop — camera follows the player
|
|
124
|
+
* map.render(ctx, { x: camX - 16, y: camY - 12, cols: 32, rows: 24 })
|
|
125
|
+
*
|
|
126
|
+
* // Collision
|
|
127
|
+
* if (!map.isSolid(playerX, playerY + 1)) playerY++
|
|
128
|
+
*
|
|
129
|
+
* // Season swap — player, gems, modified cells are untouched
|
|
130
|
+
* map.setBackground(TILE_SNOW)
|
|
131
|
+
*/
|
|
132
|
+
export declare function createTileMap(cols: number, rows: number): TileMap;
|
|
133
|
+
//# sourceMappingURL=tilemap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tilemap.d.ts","sourceRoot":"","sources":["../src/tilemap.ts"],"names":[],"mappings":"AAGA;;;GAGG;AACH,MAAM,WAAW,IAAI;IACnB,uEAAuE;IACvE,MAAM,EAAE,UAAU,CAAA;IAClB,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAA;IACX,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAA;IACb,iFAAiF;IACjF,KAAK,EAAE,OAAO,CAAA;IACd,2EAA2E;IAC3E,EAAE,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,mEAAmE;IACnE,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,+FAA+F;AAC/F,MAAM,WAAW,QAAQ;IACvB,yCAAyC;IACzC,CAAC,EAAE,MAAM,CAAA;IACT,sCAAsC;IACtC,CAAC,EAAE,MAAM,CAAA;IACT,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAA;IACZ,qCAAqC;IACrC,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;GAMG;AACH,MAAM,WAAW,OAAO;IACtB,iCAAiC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,+BAA+B;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IAErB;;;;;;;;;;;;;;;OAeG;IACH,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;IAE/B;;;OAGG;IACH,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;IAE/C;;;OAGG;IACH,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAA;IAE1C;;;OAGG;IACH,SAAS,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;IAErC,0EAA0E;IAC1E,IAAI,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;IAEtB;;;OAGG;IACH,QAAQ,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;IAEtE;;;;;;OAMG;IACH,MAAM,CAAC,GAAG,EAAE,wBAAwB,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAAA;IAEhE;;;OAGG;IACH,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,OAAO,CAAA;IAEtC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,GAAG;QAAE,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAE,EAAE,CAAA;CACtE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAyIjE"}
|
package/dist/tilemap.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import { CELL } from './palette.js';
|
|
2
|
+
import { drawSprite } from './renderer.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates a `TileMap` of `cols × rows` tiles. All cells start empty.
|
|
5
|
+
* Use `fill` / `fillRect` / `setTile` to populate terrain, and `setBackground`
|
|
6
|
+
* to register a background tile for later smart swapping.
|
|
7
|
+
*
|
|
8
|
+
* @param cols - Map width in tiles
|
|
9
|
+
* @param rows - Map height in tiles
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* import { createTileMap, C } from 'zx-kit'
|
|
13
|
+
*
|
|
14
|
+
* const map = createTileMap(64, 32)
|
|
15
|
+
* map.fill(TILE_GRASS)
|
|
16
|
+
* map.setBackground(TILE_GRASS)
|
|
17
|
+
* map.fillRect(0, 0, 64, 1, TILE_WALL) // ceiling
|
|
18
|
+
* map.fillRect(0, 31, 64, 1, TILE_WALL) // floor
|
|
19
|
+
* map.setTile(60, 15, TILE_EXIT)
|
|
20
|
+
*
|
|
21
|
+
* // In game loop — camera follows the player
|
|
22
|
+
* map.render(ctx, { x: camX - 16, y: camY - 12, cols: 32, rows: 24 })
|
|
23
|
+
*
|
|
24
|
+
* // Collision
|
|
25
|
+
* if (!map.isSolid(playerX, playerY + 1)) playerY++
|
|
26
|
+
*
|
|
27
|
+
* // Season swap — player, gems, modified cells are untouched
|
|
28
|
+
* map.setBackground(TILE_SNOW)
|
|
29
|
+
*/
|
|
30
|
+
export function createTileMap(cols, rows) {
|
|
31
|
+
const cells = new Array(cols * rows).fill(null);
|
|
32
|
+
const idIndex = new Map();
|
|
33
|
+
let currentBackground = null;
|
|
34
|
+
function inBounds(x, y) {
|
|
35
|
+
return x >= 0 && y >= 0 && x < cols && y < rows;
|
|
36
|
+
}
|
|
37
|
+
function cellKey(x, y) {
|
|
38
|
+
return `${x},${y}`;
|
|
39
|
+
}
|
|
40
|
+
function indexRemove(x, y) {
|
|
41
|
+
const existing = cells[y * cols + x];
|
|
42
|
+
if (existing === null)
|
|
43
|
+
return;
|
|
44
|
+
const set = idIndex.get(existing.id);
|
|
45
|
+
if (!set)
|
|
46
|
+
return;
|
|
47
|
+
set.delete(cellKey(x, y));
|
|
48
|
+
if (set.size === 0)
|
|
49
|
+
idIndex.delete(existing.id);
|
|
50
|
+
}
|
|
51
|
+
function indexAdd(x, y, tile) {
|
|
52
|
+
let set = idIndex.get(tile.id);
|
|
53
|
+
if (!set) {
|
|
54
|
+
set = new Set();
|
|
55
|
+
idIndex.set(tile.id, set);
|
|
56
|
+
}
|
|
57
|
+
set.add(cellKey(x, y));
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
get cols() { return cols; },
|
|
61
|
+
get rows() { return rows; },
|
|
62
|
+
setBackground(tile) {
|
|
63
|
+
if (currentBackground === null) {
|
|
64
|
+
currentBackground = { ...tile };
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const oldId = currentBackground.id;
|
|
68
|
+
for (let y = 0; y < rows; y++) {
|
|
69
|
+
for (let x = 0; x < cols; x++) {
|
|
70
|
+
const cell = cells[y * cols + x];
|
|
71
|
+
if (cell !== null && cell.id === oldId) {
|
|
72
|
+
indexRemove(x, y);
|
|
73
|
+
const copy = { ...tile };
|
|
74
|
+
cells[y * cols + x] = copy;
|
|
75
|
+
indexAdd(x, y, copy);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
currentBackground = { ...tile };
|
|
80
|
+
},
|
|
81
|
+
setTile(x, y, tile) {
|
|
82
|
+
if (!inBounds(x, y))
|
|
83
|
+
return;
|
|
84
|
+
indexRemove(x, y);
|
|
85
|
+
const copy = { ...tile };
|
|
86
|
+
cells[y * cols + x] = copy;
|
|
87
|
+
indexAdd(x, y, copy);
|
|
88
|
+
},
|
|
89
|
+
getTile(x, y) {
|
|
90
|
+
if (!inBounds(x, y))
|
|
91
|
+
return null;
|
|
92
|
+
return cells[y * cols + x];
|
|
93
|
+
},
|
|
94
|
+
clearTile(x, y) {
|
|
95
|
+
if (!inBounds(x, y))
|
|
96
|
+
return;
|
|
97
|
+
indexRemove(x, y);
|
|
98
|
+
cells[y * cols + x] = null;
|
|
99
|
+
},
|
|
100
|
+
fill(tile) {
|
|
101
|
+
idIndex.clear();
|
|
102
|
+
for (let y = 0; y < rows; y++) {
|
|
103
|
+
for (let x = 0; x < cols; x++) {
|
|
104
|
+
const copy = { ...tile };
|
|
105
|
+
cells[y * cols + x] = copy;
|
|
106
|
+
indexAdd(x, y, copy);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
fillRect(x, y, w, h, tile) {
|
|
111
|
+
const x0 = Math.max(0, x);
|
|
112
|
+
const y0 = Math.max(0, y);
|
|
113
|
+
const x1 = Math.min(cols, x + w);
|
|
114
|
+
const y1 = Math.min(rows, y + h);
|
|
115
|
+
for (let ty = y0; ty < y1; ty++) {
|
|
116
|
+
for (let tx = x0; tx < x1; tx++) {
|
|
117
|
+
indexRemove(tx, ty);
|
|
118
|
+
const copy = { ...tile };
|
|
119
|
+
cells[ty * cols + tx] = copy;
|
|
120
|
+
indexAdd(tx, ty, copy);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
render(ctx, viewport) {
|
|
125
|
+
const vx = viewport?.x ?? 0;
|
|
126
|
+
const vy = viewport?.y ?? 0;
|
|
127
|
+
const vcols = viewport?.cols ?? cols;
|
|
128
|
+
const vrows = viewport?.rows ?? rows;
|
|
129
|
+
for (let row = 0; row < vrows; row++) {
|
|
130
|
+
const ty = vy + row;
|
|
131
|
+
if (ty < 0 || ty >= rows)
|
|
132
|
+
continue;
|
|
133
|
+
for (let col = 0; col < vcols; col++) {
|
|
134
|
+
const tx = vx + col;
|
|
135
|
+
if (tx < 0 || tx >= cols)
|
|
136
|
+
continue;
|
|
137
|
+
const tile = cells[ty * cols + tx];
|
|
138
|
+
if (tile === null)
|
|
139
|
+
continue;
|
|
140
|
+
drawSprite(ctx, tile.sprite, col * CELL, row * CELL, tile.ink, tile.paper);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
isSolid(x, y) {
|
|
145
|
+
if (!inBounds(x, y))
|
|
146
|
+
return true;
|
|
147
|
+
return cells[y * cols + x]?.solid ?? false;
|
|
148
|
+
},
|
|
149
|
+
findById(id) {
|
|
150
|
+
const set = idIndex.get(id);
|
|
151
|
+
if (!set)
|
|
152
|
+
return [];
|
|
153
|
+
const result = [];
|
|
154
|
+
for (const k of set) {
|
|
155
|
+
const comma = k.indexOf(',');
|
|
156
|
+
const tx = parseInt(k.slice(0, comma), 10);
|
|
157
|
+
const ty = parseInt(k.slice(comma + 1), 10);
|
|
158
|
+
const tile = cells[ty * cols + tx];
|
|
159
|
+
if (tile !== null)
|
|
160
|
+
result.push({ x: tx, y: ty, tile });
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
//# sourceMappingURL=tilemap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tilemap.js","sourceRoot":"","sources":["../src/tilemap.ts"],"names":[],"mappings":"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"}
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import type { SpectrumColor } from './palette.js';
|
|
2
|
+
/**
|
|
3
|
+
* Border configuration for UI primitives.
|
|
4
|
+
* When the `border` object is present on a parent option, a border is rendered.
|
|
5
|
+
* `enabled` defaults to `true` — set it to `false` to suppress the border
|
|
6
|
+
* while keeping the object in place for future toggling.
|
|
7
|
+
*/
|
|
8
|
+
export type BorderOptions = {
|
|
9
|
+
/** Enable/disable border rendering. Default: `true` */
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
/** Border thickness in pixels. Default: `1` */
|
|
12
|
+
thickness?: number;
|
|
13
|
+
/**
|
|
14
|
+
* Border color. Default: same as the primary `ink` / `color` of the parent function.
|
|
15
|
+
* Accepts any {@link SpectrumColor} value.
|
|
16
|
+
*/
|
|
17
|
+
color?: SpectrumColor;
|
|
18
|
+
/**
|
|
19
|
+
* Border style.
|
|
20
|
+
* - `'solid'` — continuous lines (default)
|
|
21
|
+
* - `'dashed'` — alternating 2 px on / 2 px off segments
|
|
22
|
+
*/
|
|
23
|
+
style?: 'solid' | 'dashed';
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Options for {@link drawProgressBar}.
|
|
27
|
+
*/
|
|
28
|
+
export type DrawProgressBarOptions = {
|
|
29
|
+
/**
|
|
30
|
+
* Stable key for managed-visibility mode.
|
|
31
|
+
* The bar's last-drawn state is registered under this key so `renderUI` can
|
|
32
|
+
* redraw it on subsequent frames. Defaults to `"${x},${y}"` when omitted.
|
|
33
|
+
*/
|
|
34
|
+
id?: string;
|
|
35
|
+
/** Left edge of the bar in game pixels. */
|
|
36
|
+
x: number;
|
|
37
|
+
/** Top edge of the bar in game pixels. */
|
|
38
|
+
y: number;
|
|
39
|
+
/**
|
|
40
|
+
* Total width of the bar in game pixels.
|
|
41
|
+
* Should be a multiple of `CELL` (8) — any remainder is left blank.
|
|
42
|
+
*/
|
|
43
|
+
width: number;
|
|
44
|
+
/** Current value to represent. */
|
|
45
|
+
value: number;
|
|
46
|
+
/** Minimum value (left edge = empty). Default: `0` */
|
|
47
|
+
min?: number;
|
|
48
|
+
/** Maximum value (right edge = full). Default: `1` */
|
|
49
|
+
max?: number;
|
|
50
|
+
/** Filled-block foreground color. Default: `C.B_WHITE` */
|
|
51
|
+
ink?: SpectrumColor;
|
|
52
|
+
/** Empty-block background color. Default: `C.BLACK` */
|
|
53
|
+
paper?: SpectrumColor;
|
|
54
|
+
/** Optional border around the entire bar. */
|
|
55
|
+
border?: BorderOptions;
|
|
56
|
+
/**
|
|
57
|
+
* How long the bar stays visible after the last `drawProgressBar` call (ms).
|
|
58
|
+
* - `> 0` — auto-hides after this many milliseconds.
|
|
59
|
+
* - `0` — permanent; only cleared by `resetUI()`.
|
|
60
|
+
* Default: `500`
|
|
61
|
+
*/
|
|
62
|
+
visibilityLength?: number;
|
|
63
|
+
};
|
|
64
|
+
/**
|
|
65
|
+
* Draws a filled rectangular box using ZX-style rendering.
|
|
66
|
+
*
|
|
67
|
+
* Renders a solid background (`paper`) and an optional border.
|
|
68
|
+
* Coordinates are in game pixels (already scaled by `setupCanvas`).
|
|
69
|
+
* Does NOT render text — use `drawPanelTitle` for labelled panels.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* drawBox(ctx, {
|
|
73
|
+
* x: 8, y: 8, width: 112, height: 40,
|
|
74
|
+
* paper: C.BLACK, ink: C.B_WHITE,
|
|
75
|
+
* border: { style: 'solid', thickness: 1 },
|
|
76
|
+
* })
|
|
77
|
+
*/
|
|
78
|
+
export declare function drawBox(ctx: CanvasRenderingContext2D, options: {
|
|
79
|
+
/** Left edge in game pixels */
|
|
80
|
+
x: number;
|
|
81
|
+
/** Top edge in game pixels */
|
|
82
|
+
y: number;
|
|
83
|
+
/** Width in game pixels */
|
|
84
|
+
width: number;
|
|
85
|
+
/** Height in game pixels */
|
|
86
|
+
height: number;
|
|
87
|
+
/** Background fill color */
|
|
88
|
+
paper: SpectrumColor;
|
|
89
|
+
/** Foreground color — used as default border color when `border.color` is omitted */
|
|
90
|
+
ink?: SpectrumColor;
|
|
91
|
+
/** Optional border */
|
|
92
|
+
border?: BorderOptions;
|
|
93
|
+
}): void;
|
|
94
|
+
/**
|
|
95
|
+
* Draws a rectangular frame — border only, no background fill.
|
|
96
|
+
*
|
|
97
|
+
* Unlike `drawBox`, the inside area is left untouched.
|
|
98
|
+
* Useful for selection highlights, overlays, and ZX-style panels
|
|
99
|
+
* where the background is already painted.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* drawFrame(ctx, { x: 0, y: 0, width: 256, height: 176, color: C.B_CYAN })
|
|
103
|
+
* drawFrame(ctx, { x: 16, y: 16, width: 64, height: 32, color: C.B_RED,
|
|
104
|
+
* border: { style: 'dashed', thickness: 1 } })
|
|
105
|
+
*/
|
|
106
|
+
export declare function drawFrame(ctx: CanvasRenderingContext2D, options: {
|
|
107
|
+
/** Left edge in game pixels */
|
|
108
|
+
x: number;
|
|
109
|
+
/** Top edge in game pixels */
|
|
110
|
+
y: number;
|
|
111
|
+
/** Width in game pixels */
|
|
112
|
+
width: number;
|
|
113
|
+
/** Height in game pixels */
|
|
114
|
+
height: number;
|
|
115
|
+
/** Frame color */
|
|
116
|
+
color: SpectrumColor;
|
|
117
|
+
/** Border configuration — `color` here overrides the top-level `color` */
|
|
118
|
+
border?: BorderOptions;
|
|
119
|
+
}): void;
|
|
120
|
+
/**
|
|
121
|
+
* Draws a ZX-style panel title — a text strip with an optional background fill.
|
|
122
|
+
*
|
|
123
|
+
* Does NOT draw the surrounding container (`drawBox` / `drawFrame` are separate).
|
|
124
|
+
* The strip height is always `CELL + padding * 2` pixels.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* // Labelled panel:
|
|
128
|
+
* drawBox(ctx, { x: 8, y: 24, width: 128, height: 56, paper: C.BLACK })
|
|
129
|
+
* drawPanelTitle(ctx, { text: 'OPTIONS', x: 8, y: 24, ink: C.B_YELLOW,
|
|
130
|
+
* paper: C.BLACK, centered: true, width: 128 })
|
|
131
|
+
*
|
|
132
|
+
* // No background — ink only:
|
|
133
|
+
* drawPanelTitle(ctx, { text: 'DEBUG', x: 0, y: 0, ink: C.B_CYAN })
|
|
134
|
+
*/
|
|
135
|
+
export declare function drawPanelTitle(ctx: CanvasRenderingContext2D, options: {
|
|
136
|
+
/** Title text (ASCII — rendered via ZX ROM font) */
|
|
137
|
+
text: string;
|
|
138
|
+
/** Left edge in game pixels */
|
|
139
|
+
x: number;
|
|
140
|
+
/** Top edge of the title strip in game pixels */
|
|
141
|
+
y: number;
|
|
142
|
+
/** Text color */
|
|
143
|
+
ink: SpectrumColor;
|
|
144
|
+
/** Background behind the title strip. Omit for transparent (ink-only rendering) */
|
|
145
|
+
paper?: SpectrumColor;
|
|
146
|
+
/** Padding inside the strip in pixels. Default: `2` */
|
|
147
|
+
padding?: number;
|
|
148
|
+
/**
|
|
149
|
+
* Center the title horizontally within `width`.
|
|
150
|
+
* Requires `width` to be provided; ignored otherwise.
|
|
151
|
+
*/
|
|
152
|
+
centered?: boolean;
|
|
153
|
+
/** Width used for centering (game pixels). Required when `centered: true`. */
|
|
154
|
+
width?: number;
|
|
155
|
+
}): void;
|
|
156
|
+
/**
|
|
157
|
+
* Draws a ZX-style progress / value bar and registers it for managed redraws.
|
|
158
|
+
*
|
|
159
|
+
* The bar is drawn **immediately** on every call. Additionally, the options are
|
|
160
|
+
* stored internally so `renderUI` can repaint the bar each frame for
|
|
161
|
+
* `visibilityLength` ms without requiring the caller to supply the state again.
|
|
162
|
+
*
|
|
163
|
+
* Calling `drawProgressBar` again with the same `id` **resets the timer** — use
|
|
164
|
+
* this to refresh the display when the value changes.
|
|
165
|
+
*
|
|
166
|
+
* **Typical pattern:**
|
|
167
|
+
* ```ts
|
|
168
|
+
* // On value change only:
|
|
169
|
+
* if (consumeVolUp()) {
|
|
170
|
+
* increaseVolume()
|
|
171
|
+
* drawProgressBar(ctx, { id: 'volume', x: 88, y: 88, width: 80,
|
|
172
|
+
* value: getMasterVolume(), visibilityLength: 1500 })
|
|
173
|
+
* }
|
|
174
|
+
*
|
|
175
|
+
* // Every frame (after game world render):
|
|
176
|
+
* tickUI(dt)
|
|
177
|
+
* renderUI(ctx)
|
|
178
|
+
* ```
|
|
179
|
+
*
|
|
180
|
+
* @example
|
|
181
|
+
* // Permanent HUD health bar:
|
|
182
|
+
* drawProgressBar(ctx, {
|
|
183
|
+
* id: 'health', x: 0, y: 184, width: 40, value: lives, min: 0, max: 3,
|
|
184
|
+
* ink: C.B_GREEN, paper: C.BLACK, visibilityLength: 0,
|
|
185
|
+
* })
|
|
186
|
+
*
|
|
187
|
+
* // Temporary volume indicator (auto-hides after 1.5 s):
|
|
188
|
+
* drawProgressBar(ctx, {
|
|
189
|
+
* id: 'volume', x: 88, y: 88, width: 80, value: getMasterVolume(),
|
|
190
|
+
* ink: C.B_GREEN, paper: C.BLACK,
|
|
191
|
+
* border: { style: 'solid' },
|
|
192
|
+
* visibilityLength: 1500,
|
|
193
|
+
* })
|
|
194
|
+
*/
|
|
195
|
+
export declare function drawProgressBar(ctx: CanvasRenderingContext2D, options: DrawProgressBarOptions): void;
|
|
196
|
+
/**
|
|
197
|
+
* Advances all managed bar timers by `dtMs` milliseconds.
|
|
198
|
+
* Bars whose timer reaches zero are removed and will no longer appear in `renderUI`.
|
|
199
|
+
* Call once per frame, typically just before `renderUI`.
|
|
200
|
+
*
|
|
201
|
+
* @param dtMs - Frame delta in milliseconds
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* // End of game loop:
|
|
205
|
+
* tickUI(dt)
|
|
206
|
+
* renderUI(ctx)
|
|
207
|
+
*/
|
|
208
|
+
export declare function tickUI(dtMs: number): void;
|
|
209
|
+
/**
|
|
210
|
+
* Redraws all currently visible managed bars (timer > 0 or permanent).
|
|
211
|
+
* Call every frame **after** the game world has been rendered so bars appear on top.
|
|
212
|
+
*
|
|
213
|
+
* @param ctx - Canvas 2D rendering context
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* renderFrame(ctx, state)
|
|
217
|
+
* tickUI(dt)
|
|
218
|
+
* renderUI(ctx)
|
|
219
|
+
*/
|
|
220
|
+
export declare function renderUI(ctx: CanvasRenderingContext2D): void;
|
|
221
|
+
/**
|
|
222
|
+
* Clears all managed UI state — removes every active bar timer and its stored options.
|
|
223
|
+
* Call alongside `resetInput()` when transitioning between major game phases.
|
|
224
|
+
*
|
|
225
|
+
* @example
|
|
226
|
+
* resetInput()
|
|
227
|
+
* resetUI()
|
|
228
|
+
* appPhase = 'intro'
|
|
229
|
+
*/
|
|
230
|
+
export declare function resetUI(): void;
|
|
231
|
+
//# sourceMappingURL=ui.d.ts.map
|
package/dist/ui.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAKjD;;;;;GAKG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,uDAAuD;IACvD,OAAO,CAAC,EAAE,OAAO,CAAA;IAEjB,+CAA+C;IAC/C,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;;OAGG;IACH,KAAK,CAAC,EAAE,aAAa,CAAA;IAErB;;;;OAIG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAA;CAC3B,CAAA;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG;IACnC;;;;OAIG;IACH,EAAE,CAAC,EAAE,MAAM,CAAA;IAEX,2CAA2C;IAC3C,CAAC,EAAE,MAAM,CAAA;IAET,0CAA0C;IAC1C,CAAC,EAAE,MAAM,CAAA;IAET;;;OAGG;IACH,KAAK,EAAE,MAAM,CAAA;IAEb,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAA;IAEb,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAA;IAEZ,sDAAsD;IACtD,GAAG,CAAC,EAAE,MAAM,CAAA;IAEZ,0DAA0D;IAC1D,GAAG,CAAC,EAAE,aAAa,CAAA;IAEnB,uDAAuD;IACvD,KAAK,CAAC,EAAE,aAAa,CAAA;IAErB,6CAA6C;IAC7C,MAAM,CAAC,EAAE,aAAa,CAAA;IAEtB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,CAAA;AAkED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,OAAO,CACrB,GAAG,EAAE,wBAAwB,EAC7B,OAAO,EAAE;IACP,+BAA+B;IAC/B,CAAC,EAAE,MAAM,CAAA;IACT,8BAA8B;IAC9B,CAAC,EAAE,MAAM,CAAA;IACT,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,4BAA4B;IAC5B,KAAK,EAAE,aAAa,CAAA;IACpB,qFAAqF;IACrF,GAAG,CAAC,EAAE,aAAa,CAAA;IACnB,sBAAsB;IACtB,MAAM,CAAC,EAAE,aAAa,CAAA;CACvB,GACA,IAAI,CAKN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,wBAAwB,EAC7B,OAAO,EAAE;IACP,+BAA+B;IAC/B,CAAC,EAAE,MAAM,CAAA;IACT,8BAA8B;IAC9B,CAAC,EAAE,MAAM,CAAA;IACT,2BAA2B;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,4BAA4B;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,kBAAkB;IAClB,KAAK,EAAE,aAAa,CAAA;IACpB,0EAA0E;IAC1E,MAAM,CAAC,EAAE,aAAa,CAAA;CACvB,GACA,IAAI,CAGN;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,wBAAwB,EAC7B,OAAO,EAAE;IACP,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAA;IACZ,+BAA+B;IAC/B,CAAC,EAAE,MAAM,CAAA;IACT,iDAAiD;IACjD,CAAC,EAAE,MAAM,CAAA;IACT,iBAAiB;IACjB,GAAG,EAAE,aAAa,CAAA;IAClB,mFAAmF;IACnF,KAAK,CAAC,EAAE,aAAa,CAAA;IACrB,uDAAuD;IACvD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAA;CACf,GACA,IAAI,CAeN;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,wBAAwB,EAC7B,OAAO,EAAE,sBAAsB,GAC9B,IAAI,CASN;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAMzC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,wBAAwB,GAAG,IAAI,CAI5D;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,IAAI,IAAI,CAE9B"}
|
package/dist/ui.js
ADDED
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { C, CELL } from './palette.js';
|
|
2
|
+
import { drawChar, drawText } from './renderer.js';
|
|
3
|
+
const _bars = new Map();
|
|
4
|
+
// ─── Private helpers ──────────────────────────────────────────────────────────
|
|
5
|
+
function _drawBorder(ctx, x, y, width, height, border, fallbackColor) {
|
|
6
|
+
if (border.enabled === false)
|
|
7
|
+
return;
|
|
8
|
+
const color = border.color ?? fallbackColor;
|
|
9
|
+
const t = border.thickness ?? 1;
|
|
10
|
+
ctx.fillStyle = color;
|
|
11
|
+
if (border.style !== 'dashed') {
|
|
12
|
+
ctx.fillRect(x, y, width, t); // top
|
|
13
|
+
ctx.fillRect(x, y + height - t, width, t); // bottom
|
|
14
|
+
ctx.fillRect(x, y, t, height); // left
|
|
15
|
+
ctx.fillRect(x + width - t, y, t, height); // right
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
const d = 2; // dash length in pixels
|
|
19
|
+
for (let i = 0; i < width; i += d * 2) {
|
|
20
|
+
ctx.fillRect(x + i, y, Math.min(d, width - i), t); // top
|
|
21
|
+
ctx.fillRect(x + i, y + height - t, Math.min(d, width - i), t); // bottom
|
|
22
|
+
}
|
|
23
|
+
for (let i = 0; i < height; i += d * 2) {
|
|
24
|
+
ctx.fillRect(x, y + i, t, Math.min(d, height - i)); // left
|
|
25
|
+
ctx.fillRect(x + width - t, y + i, t, Math.min(d, height - i)); // right
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function _renderBar(ctx, o) {
|
|
30
|
+
const min = o.min ?? 0;
|
|
31
|
+
const max = o.max ?? 1;
|
|
32
|
+
const ink = o.ink ?? C.B_WHITE;
|
|
33
|
+
const paper = o.paper ?? C.BLACK;
|
|
34
|
+
const chars = Math.floor(o.width / CELL);
|
|
35
|
+
const ratio = Math.max(0, Math.min(1, (o.value - min) / (max - min)));
|
|
36
|
+
const filled = Math.round(ratio * chars);
|
|
37
|
+
ctx.fillStyle = paper;
|
|
38
|
+
ctx.fillRect(o.x, o.y, chars * CELL, CELL);
|
|
39
|
+
for (let i = 0; i < filled; i++) {
|
|
40
|
+
drawChar(ctx, 127, o.x + i * CELL, o.y, ink, paper); // 127 = solid block █
|
|
41
|
+
}
|
|
42
|
+
if (o.border)
|
|
43
|
+
_drawBorder(ctx, o.x, o.y, chars * CELL, CELL, o.border, ink);
|
|
44
|
+
}
|
|
45
|
+
// ─── Public API — stateless primitives ───────────────────────────────────────
|
|
46
|
+
/**
|
|
47
|
+
* Draws a filled rectangular box using ZX-style rendering.
|
|
48
|
+
*
|
|
49
|
+
* Renders a solid background (`paper`) and an optional border.
|
|
50
|
+
* Coordinates are in game pixels (already scaled by `setupCanvas`).
|
|
51
|
+
* Does NOT render text — use `drawPanelTitle` for labelled panels.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* drawBox(ctx, {
|
|
55
|
+
* x: 8, y: 8, width: 112, height: 40,
|
|
56
|
+
* paper: C.BLACK, ink: C.B_WHITE,
|
|
57
|
+
* border: { style: 'solid', thickness: 1 },
|
|
58
|
+
* })
|
|
59
|
+
*/
|
|
60
|
+
export function drawBox(ctx, options) {
|
|
61
|
+
const { x, y, width, height, paper, ink, border } = options;
|
|
62
|
+
ctx.fillStyle = paper;
|
|
63
|
+
ctx.fillRect(x, y, width, height);
|
|
64
|
+
if (border)
|
|
65
|
+
_drawBorder(ctx, x, y, width, height, border, ink ?? paper);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Draws a rectangular frame — border only, no background fill.
|
|
69
|
+
*
|
|
70
|
+
* Unlike `drawBox`, the inside area is left untouched.
|
|
71
|
+
* Useful for selection highlights, overlays, and ZX-style panels
|
|
72
|
+
* where the background is already painted.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* drawFrame(ctx, { x: 0, y: 0, width: 256, height: 176, color: C.B_CYAN })
|
|
76
|
+
* drawFrame(ctx, { x: 16, y: 16, width: 64, height: 32, color: C.B_RED,
|
|
77
|
+
* border: { style: 'dashed', thickness: 1 } })
|
|
78
|
+
*/
|
|
79
|
+
export function drawFrame(ctx, options) {
|
|
80
|
+
const { x, y, width, height, color, border = {} } = options;
|
|
81
|
+
_drawBorder(ctx, x, y, width, height, { ...border, color }, color);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Draws a ZX-style panel title — a text strip with an optional background fill.
|
|
85
|
+
*
|
|
86
|
+
* Does NOT draw the surrounding container (`drawBox` / `drawFrame` are separate).
|
|
87
|
+
* The strip height is always `CELL + padding * 2` pixels.
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // Labelled panel:
|
|
91
|
+
* drawBox(ctx, { x: 8, y: 24, width: 128, height: 56, paper: C.BLACK })
|
|
92
|
+
* drawPanelTitle(ctx, { text: 'OPTIONS', x: 8, y: 24, ink: C.B_YELLOW,
|
|
93
|
+
* paper: C.BLACK, centered: true, width: 128 })
|
|
94
|
+
*
|
|
95
|
+
* // No background — ink only:
|
|
96
|
+
* drawPanelTitle(ctx, { text: 'DEBUG', x: 0, y: 0, ink: C.B_CYAN })
|
|
97
|
+
*/
|
|
98
|
+
export function drawPanelTitle(ctx, options) {
|
|
99
|
+
const { text, x, y, ink, paper, padding = 2, centered, width } = options;
|
|
100
|
+
const textW = text.length * CELL;
|
|
101
|
+
const stripW = centered && width !== undefined ? width : textW + padding * 2;
|
|
102
|
+
if (paper !== undefined) {
|
|
103
|
+
ctx.fillStyle = paper;
|
|
104
|
+
ctx.fillRect(x, y, stripW, CELL + padding * 2);
|
|
105
|
+
}
|
|
106
|
+
const tx = centered && width !== undefined
|
|
107
|
+
? x + Math.floor((width - textW) / 2)
|
|
108
|
+
: x + padding;
|
|
109
|
+
drawText(ctx, text, tx, y + padding, ink, paper);
|
|
110
|
+
}
|
|
111
|
+
// ─── Public API — stateful widget ────────────────────────────────────────────
|
|
112
|
+
/**
|
|
113
|
+
* Draws a ZX-style progress / value bar and registers it for managed redraws.
|
|
114
|
+
*
|
|
115
|
+
* The bar is drawn **immediately** on every call. Additionally, the options are
|
|
116
|
+
* stored internally so `renderUI` can repaint the bar each frame for
|
|
117
|
+
* `visibilityLength` ms without requiring the caller to supply the state again.
|
|
118
|
+
*
|
|
119
|
+
* Calling `drawProgressBar` again with the same `id` **resets the timer** — use
|
|
120
|
+
* this to refresh the display when the value changes.
|
|
121
|
+
*
|
|
122
|
+
* **Typical pattern:**
|
|
123
|
+
* ```ts
|
|
124
|
+
* // On value change only:
|
|
125
|
+
* if (consumeVolUp()) {
|
|
126
|
+
* increaseVolume()
|
|
127
|
+
* drawProgressBar(ctx, { id: 'volume', x: 88, y: 88, width: 80,
|
|
128
|
+
* value: getMasterVolume(), visibilityLength: 1500 })
|
|
129
|
+
* }
|
|
130
|
+
*
|
|
131
|
+
* // Every frame (after game world render):
|
|
132
|
+
* tickUI(dt)
|
|
133
|
+
* renderUI(ctx)
|
|
134
|
+
* ```
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* // Permanent HUD health bar:
|
|
138
|
+
* drawProgressBar(ctx, {
|
|
139
|
+
* id: 'health', x: 0, y: 184, width: 40, value: lives, min: 0, max: 3,
|
|
140
|
+
* ink: C.B_GREEN, paper: C.BLACK, visibilityLength: 0,
|
|
141
|
+
* })
|
|
142
|
+
*
|
|
143
|
+
* // Temporary volume indicator (auto-hides after 1.5 s):
|
|
144
|
+
* drawProgressBar(ctx, {
|
|
145
|
+
* id: 'volume', x: 88, y: 88, width: 80, value: getMasterVolume(),
|
|
146
|
+
* ink: C.B_GREEN, paper: C.BLACK,
|
|
147
|
+
* border: { style: 'solid' },
|
|
148
|
+
* visibilityLength: 1500,
|
|
149
|
+
* })
|
|
150
|
+
*/
|
|
151
|
+
export function drawProgressBar(ctx, options) {
|
|
152
|
+
_renderBar(ctx, options);
|
|
153
|
+
const id = options.id ?? `${options.x},${options.y}`;
|
|
154
|
+
const visibilityLength = options.visibilityLength ?? 500;
|
|
155
|
+
_bars.set(id, {
|
|
156
|
+
options,
|
|
157
|
+
timer: visibilityLength,
|
|
158
|
+
permanent: visibilityLength === 0,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Advances all managed bar timers by `dtMs` milliseconds.
|
|
163
|
+
* Bars whose timer reaches zero are removed and will no longer appear in `renderUI`.
|
|
164
|
+
* Call once per frame, typically just before `renderUI`.
|
|
165
|
+
*
|
|
166
|
+
* @param dtMs - Frame delta in milliseconds
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* // End of game loop:
|
|
170
|
+
* tickUI(dt)
|
|
171
|
+
* renderUI(ctx)
|
|
172
|
+
*/
|
|
173
|
+
export function tickUI(dtMs) {
|
|
174
|
+
for (const [id, bar] of _bars) {
|
|
175
|
+
if (bar.permanent)
|
|
176
|
+
continue;
|
|
177
|
+
bar.timer -= dtMs;
|
|
178
|
+
if (bar.timer <= 0)
|
|
179
|
+
_bars.delete(id);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Redraws all currently visible managed bars (timer > 0 or permanent).
|
|
184
|
+
* Call every frame **after** the game world has been rendered so bars appear on top.
|
|
185
|
+
*
|
|
186
|
+
* @param ctx - Canvas 2D rendering context
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* renderFrame(ctx, state)
|
|
190
|
+
* tickUI(dt)
|
|
191
|
+
* renderUI(ctx)
|
|
192
|
+
*/
|
|
193
|
+
export function renderUI(ctx) {
|
|
194
|
+
for (const bar of _bars.values()) {
|
|
195
|
+
_renderBar(ctx, bar.options);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Clears all managed UI state — removes every active bar timer and its stored options.
|
|
200
|
+
* Call alongside `resetInput()` when transitioning between major game phases.
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* resetInput()
|
|
204
|
+
* resetUI()
|
|
205
|
+
* appPhase = 'intro'
|
|
206
|
+
*/
|
|
207
|
+
export function resetUI() {
|
|
208
|
+
_bars.clear();
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=ui.js.map
|
package/dist/ui.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.js","sourceRoot":"","sources":["../src/ui.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zx-kit",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Reusable ZX Spectrum primitives: font, palette, renderer, audio, input",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Reusable ZX Spectrum primitives: font, palette, renderer, audio, input, ui, tilemap",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
],
|
|
19
19
|
"scripts": {
|
|
20
20
|
"build": "tsc",
|
|
21
|
-
"prepublishOnly": "npm run build"
|
|
21
|
+
"prepublishOnly": "npm run build",
|
|
22
|
+
"test": "vitest run"
|
|
22
23
|
},
|
|
23
24
|
"keywords": [
|
|
24
25
|
"zx-spectrum",
|
|
@@ -28,6 +29,8 @@
|
|
|
28
29
|
],
|
|
29
30
|
"license": "MIT",
|
|
30
31
|
"devDependencies": {
|
|
31
|
-
"
|
|
32
|
+
"jsdom": "^29.1.1",
|
|
33
|
+
"typescript": "^6.0.3",
|
|
34
|
+
"vitest": "^4.1.5"
|
|
32
35
|
}
|
|
33
36
|
}
|