@simpludify/web 0.1.3 → 0.1.5
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 +5 -3
- package/dist/game.d.ts +10 -1
- package/dist/gamepad-bindings.d.ts +100 -0
- package/dist/gamepad-layout.d.ts +18 -0
- package/dist/gamepads.d.ts +79 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +633 -10
- package/dist/index.js.map +1 -1
- package/dist/renderer.d.ts +9 -1
- package/dist/style.css +3 -3
- package/dist/virtual-input-visual.d.ts +22 -0
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "./style.css";
|
|
2
|
-
import { ActionController, Aspect, Camera, CanvasButton as CanvasButton$1, CanvasUI, Engine, EngineError, FixedStepRunner, GeometryTilemapLayer, PointerRouting, Renderable, ReplayPlayer, Scene, ScenePointer, SceneState, Shape, TilemapLayer, Transform, canvasControlText, canvasHoverHighlight, canvasOpacity, canvasScrollbars, canvasSliderGeometry, cleanupAll, containsScreenPoint, createViewTransform, imageSize, intersectUI, isKeyCode, positive, screenPoint, screenToWorld, spriteFrameBounds, worldToScreen, wrapCanvasText } from "@simpludify/core";
|
|
2
|
+
import { ActionController, Aspect, Camera, CanvasButton as CanvasButton$1, CanvasUI, Engine, EngineError, EventBus, FixedStepRunner, GeometryTilemapLayer, Lifetime, PointerRouting, Renderable, ReplayPlayer, Scene, ScenePointer, SceneState, Shape, TilemapLayer, Transform, VirtualInput, canvasControlText, canvasHoverHighlight, canvasOpacity, canvasScrollbars, canvasSliderGeometry, cleanupAll, containsScreenPoint, createViewTransform, defineEvent, imageSize, intersectUI, isKeyCode, positive, screenPoint, screenToWorld, spriteFrameBounds, worldToScreen, wrapCanvasText } from "@simpludify/core";
|
|
3
3
|
import { Application, CanvasTextMetrics, Container, Graphics, Rectangle, RendererType, Sprite, Text, TextStyle, Texture } from "pixi.js";
|
|
4
4
|
export * from "@simpludify/core";
|
|
5
5
|
// packages/web/src/canvas-node.ts
|
|
@@ -17,6 +17,415 @@ function CanvasButton(text, click, style = {}) {
|
|
|
17
17
|
return click(signal);
|
|
18
18
|
}, style);
|
|
19
19
|
}
|
|
20
|
+
// packages/web/src/gamepad-bindings.ts
|
|
21
|
+
/** Standard mapping physical positions, not product-specific printed labels. */
|
|
22
|
+
var GamepadButton = Object.freeze({
|
|
23
|
+
South: 0,
|
|
24
|
+
East: 1,
|
|
25
|
+
West: 2,
|
|
26
|
+
North: 3,
|
|
27
|
+
LeftShoulder: 4,
|
|
28
|
+
RightShoulder: 5,
|
|
29
|
+
LeftTrigger: 6,
|
|
30
|
+
RightTrigger: 7,
|
|
31
|
+
Select: 8,
|
|
32
|
+
Start: 9,
|
|
33
|
+
LeftStick: 10,
|
|
34
|
+
RightStick: 11,
|
|
35
|
+
DPadUp: 12,
|
|
36
|
+
DPadDown: 13,
|
|
37
|
+
DPadLeft: 14,
|
|
38
|
+
DPadRight: 15,
|
|
39
|
+
Home: 16
|
|
40
|
+
});
|
|
41
|
+
var GamepadAxis = Object.freeze({
|
|
42
|
+
LeftX: 0,
|
|
43
|
+
LeftY: 1,
|
|
44
|
+
RightX: 2,
|
|
45
|
+
RightY: 3
|
|
46
|
+
});
|
|
47
|
+
var axis = (index) => Object.freeze({ axis: index });
|
|
48
|
+
/** Ready-to-use data; game Actions retain application-defined names. */
|
|
49
|
+
var GamepadBindings = Object.freeze({
|
|
50
|
+
LeftStick: Object.freeze({
|
|
51
|
+
x: axis(0),
|
|
52
|
+
y: axis(1)
|
|
53
|
+
}),
|
|
54
|
+
RightStick: Object.freeze({
|
|
55
|
+
x: axis(2),
|
|
56
|
+
y: axis(3)
|
|
57
|
+
}),
|
|
58
|
+
DPad: Object.freeze({
|
|
59
|
+
x: Object.freeze({
|
|
60
|
+
negative: Object.freeze([14]),
|
|
61
|
+
positive: Object.freeze([15])
|
|
62
|
+
}),
|
|
63
|
+
y: Object.freeze({
|
|
64
|
+
negative: Object.freeze([12]),
|
|
65
|
+
positive: Object.freeze([13])
|
|
66
|
+
})
|
|
67
|
+
}),
|
|
68
|
+
Move: Object.freeze({
|
|
69
|
+
x: Object.freeze({
|
|
70
|
+
axis: 0,
|
|
71
|
+
negative: Object.freeze([14]),
|
|
72
|
+
positive: Object.freeze([15])
|
|
73
|
+
}),
|
|
74
|
+
y: Object.freeze({
|
|
75
|
+
axis: 1,
|
|
76
|
+
negative: Object.freeze([12]),
|
|
77
|
+
positive: Object.freeze([13])
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
var fail = () => {
|
|
82
|
+
throw new EngineError("INVALID_GAMEPAD_BINDING", "Invalid gamepad binding");
|
|
83
|
+
};
|
|
84
|
+
function gamepadIndex(value) {
|
|
85
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) return fail();
|
|
86
|
+
return value;
|
|
87
|
+
}
|
|
88
|
+
function gamepadDeadZone(value) {
|
|
89
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value >= 1) return fail();
|
|
90
|
+
return value;
|
|
91
|
+
}
|
|
92
|
+
function record(value, keys) {
|
|
93
|
+
if (!value || typeof value !== "object" || Array.isArray(value) || Object.keys(value).some((k) => !keys.includes(k))) return fail();
|
|
94
|
+
return Object.fromEntries(Object.entries(value));
|
|
95
|
+
}
|
|
96
|
+
function axisBinding(value) {
|
|
97
|
+
const v = record(value, [
|
|
98
|
+
"axis",
|
|
99
|
+
"invert",
|
|
100
|
+
"negative",
|
|
101
|
+
"positive",
|
|
102
|
+
"deadZone"
|
|
103
|
+
]);
|
|
104
|
+
const indices = (value) => {
|
|
105
|
+
if (value === void 0) return Object.freeze([]);
|
|
106
|
+
if (!Array.isArray(value)) return fail();
|
|
107
|
+
return Object.freeze([...new Set(value.map(gamepadIndex))]);
|
|
108
|
+
};
|
|
109
|
+
if (v.invert !== void 0 && typeof v.invert !== "boolean") return fail();
|
|
110
|
+
return Object.freeze({
|
|
111
|
+
...v.axis === void 0 ? {} : { axis: gamepadIndex(v.axis) },
|
|
112
|
+
invert: v.invert ?? false,
|
|
113
|
+
negative: indices(v.negative),
|
|
114
|
+
positive: indices(v.positive),
|
|
115
|
+
...v.deadZone === void 0 ? {} : { deadZone: gamepadDeadZone(v.deadZone) }
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
function checkedGamepadBindings(actions, value) {
|
|
119
|
+
const v = record(value, Object.keys(actions));
|
|
120
|
+
return Object.freeze(Object.entries(v).map(([name, binding]) => {
|
|
121
|
+
if (!Object.hasOwn(actions, name)) return fail();
|
|
122
|
+
const action = actions[name];
|
|
123
|
+
if (!action) return fail();
|
|
124
|
+
let result;
|
|
125
|
+
if (action.kind === "button") {
|
|
126
|
+
const b = record(binding, ["buttons"]);
|
|
127
|
+
if (b.buttons !== void 0 && !Array.isArray(b.buttons)) return fail();
|
|
128
|
+
result = Object.freeze({ buttons: Object.freeze((b.buttons ?? []).map((item) => {
|
|
129
|
+
if (typeof item === "number") return gamepadIndex(item);
|
|
130
|
+
const b = record(item, ["button", "threshold"]), threshold = b.threshold ?? .5;
|
|
131
|
+
if (typeof threshold !== "number" || !Number.isFinite(threshold) || threshold <= 0 || threshold > 1) return fail();
|
|
132
|
+
return Object.freeze({
|
|
133
|
+
button: gamepadIndex(b.button),
|
|
134
|
+
threshold
|
|
135
|
+
});
|
|
136
|
+
})) });
|
|
137
|
+
} else if (action.kind === "axis1d") result = axisBinding(binding);
|
|
138
|
+
else {
|
|
139
|
+
const keys = action.kind === "axis2d" ? ["x", "y"] : [
|
|
140
|
+
"x",
|
|
141
|
+
"y",
|
|
142
|
+
"z"
|
|
143
|
+
], b = record(binding, keys);
|
|
144
|
+
result = Object.freeze(Object.fromEntries(keys.map((k) => [k, axisBinding(b[k] ?? {})])));
|
|
145
|
+
}
|
|
146
|
+
return Object.freeze([action, result]);
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
// packages/web/src/gamepad-layout.ts
|
|
150
|
+
var positions = Object.fromEntries(Object.entries(GamepadButton).map(([name, index]) => [index, name]));
|
|
151
|
+
var faces = Object.freeze({
|
|
152
|
+
generic: Object.freeze([
|
|
153
|
+
"South",
|
|
154
|
+
"East",
|
|
155
|
+
"West",
|
|
156
|
+
"North"
|
|
157
|
+
]),
|
|
158
|
+
xbox: Object.freeze([
|
|
159
|
+
"A",
|
|
160
|
+
"B",
|
|
161
|
+
"X",
|
|
162
|
+
"Y"
|
|
163
|
+
]),
|
|
164
|
+
playstation: Object.freeze([
|
|
165
|
+
"×",
|
|
166
|
+
"○",
|
|
167
|
+
"□",
|
|
168
|
+
"△"
|
|
169
|
+
]),
|
|
170
|
+
nintendo: Object.freeze([
|
|
171
|
+
"B",
|
|
172
|
+
"A",
|
|
173
|
+
"Y",
|
|
174
|
+
"X"
|
|
175
|
+
])
|
|
176
|
+
});
|
|
177
|
+
function gamepadButtonLabel(button, options = {}) {
|
|
178
|
+
gamepadIndex(button);
|
|
179
|
+
const layout = options.layout ?? "generic";
|
|
180
|
+
if (!Object.hasOwn(faces, layout)) throw new EngineError("INVALID_GAMEPAD_BINDING", "Unknown gamepad display layout");
|
|
181
|
+
if (options.overrides && Object.hasOwn(options.overrides, button)) {
|
|
182
|
+
const label = options.overrides[button];
|
|
183
|
+
if (typeof label !== "string") throw new EngineError("INVALID_GAMEPAD_BINDING", "Button label must be a string");
|
|
184
|
+
return label;
|
|
185
|
+
}
|
|
186
|
+
return faces[layout][button] ?? positions[button] ?? `Button ${button}`;
|
|
187
|
+
}
|
|
188
|
+
function inferredGamepadLayout(id) {
|
|
189
|
+
if (/dualsense|dualshock|playstation|054c/i.test(id)) return "playstation";
|
|
190
|
+
if (/nintendo|switch|057e/i.test(id)) return "nintendo";
|
|
191
|
+
if (/xbox|xinput|045e/i.test(id)) return "xbox";
|
|
192
|
+
return "generic";
|
|
193
|
+
}
|
|
194
|
+
// packages/web/src/gamepads.ts
|
|
195
|
+
var finite = (v, min, max) => typeof v === "number" && Number.isFinite(v) ? Math.max(min, Math.min(max, v)) : 0;
|
|
196
|
+
var buttonValue = (pad, index) => finite(pad.buttons[index]?.value, 0, 1);
|
|
197
|
+
function scalar(pad, binding, deadZone) {
|
|
198
|
+
const group = (indices) => (indices ?? []).reduce((maximum, index) => Math.max(maximum, buttonValue(pad, index)), 0);
|
|
199
|
+
const raw = finite((binding.axis === void 0 ? 0 : finite(pad.axes[binding.axis], -1, 1)) + group(binding.positive) - group(binding.negative), -1, 1) * (binding.invert ? -1 : 1);
|
|
200
|
+
const zone = binding.deadZone ?? deadZone;
|
|
201
|
+
return Math.abs(raw) <= zone ? 0 : Math.sign(raw) * (Math.abs(raw) - zone) / (1 - zone);
|
|
202
|
+
}
|
|
203
|
+
function values(pad, bindings, zone) {
|
|
204
|
+
return bindings.map(([action, binding]) => {
|
|
205
|
+
if (action.kind === "button") return [action, binding.buttons?.some((b) => buttonValue(pad, typeof b === "number" ? b : b.button) >= (typeof b === "number" ? .5 : b.threshold ?? .5)) ?? false];
|
|
206
|
+
if (action.kind === "axis1d") return [action, scalar(pad, binding, zone)];
|
|
207
|
+
const b = binding, x = scalar(pad, b.x ?? {}, zone), y = scalar(pad, b.y ?? {}, zone);
|
|
208
|
+
return [action, action.kind === "axis2d" ? {
|
|
209
|
+
x,
|
|
210
|
+
y
|
|
211
|
+
} : {
|
|
212
|
+
x,
|
|
213
|
+
y,
|
|
214
|
+
z: scalar(pad, b.z ?? {}, zone)
|
|
215
|
+
}];
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
function neutral(value) {
|
|
219
|
+
return typeof value === "boolean" ? !value : typeof value === "number" ? value === 0 : value.x === 0 && value.y === 0 && (value.z ?? 0) === 0;
|
|
220
|
+
}
|
|
221
|
+
/** A controller-owned device contribution. Reassignment does not replace its readers/Actors. */
|
|
222
|
+
var GamepadSource = class {
|
|
223
|
+
input;
|
|
224
|
+
controller;
|
|
225
|
+
life = new Lifetime();
|
|
226
|
+
source;
|
|
227
|
+
bindings;
|
|
228
|
+
assigned;
|
|
229
|
+
armed = false;
|
|
230
|
+
signature;
|
|
231
|
+
zone;
|
|
232
|
+
mapping;
|
|
233
|
+
constructor(input, controller, options) {
|
|
234
|
+
this.input = input;
|
|
235
|
+
this.controller = controller;
|
|
236
|
+
input.assertController(controller);
|
|
237
|
+
this.assigned = gamepadIndex(options.index);
|
|
238
|
+
this.zone = gamepadDeadZone(options.deadZone ?? .15);
|
|
239
|
+
this.mapping = options.mapping ?? "standard";
|
|
240
|
+
if (this.mapping !== "standard" && this.mapping !== "raw") throw new EngineError("INVALID_GAMEPAD_BINDING", "Unknown gamepad mapping policy");
|
|
241
|
+
this.bindings = checkedGamepadBindings(controller.actions, options.bindings);
|
|
242
|
+
this.source = controller.createSource();
|
|
243
|
+
this.life.onDispose(() => this.source.dispose());
|
|
244
|
+
this.life.onDispose(controller.onDispose(() => this.dispose()));
|
|
245
|
+
}
|
|
246
|
+
get disposed() {
|
|
247
|
+
return this.life.disposed;
|
|
248
|
+
}
|
|
249
|
+
get enabled() {
|
|
250
|
+
return this.source.enabled;
|
|
251
|
+
}
|
|
252
|
+
set enabled(value) {
|
|
253
|
+
this.alive();
|
|
254
|
+
if (value === this.source.enabled) return;
|
|
255
|
+
this.source.enabled = value;
|
|
256
|
+
this.clear();
|
|
257
|
+
}
|
|
258
|
+
get index() {
|
|
259
|
+
return this.assigned;
|
|
260
|
+
}
|
|
261
|
+
get info() {
|
|
262
|
+
return this.input.devices.find((d) => d.index === this.assigned);
|
|
263
|
+
}
|
|
264
|
+
get connected() {
|
|
265
|
+
return this.info?.connected ?? false;
|
|
266
|
+
}
|
|
267
|
+
get usable() {
|
|
268
|
+
return this.connected && (this.mapping === "raw" || this.info?.standardMapping === true);
|
|
269
|
+
}
|
|
270
|
+
assign(index) {
|
|
271
|
+
this.alive();
|
|
272
|
+
const checked = gamepadIndex(index);
|
|
273
|
+
if (checked === this.assigned) return;
|
|
274
|
+
this.clear();
|
|
275
|
+
this.signature = void 0;
|
|
276
|
+
this.assigned = checked;
|
|
277
|
+
}
|
|
278
|
+
rebind(bindings) {
|
|
279
|
+
this.alive();
|
|
280
|
+
const checked = checkedGamepadBindings(this.controller.actions, bindings);
|
|
281
|
+
this.clear();
|
|
282
|
+
this.bindings = checked;
|
|
283
|
+
}
|
|
284
|
+
clear() {
|
|
285
|
+
this.source.clear();
|
|
286
|
+
this.armed = false;
|
|
287
|
+
}
|
|
288
|
+
alive() {
|
|
289
|
+
if (this.disposed) throw new EngineError("OWNER_DISPOSED", "Gamepad source has been disposed");
|
|
290
|
+
}
|
|
291
|
+
/** @internal */ sample(pad, allowed) {
|
|
292
|
+
if (this.disposed) return;
|
|
293
|
+
if (!allowed || !this.enabled || !this.controller.active || !pad || this.mapping === "standard" && pad.mapping !== "standard") {
|
|
294
|
+
this.clear();
|
|
295
|
+
this.signature = void 0;
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const signature = JSON.stringify([
|
|
299
|
+
pad.id,
|
|
300
|
+
pad.mapping,
|
|
301
|
+
pad.buttons.length,
|
|
302
|
+
pad.axes.length
|
|
303
|
+
]);
|
|
304
|
+
if (signature !== this.signature) {
|
|
305
|
+
this.clear();
|
|
306
|
+
this.signature = signature;
|
|
307
|
+
}
|
|
308
|
+
const next = values(pad, this.bindings, this.zone);
|
|
309
|
+
if (!this.armed) {
|
|
310
|
+
if (next.every(([, value]) => neutral(value))) this.armed = true;
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
for (const [action, value] of next) this.source.set(action, value);
|
|
314
|
+
}
|
|
315
|
+
/** @internal */ onDispose(callback) {
|
|
316
|
+
return this.life.onDispose(callback);
|
|
317
|
+
}
|
|
318
|
+
dispose() {
|
|
319
|
+
this.life.dispose();
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
var devicesChanged = defineEvent("GamepadsChanged");
|
|
323
|
+
/** One Web host adapter; multiple sources share a single snapshot per Engine input boundary. */
|
|
324
|
+
var GamepadInput = class {
|
|
325
|
+
input;
|
|
326
|
+
life = new Lifetime();
|
|
327
|
+
changes = new EventBus();
|
|
328
|
+
sources = /* @__PURE__ */ new Set();
|
|
329
|
+
disconnected = /* @__PURE__ */ new Set();
|
|
330
|
+
listed = Object.freeze([]);
|
|
331
|
+
readable = false;
|
|
332
|
+
poll;
|
|
333
|
+
active;
|
|
334
|
+
constructor(input, options = {}) {
|
|
335
|
+
this.input = input;
|
|
336
|
+
this.life.onDispose(() => this.changes.dispose());
|
|
337
|
+
this.poll = options.poll ?? (() => typeof navigator !== "undefined" && typeof navigator.getGamepads === "function" ? navigator.getGamepads() : (() => {
|
|
338
|
+
throw new Error("Gamepad API unavailable");
|
|
339
|
+
})());
|
|
340
|
+
this.active = options.active ?? (() => true);
|
|
341
|
+
this.life.onDispose(input.addAdapter(this));
|
|
342
|
+
const events = options.events ?? (typeof window === "undefined" ? void 0 : window), abort = new AbortController();
|
|
343
|
+
this.life.onDispose(() => abort.abort());
|
|
344
|
+
events?.addEventListener("gamepaddisconnected", (event) => {
|
|
345
|
+
const index = event.gamepad.index;
|
|
346
|
+
this.disconnected.add(index);
|
|
347
|
+
for (const source of this.sources) if (source.index === index) source.clear();
|
|
348
|
+
this.publish(this.listed.filter((d) => d.index !== index));
|
|
349
|
+
}, { signal: abort.signal });
|
|
350
|
+
events?.addEventListener("gamepadconnected", (event) => {
|
|
351
|
+
const index = event.gamepad.index;
|
|
352
|
+
this.disconnected.delete(index);
|
|
353
|
+
for (const source of this.sources) if (source.index === index) source.clear();
|
|
354
|
+
}, { signal: abort.signal });
|
|
355
|
+
}
|
|
356
|
+
get disposed() {
|
|
357
|
+
return this.life.disposed;
|
|
358
|
+
}
|
|
359
|
+
/** Whether the most recent polling attempt completed; an empty list may still need a user gesture. */
|
|
360
|
+
get available() {
|
|
361
|
+
return this.readable;
|
|
362
|
+
}
|
|
363
|
+
get devices() {
|
|
364
|
+
return this.listed;
|
|
365
|
+
}
|
|
366
|
+
onChange(handler, owner) {
|
|
367
|
+
return this.changes.on(devicesChanged, handler, owner);
|
|
368
|
+
}
|
|
369
|
+
publish(devices, force = false) {
|
|
370
|
+
const changed = JSON.stringify(this.listed) !== JSON.stringify(devices);
|
|
371
|
+
this.listed = Object.freeze(devices);
|
|
372
|
+
if (changed || force) this.changes.emit(devicesChanged, void 0);
|
|
373
|
+
}
|
|
374
|
+
/** @internal */ assertController(controller) {
|
|
375
|
+
if (!this.input.hasController(controller)) throw new EngineError("INVALID_ACTION", "Gamepad controller must belong to this InputSystem");
|
|
376
|
+
}
|
|
377
|
+
bind(controller, options) {
|
|
378
|
+
if (this.disposed) throw new EngineError("OWNER_DISPOSED", "Gamepad input has been disposed");
|
|
379
|
+
const source = new GamepadSource(this, controller, options);
|
|
380
|
+
const entry = source;
|
|
381
|
+
this.sources.add(entry);
|
|
382
|
+
const remove = this.life.onDispose(() => source.dispose());
|
|
383
|
+
source.onDispose(() => {
|
|
384
|
+
this.sources.delete(entry);
|
|
385
|
+
remove();
|
|
386
|
+
});
|
|
387
|
+
return source;
|
|
388
|
+
}
|
|
389
|
+
sample() {
|
|
390
|
+
if (this.disposed) return;
|
|
391
|
+
const wasReadable = this.readable;
|
|
392
|
+
let snapshots;
|
|
393
|
+
try {
|
|
394
|
+
snapshots = this.poll();
|
|
395
|
+
if (!Array.isArray(snapshots)) throw new Error("Invalid gamepad snapshot");
|
|
396
|
+
this.readable = true;
|
|
397
|
+
} catch {
|
|
398
|
+
snapshots = [];
|
|
399
|
+
this.readable = false;
|
|
400
|
+
}
|
|
401
|
+
const pads = /* @__PURE__ */ new Map();
|
|
402
|
+
for (const pad of snapshots) if (pad?.connected && typeof pad.id === "string" && typeof pad.mapping === "string" && Array.isArray(pad.axes) && Array.isArray(pad.buttons) && Number.isSafeInteger(pad.index) && pad.index >= 0) pads.set(pad.index, pad);
|
|
403
|
+
for (const index of this.disconnected) if (!pads.has(index)) this.disconnected.delete(index);
|
|
404
|
+
for (const index of this.disconnected) pads.delete(index);
|
|
405
|
+
const listed = Object.freeze([...pads.values()].sort((a, b) => a.index - b.index).map((p) => Object.freeze({
|
|
406
|
+
id: p.id,
|
|
407
|
+
index: p.index,
|
|
408
|
+
connected: p.connected,
|
|
409
|
+
mapping: p.mapping,
|
|
410
|
+
standardMapping: p.mapping === "standard",
|
|
411
|
+
buttonCount: p.buttons.length,
|
|
412
|
+
axisCount: p.axes.length,
|
|
413
|
+
inferredLayout: inferredGamepadLayout(p.id)
|
|
414
|
+
})));
|
|
415
|
+
const allowed = this.active();
|
|
416
|
+
for (const source of this.sources) if (source.disposed) this.sources.delete(source);
|
|
417
|
+
else source.sample(pads.get(source.index), allowed);
|
|
418
|
+
this.publish(listed, wasReadable !== this.readable);
|
|
419
|
+
}
|
|
420
|
+
reset() {
|
|
421
|
+
for (const source of this.sources) source.clear();
|
|
422
|
+
}
|
|
423
|
+
dispose() {
|
|
424
|
+
this.life.dispose();
|
|
425
|
+
this.sources.clear();
|
|
426
|
+
this.listed = Object.freeze([]);
|
|
427
|
+
}
|
|
428
|
+
};
|
|
20
429
|
// packages/web/src/ui-keyboard.ts
|
|
21
430
|
/** Host-only ownership metadata; native editing is never reconstructed from game keys. */
|
|
22
431
|
var gameButtons = /* @__PURE__ */ new WeakSet();
|
|
@@ -180,6 +589,9 @@ var BrowserPointer = class {
|
|
|
180
589
|
window.addEventListener("blur", (event) => {
|
|
181
590
|
if (event.target === window) this.release();
|
|
182
591
|
}, options);
|
|
592
|
+
host.addEventListener("focusout", (event) => this.guard(() => {
|
|
593
|
+
if (!(event.relatedTarget instanceof Node) || !host.contains(event.relatedTarget) || blocksGameKeyboard(event.relatedTarget)) this.routing.releaseVirtual();
|
|
594
|
+
}), options);
|
|
183
595
|
document.addEventListener("visibilitychange", () => {
|
|
184
596
|
if (document.hidden) this.release();
|
|
185
597
|
}, options);
|
|
@@ -459,7 +871,8 @@ function nativeGeometry(el, node, ui, clip) {
|
|
|
459
871
|
el.disabled = !node.available || c.width <= 0 || c.height <= 0 || node.style.pointerEvents !== "auto";
|
|
460
872
|
const background = style.background ?? 2316622;
|
|
461
873
|
const color = `#${style.color.toString(16).padStart(6, "0")}`;
|
|
462
|
-
el.style.cssText = `left:${b.x * scale}px;top:${b.y * scale}px;width:${b.width * scale}px;height:${b.height * scale}px;padding:${style.padding * scale}px;font-
|
|
874
|
+
el.style.cssText = `left:${b.x * scale}px;top:${b.y * scale}px;width:${b.width * scale}px;height:${b.height * scale}px;padding:${style.padding * scale}px;font-size:${style.fontSize * scale}px;--s-native-color:${color};--s-native-background:#${background.toString(16).padStart(6, "0")};color-scheme:${(background >> 16) * .2126 + (background >> 8 & 255) * .7152 + (background & 255) * .0722 > 128 ? "light" : "dark"};clip-path:inset(${Math.max(0, c.y - b.y) * scale}px ${Math.max(0, b.x + b.width - c.x - c.width) * scale}px ${Math.max(0, b.y + b.height - c.y - c.height) * scale}px ${Math.max(0, c.x - b.x) * scale}px);`;
|
|
875
|
+
el.style.fontFamily = style.fontFamily;
|
|
463
876
|
if (el.disabled && document.activeElement === el) el.blur();
|
|
464
877
|
}
|
|
465
878
|
/** Native editors must not cover later Canvas hit surfaces or overlay scrollbars. */
|
|
@@ -1497,6 +1910,108 @@ var TilemapVisual = class {
|
|
|
1497
1910
|
], "Tilemap presentation cleanup failed");
|
|
1498
1911
|
}
|
|
1499
1912
|
};
|
|
1913
|
+
// packages/web/src/virtual-input-visual.ts
|
|
1914
|
+
/** Screen-space presentation only. The existing BrowserPointer is the sole event adapter. */
|
|
1915
|
+
var VirtualInputVisual = class {
|
|
1916
|
+
input;
|
|
1917
|
+
release;
|
|
1918
|
+
display = new Container();
|
|
1919
|
+
entries = /* @__PURE__ */ new Map();
|
|
1920
|
+
dead = false;
|
|
1921
|
+
off;
|
|
1922
|
+
constructor(input, release) {
|
|
1923
|
+
this.input = input;
|
|
1924
|
+
this.release = release;
|
|
1925
|
+
this.display.eventMode = "none";
|
|
1926
|
+
this.display.sortableChildren = true;
|
|
1927
|
+
this.display.zIndex = .5;
|
|
1928
|
+
this.off = input.onDispose(() => this.dispose());
|
|
1929
|
+
}
|
|
1930
|
+
get diagnostics() {
|
|
1931
|
+
return {
|
|
1932
|
+
controls: this.entries.size,
|
|
1933
|
+
visible: [...this.entries.values()].filter((e) => e.graphics.visible).length
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
update() {
|
|
1937
|
+
if (this.dead) return;
|
|
1938
|
+
const live = new Set(this.input.controls);
|
|
1939
|
+
for (const [control, entry] of this.entries) if (!live.has(control)) {
|
|
1940
|
+
this.entries.delete(control);
|
|
1941
|
+
this.release(entry.graphics);
|
|
1942
|
+
}
|
|
1943
|
+
this.display.visible = this.input.visible;
|
|
1944
|
+
for (const control of live) {
|
|
1945
|
+
let entry = this.entries.get(control);
|
|
1946
|
+
if (!entry) {
|
|
1947
|
+
entry = {
|
|
1948
|
+
graphics: new Graphics(),
|
|
1949
|
+
signature: ""
|
|
1950
|
+
};
|
|
1951
|
+
entry.graphics.zIndex = control.kind === "button" ? 1 : 0;
|
|
1952
|
+
this.entries.set(control, entry);
|
|
1953
|
+
this.display.addChild(entry.graphics);
|
|
1954
|
+
}
|
|
1955
|
+
entry.graphics.visible = control.visible;
|
|
1956
|
+
if (!control.visible || !this.input.visible) continue;
|
|
1957
|
+
const center = control.center, knob = control.knob, radius = control.radius;
|
|
1958
|
+
const signature = JSON.stringify([
|
|
1959
|
+
center,
|
|
1960
|
+
knob,
|
|
1961
|
+
radius,
|
|
1962
|
+
control.color,
|
|
1963
|
+
control.enabled,
|
|
1964
|
+
this.input.enabled,
|
|
1965
|
+
control.pressed,
|
|
1966
|
+
control.kind
|
|
1967
|
+
]);
|
|
1968
|
+
if (signature === entry.signature) continue;
|
|
1969
|
+
entry.signature = signature;
|
|
1970
|
+
const g = entry.graphics;
|
|
1971
|
+
g.clear();
|
|
1972
|
+
g.alpha = control.enabled && this.input.enabled ? 1 : .35;
|
|
1973
|
+
g.circle(center.x, center.y, radius).fill({
|
|
1974
|
+
color: control.color,
|
|
1975
|
+
alpha: .12
|
|
1976
|
+
}).stroke({
|
|
1977
|
+
color: control.color,
|
|
1978
|
+
alpha: .75,
|
|
1979
|
+
width: 2
|
|
1980
|
+
});
|
|
1981
|
+
if (control.kind === "stick") {
|
|
1982
|
+
g.moveTo(center.x, center.y).lineTo(knob.x, knob.y).stroke({
|
|
1983
|
+
color: control.color,
|
|
1984
|
+
alpha: .5,
|
|
1985
|
+
width: 3
|
|
1986
|
+
});
|
|
1987
|
+
g.circle(knob.x, knob.y, radius * .36).fill({
|
|
1988
|
+
color: control.color,
|
|
1989
|
+
alpha: control.pressed ? .85 : .45
|
|
1990
|
+
});
|
|
1991
|
+
} else {
|
|
1992
|
+
if (control.pressed) g.circle(center.x, center.y, radius * .7).fill({
|
|
1993
|
+
color: control.color,
|
|
1994
|
+
alpha: .65
|
|
1995
|
+
});
|
|
1996
|
+
if (control.directional && control.pressed) g.moveTo(center.x, center.y).lineTo(knob.x, knob.y).stroke({
|
|
1997
|
+
color: control.color,
|
|
1998
|
+
width: 4
|
|
1999
|
+
});
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
dispose() {
|
|
2004
|
+
if (this.dead) return;
|
|
2005
|
+
this.dead = true;
|
|
2006
|
+
const entries = [...this.entries.values()];
|
|
2007
|
+
this.entries.clear();
|
|
2008
|
+
cleanupAll([
|
|
2009
|
+
this.off,
|
|
2010
|
+
...entries.map((e) => () => this.release(e.graphics)),
|
|
2011
|
+
() => this.display.destroy({ children: true })
|
|
2012
|
+
], "Virtual input presentation cleanup failed");
|
|
2013
|
+
}
|
|
2014
|
+
};
|
|
1500
2015
|
// packages/web/src/renderer.ts
|
|
1501
2016
|
var PixiRenderer = class {
|
|
1502
2017
|
host;
|
|
@@ -1505,6 +2020,46 @@ var PixiRenderer = class {
|
|
|
1505
2020
|
app = new Application();
|
|
1506
2021
|
pendingUITextKeys = /* @__PURE__ */ new Set();
|
|
1507
2022
|
canvasPresentations = /* @__PURE__ */ new Set();
|
|
2023
|
+
virtualPresentations = /* @__PURE__ */ new Set();
|
|
2024
|
+
get virtualInputDiagnostics() {
|
|
2025
|
+
let controls = 0, visible = 0;
|
|
2026
|
+
for (const visual of this.virtualPresentations) {
|
|
2027
|
+
controls += visual.diagnostics.controls;
|
|
2028
|
+
visible += visual.diagnostics.visible;
|
|
2029
|
+
}
|
|
2030
|
+
return {
|
|
2031
|
+
presentations: this.virtualPresentations.size,
|
|
2032
|
+
controls,
|
|
2033
|
+
visible
|
|
2034
|
+
};
|
|
2035
|
+
}
|
|
2036
|
+
prepareVirtualInput(input) {
|
|
2037
|
+
if (!this.ready || this.dead) throw new EngineError("RENDERER_NOT_READY", "Renderer is not available");
|
|
2038
|
+
const visual = new VirtualInputVisual(input, (graphics) => {
|
|
2039
|
+
const context = graphics.context.uid;
|
|
2040
|
+
graphics.destroy({ context: true });
|
|
2041
|
+
releaseUIGraphics(this.app.renderer, graphics, context);
|
|
2042
|
+
});
|
|
2043
|
+
this.virtualPresentations.add(visual);
|
|
2044
|
+
const off = input.onDispose(() => {
|
|
2045
|
+
this.virtualPresentations.delete(visual);
|
|
2046
|
+
});
|
|
2047
|
+
let disposed = false;
|
|
2048
|
+
return {
|
|
2049
|
+
update: () => {
|
|
2050
|
+
if (disposed || input.disposed) return;
|
|
2051
|
+
if (!visual.display.parent) this.app.stage.addChild(visual.display);
|
|
2052
|
+
visual.update();
|
|
2053
|
+
},
|
|
2054
|
+
dispose: () => {
|
|
2055
|
+
if (disposed) return;
|
|
2056
|
+
disposed = true;
|
|
2057
|
+
off();
|
|
2058
|
+
this.virtualPresentations.delete(visual);
|
|
2059
|
+
visual.dispose();
|
|
2060
|
+
}
|
|
2061
|
+
};
|
|
2062
|
+
}
|
|
1508
2063
|
get canvasDiagnostics() {
|
|
1509
2064
|
let nodes = 0, visibleNodes = 0, buttons = 0, syncs = 0, textMetrics = 0;
|
|
1510
2065
|
for (const p of this.canvasPresentations) {
|
|
@@ -1684,7 +2239,15 @@ var PixiRenderer = class {
|
|
|
1684
2239
|
}
|
|
1685
2240
|
acquireImage(url, signal) {
|
|
1686
2241
|
try {
|
|
1687
|
-
|
|
2242
|
+
const parsed = new URL(url, document.baseURI);
|
|
2243
|
+
if (![
|
|
2244
|
+
"http:",
|
|
2245
|
+
"https:",
|
|
2246
|
+
"data:",
|
|
2247
|
+
"blob:",
|
|
2248
|
+
"file:"
|
|
2249
|
+
].includes(parsed.protocol)) throw new Error("Unsupported image URL protocol");
|
|
2250
|
+
return this.images.acquire(parsed.href, signal);
|
|
1688
2251
|
} catch {
|
|
1689
2252
|
return Promise.reject(new EngineError("ASSET_LOAD_FAILED", "Invalid image URL", { path: url }));
|
|
1690
2253
|
}
|
|
@@ -2090,6 +2653,10 @@ var PixiRenderer = class {
|
|
|
2090
2653
|
this.scenes.clear();
|
|
2091
2654
|
cleanupAll([
|
|
2092
2655
|
...scenes.map((scene) => scene.dispose),
|
|
2656
|
+
...[...this.virtualPresentations].map((p) => () => {
|
|
2657
|
+
this.virtualPresentations.delete(p);
|
|
2658
|
+
p.dispose();
|
|
2659
|
+
}),
|
|
2093
2660
|
...[...this.canvasPresentations].map((p) => () => {
|
|
2094
2661
|
this.canvasPresentations.delete(p);
|
|
2095
2662
|
p.dispose();
|
|
@@ -2342,10 +2909,42 @@ var Game = class {
|
|
|
2342
2909
|
engine;
|
|
2343
2910
|
element;
|
|
2344
2911
|
camera;
|
|
2912
|
+
gamepads;
|
|
2345
2913
|
renderer;
|
|
2346
2914
|
keyboard;
|
|
2347
2915
|
pointers;
|
|
2348
2916
|
pointerChannels = /* @__PURE__ */ new WeakMap();
|
|
2917
|
+
virtualInputs = /* @__PURE__ */ new WeakMap();
|
|
2918
|
+
virtualPresentation;
|
|
2919
|
+
get virtualInput() {
|
|
2920
|
+
this.assertAlive();
|
|
2921
|
+
return this.virtualFor(this.engine.scene);
|
|
2922
|
+
}
|
|
2923
|
+
virtualFor(scene) {
|
|
2924
|
+
let input = this.virtualInputs.get(scene);
|
|
2925
|
+
if (!input) {
|
|
2926
|
+
input = new VirtualInput(this.channelFor(scene), this.engine.input);
|
|
2927
|
+
this.virtualInputs.set(scene, input);
|
|
2928
|
+
const owned = input;
|
|
2929
|
+
input.onDispose(() => {
|
|
2930
|
+
if (this.virtualInputs.get(scene) === owned) this.virtualInputs.delete(scene);
|
|
2931
|
+
if (this.engine.scene === scene) {
|
|
2932
|
+
this.virtualPresentation?.dispose();
|
|
2933
|
+
this.virtualPresentation = void 0;
|
|
2934
|
+
}
|
|
2935
|
+
});
|
|
2936
|
+
}
|
|
2937
|
+
return input;
|
|
2938
|
+
}
|
|
2939
|
+
prepareVirtual(scene) {
|
|
2940
|
+
const input = this.virtualInputs.get(scene);
|
|
2941
|
+
if (!input || input.controls.length === 0) return;
|
|
2942
|
+
const renderer = this.renderer;
|
|
2943
|
+
if (!renderer.prepareVirtualInput) throw new EngineError("RENDERER_CAPABILITY", "Renderer does not support virtual input presentation");
|
|
2944
|
+
const presentation = renderer.prepareVirtualInput(input);
|
|
2945
|
+
input.onDispose(() => presentation.dispose());
|
|
2946
|
+
return presentation;
|
|
2947
|
+
}
|
|
2349
2948
|
canvas;
|
|
2350
2949
|
canvasPresentation;
|
|
2351
2950
|
defaultCanvas;
|
|
@@ -2425,8 +3024,10 @@ var Game = class {
|
|
|
2425
3024
|
camera: this.camera
|
|
2426
3025
|
});
|
|
2427
3026
|
if (options.pixelRatio !== void 0) positive(options.pixelRatio, "pixelRatio");
|
|
2428
|
-
const
|
|
2429
|
-
if (!
|
|
3027
|
+
const themeName = options.theme ?? Theme.Dark;
|
|
3028
|
+
if (!Object.hasOwn(Themes, themeName)) throw new EngineError("INVALID_THEME", "Unknown theme");
|
|
3029
|
+
const theme = Themes[themeName];
|
|
3030
|
+
for (const [name, value] of Object.entries(options.tokens ?? {})) if (!Object.hasOwn(theme, name) || typeof value !== "string" || (name === "font" ? !validFontFamily(value) : !value.trim().length || /[;{}<>\r\n\0]|url\s*\(/i.test(value) || !CSS.supports("color", value))) throw new EngineError("INVALID_THEME_TOKEN", `Invalid token: ${name}`);
|
|
2430
3031
|
const tokens = {
|
|
2431
3032
|
...theme,
|
|
2432
3033
|
...options.tokens
|
|
@@ -2454,6 +3055,7 @@ var Game = class {
|
|
|
2454
3055
|
cancel: (handle) => cancelAnimationFrame(handle)
|
|
2455
3056
|
};
|
|
2456
3057
|
this.keyboard = new BrowserInput(this.element, this.engine.input);
|
|
3058
|
+
this.gamepads = new GamepadInput(this.engine.input, { active: () => !document.hidden && document.hasFocus() && this.element.contains(document.activeElement) && !blocksGameKeyboard(document.activeElement) });
|
|
2457
3059
|
this.pointers = new BrowserPointer(this.element, (event) => {
|
|
2458
3060
|
const rect = this.element.getBoundingClientRect();
|
|
2459
3061
|
if (rect.width <= 0 || rect.height <= 0) throw new EngineError("VIEWPORT_UNAVAILABLE", "Game has no visible bounds");
|
|
@@ -2463,7 +3065,7 @@ var Game = class {
|
|
|
2463
3065
|
world: this.screenToWorld(screen),
|
|
2464
3066
|
view: this.view
|
|
2465
3067
|
};
|
|
2466
|
-
}, this.report, () => this.
|
|
3068
|
+
}, this.report, () => this.updateProjection());
|
|
2467
3069
|
document.addEventListener("visibilitychange", () => {
|
|
2468
3070
|
this.previousTime = void 0;
|
|
2469
3071
|
this.fixed?.reset();
|
|
@@ -2618,6 +3220,7 @@ var Game = class {
|
|
|
2618
3220
|
const scene = new Scene(id), nodes = [], controller = new AbortController();
|
|
2619
3221
|
let canvas;
|
|
2620
3222
|
const pointer = this.channelFor(scene);
|
|
3223
|
+
const virtualFor = () => this.virtualFor(scene);
|
|
2621
3224
|
this.staging = scene;
|
|
2622
3225
|
scene.onDispose(() => controller.abort());
|
|
2623
3226
|
try {
|
|
@@ -2633,6 +3236,9 @@ var Game = class {
|
|
|
2633
3236
|
add: (actor) => scene.add(actor),
|
|
2634
3237
|
ui: (...items) => nodes.push(...items),
|
|
2635
3238
|
pointer,
|
|
3239
|
+
get virtualInput() {
|
|
3240
|
+
return virtualFor();
|
|
3241
|
+
},
|
|
2636
3242
|
canvasUI: (root, options) => {
|
|
2637
3243
|
if (canvas) throw new EngineError("UI_OWNERSHIP", "A Scene has one Canvas UI root");
|
|
2638
3244
|
canvas = new CanvasUI(scene, root, options);
|
|
@@ -2671,6 +3277,7 @@ var Game = class {
|
|
|
2671
3277
|
async present(scene, nodes, canvas) {
|
|
2672
3278
|
let prepared, ui, committed = false;
|
|
2673
3279
|
let canvasPresentation;
|
|
3280
|
+
let virtualPresentation;
|
|
2674
3281
|
try {
|
|
2675
3282
|
const preparing = this.renderer.prepare(scene);
|
|
2676
3283
|
preparing.then((value) => {
|
|
@@ -2691,6 +3298,7 @@ var Game = class {
|
|
|
2691
3298
|
canvasPresentation = renderer.prepareCanvasUI(canvas, this.pointers.routing);
|
|
2692
3299
|
scene.onDispose(() => canvasPresentation?.dispose());
|
|
2693
3300
|
}
|
|
3301
|
+
virtualPresentation = this.prepareVirtual(scene);
|
|
2694
3302
|
scene.markReady();
|
|
2695
3303
|
this.errorBox.hidden = true;
|
|
2696
3304
|
try {
|
|
@@ -2701,18 +3309,20 @@ var Game = class {
|
|
|
2701
3309
|
this.report(error);
|
|
2702
3310
|
}
|
|
2703
3311
|
this.assertAlive();
|
|
2704
|
-
const oldPrepared = this.prepared, oldUI = this.ui, oldCanvas = this.canvasPresentation;
|
|
3312
|
+
const oldPrepared = this.prepared, oldUI = this.ui, oldCanvas = this.canvasPresentation, oldVirtual = this.virtualPresentation;
|
|
2705
3313
|
this.prepared = prepared;
|
|
2706
3314
|
this.ui = ui;
|
|
2707
3315
|
this.canvas = canvas;
|
|
2708
3316
|
this.canvasPresentation = canvasPresentation;
|
|
3317
|
+
this.virtualPresentation = virtualPresentation;
|
|
2709
3318
|
this.pointers.routing.setScene(this.channelFor(scene), canvas);
|
|
2710
3319
|
committed = true;
|
|
2711
3320
|
this.element.append(ui.element);
|
|
2712
3321
|
for (const cleanup of [
|
|
2713
3322
|
() => oldPrepared?.dispose(),
|
|
2714
3323
|
() => oldUI?.dispose(),
|
|
2715
|
-
() => oldCanvas?.dispose()
|
|
3324
|
+
() => oldCanvas?.dispose(),
|
|
3325
|
+
() => oldVirtual?.dispose()
|
|
2716
3326
|
]) try {
|
|
2717
3327
|
cleanup();
|
|
2718
3328
|
} catch (error) {
|
|
@@ -2728,7 +3338,8 @@ var Game = class {
|
|
|
2728
3338
|
if (!committed) for (const cleanup of [
|
|
2729
3339
|
() => prepared?.dispose(),
|
|
2730
3340
|
() => ui?.dispose(),
|
|
2731
|
-
() => canvasPresentation?.dispose()
|
|
3341
|
+
() => canvasPresentation?.dispose(),
|
|
3342
|
+
() => virtualPresentation?.dispose()
|
|
2732
3343
|
]) try {
|
|
2733
3344
|
cleanup();
|
|
2734
3345
|
} catch (cleanupError) {
|
|
@@ -2840,6 +3451,7 @@ var Game = class {
|
|
|
2840
3451
|
width: this.width,
|
|
2841
3452
|
height: this.height
|
|
2842
3453
|
};
|
|
3454
|
+
this.virtualInputs.get(this.engine.scene)?.refresh(viewport);
|
|
2843
3455
|
const previous = this.projection.viewport;
|
|
2844
3456
|
const c = this.camera;
|
|
2845
3457
|
if (!this.projectionDirty && previous.x === viewport.x && previous.y === viewport.y && previous.width === viewport.width && previous.height === viewport.height && this.cameraX === c.x && this.cameraY === c.y && this.cameraZoom === c.zoom) return;
|
|
@@ -2884,6 +3496,8 @@ var Game = class {
|
|
|
2884
3496
|
}
|
|
2885
3497
|
render() {
|
|
2886
3498
|
this.updateProjection();
|
|
3499
|
+
this.virtualPresentation ??= this.prepareVirtual(this.engine.scene);
|
|
3500
|
+
this.virtualPresentation?.update();
|
|
2887
3501
|
this.prepared?.render(this.projection);
|
|
2888
3502
|
}
|
|
2889
3503
|
tick = (time) => {
|
|
@@ -2897,12 +3511,14 @@ var Game = class {
|
|
|
2897
3511
|
} else {
|
|
2898
3512
|
const elapsed = this.previousTime === void 0 ? 0 : Math.max(0, (time - this.previousTime) / 1e3);
|
|
2899
3513
|
this.previousTime = time;
|
|
3514
|
+
if (this.virtualInputs.has(this.engine.scene)) this.updateProjection();
|
|
2900
3515
|
if (this.playback) this.playback.advance(elapsed, this.options.fixedStep?.maxSteps ?? 8);
|
|
2901
3516
|
else if (this.fixed) this.fixed.advance(elapsed);
|
|
2902
3517
|
else this.engine.step(Math.min(this.maxDelta, elapsed));
|
|
2903
3518
|
if (!this.dead && !this.engine.scene.disposed) this.engine.scene.advancePresentation(Math.min(this.maxDelta, elapsed));
|
|
2904
3519
|
if (this.engine.paused) this.canvas?.stopMotion();
|
|
2905
3520
|
else this.canvas?.advance(elapsed);
|
|
3521
|
+
this.virtualInputs.get(this.engine.scene)?.advance(Math.min(this.maxDelta, elapsed));
|
|
2906
3522
|
if (this.ratio !== (this.options.pixelRatio ?? Math.min(window.devicePixelRatio || 1, 2))) this.resize();
|
|
2907
3523
|
this.render();
|
|
2908
3524
|
}
|
|
@@ -2929,8 +3545,10 @@ var Game = class {
|
|
|
2929
3545
|
() => this.stopReplay(),
|
|
2930
3546
|
() => this.staging?.dispose(),
|
|
2931
3547
|
() => this.pointers.dispose(),
|
|
3548
|
+
() => this.gamepads.dispose(),
|
|
2932
3549
|
() => this.keyboard.dispose(),
|
|
2933
3550
|
() => this.canvasPresentation?.dispose(),
|
|
3551
|
+
() => this.virtualPresentation?.dispose(),
|
|
2934
3552
|
() => this.canvas?.dispose(),
|
|
2935
3553
|
() => this.observer.disconnect(),
|
|
2936
3554
|
() => this.ui?.dispose(),
|
|
@@ -2948,12 +3566,17 @@ var Game = class {
|
|
|
2948
3566
|
this.staging = void 0;
|
|
2949
3567
|
this.canvas = void 0;
|
|
2950
3568
|
this.canvasPresentation = void 0;
|
|
3569
|
+
this.virtualPresentation = void 0;
|
|
2951
3570
|
this.defaultCanvas = void 0;
|
|
2952
3571
|
this.defaultUI.length = 0;
|
|
2953
3572
|
this.scenes.clear();
|
|
2954
3573
|
this.pendingScenes.clear();
|
|
2955
3574
|
}
|
|
2956
3575
|
};
|
|
2957
|
-
|
|
3576
|
+
function validFontFamily(value) {
|
|
3577
|
+
const syntax = value.replace(/"(?:\\[^\r\n]|[^"\\])*"|'(?:\\[^\r\n]|[^'\\])*'/g, "family").replace(/\\(?:[0-9a-f]{1,6}[ \t]?|[^\r\n])/gi, "x");
|
|
3578
|
+
return value.trim().length > 0 && !/[\r\n\0]/.test(value) && !/[;{}<>\\"']|url\s*\(/i.test(syntax);
|
|
3579
|
+
}
|
|
3580
|
+
export { BrowserInput, Button, CanvasButton, Game, GamepadAxis, GamepadBindings, GamepadButton, GamepadInput, GamepadSource, HoldButton, Label, Layout, Panel, PixiRenderer, Placement, Theme, Themes, browserAbortSignal, gamepadButtonLabel, mountUI };
|
|
2958
3581
|
|
|
2959
3582
|
//# sourceMappingURL=index.js.map
|