@caperjs/core 0.2.4 → 0.3.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/build/index.mjs +3 -1
- package/build/internal/discovery.mjs +1 -1
- package/build/internal/manifest.mjs +8 -3
- package/build/internal/validate.mjs +3 -1
- package/build/plugins/assetTypes.mjs +4 -4
- package/build/plugins/caperConfig.mjs +12 -5
- package/build/plugins/lists.mjs +29 -9
- package/cli/create.mjs +57 -6
- package/cli/create.test.mjs +36 -0
- package/cli/update.mjs +1 -1
- package/lib/{CaptionsPlugin-WPAcfAkx.js → CaptionsPlugin-BLbLl3WB.js} +3 -3
- package/lib/{CaptionsPlugin-WPAcfAkx.js.map → CaptionsPlugin-BLbLl3WB.js.map} +1 -1
- package/lib/{DataAdapter-DS-twowe.js → DataAdapter-aHSNM_26.js} +12 -7
- package/lib/DataAdapter-aHSNM_26.js.map +1 -0
- package/lib/{DebugRenderer-D1ryrRYx.js → DebugRenderer-8LjHtBzA.js} +2 -2
- package/lib/{DebugRenderer-D1ryrRYx.js.map → DebugRenderer-8LjHtBzA.js.map} +1 -1
- package/lib/{DevToolsPlugin-DH7zX7HU.js → DevToolsPlugin-Vrk6aOtS.js} +2 -2
- package/lib/{DevToolsPlugin-DH7zX7HU.js.map → DevToolsPlugin-Vrk6aOtS.js.map} +1 -1
- package/lib/{GSAPPlugin-BBtRjUqe.js → GSAPPlugin-Hm3F4zdg.js} +3 -3
- package/lib/{GSAPPlugin-BBtRjUqe.js.map → GSAPPlugin-Hm3F4zdg.js.map} +1 -1
- package/lib/{LayoutPlugin-Co6vcoV4.js → LayoutPlugin-Dpgeawfw.js} +4 -4
- package/lib/{LayoutPlugin-Co6vcoV4.js.map → LayoutPlugin-Dpgeawfw.js.map} +1 -1
- package/lib/{SpinePlugin-uN641Hmk.js → SpinePlugin-B08yAw1S.js} +690 -793
- package/lib/SpinePlugin-B08yAw1S.js.map +1 -0
- package/lib/{StatsPlugin-vZQRm6wh.js → StatsPlugin-D2Xa5j97.js} +5 -2
- package/lib/StatsPlugin-D2Xa5j97.js.map +1 -0
- package/lib/{VoiceOverPlugin-KlZdGj1G.js → VoiceOverPlugin-qtFlqpRn.js} +6 -5
- package/lib/VoiceOverPlugin-qtFlqpRn.js.map +1 -0
- package/lib/caper.mjs +23 -10
- package/lib/caper.mjs.map +1 -1
- package/lib/core/Application.d.ts +8 -3
- package/lib/core/Application.d.ts.map +1 -1
- package/lib/core/Application.test.d.ts +2 -0
- package/lib/core/Application.test.d.ts.map +1 -0
- package/lib/core/config.d.ts +6 -1
- package/lib/core/config.d.ts.map +1 -1
- package/lib/core/config.test.d.ts +2 -0
- package/lib/core/config.test.d.ts.map +1 -0
- package/lib/core/interfaces/IApplication.d.ts +6 -6
- package/lib/core/interfaces/IApplication.d.ts.map +1 -1
- package/lib/core/interfaces/ICoreSignals.d.ts +2 -1
- package/lib/core/interfaces/ICoreSignals.d.ts.map +1 -1
- package/lib/display/Camera.test.d.ts +2 -0
- package/lib/display/Camera.test.d.ts.map +1 -0
- package/lib/display/Entity.d.ts +2 -2
- package/lib/display/SceneTransition.test.d.ts +1 -0
- package/lib/display/SceneTransition.test.d.ts.map +1 -0
- package/lib/display/SpineAnimation.d.ts +3 -3
- package/lib/mixins/factory/methods.d.ts.map +1 -1
- package/lib/plugins/AssetsPlugin.d.ts.map +1 -1
- package/lib/plugins/AssetsPlugin.test.d.ts +2 -0
- package/lib/plugins/AssetsPlugin.test.d.ts.map +1 -0
- package/lib/plugins/DataAdapter.d.ts.map +1 -1
- package/lib/plugins/DataAdapter.test.d.ts +2 -0
- package/lib/plugins/DataAdapter.test.d.ts.map +1 -0
- package/lib/plugins/FullScreenPlugin.d.ts.map +1 -1
- package/lib/plugins/FullScreenPlugin.test.d.ts +2 -0
- package/lib/plugins/FullScreenPlugin.test.d.ts.map +1 -0
- package/lib/plugins/GSAPPlugin.d.ts +0 -2
- package/lib/plugins/GSAPPlugin.d.ts.map +1 -1
- package/lib/plugins/GSAPPlugin.test.d.ts +2 -0
- package/lib/plugins/GSAPPlugin.test.d.ts.map +1 -0
- package/lib/plugins/LookupPlugin.d.ts +6 -1
- package/lib/plugins/LookupPlugin.d.ts.map +1 -1
- package/lib/plugins/LookupPlugin.test.d.ts +2 -0
- package/lib/plugins/LookupPlugin.test.d.ts.map +1 -0
- package/lib/plugins/PopupManagerPlugin.d.ts.map +1 -1
- package/lib/plugins/PopupManagerPlugin.test.d.ts +1 -0
- package/lib/plugins/PopupManagerPlugin.test.d.ts.map +1 -0
- package/lib/plugins/SceneManagerPlugin.d.ts +1 -0
- package/lib/plugins/SceneManagerPlugin.d.ts.map +1 -1
- package/lib/plugins/SceneManagerPlugin.test.d.ts +2 -0
- package/lib/plugins/SceneManagerPlugin.test.d.ts.map +1 -0
- package/lib/plugins/StatsPlugin.d.ts +1 -0
- package/lib/plugins/StatsPlugin.d.ts.map +1 -1
- package/lib/plugins/WebEventsPlugin.d.ts.map +1 -1
- package/lib/plugins/actions/ActionsPlugin.test.d.ts +2 -0
- package/lib/plugins/actions/ActionsPlugin.test.d.ts.map +1 -0
- package/lib/plugins/audio/AudioChannel.d.ts.map +1 -1
- package/lib/plugins/audio/AudioManagerPlugin.d.ts.map +1 -1
- package/lib/plugins/audio/AudioManagerPlugin.test.d.ts +2 -0
- package/lib/plugins/audio/AudioManagerPlugin.test.d.ts.map +1 -0
- package/lib/plugins/audio/VoiceOverPlugin.d.ts.map +1 -1
- package/lib/plugins/captions/CaptionsPlugin.test.d.ts +2 -0
- package/lib/plugins/captions/CaptionsPlugin.test.d.ts.map +1 -0
- package/lib/plugins/focus/FocusManagerPlugin.d.ts.map +1 -1
- package/lib/plugins/focus/FocusManagerPlugin.test.d.ts +2 -0
- package/lib/plugins/focus/FocusManagerPlugin.test.d.ts.map +1 -0
- package/lib/plugins/input/AbstractControls.d.ts +5 -0
- package/lib/plugins/input/AbstractControls.d.ts.map +1 -1
- package/lib/plugins/input/Controls.test.d.ts +2 -0
- package/lib/plugins/input/Controls.test.d.ts.map +1 -0
- package/lib/plugins/input/keyboard/KeyboardContols.d.ts +5 -0
- package/lib/plugins/input/keyboard/KeyboardContols.d.ts.map +1 -1
- package/lib/plugins/input/touch/VirtualControls.d.ts +6 -0
- package/lib/plugins/input/touch/VirtualControls.d.ts.map +1 -1
- package/lib/plugins/input/touch/VirtualControls.test.d.ts +2 -0
- package/lib/plugins/input/touch/VirtualControls.test.d.ts.map +1 -0
- package/lib/{registries-BLrxALZ0.js → registries-m2vKTIih.js} +156 -101
- package/lib/registries-m2vKTIih.js.map +1 -0
- package/lib/store/Store.d.ts.map +1 -1
- package/lib/ui/Button.d.ts.map +1 -1
- package/lib/ui/UICanvas.d.ts +10 -1
- package/lib/ui/UICanvas.d.ts.map +1 -1
- package/lib/ui/UICanvas.test.d.ts +0 -1
- package/lib/ui/UICanvas.test.d.ts.map +1 -1
- package/lib/utils/color.d.ts.map +1 -1
- package/lib/utils/color.test.d.ts +2 -0
- package/lib/utils/color.test.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/core/Application.test.ts +168 -0
- package/src/core/Application.ts +37 -27
- package/src/core/config.test.ts +79 -0
- package/src/core/config.ts +28 -6
- package/src/core/interfaces/IApplication.ts +6 -6
- package/src/core/interfaces/ICoreSignals.ts +2 -0
- package/src/display/Camera.test.ts +40 -0
- package/src/display/Camera.ts +3 -3
- package/src/display/Entity.ts +2 -2
- package/src/display/SceneTransition.test.ts +58 -0
- package/src/display/SceneTransition.ts +1 -1
- package/src/display/SpineAnimation.ts +4 -4
- package/src/mixins/factory/methods.ts +0 -1
- package/src/plugins/AssetsPlugin.test.ts +48 -0
- package/src/plugins/AssetsPlugin.ts +3 -1
- package/src/plugins/DataAdapter.test.ts +32 -0
- package/src/plugins/DataAdapter.ts +5 -1
- package/src/plugins/FullScreenPlugin.test.ts +48 -0
- package/src/plugins/FullScreenPlugin.ts +5 -1
- package/src/plugins/GSAPPlugin.test.ts +31 -0
- package/src/plugins/GSAPPlugin.ts +1 -4
- package/src/plugins/LookupPlugin.test.ts +42 -0
- package/src/plugins/LookupPlugin.ts +15 -3
- package/src/plugins/PopupManagerPlugin.test.ts +53 -0
- package/src/plugins/PopupManagerPlugin.ts +5 -3
- package/src/plugins/SceneManagerPlugin.test.ts +76 -0
- package/src/plugins/SceneManagerPlugin.ts +21 -11
- package/src/plugins/StatsPlugin.ts +8 -0
- package/src/plugins/WebEventsPlugin.test.ts +32 -0
- package/src/plugins/WebEventsPlugin.ts +2 -0
- package/src/plugins/actions/ActionsPlugin.test.ts +68 -0
- package/src/plugins/actions/ActionsPlugin.ts +5 -5
- package/src/plugins/audio/AudioChannel.test.ts +19 -0
- package/src/plugins/audio/AudioChannel.ts +8 -1
- package/src/plugins/audio/AudioManagerPlugin.test.ts +41 -0
- package/src/plugins/audio/AudioManagerPlugin.ts +3 -2
- package/src/plugins/audio/VoiceOverPlugin.ts +1 -0
- package/src/plugins/captions/CaptionsPlugin.test.ts +40 -0
- package/src/plugins/captions/CaptionsPlugin.ts +1 -1
- package/src/plugins/focus/FocusManagerPlugin.test.ts +71 -0
- package/src/plugins/focus/FocusManagerPlugin.ts +7 -0
- package/src/plugins/input/AbstractControls.ts +6 -0
- package/src/plugins/input/Controls.test.ts +149 -0
- package/src/plugins/input/keyboard/KeyboardContols.ts +29 -5
- package/src/plugins/input/touch/VirtualControls.test.ts +38 -0
- package/src/plugins/input/touch/VirtualControls.ts +44 -10
- package/src/plugins/spine/SpinePlugin.ts +2 -2
- package/src/store/Store.test.ts +15 -0
- package/src/store/Store.ts +4 -1
- package/src/ui/Button.test.ts +12 -0
- package/src/ui/Button.ts +1 -0
- package/src/ui/UICanvas.test.ts +109 -6
- package/src/ui/UICanvas.ts +41 -3
- package/src/utils/color.test.ts +16 -0
- package/src/utils/color.ts +2 -1
- package/src/utils/platform.ts +1 -1
- package/src/version.ts +1 -1
- package/lib/DataAdapter-DS-twowe.js.map +0 -1
- package/lib/SpinePlugin-uN641Hmk.js.map +0 -1
- package/lib/StatsPlugin-vZQRm6wh.js.map +0 -1
- package/lib/VoiceOverPlugin-KlZdGj1G.js.map +0 -1
- package/lib/registries-BLrxALZ0.js.map +0 -1
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { bindAllMethods } from '../../../utils';
|
|
1
|
+
import { bindAllMethods, Logger } from '../../../utils';
|
|
2
2
|
import type { ControlsActionMap, JoystickControlsScheme, TouchControlsMap, TouchControlsScheme } from '../types';
|
|
3
3
|
|
|
4
4
|
import { JoystickDirection } from '..';
|
|
5
5
|
import { Application } from '../../../core/Application';
|
|
6
6
|
import { WithSignals } from '../../../mixins/signals';
|
|
7
|
+
import type { SignalConnection } from '../../../signals';
|
|
7
8
|
import type { IButton, IJoystick } from '../../../ui';
|
|
8
9
|
import type { Action } from '../../actions';
|
|
9
10
|
import { AbstractControls } from '../AbstractControls';
|
|
@@ -20,6 +21,8 @@ export class VirtualControls extends WithSignals(AbstractControls) {
|
|
|
20
21
|
private _activeButtonDownIds: Map<string, Action> = new Map();
|
|
21
22
|
private _activeButtonUpIds: Map<string, Action> = new Map();
|
|
22
23
|
private _combinationsMap: Map<string[], Action> = new Map();
|
|
24
|
+
private _buttonConnections: Map<IButton, SignalConnection[]> = new Map();
|
|
25
|
+
private _warnedMissingActions: Set<string> = new Set();
|
|
23
26
|
|
|
24
27
|
constructor() {
|
|
25
28
|
super();
|
|
@@ -44,12 +47,14 @@ export class VirtualControls extends WithSignals(AbstractControls) {
|
|
|
44
47
|
if (!button || this._buttons.has(button)) {
|
|
45
48
|
return;
|
|
46
49
|
}
|
|
47
|
-
|
|
50
|
+
const connections = [
|
|
48
51
|
button.onDown.connect(() => this._handleButtonDown(button)),
|
|
49
52
|
button.onUp.connect(() => this._handleButtonUp(button)),
|
|
50
53
|
button.onUpOutside.connect(() => this._handleButtonUp(button)),
|
|
51
54
|
button.onDestroy.connect(() => this.removeButton(button)),
|
|
52
|
-
|
|
55
|
+
];
|
|
56
|
+
this.addSignalConnection(...connections);
|
|
57
|
+
this._buttonConnections.set(button, connections);
|
|
53
58
|
this._buttons.add(button);
|
|
54
59
|
}
|
|
55
60
|
|
|
@@ -57,10 +62,10 @@ export class VirtualControls extends WithSignals(AbstractControls) {
|
|
|
57
62
|
if (!button || !this._buttons.has(button)) {
|
|
58
63
|
return;
|
|
59
64
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
// signals disconnect by identity — disconnect the connections made in addButton
|
|
66
|
+
this._buttonConnections.get(button)?.forEach((connection) => connection.disconnect());
|
|
67
|
+
this._buttonConnections.delete(button);
|
|
68
|
+
this._singleDownButtons.delete(button.id!);
|
|
64
69
|
this._buttons.delete(button);
|
|
65
70
|
}
|
|
66
71
|
|
|
@@ -69,7 +74,7 @@ export class VirtualControls extends WithSignals(AbstractControls) {
|
|
|
69
74
|
this._buttonDownMap = scheme.down || {};
|
|
70
75
|
this._buttonUpMap = scheme.up || {};
|
|
71
76
|
this._joystickMap = scheme.joystick || {};
|
|
72
|
-
this.app.signal.onActionContextChanged.connect(this._sortActions);
|
|
77
|
+
this.addSignalConnection(this.app.signal.onActionContextChanged.connect(this._sortActions));
|
|
73
78
|
this._sortActions();
|
|
74
79
|
}
|
|
75
80
|
|
|
@@ -77,13 +82,18 @@ export class VirtualControls extends WithSignals(AbstractControls) {
|
|
|
77
82
|
this.app.ticker.add(this._update);
|
|
78
83
|
}
|
|
79
84
|
|
|
85
|
+
public destroy(): void {
|
|
86
|
+
this.app.ticker.remove(this._update);
|
|
87
|
+
super.destroy();
|
|
88
|
+
}
|
|
89
|
+
|
|
80
90
|
isActionActive(action: Action): boolean {
|
|
81
91
|
const buttonAction = this.scheme['down']?.[action] ?? null;
|
|
82
92
|
if (buttonAction) {
|
|
83
93
|
if (Array.isArray(buttonAction)) {
|
|
84
|
-
return this.
|
|
94
|
+
return buttonAction.some((input) => this._isInputActive(input));
|
|
85
95
|
} else {
|
|
86
|
-
return this.
|
|
96
|
+
return this._isInputActive(buttonAction);
|
|
87
97
|
}
|
|
88
98
|
} else {
|
|
89
99
|
const joystickAction = this.scheme['joystick']?.[action] ?? null;
|
|
@@ -98,6 +108,22 @@ export class VirtualControls extends WithSignals(AbstractControls) {
|
|
|
98
108
|
return false;
|
|
99
109
|
}
|
|
100
110
|
|
|
111
|
+
/** A single button id, or a `+` separated combination requiring every input to be down. */
|
|
112
|
+
private _isInputActive(input: string): boolean {
|
|
113
|
+
if (input.includes('+')) {
|
|
114
|
+
return input.split('+').every((id) => this._singleDownButtons.has(id) || this._joystick?.direction === id);
|
|
115
|
+
}
|
|
116
|
+
return this._singleDownButtons.has(input);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private _warnMissingAction(key: string) {
|
|
120
|
+
if (this._warnedMissingActions.has(key)) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
this._warnedMissingActions.add(key);
|
|
124
|
+
Logger.warn(`VirtualControls: scheme references unknown action "${key}"`);
|
|
125
|
+
}
|
|
126
|
+
|
|
101
127
|
private _sortActions() {
|
|
102
128
|
const actions = this.app.actionsPlugin.getActions();
|
|
103
129
|
this._combinations = [];
|
|
@@ -110,6 +136,10 @@ export class VirtualControls extends WithSignals(AbstractControls) {
|
|
|
110
136
|
buttons.forEach((key) => {
|
|
111
137
|
const item = this._buttonDownMap[key];
|
|
112
138
|
const action = actions[key];
|
|
139
|
+
if (!action) {
|
|
140
|
+
this._warnMissingAction(key);
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
113
143
|
if (
|
|
114
144
|
action.context !== '*' &&
|
|
115
145
|
action.context !== this.app.actionContext &&
|
|
@@ -141,6 +171,10 @@ export class VirtualControls extends WithSignals(AbstractControls) {
|
|
|
141
171
|
buttons.forEach((key) => {
|
|
142
172
|
const item = this._buttonUpMap[key];
|
|
143
173
|
const action = actions[key];
|
|
174
|
+
if (!action) {
|
|
175
|
+
this._warnMissingAction(key);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
144
178
|
if (
|
|
145
179
|
action.context !== '*' &&
|
|
146
180
|
action.context !== this.app.actionContext &&
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { extensions } from 'pixi.js';
|
|
2
2
|
import { Plugin } from '../Plugin';
|
|
3
|
-
import { Spine, spineLoaderExtension,
|
|
3
|
+
import { Spine, spineLoaderExtension, spineTextureAtlasLoader } from './pixi-spine';
|
|
4
4
|
|
|
5
5
|
export class SpinePlugin extends Plugin {
|
|
6
6
|
public readonly id = 'SpinePlugin';
|
|
@@ -8,7 +8,7 @@ export class SpinePlugin extends Plugin {
|
|
|
8
8
|
public async initialize() {
|
|
9
9
|
extensions.add(spineTextureAtlasLoader);
|
|
10
10
|
extensions.add(spineLoaderExtension);
|
|
11
|
-
|
|
11
|
+
// SpinePipe and DarkTintBatcher register themselves at module scope in ./pixi-spine
|
|
12
12
|
(window as any).Spine = Spine;
|
|
13
13
|
}
|
|
14
14
|
}
|
package/src/store/Store.test.ts
CHANGED
|
@@ -143,4 +143,19 @@ describe('Store', () => {
|
|
|
143
143
|
expect(saveA).toHaveBeenCalledWith('k', 1);
|
|
144
144
|
expect(saveB).toHaveBeenCalledWith('k', 1);
|
|
145
145
|
});
|
|
146
|
+
|
|
147
|
+
it('save with "*" anywhere in an adapter array fans out to every storage-capable plugin', async () => {
|
|
148
|
+
const saveA = vi.fn(async () => 'a');
|
|
149
|
+
const saveB = vi.fn(async () => 'b');
|
|
150
|
+
const store = new Store().initialize(
|
|
151
|
+
makeApp([
|
|
152
|
+
{ id: 'a', save: saveA, load: async () => undefined },
|
|
153
|
+
{ id: 'b', save: saveB, load: async () => undefined },
|
|
154
|
+
{ id: 'c' }, // not storage-capable → must be skipped
|
|
155
|
+
]),
|
|
156
|
+
);
|
|
157
|
+
await store.save(['a', '*'] as never, 'k', 1);
|
|
158
|
+
expect(saveA).toHaveBeenCalledWith('k', 1);
|
|
159
|
+
expect(saveB).toHaveBeenCalledWith('k', 1);
|
|
160
|
+
});
|
|
146
161
|
});
|
package/src/store/Store.ts
CHANGED
|
@@ -126,7 +126,10 @@ export class Store implements IStore {
|
|
|
126
126
|
keys = adapterId as string[] | Partial<AdapterSaveConfig>[];
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
|
|
129
|
+
const hasStarKey = keys.some(
|
|
130
|
+
(k) => (k as string) === '*' || (k as Partial<AdapterSaveConfig>)?.adapterId === '*',
|
|
131
|
+
);
|
|
132
|
+
if (hasStarKey) {
|
|
130
133
|
keys = this.allAdapterIds();
|
|
131
134
|
}
|
|
132
135
|
|
package/src/ui/Button.test.ts
CHANGED
|
@@ -147,6 +147,18 @@ describe('Button press-state machine', () => {
|
|
|
147
147
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
148
148
|
});
|
|
149
149
|
|
|
150
|
+
it('dispatches a click action with no data without throwing, attaching the button to the payload', () => {
|
|
151
|
+
const button = makeButton();
|
|
152
|
+
(button as any).config.actions = { click: { id: 'foo' } };
|
|
153
|
+
const action = vi.fn();
|
|
154
|
+
Object.defineProperty(button, 'app', { value: { action }, configurable: true });
|
|
155
|
+
|
|
156
|
+
(button as any).handlePointerDown({ pointerId: 1 });
|
|
157
|
+
expect(() => (button as any).handleClick({ pointerId: 1 })).not.toThrow();
|
|
158
|
+
|
|
159
|
+
expect(action).toHaveBeenCalledWith('foo', { button });
|
|
160
|
+
});
|
|
161
|
+
|
|
150
162
|
it('still clicks for a synthetic keyboard activation (no pointerId)', () => {
|
|
151
163
|
const button = makeButton();
|
|
152
164
|
const onClick = vi.fn();
|
package/src/ui/Button.ts
CHANGED
package/src/ui/UICanvas.test.ts
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { Container as PIXIContainer } from 'pixi.js';
|
|
2
3
|
|
|
3
4
|
// UICanvas transitively imports Application → Pixi display graph. Stub it — the
|
|
4
|
-
// pure padding helper needs none of it.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
// pure padding helper needs none of it. The "element reordering" describe block
|
|
6
|
+
// below constructs real UICanvas instances, which internally build FlexContainer
|
|
7
|
+
// and Container children; those classes read `Application.getInstance()` for
|
|
8
|
+
// their own `app` getter (independent of UICanvas's own `app` override), so the
|
|
9
|
+
// stub needs a minimal shape (`config.useLayout`) satisfying their constructors too.
|
|
10
|
+
vi.mock('../core/Application', async () => {
|
|
11
|
+
const { Signal } = await import('../signals');
|
|
12
|
+
const fakeApp = {
|
|
13
|
+
config: { useLayout: true },
|
|
14
|
+
renderer: { layout: undefined },
|
|
15
|
+
ticker: { addOnce: () => {} },
|
|
16
|
+
onResize: new Signal(),
|
|
17
|
+
};
|
|
18
|
+
return { Application: { getInstance: () => fakeApp } };
|
|
19
|
+
});
|
|
8
20
|
|
|
9
|
-
|
|
21
|
+
// Registers the factory method table `Factory()` (part of Container's mixin
|
|
22
|
+
// chain) reads from — must be imported before any display/ui class is
|
|
23
|
+
// constructed. See importOrder.containerFirst.test.ts.
|
|
24
|
+
import '../mixins/factory/const';
|
|
25
|
+
import { Signal } from '../signals';
|
|
26
|
+
import { computeEffectivePadding, UICanvas } from './UICanvas';
|
|
10
27
|
|
|
11
28
|
const zero = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
12
29
|
|
|
@@ -33,3 +50,89 @@ describe('computeEffectivePadding', () => {
|
|
|
33
50
|
expect(computeEffectivePadding(base, safeArea).top).toBe(54);
|
|
34
51
|
});
|
|
35
52
|
});
|
|
53
|
+
|
|
54
|
+
describe('UICanvas element reordering', () => {
|
|
55
|
+
let app: any;
|
|
56
|
+
let originalAppDescriptor: PropertyDescriptor | undefined;
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
app = {
|
|
60
|
+
config: { useLayout: true },
|
|
61
|
+
size: { width: 800, height: 600 },
|
|
62
|
+
onResize: new Signal<() => void>(),
|
|
63
|
+
ticker: { addOnce: vi.fn() },
|
|
64
|
+
};
|
|
65
|
+
originalAppDescriptor = Object.getOwnPropertyDescriptor(UICanvas.prototype, 'app');
|
|
66
|
+
Object.defineProperty(UICanvas.prototype, 'app', { get: () => app, configurable: true });
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
if (originalAppDescriptor) {
|
|
71
|
+
Object.defineProperty(UICanvas.prototype, 'app', originalAppDescriptor);
|
|
72
|
+
} else {
|
|
73
|
+
delete (UICanvas.prototype as any).app;
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
function addThree(canvas: UICanvas) {
|
|
78
|
+
const a = new PIXIContainer({ label: 'a' });
|
|
79
|
+
const b = new PIXIContainer({ label: 'b' });
|
|
80
|
+
const c = new PIXIContainer({ label: 'c' });
|
|
81
|
+
canvas.addElement(a, { align: 'top left' });
|
|
82
|
+
canvas.addElement(b, { align: 'top left' });
|
|
83
|
+
canvas.addElement(c, { align: 'top left' });
|
|
84
|
+
const container = (canvas as any)._childMap.get(a);
|
|
85
|
+
return { a, b, c, container };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
it('reorderElement moves an element within its region', () => {
|
|
89
|
+
const canvas = new UICanvas({});
|
|
90
|
+
const { a, b, c, container } = addThree(canvas);
|
|
91
|
+
|
|
92
|
+
canvas.reorderElement(a, 2);
|
|
93
|
+
|
|
94
|
+
expect(container.children.indexOf(a)).toBeGreaterThanOrEqual(0);
|
|
95
|
+
expect([b, c, a].every((child, i) => container.children[i] === child)).toBe(true);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('bringToFront moves an element to the last index in its region', () => {
|
|
99
|
+
const canvas = new UICanvas({});
|
|
100
|
+
const { a, b, c, container } = addThree(canvas);
|
|
101
|
+
|
|
102
|
+
canvas.bringToFront(a);
|
|
103
|
+
|
|
104
|
+
expect([b, c, a].every((child, i) => container.children[i] === child)).toBe(true);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('sendToBack moves an element to the first index in its region', () => {
|
|
108
|
+
const canvas = new UICanvas({});
|
|
109
|
+
const { a, b, c, container } = addThree(canvas);
|
|
110
|
+
|
|
111
|
+
canvas.sendToBack(c);
|
|
112
|
+
|
|
113
|
+
expect([c, a, b].every((child, i) => container.children[i] === child)).toBe(true);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('reorderElement throws when the child was never added via addElement', () => {
|
|
117
|
+
const canvas = new UICanvas({});
|
|
118
|
+
const neverAdded = new PIXIContainer({ label: 'never-added' });
|
|
119
|
+
|
|
120
|
+
expect(() => canvas.reorderElement(neverAdded, 0)).toThrow(/Cannot reorder element/);
|
|
121
|
+
expect(() => canvas.reorderElement(neverAdded, 0)).toThrow(/not added via addElement/);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('bringToFront and sendToBack throw when the child was never added via addElement', () => {
|
|
125
|
+
const canvas = new UICanvas({});
|
|
126
|
+
const neverAdded = new PIXIContainer({ label: 'never-added' });
|
|
127
|
+
|
|
128
|
+
expect(() => canvas.bringToFront(neverAdded)).toThrow(/Cannot reorder element/);
|
|
129
|
+
expect(() => canvas.sendToBack(neverAdded)).toThrow(/Cannot reorder element/);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it('setChildIndex throws a guard error instead of the old Pixi passthrough', () => {
|
|
133
|
+
const canvas = new UICanvas({});
|
|
134
|
+
const { a } = addThree(canvas);
|
|
135
|
+
|
|
136
|
+
expect(() => canvas.setChildIndex(a, 0)).toThrow(/Do not call setChildIndex/);
|
|
137
|
+
});
|
|
138
|
+
});
|
package/src/ui/UICanvas.ts
CHANGED
|
@@ -490,9 +490,11 @@ export class UICanvas extends _UICanvas {
|
|
|
490
490
|
/**
|
|
491
491
|
* Sets the index of the child in the container
|
|
492
492
|
*/
|
|
493
|
-
public setChildIndex = <U extends PIXIContainer>(
|
|
494
|
-
|
|
495
|
-
|
|
493
|
+
public setChildIndex = <U extends PIXIContainer>(_child: U, _index: number): void => {
|
|
494
|
+
throw new Error(
|
|
495
|
+
`UICanvas: Do not call setChildIndex() directly. Use reorderElement(child, index), bringToFront(child), or sendToBack(child) instead.\n` +
|
|
496
|
+
`Example: uiCanvas.reorderElement(myChild, 0)`,
|
|
497
|
+
);
|
|
496
498
|
};
|
|
497
499
|
|
|
498
500
|
/**
|
|
@@ -668,6 +670,42 @@ export class UICanvas extends _UICanvas {
|
|
|
668
670
|
return child;
|
|
669
671
|
}
|
|
670
672
|
|
|
673
|
+
/**
|
|
674
|
+
* Reorders an element within its region container, changing its position in
|
|
675
|
+
* the flex layout and paint order.
|
|
676
|
+
*/
|
|
677
|
+
public reorderElement<U extends PIXIContainer>(child: U, index: number): U {
|
|
678
|
+
const container = this._childMap.get(child);
|
|
679
|
+
if (!container) {
|
|
680
|
+
throw new Error(
|
|
681
|
+
`UICanvas: Cannot reorder element — it was not added via addElement().\n` +
|
|
682
|
+
`Child label: "${child.label ?? '(unlabeled)'}"`,
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
container.setChildIndex(child, index);
|
|
687
|
+
this._updateLayout();
|
|
688
|
+
return child;
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
/** Moves an element to the last index (painted/laid-out last) within its region. */
|
|
692
|
+
public bringToFront<U extends PIXIContainer>(child: U): U {
|
|
693
|
+
const container = this._childMap.get(child);
|
|
694
|
+
if (!container) {
|
|
695
|
+
throw new Error(
|
|
696
|
+
`UICanvas: Cannot reorder element — it was not added via addElement().\n` +
|
|
697
|
+
`Child label: "${child.label ?? '(unlabeled)'}"`,
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
return this.reorderElement(child, container.children.length - 1);
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/** Moves an element to the first index (painted/laid-out first) within its region. */
|
|
705
|
+
public sendToBack<U extends PIXIContainer>(child: U): U {
|
|
706
|
+
return this.reorderElement(child, 0);
|
|
707
|
+
}
|
|
708
|
+
|
|
671
709
|
private _childAdded(child: PIXIContainer) {
|
|
672
710
|
if (this.config.autoLayoutChildren) {
|
|
673
711
|
if (!child.layout) {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { Color } from './color';
|
|
3
|
+
|
|
4
|
+
describe('Color.rgbToHexString', () => {
|
|
5
|
+
it('rounds fractional rgb components to a valid 2-digit hex pair', () => {
|
|
6
|
+
expect(Color.rgbToHexString(127.5)).toBe('80');
|
|
7
|
+
expect(Color.rgbToHexString(0.4)).toBe('00');
|
|
8
|
+
expect(Color.rgbToHexString(254.9)).toBe('ff');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('produces a valid 6-digit hex string from fractional rgb components', () => {
|
|
12
|
+
const hex = Color.rgbToFullHexString(127.5, 0.4, 254.9);
|
|
13
|
+
expect(hex).toMatch(/^[0-9a-f]{6}$/);
|
|
14
|
+
expect(hex).toBe('8000ff');
|
|
15
|
+
});
|
|
16
|
+
});
|
package/src/utils/color.ts
CHANGED
|
@@ -64,7 +64,8 @@ export class Color {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
public static rgbToHexString(pNumber: number): string {
|
|
67
|
-
|
|
67
|
+
const clamped = Math.max(0, Math.min(255, Math.round(Number(pNumber))));
|
|
68
|
+
let hex = clamped.toString(16);
|
|
68
69
|
if (hex.length < 2) {
|
|
69
70
|
hex = '0' + hex;
|
|
70
71
|
}
|
package/src/utils/platform.ts
CHANGED
|
@@ -20,7 +20,7 @@ export const isRetina =
|
|
|
20
20
|
*/
|
|
21
21
|
export const isTouch: boolean =
|
|
22
22
|
typeof window !== 'undefined'
|
|
23
|
-
? 'ontouchstart' in window || navigator.maxTouchPoints > 0
|
|
23
|
+
? 'ontouchstart' in window || navigator.maxTouchPoints > 0
|
|
24
24
|
: false;
|
|
25
25
|
|
|
26
26
|
/**
|
package/src/version.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"DataAdapter-DS-twowe.js","names":[],"sources":["../src/plugins/DataAdapter.ts"],"sourcesContent":["import { Signal } from '../signals';\nimport { AppTypeOverrides, deepMerge, DeepPartial } from '../utils';\nimport { Plugin } from './Plugin';\n\nexport type DataSchema = {\n [key: string]: any;\n};\n\n/**\n * Interface for the options of the DataAdapter.\n */\nexport interface IDataAdapterOptions {\n initial: Partial<AppTypeOverrides['Data']>;\n /**\n * The namespace to use for the keys in local storage.\n */\n namespace: string;\n /**\n * Whether to override the data with the data from local storage.\n */\n overrideWithLocalStorage: boolean;\n /**\n * Whether to backup all data to local storage.\n */\n backupAll: boolean;\n /**\n * The keys to backup to local storage.\n */\n backupKeys: Array<keyof AppTypeOverrides['Data']>;\n}\n\nexport interface DataChangeSignalDetail {\n key?: any;\n value?: any;\n restore?: boolean;\n clear?: boolean;\n}\n\ntype DataAdapterType = AppTypeOverrides['Data'];\n\nexport interface IDataAdapter {\n get(): DataAdapterType;\n get<K extends keyof DataAdapterType>(key: K): DataAdapterType[K] | undefined;\n set<K extends keyof DataAdapterType>(key: K, data: DataAdapterType[K]): DataAdapterType;\n set(data: DeepPartial<DataAdapterType>, merge?: boolean): DataAdapterType;\n clear(): void;\n clear<K extends keyof DataAdapterType>(key: K): void;\n increment<\n K extends keyof DataAdapterType &\n { [P in keyof DataAdapterType]: DataAdapterType[P] extends number ? P : never }[keyof DataAdapterType],\n >(\n key: K,\n amount?: number,\n ): DataAdapterType[K];\n concat<\n K extends keyof DataAdapterType &\n { [P in keyof DataAdapterType]: DataAdapterType[P] extends any[] ? P : never }[keyof DataAdapterType],\n >(\n key: K,\n value: DataAdapterType[K] extends (infer E)[] ? E | E[] : never,\n ): DataAdapterType[K];\n append<\n K extends keyof DataAdapterType &\n { [P in keyof DataAdapterType]: DataAdapterType[P] extends string ? P : never }[keyof DataAdapterType],\n >(\n key: K,\n value: string,\n separator?: string,\n ): DataAdapterType[K];\n snapshot(): DataAdapterType;\n snapshot<K extends keyof DataAdapterType>(key?: K): DataAdapterType[K];\n onDataChange: Signal<(detail: DataChangeSignalDetail) => void>;\n clearOnResume?: boolean;\n}\n\nexport function defineData<T extends DataSchema>(data: T): T {\n return data;\n}\n\n/**\n * An in-memory game-data plugin with optional localStorage backup.\n *\n * Not `IStorageCapability` — its API (`get`/`set`/`increment`/...) is richer\n * than the generic save/load contract. Accessed directly via `app.data`.\n */\nexport class DataAdapter extends Plugin<Partial<IDataAdapterOptions>> implements IDataAdapter {\n public data: DataAdapterType;\n\n public onDataChange: Signal<(detail: DataChangeSignalDetail) => void> = new Signal();\n\n private backupKeys: Array<keyof DataAdapterType> = [];\n private backupAll: boolean = false;\n private namespace: string = '';\n private overrideWithLocalStorage: boolean = true;\n\n constructor(id: string = 'data') {\n super(id);\n }\n\n public getCoreSignals(): string[] {\n return ['onDataChange'];\n }\n\n destroy() {\n this.data = {} as DataAdapterType;\n }\n\n public initialize(options?: Partial<IDataAdapterOptions>): void {\n this.namespace = options?.namespace || this.app.appName;\n this.data = (options?.initial as DataAdapterType) || ({} as DataAdapterType);\n this.backupKeys = options?.backupKeys || [];\n this.backupAll = options?.backupAll || false;\n this.overrideWithLocalStorage = options?.overrideWithLocalStorage === false ? false : true;\n\n if (this.backupAll || this.backupKeys.length > 0) {\n if (this.overrideWithLocalStorage) {\n this.restoreFromLocalStorage(this.backupKeys);\n }\n this.backupToLocalStorage(this.backupKeys);\n }\n }\n\n set<K extends keyof DataAdapterType>(key: K, data: DataAdapterType[K]): DataAdapterType;\n set(data: DeepPartial<DataAdapterType>, merge?: boolean): DataAdapterType;\n set<K extends keyof DataAdapterType>(\n keyOrData: K | DeepPartial<DataAdapterType>,\n dataOrMerge?: DataAdapterType[K] | boolean,\n ): DataAdapterType {\n if (typeof keyOrData === 'string' || typeof keyOrData === 'number' || typeof keyOrData === 'symbol') {\n const key = keyOrData;\n const data = dataOrMerge as DataAdapterType[K];\n\n this.data[key] = data;\n if (this.backupAll || this.backupKeys.includes(key)) {\n this.backupToLocalStorage([key]);\n }\n this.onDataChange.emit({ key, value: data });\n return this.data;\n }\n\n const data = keyOrData as DeepPartial<DataAdapterType>;\n const merge = (dataOrMerge as boolean) ?? true;\n\n if (merge) {\n this.data = deepMerge({ ...this.data }, data);\n } else {\n this.data = data as DataAdapterType;\n }\n\n if (this.backupAll || this.backupKeys.length > 0) {\n this.backupToLocalStorage(this.backupKeys);\n }\n\n this.onDataChange.emit({\n key: Object.keys(data)?.length === 1 ? Object.keys(data)[0] : Object.keys(data),\n value: data[Object.keys(data)[0]] as DataAdapterType[keyof DataAdapterType],\n });\n\n return this.data;\n }\n\n get(): DataAdapterType;\n get<K extends keyof DataAdapterType>(key?: K): DataAdapterType[K] | undefined;\n get<K extends keyof DataAdapterType>(key?: K): DataAdapterType[K] | DataAdapterType | undefined {\n if (key === undefined) {\n return this.data;\n }\n return this.data[key];\n }\n\n increment<\n K extends keyof DataAdapterType &\n { [P in keyof DataAdapterType]: DataAdapterType[P] extends number ? P : never }[keyof DataAdapterType],\n >(key: K, amount: number = 1): DataAdapterType[K] {\n const currentValue = this.data[key] || 0;\n const newValue = currentValue + amount;\n this.set({ [key]: newValue } as DeepPartial<DataAdapterType>);\n return newValue as DataAdapterType[K];\n }\n\n concat<\n K extends keyof DataAdapterType &\n { [P in keyof DataAdapterType]: DataAdapterType[P] extends any[] ? P : never }[keyof DataAdapterType],\n >(key: K, value: DataAdapterType[K] extends (infer E)[] ? E | E[] : never): DataAdapterType[K] {\n const currentValue = this.data[key];\n const newValue = [...currentValue, ...(Array.isArray(value) ? value : [value])];\n this.set({ [key]: newValue } as DeepPartial<DataAdapterType>);\n return newValue as DataAdapterType[K];\n }\n\n append<\n K extends keyof DataAdapterType &\n { [P in keyof DataAdapterType]: DataAdapterType[P] extends string ? P : never }[keyof DataAdapterType],\n >(key: K, value: string, separator: string = ''): DataAdapterType[K] {\n const currentValue = this.data[key];\n let newValue = value;\n if ((currentValue as string)?.length > 0) {\n newValue = currentValue + separator + value;\n }\n this.set({ [key]: newValue } as DeepPartial<DataAdapterType>);\n return newValue as DataAdapterType[K];\n }\n\n clear(): void;\n clear<K extends keyof DataAdapterType>(key?: K): void {\n if (key === undefined) {\n this.data = {} as DataAdapterType;\n localStorage.clear();\n this.onDataChange.emit({ key, clear: true });\n } else {\n delete this.data[key];\n localStorage.removeItem(`${this.namespace}-${key as string}`);\n this.onDataChange.emit({ clear: true });\n }\n }\n\n snapshot(): DataAdapterType;\n snapshot<K extends keyof DataAdapterType>(key?: K): DataAdapterType[K];\n snapshot<K extends keyof DataAdapterType>(key?: K): DataAdapterType[K] | DataAdapterType {\n if (key === undefined) {\n return JSON.parse(JSON.stringify(this.data));\n }\n return JSON.parse(JSON.stringify(this.data[key]));\n }\n\n private backupToLocalStorage(keys?: Array<keyof DataAdapterType>): void {\n const dataToBackup =\n keys && keys?.length > 0 ? Object.fromEntries(keys.map((key) => [key, this.data[key]])) : this.data;\n\n for (const key in dataToBackup) {\n localStorage.setItem(`${this.namespace}-${key}`, JSON.stringify(dataToBackup[key]));\n }\n }\n\n private restoreFromLocalStorage(keys?: Array<keyof DataAdapterType>): void {\n const keysToRestore = keys && keys?.length > 0 ? keys : (Object.keys(this.data) as Array<keyof DataAdapterType>);\n keysToRestore.forEach((key) => {\n try {\n const loadedData = localStorage.getItem(`${this.namespace}-${String(key)}`);\n if (loadedData !== null) {\n try {\n const parsedData = JSON.parse(loadedData);\n this.data[key] = parsedData;\n } catch (parseError) {\n console.warn(\n `Failed to parse data for key \"${String(key)}\" in namespace \"${this.namespace}\". Using default value.`,\n );\n }\n }\n } catch (storageError) {\n console.error(\n `Error accessing localStorage for key \"${String(key)}\" in namespace \"${this.namespace}\":`,\n storageError,\n );\n }\n });\n this.onDataChange.emit({ restore: true });\n }\n}\n"],"mappings":"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"}
|