sci2-ts 0.16.0 → 0.18.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/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to this project. Versions follow [Semantic Versioning](https
4
4
  and the entries are generated from commit messages
5
5
  ([Conventional Commits](https://www.conventionalcommits.org/)) when a release is made.
6
6
 
7
+ ## [0.18.0](https://github.com/rlueder/sci-ts/compare/v0.17.0...v0.18.0) (2026-10-03)
8
+
9
+ ### Added
10
+
11
+ * **lib:** painted game menu with lit rows, arrow keys, Escape and unavailable choices ([#21](https://github.com/rlueder/sci-ts/issues/21)) ([17dfd28](https://github.com/rlueder/sci-ts/commit/17dfd28c0f16379714a058869b7b698dc5338344))
12
+
13
+ ## [0.17.0](https://github.com/rlueder/sci-ts/compare/v0.16.0...v0.17.0) (2026-10-02)
14
+
15
+ ### Added
16
+
17
+ * **lib:** a sliding icon bar, and portraits at the bottom of the screen ([#20](https://github.com/rlueder/sci-ts/issues/20)) ([31e82ca](https://github.com/rlueder/sci-ts/commit/31e82ca33478c163b80b4f4fb16c5718b3c36bae))
18
+
7
19
  ## [0.16.0](https://github.com/rlueder/sci-ts/compare/v0.15.1...v0.16.0) (2026-10-02)
8
20
 
9
21
  ### Added
package/lib/991.sc CHANGED
@@ -31,10 +31,16 @@
31
31
  top -1
32
32
  box 0
33
33
  icons 0
34
- height 0)
34
+ height 0
35
+ slides TRUE ; slides down from above the screen, and back up when it closes
36
+ shown 0 ; how far down it is: 0 all the way, -height out of sight
37
+ leaving 0) ; sliding back up
35
38
 
36
39
  (method (show &tmp x y step)
37
- (if box (return))
40
+ ;; Opened again while it slides away: it comes back down.
41
+ (if box
42
+ (if leaving (= leaving 0) (= dialog self))
43
+ (return))
38
44
  (if (!= skin -1)
39
45
  (= box ((View new:) view: skin loop: 0 cel: 0 x: 0 y: 0 yourself:))
40
46
  (box init:)
@@ -66,7 +72,37 @@
66
72
  (self addIcon: (+ x (* 4 step)) y -1 V_ITEM)
67
73
  (self addIcon: (+ x (* 5 step)) y 4 ICON_INVENTORY)
68
74
  (self addIcon: (+ x (* 6 step)) y 5 ICON_MENU)
69
- (= dialog self))
75
+ (= dialog self)
76
+ (if slides
77
+ (= shown 0)
78
+ (self moveBy: (- height))
79
+ (= shown (- height))))
80
+
81
+ ;; Everything in the bar up or down by dy.
82
+ (method (moveBy dy &tmp node icon)
83
+ (box y: (+ (box y?) dy))
84
+ (UpdateScreenItem box)
85
+ (for ((= node (FirstNode (icons elements?)))) node ((= node (NextNode node)))
86
+ (= icon (NodeValue node))
87
+ (icon y: (+ (icon y?) dy))
88
+ (UpdateScreenItem icon)))
89
+
90
+ ;; A cycle of sliding (the User calls it while the bar is up): down while it opens, up
91
+ ;; while it closes, quickly at first and easing to a stop.
92
+ (method (step &tmp d)
93
+ (cond
94
+ ((not box) 0)
95
+ (leaving
96
+ (= d (+ 4 (/ (+ height shown) 3)))
97
+ (if (> d (+ height shown)) (= d (+ height shown)))
98
+ (self moveBy: (- d))
99
+ (-= shown d)
100
+ (if (<= shown (- height)) (self remove:)))
101
+ ((< shown 0)
102
+ (= d (+ 2 (/ (- shown) 3)))
103
+ (if (> d (- shown)) (= d (- shown)))
104
+ (self moveBy: d)
105
+ (+= shown d))))
70
106
 
71
107
  ;; An icon at (x, y): cel c of the bar's view (-1: the item in use), standing for `what`.
72
108
  (method (addIcon theX theY c what &tmp icon)
@@ -85,8 +121,17 @@
85
121
  (icon plane: uiPlane setPri: (+ (box priority?) 1))
86
122
  (icons add: icon))
87
123
 
124
+ ;; Closes it: it slides away (or goes at once, without slides), and stops taking clicks.
88
125
  (method (hide)
89
126
  (if (== dialog self) (= dialog 0))
127
+ (if (and slides box)
128
+ (= leaving 1)
129
+ else
130
+ (self remove:)))
131
+
132
+ (method (remove)
133
+ (= leaving 0)
134
+ (= shown 0)
90
135
  (if icons
91
136
  (icons eachElementDo: #dispose)
92
137
  (icons dispose:)
package/lib/992.sc CHANGED
@@ -51,14 +51,18 @@
51
51
  field 0 ; the description being typed
52
52
  slot 0) ; the save it goes into
53
53
 
54
+ ;; Painted with the textStyle's menuSkin, if it has one; Restore is unavailable there until
55
+ ;; there's a save.
54
56
  (method (showMenu &tmp menu)
55
57
  (= mode SR_MENU)
56
- (= menu (Menu new:))
58
+ (self load:)
59
+ (= menu ((Menu new:) painted: TRUE yourself:))
57
60
  (menu
58
61
  add: SR_SAVE "Save the game"
59
62
  add: SR_RESTORE "Restore a game"
60
63
  add: SR_RESTART "Start again"
61
64
  add: SR_TEXT (String STRING_FORMAT "Text speed: %s" (self textSpeedName:)))
65
+ (if (not count) ((menu valued: SR_RESTORE) disabled: TRUE))
62
66
  ;; Only a game with recorded lines asks how to have them.
63
67
  (if (DoAudio 9)
64
68
  (menu add: SR_SPEECH (String STRING_FORMAT "Speech: %s" (self speechName:))))
package/lib/995.sc CHANGED
@@ -73,11 +73,13 @@
73
73
 
74
74
  (method (doit)
75
75
  (self showCursor:)
76
+ (if (and iconBar (iconBar box?)) (iconBar step:))
76
77
  (while (GetEvent EV_ALL theEvent)
77
78
  (self handleEvent: theEvent))
78
- ;; theEvent now says where the pointer is: at the top edge, the icon bar comes down.
79
+ ;; theEvent now says where the pointer is: an open menu or the icon bar follows it, and at
80
+ ;; the top edge the icon bar comes down.
79
81
  (cond
80
- ((== dialog iconBar) (iconBar pointerAt: (theEvent x?) (theEvent y?)))
82
+ ((and dialog (dialog respondsTo: #pointerAt)) (dialog pointerAt: (theEvent x?) (theEvent y?)))
81
83
  ((and canInput (not dialog) (not talking) (<= (theEvent y?) ICON_BAR_EDGE)) (iconBar show:))))
82
84
 
83
85
  ;; The verb right-click goes to: walk, do, look, talk, the item picked (if any), walk.
package/lib/996.sc CHANGED
@@ -10,6 +10,14 @@
10
10
  ;; A frame is a view whose loop 0 has eight cels, each anchored at its top-left corner: the
11
11
  ;; corners (top-left, top-right, bottom-left, bottom-right), then the edges (top, bottom, left,
12
12
  ;; right). The edges are tiled between the corners; transparent pixels show the paper.
13
+ ;;
14
+ ;; The game menu can be painted instead (the casebook, say): `menuSkin` is a view whose loop
15
+ ;; 0, cel 0 is the panel, its title painted in, loop 1 the row behind a choice and loop 2 the
16
+ ;; bullet before it, each with a cel for a choice as it is, under the pointer or keyboard
17
+ ;; focus, pressed and unavailable; all anchored at their top-left corners. The panel goes at
18
+ ;; menuX, menuY (-1: centred), the rows from menuRowX, menuRowY down by menuRowStep, the
19
+ ;; bullet and the text at their offsets in the row. The text is in the four menu colours.
20
+ ;; (textStyle menuSkin: 269 menuX: 38 menuY: 11 menuRowX: 10 menuRowY: 29 menuRowStep: 22)
13
21
  (class TextStyle of Obj
14
22
  (properties
15
23
  font 0
@@ -20,8 +28,17 @@
20
28
  margin 4 ; between the text and the border or frame
21
29
  portraitFrame -1 ; a view around portraits: loop 0 behind the face, loop 1 over its edges
22
30
  portraitX 8 ; where a portrait's face goes on the left (mirrored on the right)
23
- portraitY 8
24
- nameFont -1)) ; the font for a speaker's name before their line (-1: the text's)
31
+ portraitY 8 ; its top; below 0, its bottom that far above the screen's, its text with it
32
+ nameFont -1 ; the font for a speaker's name before their line (-1: the text's)
33
+ menuSkin -1 ; the game menu's painted panel, rows and bullets, or -1 (above)
34
+ menuX -1 menuY -1
35
+ menuRowX 10 menuRowY 29 menuRowStep 22
36
+ menuBulletX 5 menuBulletY 5
37
+ menuTextX 25 menuTextY 4
38
+ menuFore -1 ; the text of a choice, under the pointer, pressed, unavailable (-1: fore)
39
+ menuHover -1
40
+ menuPressed -1
41
+ menuDisabled -1))
25
42
 
26
43
  ;; A box of text over the room: drawn by the interpreter into a bitmap, shown as a screen
27
44
  ;; item in the UI plane. Set text (and width, x, y), then init; it's as tall as the text
@@ -44,6 +61,7 @@
44
61
  borderColor -2 ; -1: none
45
62
  frame -2 ; -1: none
46
63
  textLeft 0 textTop 0 textRight -1 textBottom -1
64
+ margin -1 ; between the text and the edge, or the frame (-1: the textStyle's)
47
65
  width 200
48
66
  height 0
49
67
  bottom -1) ; -1: placed at y
@@ -55,7 +73,7 @@
55
73
  (if (== frame -2) (= frame (textStyle frame?)))
56
74
  (if (== borderColor -2) (= borderColor (if (!= frame -1) -1 else (textStyle border?))))
57
75
  ;; As tall as the text needs, inside the margin and the frame.
58
- (= m (textStyle margin?))
76
+ (= m (if (== margin -1) (textStyle margin?) else margin))
59
77
  (= padL m) (= padT m) (= padR m) (= padB m)
60
78
  (if (!= frame -1)
61
79
  (+= padL (CelWide frame 0 6))
@@ -245,21 +263,29 @@
245
263
  (if (and (!= self heroTalker) heroTalker (heroTalker respondsTo: #otherSide)) (heroTalker otherSide: s))
246
264
  (return s))
247
265
 
248
- ;; Whether the portrait on side s (frame and all) would be over the speaker or the hero.
249
- (method (covers face s &tmp pw ph over left right bottom)
266
+ ;; Where the face's top goes: textStyle's portraitY, or below 0, counted up from the bottom.
267
+ (method (faceTop face &tmp py)
268
+ (= py (textStyle portraitY?))
269
+ (return (if (< py 0) (- (+ SCREEN_HEIGHT py) (CelHigh (face view?) 0 0)) else py)))
270
+
271
+ ;; Whether the portrait on side s (frame and all) would be over the speaker's or the hero's
272
+ ;; head.
273
+ (method (covers face s &tmp pw ph over left right top bottom f)
250
274
  (= pw (CelWide (face view?) 0 0))
251
275
  (= ph (CelHigh (face view?) 0 0))
252
- (= over (if (!= (textStyle portraitFrame?) -1) (/ (- (CelWide (textStyle portraitFrame?) 0 0) pw) 2) else 0))
276
+ (= f (textStyle portraitFrame?))
277
+ (= over (if (!= f -1) (/ (- (CelWide f 0 0) pw) 2) else 0))
253
278
  (= left (if s (- SCREEN_WIDTH (+ (textStyle portraitX?) pw over)) else (- (textStyle portraitX?) over)))
254
279
  (= right (+ left pw over over))
255
- (= bottom (+ (textStyle portraitY?) ph over))
280
+ (= top (- (self faceTop: face) (if (!= f -1) (- (- (CelHigh f 0 0) ph) over) else 0)))
281
+ (= bottom (+ (self faceTop: face) ph over))
256
282
  (return
257
283
  (or
258
- (self over: (if (== self heroTalker) ego else who) left right bottom)
259
- (and (!= self heroTalker) (self over: ego left right bottom)))))
284
+ (self over: (if (== self heroTalker) ego else who) left right top bottom)
285
+ (and (!= self heroTalker) (self over: ego left right top bottom)))))
260
286
 
261
- ;; Whether someone's figure reaches into the screen's top band from left to right, down to bottom.
262
- (method (over body left right bottom &tmp w h)
287
+ ;; Whether someone's head (the top quarter of their figure) is in the box.
288
+ (method (over body left right top bottom &tmp w h)
263
289
  (if (or (not body) (not (body respondsTo: #view)) (& (body signal?) SIG_HIDDEN)) (return FALSE))
264
290
  (= w (CelWide (body view?) (body loop?) (body cel?)))
265
291
  (= h (CelHigh (body view?) (body loop?) (body cel?)))
@@ -267,7 +293,8 @@
267
293
  (= w (/ (* w (body scaleX?)) 128))
268
294
  (= h (/ (* h (body scaleY?)) 128)))
269
295
  (return
270
- (and (< (- (body x?) (/ w 2)) right) (> (+ (body x?) (/ w 2)) left) (< (- (body y?) h) bottom))))
296
+ (and (< (- (body x?) (/ w 2)) right) (> (+ (body x?) (/ w 2)) left)
297
+ (< (- (body y?) h) bottom) (> (+ (- (body y?) h) (/ h 4)) top))))
271
298
 
272
299
  (method (say txt whoCares &tmp face l pw f fx fy over)
273
300
  (self init: makeParts:)
@@ -279,7 +306,7 @@
279
306
  (= f (textStyle portraitFrame?))
280
307
  (= over (if (!= f -1) (/ (- (CelWide f 0 0) pw) 2) else 0))
281
308
  (= fx (if side (- SCREEN_WIDTH (+ (textStyle portraitX?) pw)) else (textStyle portraitX?)))
282
- (= fy (textStyle portraitY?))
309
+ (= fy (self faceTop: face))
283
310
  (face x: fx y: fy loop: l cel: 0)
284
311
  (if mouth (mouth x: fx y: fy loop: (+ l 1) cel: 0))
285
312
  (if eyes (eyes x: fx y: fy loop: (+ l 2) cel: 0))
@@ -288,8 +315,9 @@
288
315
  (back x: fx y: fy)
289
316
  (if (and (not front) (>= (NumLoops back) 2)) (= front ((View new:) view: f loop: 1 cel: 0 yourself:)))
290
317
  (if front (front x: fx y: fy)))
291
- ;; The text beside it, towards the middle.
292
- (= y fy)
318
+ ;; The text beside it, towards the middle: level with its top, or at the bottom of the
319
+ ;; screen, ending level with its frame's bottom and growing upwards.
320
+ (= y (if (< (textStyle portraitY?) 0) (- (+ fy (CelHigh (face view?) l 0) over) SCREEN_HEIGHT) else fy))
293
321
  (if side
294
322
  (= x 6)
295
323
  (= width (- fx (+ over 12)))
@@ -379,19 +407,57 @@
379
407
  (method (cue)
380
408
  (self sayNext:)))
381
409
 
382
- ;; One choice in a Menu.
410
+ ;; One choice in a Menu. In a painted menu it has a row and a bullet behind it, and is in one
411
+ ;; of the skin's states (MENU_NORMAL, MENU_HOVER, MENU_PRESSED, MENU_DISABLED).
383
412
  (class MenuItem of TextItem
384
413
  (properties
385
- value 0)
414
+ value 0
415
+ disabled 0 ; can't be chosen: shown unavailable in a painted menu (a plain one ignores it)
416
+ row 0
417
+ bullet 0
418
+ state -1)
419
+
420
+ ;; Anywhere on its row, if it has one.
421
+ (method (onMe theX theY &tmp v)
422
+ (if (not row) (return (super onMe: theX theY)))
423
+ (= v (row view?))
424
+ (return
425
+ (and (>= theX (row x?)) (< theX (+ (row x?) (CelWide v 1 0)))
426
+ (>= theY (row y?)) (< theY (+ (row y?) (CelHigh v 1 0))))))
427
+
428
+ ;; Its row, bullet and text as they look in state st.
429
+ (method (setState st &tmp c)
430
+ (if (or (not row) (== st state)) (return))
431
+ (= state st)
432
+ (row setCel: st)
433
+ (bullet setCel: st)
434
+ (= c
435
+ (switch st
436
+ (MENU_HOVER (textStyle menuHover?))
437
+ (MENU_PRESSED (textStyle menuPressed?))
438
+ (MENU_DISABLED (textStyle menuDisabled?))
439
+ (else (textStyle menuFore?))))
440
+ (= fore (if (== c -1) (textStyle fore?) else c))
441
+ (if bitmap
442
+ (Bitmap BITMAP_DISPOSE bitmap)
443
+ (= bitmap (CreateTextBitmap 0 width height self))
444
+ (UpdateScreenItem self)))
386
445
 
387
446
  (method (dispose)
447
+ (if row (row dispose:) (= row 0))
448
+ (if bullet (bullet dispose:) (= bullet 0))
388
449
  ;; Its text was made for it (a literal string is left alone).
389
450
  (String ARRAY_FREE text)
390
451
  (super dispose:)))
391
452
 
392
- ;; Choices one under another in a box (the textStyle's frame around them all); the first
393
- ;; click on one ends it. While it's open it gets every click (it's the `dialog`). The caller
394
- ;; hears the choice by `choose: value`.
453
+ ;; Choices one under another in a box (the textStyle's frame around them all); a click on one
454
+ ;; ends it, and Escape chooses the one worth 0 (Cancel, Carry on, Goodbye). While it's open it
455
+ ;; gets every event (it's the `dialog`). The caller hears the choice by `choose: value`.
456
+ ;;
457
+ ;; A `painted` menu is drawn with the textStyle's menuSkin instead, if it has one and the
458
+ ;; choices fit on its panel: each on its row, lit under the pointer, pressed while the button
459
+ ;; is down; the arrow keys move between them and Enter or Space chooses, and an unavailable
460
+ ;; one is passed over.
395
461
  (class Menu of Obj
396
462
  (properties
397
463
  items 0
@@ -399,56 +465,175 @@
399
465
  panel 0
400
466
  y 20 ; the top of the box
401
467
  width 240
402
- next 0) ; where the next choice goes
468
+ next 0 ; where the next choice goes
469
+ painted 0 ; drawn with the textStyle's menuSkin, if it fits
470
+ skin -1 ; the skin it's drawn with, or -1
471
+ focus 0 ; the choice that's lit
472
+ pressed 0 ; the choice the button went down on
473
+ pointerX -1 pointerY -1)
474
+
475
+ (method (add v txt &tmp item)
476
+ (if (not items) (= items (List new:)))
477
+ (= item ((MenuItem new:) value: v text: txt yourself:))
478
+ (items add: item)
479
+ (return self))
480
+
481
+ ;; The choices on their box, then it waits for one.
482
+ (method (show whoCares &tmp s)
483
+ (= caller whoCares)
484
+ (if (not items) (return))
485
+ (= s (textStyle menuSkin?))
486
+ (if (and painted (!= s -1)
487
+ (<= (+ (textStyle menuRowY?) (* (items size?) (textStyle menuRowStep?))) (CelHigh s 0 0)))
488
+ (= skin s)
489
+ (self showPainted:)
490
+ else
491
+ (self showPlain:))
492
+ (= dialog self))
403
493
 
404
- (method (add v txt &tmp item f)
494
+ (method (showPlain &tmp f node item)
405
495
  (= f (textStyle frame?))
406
- (if (not items)
407
- (= items (List new:))
408
- (= next (+ y (if (!= f -1) (CelHigh f 0 4) else 1))))
409
- (= item
410
- ((MenuItem new:)
411
- value: v
412
- text: txt
496
+ (= next (+ y (if (!= f -1) (CelHigh f 0 4) else 1)))
497
+ (for ((= node (FirstNode (items elements?)))) node ((= node (NextNode node)))
498
+ (= item (NodeValue node))
499
+ (item
413
500
  frame: -1
414
501
  borderColor: -1
415
502
  width: (- width (if (!= f -1) (+ (CelWide f 0 6) (CelWide f 0 7)) else 2))
416
503
  x: (+ (/ (- SCREEN_WIDTH width) 2) (if (!= f -1) (CelWide f 0 6) else 1))
417
504
  y: next
505
+ disabled: 0)
506
+ (item init:)
507
+ (+= next (item height?)))
508
+ (= panel
509
+ ((TextItem new:)
510
+ text: ""
511
+ x: (/ (- SCREEN_WIDTH width) 2)
512
+ y: y
513
+ width: width
514
+ height: (+ (- next y) (if (!= f -1) (CelHigh f 0 5) else 1))
515
+ priority: 99
418
516
  yourself:))
419
- (item init:)
420
- (+= next (item height?))
421
- (items add: item)
422
- (return self))
423
-
424
- ;; The box behind the choices, then it waits for one.
425
- (method (show whoCares &tmp f)
426
- (= f (textStyle frame?))
427
- (= caller whoCares)
428
- (if items
429
- (= panel
430
- ((TextItem new:)
431
- text: ""
432
- x: (/ (- SCREEN_WIDTH width) 2)
433
- y: y
434
- width: width
435
- height: (+ (- next y) (if (!= f -1) (CelHigh f 0 5) else 1))
436
- priority: 99
437
- yourself:))
438
- (panel init:))
439
- (= dialog self))
440
-
441
- (method (handleEvent event &tmp item v c)
517
+ (panel init:))
518
+
519
+ (method (showPainted &tmp px py rx ry node item)
520
+ (= px (if (== (textStyle menuX?) -1) (/ (- SCREEN_WIDTH (CelWide skin 0 0)) 2) else (textStyle menuX?)))
521
+ (= py (if (== (textStyle menuY?) -1) (/ (- SCREEN_HEIGHT (CelHigh skin 0 0)) 2) else (textStyle menuY?)))
522
+ (= panel (self part: 0 0 px py 99))
523
+ (= rx (+ px (textStyle menuRowX?)))
524
+ (= ry (+ py (textStyle menuRowY?)))
525
+ (for ((= node (FirstNode (items elements?)))) node ((= node (NextNode node)))
526
+ (= item (NodeValue node))
527
+ (item
528
+ row: (self part: 1 0 rx ry 100)
529
+ bullet: (self part: 2 0 (+ rx (textStyle menuBulletX?)) (+ ry (textStyle menuBulletY?)) 101)
530
+ frame: -1
531
+ borderColor: -1
532
+ margin: 0
533
+ back: 254
534
+ x: (+ rx (textStyle menuTextX?))
535
+ y: (+ ry (textStyle menuTextY?))
536
+ width: (- (CelWide skin 1 0) (textStyle menuTextX?))
537
+ height: (- (CelHigh skin 1 0) (textStyle menuTextY?))
538
+ priority: 102)
539
+ (item setState: (if (item disabled?) MENU_DISABLED else MENU_NORMAL))
540
+ (item init:)
541
+ (+= ry (textStyle menuRowStep?))))
542
+
543
+ ;; A cel of the skin on the screen, over the room whether or not there is one.
544
+ (method (part l c theX theY pri &tmp p)
545
+ (= p
546
+ ((View new:)
547
+ view: skin loop: l cel: c x: theX y: theY
548
+ plane: uiPlane priority: pri fixPriority: 1
549
+ yourself:))
550
+ (cast add: p)
551
+ (AddScreenItem p)
552
+ (return p))
553
+
554
+ ;; Lights choice it (0: none), and only it.
555
+ (method (light it &tmp node item)
556
+ (= focus it)
557
+ (for ((= node (FirstNode (items elements?)))) node ((= node (NextNode node)))
558
+ (= item (NodeValue node))
559
+ (item setState:
560
+ (cond
561
+ ((item disabled?) MENU_DISABLED)
562
+ ((and (== item pressed) (== item it)) MENU_PRESSED)
563
+ ((== item it) MENU_HOVER)
564
+ (else MENU_NORMAL)))))
565
+
566
+ ;; The User says where the pointer is each cycle: when it moves, what's under it is lit.
567
+ (method (pointerAt theX theY &tmp item)
568
+ (if (or (== skin -1) (and (== theX pointerX) (== theY pointerY))) (return))
569
+ (= pointerX theX)
570
+ (= pointerY theY)
571
+ (= item (items firstTrue: #onMe theX theY))
572
+ (if (and item (item disabled?)) (= item 0))
573
+ (if (!= item focus) (self light: item)))
574
+
575
+ ;; The i-th choice, from 0.
576
+ (method (nth i &tmp node)
577
+ (for ((= node (FirstNode (items elements?)))) (and node i) ((= node (NextNode node)))
578
+ (-- i))
579
+ (return (if node (NodeValue node) else 0)))
580
+
581
+ ;; The choice worth v, or 0.
582
+ (method (valued v &tmp node)
583
+ (for ((= node (FirstNode (items elements?)))) node ((= node (NextNode node)))
584
+ (if (== ((NodeValue node) value?) v) (return (NodeValue node))))
585
+ (return 0))
586
+
587
+ ;; Lights the next choice that's available, dir 1 down or -1 up, round from the end.
588
+ (method (step dir &tmp i n k node item)
589
+ (= n (items size?))
590
+ (= i (if (> dir 0) -1 else n))
591
+ (= k 0)
592
+ (for ((= node (FirstNode (items elements?)))) node ((= node (NextNode node)))
593
+ (if (== (NodeValue node) focus) (= i k))
594
+ (++ k))
595
+ (for ((= k 0)) (< k n) ((++ k))
596
+ (= i (mod (+ i dir n) n))
597
+ (= item (self nth: i))
598
+ (if (not (item disabled?))
599
+ (self light: item)
600
+ (return))))
601
+
602
+ (method (handleEvent event &tmp item)
442
603
  (event claimed: TRUE)
443
- (if (!= (event type?) EV_MOUSE_DOWN) (return TRUE))
444
- (= item (items firstTrue: #onMe (event x?) (event y?)))
445
- (if item
446
- (= v (item value?))
447
- (= c caller)
448
- (self dispose:)
449
- (c choose: v))
604
+ (switch (event type?)
605
+ (EV_KEY_DOWN
606
+ (switch (event message?)
607
+ (KEY_ESCAPE (self pick: (self valued: 0)))
608
+ (KEY_UP (if (!= skin -1) (self step: -1)))
609
+ (KEY_DOWN (if (!= skin -1) (self step: 1)))
610
+ (KEY_ENTER (if focus (self pick: focus)))
611
+ (KEY_SPACE (if focus (self pick: focus)))))
612
+ (EV_MOUSE_DOWN
613
+ (= item (items firstTrue: #onMe (event x?) (event y?)))
614
+ (cond
615
+ ((== skin -1) (self pick: item))
616
+ ((and item (not (item disabled?)))
617
+ (= pressed item)
618
+ (self light: item))))
619
+ (EV_MOUSE_UP
620
+ (if pressed
621
+ (= item (items firstTrue: #onMe (event x?) (event y?)))
622
+ (if (== item pressed)
623
+ (self pick: item)
624
+ else
625
+ (= pressed 0)
626
+ (self light: (if (and item (not (item disabled?))) item else 0))))))
450
627
  (return TRUE))
451
628
 
629
+ ;; Ends it with that choice (none: it stays open).
630
+ (method (pick item &tmp v c)
631
+ (if (or (not item) (item disabled?)) (return))
632
+ (= v (item value?))
633
+ (= c caller)
634
+ (self dispose:)
635
+ (c choose: v))
636
+
452
637
  ;; Closed without a choice.
453
638
  (method (dismiss)
454
639
  (self dispose:))
package/lib/system.sh CHANGED
@@ -39,6 +39,12 @@
39
39
 
40
40
  (define SAVE_DESCRIPTION 36)
41
41
  (define SAVES_SHOWN 8)
42
+ ;; A painted menu choice's look: the cels of the skin's row and bullet loops.
43
+ (define MENU_NORMAL 0)
44
+ (define MENU_HOVER 1)
45
+ (define MENU_PRESSED 2)
46
+ (define MENU_DISABLED 3)
47
+
42
48
  (define SR_MENU 1)
43
49
  (define SR_SAVE 2)
44
50
  (define SR_RESTORE 3)
@@ -74,6 +80,8 @@
74
80
  (define KEY_SPACE 32)
75
81
  (define KEY_PERIOD 46)
76
82
  (define KEY_ESCAPE 27)
83
+ (define KEY_UP $4800)
84
+ (define KEY_DOWN $5000)
77
85
  (define KEY_F5 $3f00)
78
86
  (define KEY_F7 $4100)
79
87
  (define KEY_I 73)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sci2-ts",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "An SCI2 adventure game engine in TypeScript, and the tools to make games with it",
5
5
  "license": "MIT",
6
6
  "type": "module",