@datamoc/mw_games 0.14.0 → 0.16.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.
Files changed (71) hide show
  1. package/CHANGELOG.md +69 -1
  2. package/dist/audio/Music.d.ts +27 -0
  3. package/dist/audio/Music.d.ts.map +1 -1
  4. package/dist/audio/Music.js +70 -2
  5. package/dist/audio/Music.js.map +1 -1
  6. package/dist/audio/Orchestrator.d.ts +4 -0
  7. package/dist/audio/Orchestrator.d.ts.map +1 -1
  8. package/dist/audio/Orchestrator.js +12 -0
  9. package/dist/audio/Orchestrator.js.map +1 -1
  10. package/dist/audio/Sound.d.ts +9 -0
  11. package/dist/audio/Sound.d.ts.map +1 -1
  12. package/dist/audio/Sound.js +18 -0
  13. package/dist/audio/Sound.js.map +1 -1
  14. package/dist/core/Settings.d.ts +96 -0
  15. package/dist/core/Settings.d.ts.map +1 -0
  16. package/dist/core/Settings.js +189 -0
  17. package/dist/core/Settings.js.map +1 -0
  18. package/dist/core/index.d.ts +2 -0
  19. package/dist/core/index.d.ts.map +1 -1
  20. package/dist/core/index.js +1 -0
  21. package/dist/core/index.js.map +1 -1
  22. package/dist/mw_games.global.js +115 -115
  23. package/dist/mwl/catalog.d.ts +42 -0
  24. package/dist/mwl/catalog.d.ts.map +1 -1
  25. package/dist/mwl/catalog.js +88 -0
  26. package/dist/mwl/catalog.js.map +1 -1
  27. package/dist/mwl/index.d.ts +1 -1
  28. package/dist/mwl/index.d.ts.map +1 -1
  29. package/dist/two-d/Game.d.ts +40 -0
  30. package/dist/two-d/Game.d.ts.map +1 -1
  31. package/dist/two-d/Game.js +66 -3
  32. package/dist/two-d/Game.js.map +1 -1
  33. package/dist/two-d/QualityScaler.d.ts +63 -0
  34. package/dist/two-d/QualityScaler.d.ts.map +1 -0
  35. package/dist/two-d/QualityScaler.js +92 -0
  36. package/dist/two-d/QualityScaler.js.map +1 -0
  37. package/dist/two-d/index.d.ts +3 -1
  38. package/dist/two-d/index.d.ts.map +1 -1
  39. package/dist/two-d/index.js +1 -0
  40. package/dist/two-d/index.js.map +1 -1
  41. package/dist/two-d/render/RpgmAutotile.d.ts +217 -0
  42. package/dist/two-d/render/RpgmAutotile.d.ts.map +1 -1
  43. package/dist/two-d/render/RpgmAutotile.js +756 -0
  44. package/dist/two-d/render/RpgmAutotile.js.map +1 -1
  45. package/dist/two-d/render/SpriteGroup.d.ts +83 -0
  46. package/dist/two-d/render/SpriteGroup.d.ts.map +1 -0
  47. package/dist/two-d/render/SpriteGroup.js +107 -0
  48. package/dist/two-d/render/SpriteGroup.js.map +1 -0
  49. package/dist/two-d/render/TileMap.d.ts +90 -0
  50. package/dist/two-d/render/TileMap.d.ts.map +1 -1
  51. package/dist/two-d/render/TileMap.js +319 -31
  52. package/dist/two-d/render/TileMap.js.map +1 -1
  53. package/dist/two-d/render/index.d.ts +5 -3
  54. package/dist/two-d/render/index.d.ts.map +1 -1
  55. package/dist/two-d/render/index.js +2 -1
  56. package/dist/two-d/render/index.js.map +1 -1
  57. package/dist/two-d/ui/Meter.d.ts +76 -0
  58. package/dist/two-d/ui/Meter.d.ts.map +1 -0
  59. package/dist/two-d/ui/Meter.js +134 -0
  60. package/dist/two-d/ui/Meter.js.map +1 -0
  61. package/dist/two-d/ui/SettingsScreen.d.ts +112 -0
  62. package/dist/two-d/ui/SettingsScreen.d.ts.map +1 -0
  63. package/dist/two-d/ui/SettingsScreen.js +338 -0
  64. package/dist/two-d/ui/SettingsScreen.js.map +1 -0
  65. package/dist/two-d/ui/index.d.ts +4 -0
  66. package/dist/two-d/ui/index.d.ts.map +1 -1
  67. package/dist/two-d/ui/index.js +2 -0
  68. package/dist/two-d/ui/index.js.map +1 -1
  69. package/dist/version.d.ts +1 -1
  70. package/dist/version.js +1 -1
  71. package/package.json +1 -1
@@ -0,0 +1,76 @@
1
+ import { Container } from 'pixi.js';
2
+ import type { Texture2D } from '../render/Types2D.ts';
3
+ export interface MeterOptions {
4
+ /** total icons; fractional counts floor, below 1 becomes 1 */
5
+ count: number;
6
+ /** filled icons, fractions allowed (a half heart is 0.5); defaults to full */
7
+ value?: number;
8
+ /** icon diameter in pixels */
9
+ size?: number;
10
+ /** gap between icons in pixels */
11
+ gap?: number;
12
+ /** the filled icon's art; `emptyTexture` must come with it, or neither */
13
+ filledTexture?: Texture2D;
14
+ /** the empty icon's art; `filledTexture` must come with it, or neither */
15
+ emptyTexture?: Texture2D;
16
+ /** filled colour in flat mode; tints `filledTexture` too, when both are given */
17
+ color?: number;
18
+ /** empty colour in flat mode; the theme's panel fill when omitted */
19
+ emptyColor?: number;
20
+ }
21
+ /**
22
+ * A discrete meter: N icons with a value filled, the Zelda heart row or star rating to
23
+ * `Bar`'s continuous fill. Fractions clip mid-icon through a mask, so halves (or any
24
+ * fraction) work identically for flat shapes and game-supplied textures, with no extra
25
+ * half-state art to author.
26
+ *
27
+ * Flat icons are theme-drawn pips; game art arrives as a `filledTexture`/`emptyTexture`
28
+ * pair, which must come together - one without the other throws, since a half-drawn
29
+ * meter would only guess. Colour follows the theme the way `Bar` does unless the game
30
+ * passes its own, and a passed colour survives later theme changes.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import { Meter } from '@datamoc/mw_games/two-d/ui';
35
+ *
36
+ * const hearts = new Meter({ count: 5, value: 3 });
37
+ *
38
+ * hearts.setValue(2.5); // down to two and a half hearts
39
+ * console.log(hearts.value); // 2.5
40
+ * ```
41
+ */
42
+ export declare class Meter extends Container {
43
+ private emptyLayer;
44
+ private filledLayer;
45
+ private maskShape;
46
+ private count_;
47
+ private size_;
48
+ private gap_;
49
+ private value_;
50
+ private explicitColor;
51
+ private fillColor;
52
+ private emptyColor_?;
53
+ private readonly filledTexture?;
54
+ private readonly emptyTexture?;
55
+ private readonly themeListener;
56
+ constructor(options: MeterOptions);
57
+ /** filled icons, fractions included */
58
+ get value(): number;
59
+ /** total icons */
60
+ get count(): number;
61
+ /** the resolved filled colour: the explicit one, or the theme's highlight */
62
+ get color(): number;
63
+ setValue(value: number, count?: number): void;
64
+ /**
65
+ * Recolours the fill at runtime, counting as explicit exactly as passing `color` does,
66
+ * so a later theme change cannot throw the game's own colour away.
67
+ */
68
+ setColor(color: number): void;
69
+ resize(size: number, gap?: number): void;
70
+ private get totalWidth();
71
+ private iconX;
72
+ private draw;
73
+ private icon;
74
+ destroy(options?: Parameters<Container['destroy']>[0]): void;
75
+ }
76
+ //# sourceMappingURL=Meter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Meter.d.ts","sourceRoot":"","sources":["../../../src/two-d/ui/Meter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAoB,MAAM,SAAS,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAEtD,MAAM,WAAW,YAAY;IAC5B,8DAA8D;IAC9D,KAAK,EAAE,MAAM,CAAC;IACd,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb,0EAA0E;IAC1E,aAAa,CAAC,EAAE,SAAS,CAAC;IAC1B,0EAA0E;IAC1E,YAAY,CAAC,EAAE,SAAS,CAAC;IAEzB,iFAAiF;IACjF,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,qBAAa,KAAM,SAAQ,SAAS;IACnC,OAAO,CAAC,UAAU,CAAmB;IACrC,OAAO,CAAC,WAAW,CAAmB;IACtC,OAAO,CAAC,SAAS,CAAkB;IAEnC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,aAAa,CAAU;IAC/B,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,WAAW,CAAC,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAY;IAC3C,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAY;IAE1C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IAEnD,YAAY,OAAO,EAAE,YAAY,EAuBhC;IAED,uCAAuC;IACvC,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,kBAAkB;IAClB,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,6EAA6E;IAC7E,IAAI,KAAK,IAAI,MAAM,CAElB;IAED,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAoB,GAAG,IAAI,CAIzD;IAED;;;OAGG;IACH,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAI5B;IAED,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,GAAE,MAAkB,GAAG,IAAI,CAIlD;IAED,OAAO,KAAK,UAAU,GAErB;IAED,OAAO,CAAC,KAAK;IAKb,OAAO,CAAC,IAAI;IAkBZ,OAAO,CAAC,IAAI;IAeH,OAAO,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,IAAI,CAGpE;CACD"}
@@ -0,0 +1,134 @@
1
+ import { Container, Graphics, Sprite } from 'pixi.js';
2
+ import { theme, themeChanged } from './theme.js';
3
+ /**
4
+ * A discrete meter: N icons with a value filled, the Zelda heart row or star rating to
5
+ * `Bar`'s continuous fill. Fractions clip mid-icon through a mask, so halves (or any
6
+ * fraction) work identically for flat shapes and game-supplied textures, with no extra
7
+ * half-state art to author.
8
+ *
9
+ * Flat icons are theme-drawn pips; game art arrives as a `filledTexture`/`emptyTexture`
10
+ * pair, which must come together - one without the other throws, since a half-drawn
11
+ * meter would only guess. Colour follows the theme the way `Bar` does unless the game
12
+ * passes its own, and a passed colour survives later theme changes.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { Meter } from '@datamoc/mw_games/two-d/ui';
17
+ *
18
+ * const hearts = new Meter({ count: 5, value: 3 });
19
+ *
20
+ * hearts.setValue(2.5); // down to two and a half hearts
21
+ * console.log(hearts.value); // 2.5
22
+ * ```
23
+ */
24
+ export class Meter extends Container {
25
+ constructor(options) {
26
+ super();
27
+ this.emptyLayer = new Container();
28
+ this.filledLayer = new Container();
29
+ this.maskShape = new Graphics();
30
+ this.themeListener = () => this.draw();
31
+ if ((options.filledTexture === undefined) !== (options.emptyTexture === undefined)) {
32
+ throw new Error('a Meter takes filledTexture and emptyTexture together, or neither');
33
+ }
34
+ this.count_ = normalizeCount(options.count);
35
+ this.size_ = options.size ?? 16;
36
+ this.gap_ = options.gap ?? 4;
37
+ this.value_ = clampValue(options.value ?? this.count_, this.count_);
38
+ this.explicitColor = options.color !== undefined;
39
+ this.fillColor = options.color ?? theme().color.textHighlight;
40
+ this.emptyColor_ = options.emptyColor;
41
+ this.filledTexture = options.filledTexture;
42
+ this.emptyTexture = options.emptyTexture;
43
+ this.addChild(this.emptyLayer);
44
+ this.addChild(this.filledLayer);
45
+ this.addChild(this.maskShape);
46
+ this.filledLayer.mask = this.maskShape;
47
+ this.draw();
48
+ themeChanged.add(this.themeListener);
49
+ }
50
+ /** filled icons, fractions included */
51
+ get value() {
52
+ return this.value_;
53
+ }
54
+ /** total icons */
55
+ get count() {
56
+ return this.count_;
57
+ }
58
+ /** the resolved filled colour: the explicit one, or the theme's highlight */
59
+ get color() {
60
+ return this.fillColor;
61
+ }
62
+ setValue(value, count = this.count_) {
63
+ this.count_ = normalizeCount(count);
64
+ this.value_ = clampValue(value, this.count_);
65
+ this.draw();
66
+ }
67
+ /**
68
+ * Recolours the fill at runtime, counting as explicit exactly as passing `color` does,
69
+ * so a later theme change cannot throw the game's own colour away.
70
+ */
71
+ setColor(color) {
72
+ this.explicitColor = true;
73
+ this.fillColor = color;
74
+ this.draw();
75
+ }
76
+ resize(size, gap = this.gap_) {
77
+ this.size_ = size;
78
+ this.gap_ = gap;
79
+ this.draw();
80
+ }
81
+ get totalWidth() {
82
+ return this.count_ * this.size_ + (this.count_ - 1) * this.gap_;
83
+ }
84
+ iconX(index) {
85
+ const x = index * (this.size_ + this.gap_);
86
+ return theme().direction === 'rtl' ? this.totalWidth - x - this.size_ : x;
87
+ }
88
+ draw() {
89
+ const t = theme();
90
+ if (!this.explicitColor)
91
+ this.fillColor = t.color.textHighlight;
92
+ const empty = this.emptyColor_ ?? t.color.panelFill;
93
+ this.emptyLayer.removeChildren();
94
+ this.filledLayer.removeChildren();
95
+ for (let i = 0; i < this.count_; i++) {
96
+ const x = this.iconX(i);
97
+ this.emptyLayer.addChild(this.icon(x, empty, this.emptyTexture, false));
98
+ this.filledLayer.addChild(this.icon(x, this.fillColor, this.filledTexture, true));
99
+ }
100
+ const filledWidth = this.totalWidth * (this.count_ > 0 ? this.value_ / this.count_ : 0);
101
+ const maskX = t.direction === 'rtl' ? this.totalWidth - filledWidth : 0;
102
+ this.maskShape.clear().rect(maskX, 0, filledWidth, this.size_).fill({ color: 0xffffff });
103
+ }
104
+ icon(x, color, texture, tint) {
105
+ if (texture) {
106
+ const sprite = new Sprite(texture);
107
+ sprite.x = x;
108
+ sprite.width = this.size_;
109
+ sprite.height = this.size_;
110
+ if (tint && this.explicitColor)
111
+ sprite.tint = color;
112
+ return sprite;
113
+ }
114
+ const shape = new Graphics();
115
+ const radius = this.size_ / 2;
116
+ shape.circle(x + radius, radius, radius).fill({ color });
117
+ return shape;
118
+ }
119
+ destroy(options) {
120
+ themeChanged.remove(this.themeListener);
121
+ super.destroy(options);
122
+ }
123
+ }
124
+ function clampValue(value, count) {
125
+ if (!Number.isFinite(value))
126
+ return count;
127
+ return Math.max(0, Math.min(count, value));
128
+ }
129
+ function normalizeCount(count) {
130
+ if (!Number.isFinite(count))
131
+ return 1;
132
+ return Math.max(1, Math.floor(count));
133
+ }
134
+ //# sourceMappingURL=Meter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Meter.js","sourceRoot":"","sources":["../../../src/two-d/ui/Meter.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,112 @@
1
+ import { Container } from 'pixi.js';
2
+ import type { Action } from '../../core/Input.ts';
3
+ import { Settings } from '../../core/Settings.ts';
4
+ /** one game-defined row over the `Settings` custom bag (`hints`, `violence`, ...) */
5
+ export type SettingsCustomRow = {
6
+ kind: 'boolean';
7
+ key: string;
8
+ label: string;
9
+ } | {
10
+ kind: 'number';
11
+ key: string;
12
+ label: string;
13
+ min: number;
14
+ max: number;
15
+ step?: number;
16
+ } | {
17
+ kind: 'choice';
18
+ key: string;
19
+ label: string;
20
+ options: readonly [string, ...string[]];
21
+ };
22
+ export interface SettingsScreenOptions {
23
+ settings: Settings;
24
+ width?: number;
25
+ rowHeight?: number;
26
+ /** English defaults; a game mapping these to translated strings overrides them */
27
+ labels?: {
28
+ music?: string;
29
+ sfx?: string;
30
+ muted?: string;
31
+ zoom?: string;
32
+ controls?: string;
33
+ reset?: string;
34
+ rebindHint?: string;
35
+ };
36
+ zoomMin?: number;
37
+ zoomMax?: number;
38
+ zoomStep?: number;
39
+ /** game-defined rows appended after zoom, in order */
40
+ custom?: readonly SettingsCustomRow[];
41
+ /** which actions the controls page lists; the stored binding keys when omitted */
42
+ actions?: readonly Action[];
43
+ /** passed through to the controls page's `RebindScreen` */
44
+ onConflict?: (key: string, action: Action, previousOwners: readonly Action[]) => boolean;
45
+ }
46
+ /**
47
+ * A ready-made settings screen over `core.Settings`: music and sound-effect sliders,
48
+ * a mute checkbox, a zoom slider, one row per game-defined `custom` descriptor, a
49
+ * controls page (`RebindScreen`), and a reset-to-defaults row. Every change writes
50
+ * straight through to the `Settings` it was given, so there is no apply step and no
51
+ * second persistence mechanism.
52
+ *
53
+ * Bindings captured on the controls page reach `Input` directly, bypassing `Settings`;
54
+ * leaving the page writes them back into it, so a rebind survives a restart. `cancel` on
55
+ * the main page is left to the caller (a `Window` closes, typically), reported as `false`.
56
+ *
57
+ * @example
58
+ * ```ts
59
+ * import { Settings } from '@datamoc/mw_games/core';
60
+ * import { SettingsScreen } from '@datamoc/mw_games/two-d/ui';
61
+ *
62
+ * const settings = new Settings({ namespace: 'my-game' });
63
+ * const screen = new SettingsScreen({
64
+ * settings,
65
+ * custom: [{ kind: 'boolean', key: 'hints', label: 'Hints' }],
66
+ * });
67
+ *
68
+ * screen.handleAction('down');
69
+ * console.log(screen.selectedRow); // 'sfx'
70
+ * ```
71
+ */
72
+ export declare class SettingsScreen extends Container {
73
+ private readonly settings;
74
+ private readonly width_;
75
+ private readonly rowHeight;
76
+ private readonly labels;
77
+ private readonly zoomMin;
78
+ private readonly zoomMax;
79
+ private readonly zoomStep;
80
+ private readonly customRows;
81
+ private readonly actionsOption;
82
+ private readonly onConflict;
83
+ private readonly highlight;
84
+ private readonly main;
85
+ private readonly rebindLayer;
86
+ private rows;
87
+ private selected;
88
+ private rebind;
89
+ private readonly themeListener;
90
+ constructor(options: SettingsScreenOptions);
91
+ /** the selected main-page row: 'music', 'sfx', 'muted', 'zoom', `custom:${key}`, 'controls' or 'reset' */
92
+ get selectedRow(): string;
93
+ /** true while the controls (rebind) page is open */
94
+ get isRebinding(): boolean;
95
+ /** @returns true when the action was used; `cancel` on the main page returns false */
96
+ handleAction(action: Action): boolean;
97
+ /** re-reads every row from `Settings`, for a screen kept alive while settings change elsewhere */
98
+ refresh(): void;
99
+ private select;
100
+ private drawHighlight;
101
+ private buildMain;
102
+ private addSliderRow;
103
+ private readSlider;
104
+ private addToggleRow;
105
+ private readToggle;
106
+ private addCustomRow;
107
+ private addActionRow;
108
+ private openRebind;
109
+ private closeRebind;
110
+ destroy(options?: Parameters<Container['destroy']>[0]): void;
111
+ }
112
+ //# sourceMappingURL=SettingsScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/two-d/ui/SettingsScreen.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,338 @@
1
+ import { Container, Graphics } from 'pixi.js';
2
+ import * as Input from '../../core/Input.js';
3
+ import { Settings } from '../../core/Settings.js';
4
+ import { Button } from './Button.js';
5
+ import { Checkbox } from './Checkbox.js';
6
+ import { Label } from './Label.js';
7
+ import { RebindScreen } from './RebindScreen.js';
8
+ import { Slider } from './Slider.js';
9
+ import { Spinner } from './Spinner.js';
10
+ import { theme, themeChanged } from './theme.js';
11
+ /**
12
+ * A ready-made settings screen over `core.Settings`: music and sound-effect sliders,
13
+ * a mute checkbox, a zoom slider, one row per game-defined `custom` descriptor, a
14
+ * controls page (`RebindScreen`), and a reset-to-defaults row. Every change writes
15
+ * straight through to the `Settings` it was given, so there is no apply step and no
16
+ * second persistence mechanism.
17
+ *
18
+ * Bindings captured on the controls page reach `Input` directly, bypassing `Settings`;
19
+ * leaving the page writes them back into it, so a rebind survives a restart. `cancel` on
20
+ * the main page is left to the caller (a `Window` closes, typically), reported as `false`.
21
+ *
22
+ * @example
23
+ * ```ts
24
+ * import { Settings } from '@datamoc/mw_games/core';
25
+ * import { SettingsScreen } from '@datamoc/mw_games/two-d/ui';
26
+ *
27
+ * const settings = new Settings({ namespace: 'my-game' });
28
+ * const screen = new SettingsScreen({
29
+ * settings,
30
+ * custom: [{ kind: 'boolean', key: 'hints', label: 'Hints' }],
31
+ * });
32
+ *
33
+ * screen.handleAction('down');
34
+ * console.log(screen.selectedRow); // 'sfx'
35
+ * ```
36
+ */
37
+ export class SettingsScreen extends Container {
38
+ constructor(options) {
39
+ super();
40
+ this.highlight = new Graphics();
41
+ this.main = new Container();
42
+ this.rebindLayer = new Container();
43
+ this.rows = [];
44
+ this.selected = 0;
45
+ this.rebind = null;
46
+ this.themeListener = () => this.drawHighlight();
47
+ this.settings = options.settings;
48
+ this.width_ = options.width ?? 320;
49
+ this.rowHeight = options.rowHeight ?? 36;
50
+ this.labels = {
51
+ music: 'Music',
52
+ sfx: 'Sound effects',
53
+ muted: 'Mute',
54
+ zoom: 'Zoom',
55
+ controls: 'Controls...',
56
+ reset: 'Reset to defaults',
57
+ rebindHint: 'Keys (cancel: back)',
58
+ ...options.labels,
59
+ };
60
+ this.zoomMin = options.zoomMin ?? 0.5;
61
+ this.zoomMax = options.zoomMax ?? 4;
62
+ this.zoomStep = options.zoomStep ?? 0.25;
63
+ this.customRows = options.custom ?? [];
64
+ this.actionsOption = options.actions;
65
+ this.onConflict = options.onConflict;
66
+ this.addChild(this.highlight);
67
+ this.addChild(this.main);
68
+ this.addChild(this.rebindLayer);
69
+ this.rebindLayer.visible = false;
70
+ this.buildMain();
71
+ this.refresh();
72
+ this.drawHighlight();
73
+ themeChanged.add(this.themeListener);
74
+ }
75
+ /** the selected main-page row: 'music', 'sfx', 'muted', 'zoom', `custom:${key}`, 'controls' or 'reset' */
76
+ get selectedRow() {
77
+ return this.rows[this.selected]?.id ?? '';
78
+ }
79
+ /** true while the controls (rebind) page is open */
80
+ get isRebinding() {
81
+ return this.rebind !== null;
82
+ }
83
+ /** @returns true when the action was used; `cancel` on the main page returns false */
84
+ handleAction(action) {
85
+ if (this.rebind) {
86
+ if (action === 'cancel' && !this.rebind.isCapturing) {
87
+ this.closeRebind();
88
+ return true;
89
+ }
90
+ return this.rebind.handleAction(action);
91
+ }
92
+ const row = this.rows[this.selected];
93
+ if (!row)
94
+ return false;
95
+ switch (action) {
96
+ case 'up':
97
+ this.select((this.selected + this.rows.length - 1) % this.rows.length);
98
+ return true;
99
+ case 'down':
100
+ this.select((this.selected + 1) % this.rows.length);
101
+ return true;
102
+ case 'left':
103
+ row.adjust(-1);
104
+ return true;
105
+ case 'right':
106
+ row.adjust(1);
107
+ return true;
108
+ case 'confirm':
109
+ row.activate();
110
+ return true;
111
+ default:
112
+ return false;
113
+ }
114
+ }
115
+ /** re-reads every row from `Settings`, for a screen kept alive while settings change elsewhere */
116
+ refresh() {
117
+ for (const row of this.rows)
118
+ row.refresh();
119
+ }
120
+ select(index) {
121
+ this.selected = index;
122
+ this.drawHighlight();
123
+ }
124
+ drawHighlight() {
125
+ if (this.rows.length === 0) {
126
+ this.highlight.clear();
127
+ return;
128
+ }
129
+ this.highlight
130
+ .clear()
131
+ .rect(0, this.selected * this.rowHeight, this.width_, this.rowHeight)
132
+ .fill({ color: theme().color.selection });
133
+ }
134
+ buildMain() {
135
+ const current = this.settings.current;
136
+ this.addSliderRow('music', this.labels.music, 0, 100, 1, 10, Math.round(current.musicVolume * 100), (v) => this.settings.setMusicVolume(v / 100));
137
+ this.addSliderRow('sfx', this.labels.sfx, 0, 100, 1, 10, Math.round(current.sfxVolume * 100), (v) => this.settings.setSfxVolume(v / 100));
138
+ this.addToggleRow('muted', this.labels.muted, current.muted, (v) => this.settings.setMuted(v));
139
+ this.addSliderRow('zoom', this.labels.zoom, this.zoomMin, this.zoomMax, this.zoomStep, this.zoomStep, current.zoom, (v) => this.settings.setZoom(v));
140
+ for (const descriptor of this.customRows)
141
+ this.addCustomRow(descriptor);
142
+ this.addActionRow('controls', this.labels.controls, () => this.openRebind());
143
+ this.addActionRow('reset', this.labels.reset, () => {
144
+ this.settings.reset();
145
+ this.refresh();
146
+ });
147
+ this.main.children.forEach((child, index) => {
148
+ child.y = index * this.rowHeight;
149
+ });
150
+ }
151
+ addSliderRow(id, label, min, max, step, keyStep, initial, write) {
152
+ const root = new Container();
153
+ const caption = new Label({ text: label });
154
+ caption.y = 10;
155
+ const control = new Slider({ width: 140, min, max, step, value: initial });
156
+ control.x = this.width_ - 148;
157
+ control.y = 10;
158
+ root.addChild(caption, control);
159
+ this.main.addChild(root);
160
+ const show = () => {
161
+ caption.setText(`${label}: ${control.value}`);
162
+ };
163
+ show();
164
+ control.onChange.add((value) => {
165
+ write(value);
166
+ show();
167
+ });
168
+ this.rows.push({
169
+ id,
170
+ root,
171
+ adjust: (delta) => control.setValue(control.value + delta * keyStep),
172
+ activate: () => { },
173
+ refresh: () => {
174
+ control.setValue(this.readSlider(id));
175
+ show();
176
+ },
177
+ });
178
+ }
179
+ readSlider(id) {
180
+ const current = this.settings.current;
181
+ switch (id) {
182
+ case 'music':
183
+ return Math.round(current.musicVolume * 100);
184
+ case 'sfx':
185
+ return Math.round(current.sfxVolume * 100);
186
+ case 'zoom':
187
+ return current.zoom;
188
+ default:
189
+ return 0;
190
+ }
191
+ }
192
+ addToggleRow(id, label, initial, write) {
193
+ const root = new Container();
194
+ const caption = new Label({ text: label });
195
+ caption.y = 10;
196
+ const control = new Checkbox({ checked: initial });
197
+ control.x = this.width_ - 28;
198
+ control.y = 8;
199
+ root.addChild(caption, control);
200
+ this.main.addChild(root);
201
+ control.onChange.add(write);
202
+ this.rows.push({
203
+ id,
204
+ root,
205
+ adjust: () => control.toggle(),
206
+ activate: () => control.toggle(),
207
+ refresh: () => control.setChecked(this.readToggle(id)),
208
+ });
209
+ }
210
+ readToggle(id) {
211
+ if (id === 'muted')
212
+ return this.settings.current.muted;
213
+ return this.settings.getCustom(id.slice('custom:'.length), false) === true;
214
+ }
215
+ addCustomRow(descriptor) {
216
+ const id = `custom:${descriptor.key}`;
217
+ if (descriptor.kind === 'boolean') {
218
+ const stored = this.settings.getCustom(descriptor.key, false);
219
+ this.addToggleRow(id, descriptor.label, stored === true, (v) => this.settings.setCustom(descriptor.key, v));
220
+ }
221
+ else if (descriptor.kind === 'number') {
222
+ const stored = this.settings.getCustom(descriptor.key, descriptor.min);
223
+ const initial = typeof stored === 'number' ? stored : descriptor.min;
224
+ const grid = descriptor.step ?? 0;
225
+ const keyStep = grid > 0 ? grid : (descriptor.max - descriptor.min) / 20;
226
+ const root = new Container();
227
+ const caption = new Label({ text: descriptor.label });
228
+ caption.y = 10;
229
+ const control = new Slider({
230
+ width: 140,
231
+ min: descriptor.min,
232
+ max: descriptor.max,
233
+ step: descriptor.step ?? 0,
234
+ value: initial,
235
+ });
236
+ control.x = this.width_ - 148;
237
+ control.y = 10;
238
+ root.addChild(caption, control);
239
+ this.main.addChild(root);
240
+ const show = () => {
241
+ caption.setText(`${descriptor.label}: ${control.value}`);
242
+ };
243
+ show();
244
+ control.onChange.add((value) => {
245
+ this.settings.setCustom(descriptor.key, value);
246
+ show();
247
+ });
248
+ this.rows.push({
249
+ id,
250
+ root,
251
+ adjust: (delta) => control.setValue(control.value + delta * keyStep),
252
+ activate: () => { },
253
+ refresh: () => {
254
+ const value = this.settings.getCustom(descriptor.key, descriptor.min);
255
+ control.setValue(typeof value === 'number' ? value : descriptor.min);
256
+ show();
257
+ },
258
+ });
259
+ }
260
+ else {
261
+ const options = descriptor.options;
262
+ const root = new Container();
263
+ const caption = new Label({ text: descriptor.label });
264
+ caption.y = 10;
265
+ const control = new Spinner({ min: 0, max: options.length - 1, step: 1, value: 0, wrap: true });
266
+ control.x = this.width_ - 32;
267
+ control.y = 2;
268
+ root.addChild(caption, control);
269
+ this.main.addChild(root);
270
+ const indexOf = (value) => {
271
+ const index = typeof value === 'string' ? options.indexOf(value) : -1;
272
+ return index >= 0 ? index : 0;
273
+ };
274
+ const show = () => {
275
+ caption.setText(`${descriptor.label}: ${options[control.value]}`);
276
+ };
277
+ control.setValue(indexOf(this.settings.getCustom(descriptor.key, options[0])));
278
+ show();
279
+ control.onChange.add((index) => {
280
+ this.settings.setCustom(descriptor.key, options[index]);
281
+ show();
282
+ });
283
+ this.rows.push({
284
+ id,
285
+ root,
286
+ adjust: (delta) => (delta > 0 ? control.increment() : control.decrement()),
287
+ activate: () => control.increment(),
288
+ refresh: () => {
289
+ control.setValue(indexOf(this.settings.getCustom(descriptor.key, options[0])));
290
+ show();
291
+ },
292
+ });
293
+ }
294
+ }
295
+ addActionRow(id, label, run) {
296
+ const root = new Container();
297
+ const control = new Button({ width: this.width_ - 16, height: 28, text: label, onClick: run });
298
+ control.x = 8;
299
+ control.y = 4;
300
+ root.addChild(control);
301
+ this.main.addChild(root);
302
+ this.rows.push({ id, root, adjust: () => { }, activate: run, refresh: () => { } });
303
+ }
304
+ openRebind() {
305
+ const actions = this.actionsOption ?? Object.keys(this.settings.current.bindings);
306
+ const hint = new Label({ text: this.labels.rebindHint });
307
+ const list = new RebindScreen({
308
+ width: this.width_,
309
+ height: Math.max(1, actions.length) * this.rowHeight,
310
+ actions,
311
+ onConflict: this.onConflict,
312
+ });
313
+ hint.y = 0;
314
+ list.y = this.rowHeight;
315
+ this.rebindLayer.addChild(hint, list);
316
+ this.rebindLayer.visible = true;
317
+ this.main.visible = false;
318
+ this.highlight.visible = false;
319
+ this.rebind = list;
320
+ }
321
+ closeRebind() {
322
+ //captures bypass Settings straight into Input, so sync them back on exit
323
+ this.settings.setBindings(Input.exportBindings());
324
+ this.rebindLayer.removeChildren();
325
+ this.rebind?.destroy();
326
+ this.rebind = null;
327
+ this.rebindLayer.visible = false;
328
+ this.main.visible = true;
329
+ this.highlight.visible = true;
330
+ }
331
+ destroy(options) {
332
+ themeChanged.remove(this.themeListener);
333
+ this.rebind?.destroy();
334
+ this.rebind = null;
335
+ super.destroy(options);
336
+ }
337
+ }
338
+ //# sourceMappingURL=SettingsScreen.js.map