@simpludify/core 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/foundation/version.d.ts +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +834 -17
- package/dist/index.js.map +1 -1
- package/dist/interaction/actions.d.ts +5 -0
- package/dist/interaction/input-source.d.ts +3 -0
- package/dist/interaction/input.d.ts +11 -0
- package/dist/interaction/pointer-routing.d.ts +9 -0
- package/dist/interaction/pointer-target.d.ts +14 -0
- package/dist/interaction/virtual-control.d.ts +140 -0
- package/dist/interaction/virtual-input.d.ts +49 -0
- package/dist/interaction/virtual-regions.d.ts +68 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1583,7 +1583,7 @@ var RandomManager = class {
|
|
|
1583
1583
|
};
|
|
1584
1584
|
// packages/core/src/foundation/version.ts
|
|
1585
1585
|
/** Source workspace version; checked against package.json by the build validator. */
|
|
1586
|
-
var ENGINE_VERSION = "0.1.
|
|
1586
|
+
var ENGINE_VERSION = "0.1.5";
|
|
1587
1587
|
// packages/core/src/scene/actor.ts
|
|
1588
1588
|
var inputPhase$1 = ComponentPhase.Input;
|
|
1589
1589
|
var updatePhase$1 = ComponentPhase.Update;
|
|
@@ -4452,16 +4452,27 @@ function defineActions(definitions) {
|
|
|
4452
4452
|
/** @internal Shared lifetime for logical and device contributions. */
|
|
4453
4453
|
var OwnedInputSource = class {
|
|
4454
4454
|
dead = false;
|
|
4455
|
+
accepting = true;
|
|
4455
4456
|
removeOwner;
|
|
4456
4457
|
get disposed() {
|
|
4457
4458
|
return this.dead;
|
|
4458
4459
|
}
|
|
4460
|
+
get enabled() {
|
|
4461
|
+
return this.accepting;
|
|
4462
|
+
}
|
|
4463
|
+
set enabled(value) {
|
|
4464
|
+
if (this.dead) throw new EngineError("OWNER_DISPOSED", "Input source has been disposed");
|
|
4465
|
+
if (typeof value !== "boolean") throw new EngineError("INVALID_VALUE", "Source enabled must be boolean");
|
|
4466
|
+
if (value === this.accepting) return;
|
|
4467
|
+
this.accepting = value;
|
|
4468
|
+
if (!value) this.release(false);
|
|
4469
|
+
}
|
|
4459
4470
|
/** @internal */ own(owner) {
|
|
4460
4471
|
if (owner.disposed) throw new EngineError("OWNER_DISPOSED", "Input owner has been disposed");
|
|
4461
4472
|
this.removeOwner = owner.onDispose(() => this.dispose());
|
|
4462
4473
|
}
|
|
4463
4474
|
writable(neutral) {
|
|
4464
|
-
if (!this.dead) return
|
|
4475
|
+
if (!this.dead) return this.accepting;
|
|
4465
4476
|
if (neutral) return false;
|
|
4466
4477
|
throw new EngineError("OWNER_DISPOSED", "Input source has been disposed");
|
|
4467
4478
|
}
|
|
@@ -4509,13 +4520,17 @@ var ActionController = class {
|
|
|
4509
4520
|
entries = /* @__PURE__ */ new Map();
|
|
4510
4521
|
sources = /* @__PURE__ */ new Map();
|
|
4511
4522
|
bus = new EventBus();
|
|
4523
|
+
keyboardAllowed = true;
|
|
4524
|
+
blockedKeys = /* @__PURE__ */ new Set();
|
|
4512
4525
|
pending = [];
|
|
4513
4526
|
transitions = [];
|
|
4514
4527
|
constructor(actions, options) {
|
|
4515
4528
|
this.actions = actions;
|
|
4516
4529
|
this.options = options;
|
|
4530
|
+
if (options.keyboard !== void 0 && typeof options.keyboard !== "boolean") throw new EngineError("INVALID_VALUE", "Keyboard enabled must be boolean");
|
|
4531
|
+
this.keyboardAllowed = options.keyboard ?? true;
|
|
4517
4532
|
for (const action of Object.values(actions)) {
|
|
4518
|
-
const binding = checkedBinding(action, options.bindings
|
|
4533
|
+
const binding = checkedBinding(action, options.bindings && Object.hasOwn(options.bindings, action.name) ? options.bindings[action.name] ?? {} : {});
|
|
4519
4534
|
const value = neutral(action);
|
|
4520
4535
|
this.entries.set(action, {
|
|
4521
4536
|
value,
|
|
@@ -4543,6 +4558,20 @@ var ActionController = class {
|
|
|
4543
4558
|
get active() {
|
|
4544
4559
|
return !this.disposed && this.options.scene.state === SceneState.Active && this.options.scene.inputOwner === this.options.input;
|
|
4545
4560
|
}
|
|
4561
|
+
get keyboardEnabled() {
|
|
4562
|
+
return this.keyboardAllowed;
|
|
4563
|
+
}
|
|
4564
|
+
set keyboardEnabled(value) {
|
|
4565
|
+
if (this.disposed) throw new EngineError("OWNER_DISPOSED", "Action controller has been disposed");
|
|
4566
|
+
if (typeof value !== "boolean") throw new EngineError("INVALID_VALUE", "Keyboard enabled must be boolean");
|
|
4567
|
+
if (value === this.keyboardAllowed) return;
|
|
4568
|
+
this.keyboardAllowed = value;
|
|
4569
|
+
this.blockedKeys.clear();
|
|
4570
|
+
if (value) {
|
|
4571
|
+
for (const entry of this.entries.values()) for (const list of keyLists(entry.binding)) for (const key of list.keys) if (this.options.input.keyDown(key)) this.blockedKeys.add(key);
|
|
4572
|
+
}
|
|
4573
|
+
for (const action of this.entries.keys()) this.recompute(action);
|
|
4574
|
+
}
|
|
4546
4575
|
get sourceCount() {
|
|
4547
4576
|
return this.sources.size;
|
|
4548
4577
|
}
|
|
@@ -4641,8 +4670,9 @@ var ActionController = class {
|
|
|
4641
4670
|
if (!this.disposed) for (const action of actions) this.recompute(action);
|
|
4642
4671
|
}
|
|
4643
4672
|
keyboard(action) {
|
|
4673
|
+
if (!this.keyboardAllowed) return neutral(action);
|
|
4644
4674
|
const binding = this.entry(action).binding;
|
|
4645
|
-
const held = (keys) => keys?.some((k) => this.options.input.keyDown(k)) ?? false;
|
|
4675
|
+
const held = (keys) => keys?.some((k) => !this.blockedKeys.has(k) && this.options.input.keyDown(k)) ?? false;
|
|
4646
4676
|
const axis = (b) => Number(held(b?.positive)) - Number(held(b?.negative));
|
|
4647
4677
|
if (action.kind === "button") return held(binding.keys);
|
|
4648
4678
|
if (action.kind === "axis1d") return axis(binding);
|
|
@@ -4696,10 +4726,11 @@ var ActionController = class {
|
|
|
4696
4726
|
entry.value = value;
|
|
4697
4727
|
}
|
|
4698
4728
|
/** @internal */ keyChanged() {
|
|
4729
|
+
for (const key of this.blockedKeys) if (!this.options.input.keyDown(key)) this.blockedKeys.delete(key);
|
|
4699
4730
|
if (this.active || !this.disposed && this.options.scene.inputOwner === this.options.input && (this.options.scene.state === SceneState.Preparing || this.options.scene.state === SceneState.Ready)) for (const action of this.entries.keys()) this.recompute(action);
|
|
4700
4731
|
}
|
|
4701
4732
|
/** @internal */ accepts(code) {
|
|
4702
|
-
return this.active && [...this.entries.values()].some((e) => keyLists(e.binding).some((list) => list.keys.includes(code)));
|
|
4733
|
+
return this.active && this.keyboardAllowed && [...this.entries.values()].some((e) => keyLists(e.binding).some((list) => list.keys.includes(code)));
|
|
4703
4734
|
}
|
|
4704
4735
|
/** @internal */ capture(override) {
|
|
4705
4736
|
if (!this.active) return;
|
|
@@ -4741,6 +4772,7 @@ var ActionController = class {
|
|
|
4741
4772
|
}
|
|
4742
4773
|
/** Clears held contributions and unconsumed edges; no synthetic button release. */
|
|
4743
4774
|
reset() {
|
|
4775
|
+
this.blockedKeys.clear();
|
|
4744
4776
|
for (const values of this.sources.values()) values.clear();
|
|
4745
4777
|
this.pending = [];
|
|
4746
4778
|
for (const [action, entry] of this.entries) entry.value = neutral(action);
|
|
@@ -4840,10 +4872,15 @@ var KeyboardSource = class extends OwnedInputSource {
|
|
|
4840
4872
|
/** Engine-owned routing and update boundary. No raw game-state/alternate movement API. */
|
|
4841
4873
|
var InputSystem = class {
|
|
4842
4874
|
actions = /* @__PURE__ */ new Set();
|
|
4875
|
+
adapters = /* @__PURE__ */ new Set();
|
|
4843
4876
|
keyboards = /* @__PURE__ */ new Map();
|
|
4844
4877
|
dead = false;
|
|
4845
4878
|
playback = false;
|
|
4846
4879
|
frames;
|
|
4880
|
+
/** @internal Device hosts may reject live gestures during Replay playback. */
|
|
4881
|
+
get liveInput() {
|
|
4882
|
+
return !this.dead && !this.playback;
|
|
4883
|
+
}
|
|
4847
4884
|
get sourceCount() {
|
|
4848
4885
|
let count = this.keyboards.size;
|
|
4849
4886
|
for (const actions of this.actions) count += actions.sourceCount;
|
|
@@ -4856,6 +4893,14 @@ var InputSystem = class {
|
|
|
4856
4893
|
this.actions.delete(actions);
|
|
4857
4894
|
};
|
|
4858
4895
|
}
|
|
4896
|
+
/** Host integration boundary; ordinary game input uses ActionSource instead. */
|
|
4897
|
+
addAdapter(adapter) {
|
|
4898
|
+
if (this.dead) throw new EngineError("OWNER_DISPOSED", "Input has been disposed");
|
|
4899
|
+
this.adapters.add(adapter);
|
|
4900
|
+
return () => {
|
|
4901
|
+
this.adapters.delete(adapter);
|
|
4902
|
+
};
|
|
4903
|
+
}
|
|
4859
4904
|
acceptsKey(code) {
|
|
4860
4905
|
if (this.playback) return false;
|
|
4861
4906
|
for (const actions of this.actions) if (actions.accepts(code)) return true;
|
|
@@ -4871,6 +4916,9 @@ var InputSystem = class {
|
|
|
4871
4916
|
}
|
|
4872
4917
|
/** @internal Freeze every controller before invoking any callbacks. */
|
|
4873
4918
|
beginStep(captured) {
|
|
4919
|
+
if (!this.playback && this.adapters.size) {
|
|
4920
|
+
for (const adapter of [...this.adapters]) if (this.adapters.has(adapter)) adapter.sample();
|
|
4921
|
+
}
|
|
4874
4922
|
if (!this.actions.size) {
|
|
4875
4923
|
captured?.();
|
|
4876
4924
|
return;
|
|
@@ -4945,7 +4993,9 @@ var InputSystem = class {
|
|
|
4945
4993
|
}
|
|
4946
4994
|
clear() {
|
|
4947
4995
|
for (const keys of this.keyboards.values()) keys.clear();
|
|
4948
|
-
|
|
4996
|
+
cleanupAll([...[...this.actions].map((actions) => () => actions.reset()), ...[...this.adapters].map((adapter) => () => {
|
|
4997
|
+
if (this.adapters.has(adapter)) adapter.reset();
|
|
4998
|
+
})], "Input reset failed");
|
|
4949
4999
|
}
|
|
4950
5000
|
/** Device diagnostics only; game state is ActionController.snapshot(). */
|
|
4951
5001
|
snapshot() {
|
|
@@ -4957,8 +5007,13 @@ var InputSystem = class {
|
|
|
4957
5007
|
if (this.dead) return;
|
|
4958
5008
|
this.dead = true;
|
|
4959
5009
|
try {
|
|
4960
|
-
cleanupAll([
|
|
5010
|
+
cleanupAll([
|
|
5011
|
+
...[...this.adapters].map((a) => () => a.dispose()),
|
|
5012
|
+
...[...this.actions].map((a) => () => a.dispose()),
|
|
5013
|
+
...[...this.keyboards.keys()].map((s) => () => s.dispose())
|
|
5014
|
+
], "Input cleanup failed");
|
|
4961
5015
|
} finally {
|
|
5016
|
+
this.adapters.clear();
|
|
4962
5017
|
this.actions.clear();
|
|
4963
5018
|
this.keyboards.clear();
|
|
4964
5019
|
}
|
|
@@ -5258,6 +5313,18 @@ var ScenePointer = class {
|
|
|
5258
5313
|
lifetime = new Lifetime();
|
|
5259
5314
|
events = new EventBus();
|
|
5260
5315
|
removeScene;
|
|
5316
|
+
targets;
|
|
5317
|
+
/** @internal Exactly one Scene virtual-input provider shares the existing router. */
|
|
5318
|
+
attachTargets(provider) {
|
|
5319
|
+
if (this.disposed || this.targets) throw new EngineError("INVALID_VIRTUAL_INPUT", "Scene already has virtual input or is disposed");
|
|
5320
|
+
this.targets = provider;
|
|
5321
|
+
return () => {
|
|
5322
|
+
if (this.targets === provider) this.targets = void 0;
|
|
5323
|
+
};
|
|
5324
|
+
}
|
|
5325
|
+
/** @internal */ pickTarget(data) {
|
|
5326
|
+
return this.targets?.disposed ? void 0 : this.targets?.pick(data);
|
|
5327
|
+
}
|
|
5261
5328
|
constructor(scene) {
|
|
5262
5329
|
this.scene = scene;
|
|
5263
5330
|
this.removeScene = scene.onDispose(() => this.dispose());
|
|
@@ -5280,6 +5347,7 @@ var ScenePointer = class {
|
|
|
5280
5347
|
/** @internal Device-independent routing state; DOM is only an adapter. */
|
|
5281
5348
|
var PointerRouting = class {
|
|
5282
5349
|
released;
|
|
5350
|
+
targetOwners = /* @__PURE__ */ new Map();
|
|
5283
5351
|
controlOwners = /* @__PURE__ */ new Map();
|
|
5284
5352
|
scrollOwners = /* @__PURE__ */ new Map();
|
|
5285
5353
|
suppressed = /* @__PURE__ */ new Set();
|
|
@@ -5304,7 +5372,8 @@ var PointerRouting = class {
|
|
|
5304
5372
|
keyboardNodes: this.keys.size,
|
|
5305
5373
|
deliveries: this.deliveries,
|
|
5306
5374
|
scrollDrags: this.scrollOwners.size,
|
|
5307
|
-
controlDrags: this.controlOwners.size
|
|
5375
|
+
controlDrags: this.controlOwners.size,
|
|
5376
|
+
virtualDrags: this.targetOwners.size
|
|
5308
5377
|
};
|
|
5309
5378
|
}
|
|
5310
5379
|
setScene(channel, ui) {
|
|
@@ -5330,6 +5399,14 @@ var PointerRouting = class {
|
|
|
5330
5399
|
}
|
|
5331
5400
|
}
|
|
5332
5401
|
emit(gesture, type, info) {
|
|
5402
|
+
if (gesture.target) {
|
|
5403
|
+
const phase = type === PointerDown ? "down" : type === PointerMove ? "move" : type === PointerUp ? "up" : type === PointerCancel ? "cancel" : void 0;
|
|
5404
|
+
if (phase) {
|
|
5405
|
+
this.deliveries++;
|
|
5406
|
+
gesture.target.deliver(phase, info);
|
|
5407
|
+
}
|
|
5408
|
+
return;
|
|
5409
|
+
}
|
|
5333
5410
|
if (gesture.channel.disposed || gesture.node?.disposed) return;
|
|
5334
5411
|
this.deliveries++;
|
|
5335
5412
|
(gesture.node?.events ?? gesture.channel.events).emit(type, info);
|
|
@@ -5337,7 +5414,7 @@ var PointerRouting = class {
|
|
|
5337
5414
|
inside(g, data) {
|
|
5338
5415
|
return Object.freeze({
|
|
5339
5416
|
...data,
|
|
5340
|
-
inside: g.scroll && g.ui ? g.ui.containsNode(g.scroll, data.screen.x / g.ui.scale, data.screen.y / g.ui.scale) : g.node ? g.ui?.hitTest(data.screen.x / g.ui.scale, data.screen.y / g.ui.scale) === g.node : data.inside
|
|
5417
|
+
inside: g.target ? g.target.contains(data) : g.scroll && g.ui ? g.ui.containsNode(g.scroll, data.screen.x / g.ui.scale, data.screen.y / g.ui.scale) : g.node ? g.ui?.hitTest(data.screen.x / g.ui.scale, data.screen.y / g.ui.scale) === g.node : data.inside
|
|
5341
5418
|
});
|
|
5342
5419
|
}
|
|
5343
5420
|
down(data) {
|
|
@@ -5347,6 +5424,11 @@ var PointerRouting = class {
|
|
|
5347
5424
|
this.suppressed.delete(data.pointerId);
|
|
5348
5425
|
const pick = this.ui?.pick(data.screen.x / this.ui.scale, data.screen.y / this.ui.scale);
|
|
5349
5426
|
const node = pick?.node, bar = pick?.bar;
|
|
5427
|
+
const target = !node && data.button === 0 ? this.channel.pickTarget(data) : void 0;
|
|
5428
|
+
if (target && this.targetOwners.has(target)) {
|
|
5429
|
+
this.suppressed.add(data.pointerId);
|
|
5430
|
+
return;
|
|
5431
|
+
}
|
|
5350
5432
|
if (bar && data.button === 0 && (!bar.enabled || this.scrollOwners.has(bar.node))) {
|
|
5351
5433
|
this.suppressed.add(data.pointerId);
|
|
5352
5434
|
return node;
|
|
@@ -5355,20 +5437,24 @@ var PointerRouting = class {
|
|
|
5355
5437
|
this.suppressed.add(data.pointerId);
|
|
5356
5438
|
return node;
|
|
5357
5439
|
}
|
|
5358
|
-
if (!node && !data.inside) return;
|
|
5440
|
+
if (!node && !target && !data.inside) return;
|
|
5359
5441
|
const g = {
|
|
5360
5442
|
channel: this.channel,
|
|
5361
5443
|
last: data,
|
|
5362
5444
|
primary: data.button === 0,
|
|
5363
5445
|
start: data,
|
|
5364
5446
|
scale: this.ui?.scale ?? 1,
|
|
5365
|
-
candidates: this.ui?.scrollTargets(data.screen.x / this.ui.scale, data.screen.y / this.ui.scale) ?? [],
|
|
5447
|
+
candidates: !target ? this.ui?.scrollTargets(data.screen.x / this.ui.scale, data.screen.y / this.ui.scale) ?? [] : [],
|
|
5366
5448
|
candidateRevisions: [],
|
|
5367
5449
|
vx: 0,
|
|
5368
5450
|
vy: 0,
|
|
5369
5451
|
...node ? {
|
|
5370
5452
|
node,
|
|
5371
5453
|
ui: this.ui
|
|
5454
|
+
} : {},
|
|
5455
|
+
...target ? {
|
|
5456
|
+
target,
|
|
5457
|
+
targetRevision: target.revision
|
|
5372
5458
|
} : {}
|
|
5373
5459
|
};
|
|
5374
5460
|
g.candidateRevisions = g.candidates.map((n) => n.scrollRevision);
|
|
@@ -5390,6 +5476,17 @@ var PointerRouting = class {
|
|
|
5390
5476
|
if (node?.control) g.controlRevision = node.control.revision;
|
|
5391
5477
|
this.gestures.set(data.pointerId, g);
|
|
5392
5478
|
if (node) g.off = node.onDispose(() => this.cancel(data.pointerId));
|
|
5479
|
+
if (target) {
|
|
5480
|
+
this.targetOwners.set(target, data.pointerId);
|
|
5481
|
+
const off = target.subscribe(() => {
|
|
5482
|
+
if (!target.available || target.revision !== g.targetRevision) this.cancel(data.pointerId);
|
|
5483
|
+
});
|
|
5484
|
+
const disposed = target.onDispose(() => this.cancel(data.pointerId));
|
|
5485
|
+
g.off = () => {
|
|
5486
|
+
off();
|
|
5487
|
+
disposed();
|
|
5488
|
+
};
|
|
5489
|
+
}
|
|
5393
5490
|
cleanupAll([
|
|
5394
5491
|
() => {
|
|
5395
5492
|
const port = node?.control;
|
|
@@ -5447,7 +5544,7 @@ var PointerRouting = class {
|
|
|
5447
5544
|
const dx = Math.abs(data.screen.x - g.start.screen.x), dy = Math.abs(data.screen.y - g.start.screen.y);
|
|
5448
5545
|
if (dx >= g.control.style.dragThreshold && dx > dy) g.controlLocked = true;
|
|
5449
5546
|
}
|
|
5450
|
-
if (!g.scroll && g.primary && !g.controlLocked) this.claimScroll(g, data);
|
|
5547
|
+
if (!g.target && !g.scroll && g.primary && !g.controlLocked) this.claimScroll(g, data);
|
|
5451
5548
|
if (this.gestures.get(data.pointerId) !== g) return;
|
|
5452
5549
|
if (g.bar && g.scroll && g.ui) {
|
|
5453
5550
|
const bar = canvasScrollbars(g.scroll).find((b) => b.axis === g.bar?.axis);
|
|
@@ -5588,6 +5685,7 @@ var PointerRouting = class {
|
|
|
5588
5685
|
g = this.gestures.get(data.pointerId);
|
|
5589
5686
|
if (!g) return;
|
|
5590
5687
|
this.gestures.delete(data.pointerId);
|
|
5688
|
+
if (g.target) this.targetOwners.delete(g.target);
|
|
5591
5689
|
if (g.control) this.controlOwners.delete(g.control);
|
|
5592
5690
|
if (g.scroll) {
|
|
5593
5691
|
this.scrollOwners.delete(g.scroll);
|
|
@@ -5605,7 +5703,7 @@ var PointerRouting = class {
|
|
|
5605
5703
|
if (!g.node || g.node.available) this.emit(g, PointerUp, info);
|
|
5606
5704
|
},
|
|
5607
5705
|
() => {
|
|
5608
|
-
if (!g.scroll && g.primary && info.inside && (!g.node || g.node.available)) this.emit(g, PointerClick, info);
|
|
5706
|
+
if (!g.target && !g.scroll && g.primary && info.inside && (!g.node || g.node.available)) this.emit(g, PointerClick, info);
|
|
5609
5707
|
},
|
|
5610
5708
|
() => {
|
|
5611
5709
|
if (!g.scroll && g.primary && info.inside && g.node?.available) g.node.activate("pointer");
|
|
@@ -5617,6 +5715,7 @@ var PointerRouting = class {
|
|
|
5617
5715
|
const g = this.gestures.get(id);
|
|
5618
5716
|
if (!g) return;
|
|
5619
5717
|
this.gestures.delete(id);
|
|
5718
|
+
if (g.target) this.targetOwners.delete(g.target);
|
|
5620
5719
|
if (g.control) this.controlOwners.delete(g.control);
|
|
5621
5720
|
if (g.scroll) {
|
|
5622
5721
|
this.scrollOwners.delete(g.scroll);
|
|
@@ -5687,6 +5786,10 @@ var PointerRouting = class {
|
|
|
5687
5786
|
...[...this.keys.keys()].map((n) => () => this.releaseKeys(n))
|
|
5688
5787
|
], "Pointer release failed");
|
|
5689
5788
|
}
|
|
5789
|
+
/** @internal Host focus changes interrupt virtual devices without changing Canvas editing rules. */
|
|
5790
|
+
releaseVirtual() {
|
|
5791
|
+
cleanupAll([...this.gestures].filter(([, g]) => g.target).map(([id]) => () => this.cancel(id)), "Virtual pointer release failed");
|
|
5792
|
+
}
|
|
5690
5793
|
dispose() {
|
|
5691
5794
|
try {
|
|
5692
5795
|
this.reset();
|
|
@@ -5702,6 +5805,716 @@ var PointerRouting = class {
|
|
|
5702
5805
|
cleanupAll([...off, () => this.release()], "Pointer routing cleanup failed");
|
|
5703
5806
|
}
|
|
5704
5807
|
};
|
|
5808
|
+
// packages/core/src/interaction/virtual-regions.ts
|
|
5809
|
+
var VirtualRegions = Object.freeze({
|
|
5810
|
+
/** A shared edge is not an overlap. Use separate rectangles to leave a center gap. */
|
|
5811
|
+
Left: Object.freeze({
|
|
5812
|
+
kind: "rect",
|
|
5813
|
+
x: 0,
|
|
5814
|
+
y: 0,
|
|
5815
|
+
width: .5,
|
|
5816
|
+
height: 1
|
|
5817
|
+
}),
|
|
5818
|
+
Right: Object.freeze({
|
|
5819
|
+
kind: "rect",
|
|
5820
|
+
x: .5,
|
|
5821
|
+
y: 0,
|
|
5822
|
+
width: .5,
|
|
5823
|
+
height: 1
|
|
5824
|
+
})
|
|
5825
|
+
});
|
|
5826
|
+
/** @internal */
|
|
5827
|
+
function virtualFinite(value, field) {
|
|
5828
|
+
if (typeof value !== "number" || !Number.isFinite(value)) throw new EngineError("INVALID_VIRTUAL_INPUT", `${field} must be finite`);
|
|
5829
|
+
return value;
|
|
5830
|
+
}
|
|
5831
|
+
/** @internal */
|
|
5832
|
+
function virtualBounds(value) {
|
|
5833
|
+
const result = {
|
|
5834
|
+
x: virtualFinite(value.x, "x"),
|
|
5835
|
+
y: virtualFinite(value.y, "y"),
|
|
5836
|
+
width: virtualFinite(value.width, "width"),
|
|
5837
|
+
height: virtualFinite(value.height, "height")
|
|
5838
|
+
};
|
|
5839
|
+
if (result.width < 0 || result.height < 0 || !Number.isFinite(result.x + result.width) || !Number.isFinite(result.y + result.height)) throw new EngineError("INVALID_VIRTUAL_INPUT", "Region extents must be finite and nonnegative");
|
|
5840
|
+
return Object.freeze(result);
|
|
5841
|
+
}
|
|
5842
|
+
/** @internal */
|
|
5843
|
+
function virtualPosition(value, viewport) {
|
|
5844
|
+
const space = value.space ?? "viewport";
|
|
5845
|
+
if (space !== "viewport" && space !== "screen") throw new EngineError("INVALID_VIRTUAL_INPUT", "Unknown virtual coordinate space");
|
|
5846
|
+
const x = virtualFinite(value.x, "position.x"), y = virtualFinite(value.y, "position.y");
|
|
5847
|
+
return Object.freeze(space === "screen" ? {
|
|
5848
|
+
x,
|
|
5849
|
+
y
|
|
5850
|
+
} : {
|
|
5851
|
+
x: virtualFinite(viewport.x + x * viewport.width, "resolved x"),
|
|
5852
|
+
y: virtualFinite(viewport.y + y * viewport.height, "resolved y")
|
|
5853
|
+
});
|
|
5854
|
+
}
|
|
5855
|
+
/** @internal Copy configuration before retaining it. Predicate functions are deliberately live. */
|
|
5856
|
+
function checkedVirtualRegion(region) {
|
|
5857
|
+
if (!region || ![
|
|
5858
|
+
"rect",
|
|
5859
|
+
"circle",
|
|
5860
|
+
"custom"
|
|
5861
|
+
].includes(region.kind)) throw new EngineError("INVALID_VIRTUAL_INPUT", "Unknown activation region");
|
|
5862
|
+
const copy = Object.freeze({ ...region });
|
|
5863
|
+
if (copy.kind === "custom") {
|
|
5864
|
+
if (typeof copy.bounds !== "function" || typeof copy.contains !== "function") throw new EngineError("INVALID_VIRTUAL_INPUT", "Custom regions require bounds and contains");
|
|
5865
|
+
} else resolveVirtualRegion(copy, {
|
|
5866
|
+
x: 0,
|
|
5867
|
+
y: 0,
|
|
5868
|
+
width: 1,
|
|
5869
|
+
height: 1
|
|
5870
|
+
});
|
|
5871
|
+
return copy;
|
|
5872
|
+
}
|
|
5873
|
+
/** @internal */
|
|
5874
|
+
function resolveVirtualRegion(region, viewport) {
|
|
5875
|
+
if (region.kind === "custom") {
|
|
5876
|
+
if (typeof region.bounds !== "function" || typeof region.contains !== "function") throw new EngineError("INVALID_VIRTUAL_INPUT", "Custom regions require bounds and contains functions");
|
|
5877
|
+
return {
|
|
5878
|
+
kind: "custom",
|
|
5879
|
+
bounds: virtualBounds(region.bounds(viewport)),
|
|
5880
|
+
test: region.contains
|
|
5881
|
+
};
|
|
5882
|
+
}
|
|
5883
|
+
const center = virtualPosition(region, viewport);
|
|
5884
|
+
if (region.kind === "rect") {
|
|
5885
|
+
const relative = (region.space ?? "viewport") === "viewport";
|
|
5886
|
+
const width = virtualFinite(region.width, "region.width"), height = virtualFinite(region.height, "region.height");
|
|
5887
|
+
if (width < 0 || height < 0) throw new EngineError("INVALID_VIRTUAL_INPUT", "Region sizes must be nonnegative");
|
|
5888
|
+
return {
|
|
5889
|
+
kind: "rect",
|
|
5890
|
+
bounds: virtualBounds({
|
|
5891
|
+
...center,
|
|
5892
|
+
width: width * (relative ? viewport.width : 1),
|
|
5893
|
+
height: height * (relative ? viewport.height : 1)
|
|
5894
|
+
})
|
|
5895
|
+
};
|
|
5896
|
+
}
|
|
5897
|
+
const radius = virtualFinite(region.radius, "region.radius") * ((region.space ?? "viewport") === "viewport" ? Math.min(viewport.width, viewport.height) : 1);
|
|
5898
|
+
if (radius < 0) throw new EngineError("INVALID_VIRTUAL_INPUT", "Region radius must be nonnegative");
|
|
5899
|
+
return {
|
|
5900
|
+
kind: "circle",
|
|
5901
|
+
center,
|
|
5902
|
+
radius,
|
|
5903
|
+
bounds: virtualBounds({
|
|
5904
|
+
x: center.x - radius,
|
|
5905
|
+
y: center.y - radius,
|
|
5906
|
+
width: radius * 2,
|
|
5907
|
+
height: radius * 2
|
|
5908
|
+
})
|
|
5909
|
+
};
|
|
5910
|
+
}
|
|
5911
|
+
/** @internal Rectangles are half-open; circles exclude their circumference. */
|
|
5912
|
+
function virtualContains(region, point, viewport) {
|
|
5913
|
+
const b = region.bounds;
|
|
5914
|
+
if (point.x < b.x || point.y < b.y || point.x >= b.x + b.width || point.y >= b.y + b.height) return false;
|
|
5915
|
+
if (region.kind === "circle") return Math.hypot(point.x - region.center.x, point.y - region.center.y) < region.radius;
|
|
5916
|
+
if (region.kind === "custom") {
|
|
5917
|
+
const value = region.test(point, viewport);
|
|
5918
|
+
if (typeof value !== "boolean") throw new EngineError("INVALID_VIRTUAL_INPUT", "Custom region predicate must return boolean");
|
|
5919
|
+
return value;
|
|
5920
|
+
}
|
|
5921
|
+
return true;
|
|
5922
|
+
}
|
|
5923
|
+
/** @internal Exact built-in intersection; custom bounds are conservatively ambiguous. */
|
|
5924
|
+
function virtualOverlap(a, b) {
|
|
5925
|
+
const x = a.bounds, y = b.bounds;
|
|
5926
|
+
if (Math.min(x.x + x.width, y.x + y.width) <= Math.max(x.x, y.x) || Math.min(x.y + x.height, y.y + y.height) <= Math.max(x.y, y.y)) return false;
|
|
5927
|
+
if (a.kind === "circle" && b.kind === "circle") return Math.hypot(a.center.x - b.center.x, a.center.y - b.center.y) < a.radius + b.radius;
|
|
5928
|
+
const circle = a.kind === "circle" ? a : b.kind === "circle" ? b : void 0;
|
|
5929
|
+
const rect = a.kind === "circle" ? b : a;
|
|
5930
|
+
if (circle && rect.kind === "rect") {
|
|
5931
|
+
const c = circle.center, r = rect.bounds;
|
|
5932
|
+
return Math.hypot(c.x - Math.max(r.x, Math.min(r.x + r.width, c.x)), c.y - Math.max(r.y, Math.min(r.y + r.height, c.y))) < circle.radius;
|
|
5933
|
+
}
|
|
5934
|
+
return true;
|
|
5935
|
+
}
|
|
5936
|
+
// packages/core/src/interaction/virtual-control.ts
|
|
5937
|
+
var VirtualStickMode = {
|
|
5938
|
+
Fixed: "fixed",
|
|
5939
|
+
Floating: "floating",
|
|
5940
|
+
Follow: "follow"
|
|
5941
|
+
};
|
|
5942
|
+
var screenPoint$1 = (x, y) => ({
|
|
5943
|
+
space: "screen",
|
|
5944
|
+
x: virtualFinite(x, "screen.x"),
|
|
5945
|
+
y: virtualFinite(y, "screen.y")
|
|
5946
|
+
});
|
|
5947
|
+
var zero = Object.freeze({
|
|
5948
|
+
x: 0,
|
|
5949
|
+
y: 0
|
|
5950
|
+
});
|
|
5951
|
+
var Start = defineEvent("VirtualStart");
|
|
5952
|
+
var Change = defineEvent("VirtualChange");
|
|
5953
|
+
var End = defineEvent("VirtualEnd");
|
|
5954
|
+
var Cancel = defineEvent("VirtualCancel");
|
|
5955
|
+
var Hold = defineEvent("VirtualHold");
|
|
5956
|
+
function flag(value) {
|
|
5957
|
+
if (typeof value !== "boolean") throw new EngineError("INVALID_VIRTUAL_INPUT", "Virtual control flags must be boolean");
|
|
5958
|
+
return value;
|
|
5959
|
+
}
|
|
5960
|
+
function zone(value) {
|
|
5961
|
+
virtualFinite(value, "deadZone");
|
|
5962
|
+
if (value < 0 || value >= 1) throw new EngineError("INVALID_VIRTUAL_INPUT", "Dead zone must satisfy 0 <= value < 1");
|
|
5963
|
+
return value;
|
|
5964
|
+
}
|
|
5965
|
+
function mode(value) {
|
|
5966
|
+
if (!Object.values(VirtualStickMode).includes(value)) throw new EngineError("INVALID_VIRTUAL_INPUT", "Unknown virtual stick mode");
|
|
5967
|
+
return value;
|
|
5968
|
+
}
|
|
5969
|
+
/** @internal Validate before allocating ActionSources or registering a target. */
|
|
5970
|
+
function checkVirtualOptions(options) {
|
|
5971
|
+
checkedVirtualRegion(options.region);
|
|
5972
|
+
positive(options.radius ?? 64, "radius");
|
|
5973
|
+
zone(options.deadZone ?? .15);
|
|
5974
|
+
flag(options.enabled ?? true);
|
|
5975
|
+
flag(options.visible ?? true);
|
|
5976
|
+
flag(options.direction ?? false);
|
|
5977
|
+
mode(options.mode ?? VirtualStickMode.Fixed);
|
|
5978
|
+
if (virtualFinite(options.holdDelay ?? .5, "holdDelay") < 0) throw new EngineError("INVALID_VIRTUAL_INPUT", "Hold delay must be nonnegative");
|
|
5979
|
+
if (options.position) {
|
|
5980
|
+
virtualFinite(options.position.x, "position.x");
|
|
5981
|
+
virtualFinite(options.position.y, "position.y");
|
|
5982
|
+
if (options.position.space !== void 0 && options.position.space !== "screen" && options.position.space !== "viewport") throw new EngineError("INVALID_VIRTUAL_INPUT", "Unknown position space");
|
|
5983
|
+
}
|
|
5984
|
+
const color = options.color ?? 6736821;
|
|
5985
|
+
if (!Number.isInteger(color) || color < 0 || color > 16777215) throw new EngineError("INVALID_VIRTUAL_INPUT", "Virtual color must be RGB");
|
|
5986
|
+
}
|
|
5987
|
+
/** Portable gesture state. Routing, Action composition and rendering retain their existing owners. */
|
|
5988
|
+
var VirtualControl = class {
|
|
5989
|
+
kind;
|
|
5990
|
+
host;
|
|
5991
|
+
binding;
|
|
5992
|
+
life = new Lifetime();
|
|
5993
|
+
events = new EventBus();
|
|
5994
|
+
observers = /* @__PURE__ */ new Set();
|
|
5995
|
+
activation;
|
|
5996
|
+
activeFlag;
|
|
5997
|
+
shown;
|
|
5998
|
+
size;
|
|
5999
|
+
deadZoneValue;
|
|
6000
|
+
version = 0;
|
|
6001
|
+
gesture;
|
|
6002
|
+
vector = zero;
|
|
6003
|
+
dragMode;
|
|
6004
|
+
position;
|
|
6005
|
+
holdDelay;
|
|
6006
|
+
color;
|
|
6007
|
+
directional;
|
|
6008
|
+
constructor(kind, options, host, binding) {
|
|
6009
|
+
this.kind = kind;
|
|
6010
|
+
this.host = host;
|
|
6011
|
+
this.binding = binding;
|
|
6012
|
+
checkVirtualOptions(options);
|
|
6013
|
+
this.activation = checkedVirtualRegion(options.region);
|
|
6014
|
+
this.position = options.position ? Object.freeze({ ...options.position }) : void 0;
|
|
6015
|
+
this.activeFlag = options.enabled ?? true;
|
|
6016
|
+
this.shown = options.visible ?? true;
|
|
6017
|
+
this.size = options.radius ?? (kind === "stick" ? 64 : 28);
|
|
6018
|
+
this.deadZoneValue = options.deadZone ?? .15;
|
|
6019
|
+
this.dragMode = mode(options.mode ?? VirtualStickMode.Fixed);
|
|
6020
|
+
this.holdDelay = options.holdDelay ?? .5;
|
|
6021
|
+
this.color = options.color ?? 6736821;
|
|
6022
|
+
this.directional = kind === "stick" || (options.direction ?? false);
|
|
6023
|
+
this.life.onDispose(binding.onDispose(() => this.dispose()));
|
|
6024
|
+
}
|
|
6025
|
+
get disposed() {
|
|
6026
|
+
return this.life.disposed;
|
|
6027
|
+
}
|
|
6028
|
+
get available() {
|
|
6029
|
+
return !this.disposed && this.activeFlag && this.host.available() && this.binding.active();
|
|
6030
|
+
}
|
|
6031
|
+
get revision() {
|
|
6032
|
+
return this.version;
|
|
6033
|
+
}
|
|
6034
|
+
get enabled() {
|
|
6035
|
+
return this.activeFlag;
|
|
6036
|
+
}
|
|
6037
|
+
set enabled(value) {
|
|
6038
|
+
this.alive();
|
|
6039
|
+
const next = flag(value);
|
|
6040
|
+
if (next === this.activeFlag) return;
|
|
6041
|
+
this.activeFlag = next;
|
|
6042
|
+
this.interrupt();
|
|
6043
|
+
}
|
|
6044
|
+
get visible() {
|
|
6045
|
+
return this.shown;
|
|
6046
|
+
}
|
|
6047
|
+
set visible(value) {
|
|
6048
|
+
this.alive();
|
|
6049
|
+
this.shown = flag(value);
|
|
6050
|
+
}
|
|
6051
|
+
get radius() {
|
|
6052
|
+
return this.size;
|
|
6053
|
+
}
|
|
6054
|
+
set radius(value) {
|
|
6055
|
+
this.alive();
|
|
6056
|
+
const next = positive(value, "radius");
|
|
6057
|
+
if (next === this.size) return;
|
|
6058
|
+
this.size = next;
|
|
6059
|
+
this.interrupt();
|
|
6060
|
+
}
|
|
6061
|
+
get deadZone() {
|
|
6062
|
+
return this.deadZoneValue;
|
|
6063
|
+
}
|
|
6064
|
+
set deadZone(value) {
|
|
6065
|
+
this.alive();
|
|
6066
|
+
const next = zone(value);
|
|
6067
|
+
if (next === this.deadZoneValue) return;
|
|
6068
|
+
this.deadZoneValue = next;
|
|
6069
|
+
this.interrupt();
|
|
6070
|
+
}
|
|
6071
|
+
get region() {
|
|
6072
|
+
return this.activation;
|
|
6073
|
+
}
|
|
6074
|
+
set region(value) {
|
|
6075
|
+
this.alive();
|
|
6076
|
+
const next = checkedVirtualRegion(value);
|
|
6077
|
+
this.host.region(this, next);
|
|
6078
|
+
this.activation = next;
|
|
6079
|
+
this.interrupt();
|
|
6080
|
+
}
|
|
6081
|
+
get stickMode() {
|
|
6082
|
+
return this.dragMode;
|
|
6083
|
+
}
|
|
6084
|
+
set stickMode(value) {
|
|
6085
|
+
this.alive();
|
|
6086
|
+
const next = mode(value);
|
|
6087
|
+
if (next === this.dragMode) return;
|
|
6088
|
+
this.dragMode = next;
|
|
6089
|
+
this.interrupt();
|
|
6090
|
+
}
|
|
6091
|
+
get pointerId() {
|
|
6092
|
+
return this.gesture?.last.pointerId;
|
|
6093
|
+
}
|
|
6094
|
+
get pressed() {
|
|
6095
|
+
return this.gesture !== void 0;
|
|
6096
|
+
}
|
|
6097
|
+
get elapsed() {
|
|
6098
|
+
return this.gesture?.elapsed ?? 0;
|
|
6099
|
+
}
|
|
6100
|
+
get direction() {
|
|
6101
|
+
return this.vector;
|
|
6102
|
+
}
|
|
6103
|
+
get center() {
|
|
6104
|
+
const p = this.gesture?.center ?? this.host.center(this);
|
|
6105
|
+
return Object.freeze(screenPoint$1(p.x, p.y));
|
|
6106
|
+
}
|
|
6107
|
+
get knob() {
|
|
6108
|
+
const center = this.center, last = this.gesture?.last.screen;
|
|
6109
|
+
if (!last || !this.directional) return center;
|
|
6110
|
+
const origin = this.kind === "button" ? this.gesture?.origin ?? center : center;
|
|
6111
|
+
const dx = last.x - origin.x, dy = last.y - origin.y, length = Math.hypot(dx, dy);
|
|
6112
|
+
const scale = length > this.size ? this.size / length : 1;
|
|
6113
|
+
return Object.freeze(screenPoint$1(center.x + dx * scale, center.y + dy * scale));
|
|
6114
|
+
}
|
|
6115
|
+
onDispose(callback) {
|
|
6116
|
+
return this.life.onDispose(callback);
|
|
6117
|
+
}
|
|
6118
|
+
/** @internal Geometry/availability invalidation, not a per-frame observer. */
|
|
6119
|
+
subscribe(callback) {
|
|
6120
|
+
this.alive();
|
|
6121
|
+
this.observers.add(callback);
|
|
6122
|
+
return () => {
|
|
6123
|
+
this.observers.delete(callback);
|
|
6124
|
+
};
|
|
6125
|
+
}
|
|
6126
|
+
onStart(callback, owner) {
|
|
6127
|
+
return this.events.on(Start, callback, owner);
|
|
6128
|
+
}
|
|
6129
|
+
onChange(callback, owner) {
|
|
6130
|
+
return this.events.on(Change, callback, owner);
|
|
6131
|
+
}
|
|
6132
|
+
onEnd(callback, owner) {
|
|
6133
|
+
return this.events.on(End, callback, owner);
|
|
6134
|
+
}
|
|
6135
|
+
onCancel(callback, owner) {
|
|
6136
|
+
return this.events.on(Cancel, callback, owner);
|
|
6137
|
+
}
|
|
6138
|
+
onHold(callback, owner) {
|
|
6139
|
+
return this.events.on(Hold, callback, owner);
|
|
6140
|
+
}
|
|
6141
|
+
/** Release/cancel only; the captured physical ID stays suppressed until its release. */
|
|
6142
|
+
cancel() {
|
|
6143
|
+
this.alive();
|
|
6144
|
+
this.interrupt();
|
|
6145
|
+
}
|
|
6146
|
+
/** @internal */
|
|
6147
|
+
invalidate() {
|
|
6148
|
+
this.interrupt();
|
|
6149
|
+
}
|
|
6150
|
+
alive() {
|
|
6151
|
+
if (this.disposed) throw new EngineError("OWNER_DISPOSED", "Virtual control has been disposed");
|
|
6152
|
+
}
|
|
6153
|
+
interrupt() {
|
|
6154
|
+
this.version++;
|
|
6155
|
+
cleanupAll([...[...this.observers], () => {
|
|
6156
|
+
if (this.gesture) this.finish(true);
|
|
6157
|
+
else this.binding.clear();
|
|
6158
|
+
}], "Virtual gesture interruption failed");
|
|
6159
|
+
}
|
|
6160
|
+
contains(data) {
|
|
6161
|
+
return this.host.contains(this, data);
|
|
6162
|
+
}
|
|
6163
|
+
snapshot(g) {
|
|
6164
|
+
return Object.freeze({
|
|
6165
|
+
control: this,
|
|
6166
|
+
pointerId: g.last.pointerId,
|
|
6167
|
+
pointerType: g.last.pointerType,
|
|
6168
|
+
origin: g.origin,
|
|
6169
|
+
position: Object.freeze(screenPoint$1(g.last.screen.x, g.last.screen.y)),
|
|
6170
|
+
center: g.center,
|
|
6171
|
+
direction: this.vector,
|
|
6172
|
+
inside: this.contains(g.last),
|
|
6173
|
+
elapsed: g.elapsed
|
|
6174
|
+
});
|
|
6175
|
+
}
|
|
6176
|
+
update(g) {
|
|
6177
|
+
if (!this.directional) {
|
|
6178
|
+
this.vector = zero;
|
|
6179
|
+
return;
|
|
6180
|
+
}
|
|
6181
|
+
const start = this.kind === "button" ? g.origin : g.center;
|
|
6182
|
+
const dx = g.last.screen.x - start.x, dy = g.last.screen.y - start.y;
|
|
6183
|
+
const length = Math.hypot(dx, dy);
|
|
6184
|
+
if (this.kind === "stick" && this.dragMode === VirtualStickMode.Follow && length > this.size) g.center = Object.freeze(screenPoint$1(g.last.screen.x - dx * this.size / length, g.last.screen.y - dy * this.size / length));
|
|
6185
|
+
const magnitude = Math.min(1, length / this.size);
|
|
6186
|
+
const output = magnitude <= this.deadZoneValue ? 0 : (magnitude - this.deadZoneValue) / (1 - this.deadZoneValue);
|
|
6187
|
+
this.vector = output === 0 ? zero : Object.freeze({
|
|
6188
|
+
x: dx / length * output,
|
|
6189
|
+
y: dy / length * output
|
|
6190
|
+
});
|
|
6191
|
+
}
|
|
6192
|
+
deliver(type, data) {
|
|
6193
|
+
if (type === "cancel") {
|
|
6194
|
+
if (this.gesture?.last.pointerId === data.pointerId) this.finish(true);
|
|
6195
|
+
return;
|
|
6196
|
+
}
|
|
6197
|
+
if (type === "down") {
|
|
6198
|
+
if (!this.available || this.gesture) return;
|
|
6199
|
+
const center = this.kind === "stick" && this.dragMode !== VirtualStickMode.Fixed ? data.screen : this.host.center(this);
|
|
6200
|
+
const g = {
|
|
6201
|
+
last: data,
|
|
6202
|
+
origin: Object.freeze(screenPoint$1(data.screen.x, data.screen.y)),
|
|
6203
|
+
center: Object.freeze(screenPoint$1(center.x, center.y)),
|
|
6204
|
+
elapsed: 0,
|
|
6205
|
+
held: false
|
|
6206
|
+
};
|
|
6207
|
+
this.gesture = g;
|
|
6208
|
+
this.update(g);
|
|
6209
|
+
this.binding.write(this.vector, true);
|
|
6210
|
+
const event = this.snapshot(g);
|
|
6211
|
+
cleanupAll([() => this.events.emit(Start, event), () => {
|
|
6212
|
+
if (this.gesture === g) this.events.emit(Change, event);
|
|
6213
|
+
}], "Virtual start handlers failed");
|
|
6214
|
+
return;
|
|
6215
|
+
}
|
|
6216
|
+
const g = this.gesture;
|
|
6217
|
+
if (!g || g.last.pointerId !== data.pointerId) return;
|
|
6218
|
+
if (!this.available) {
|
|
6219
|
+
this.interrupt();
|
|
6220
|
+
return;
|
|
6221
|
+
}
|
|
6222
|
+
if (data.screen.x !== g.last.screen.x || data.screen.y !== g.last.screen.y) {
|
|
6223
|
+
g.last = data;
|
|
6224
|
+
this.update(g);
|
|
6225
|
+
this.binding.write(this.vector, true);
|
|
6226
|
+
this.events.emit(Change, this.snapshot(g));
|
|
6227
|
+
}
|
|
6228
|
+
if (type === "up" && this.gesture === g) {
|
|
6229
|
+
g.last = data;
|
|
6230
|
+
this.finish(false);
|
|
6231
|
+
}
|
|
6232
|
+
}
|
|
6233
|
+
/** @internal Explicit presentation clock; no browser/OS timer in Core. */
|
|
6234
|
+
advance(seconds) {
|
|
6235
|
+
const g = this.gesture;
|
|
6236
|
+
if (!g) return;
|
|
6237
|
+
if (!this.available) {
|
|
6238
|
+
this.interrupt();
|
|
6239
|
+
return;
|
|
6240
|
+
}
|
|
6241
|
+
g.elapsed += seconds;
|
|
6242
|
+
if (!g.held && g.elapsed >= this.holdDelay) {
|
|
6243
|
+
g.held = true;
|
|
6244
|
+
this.events.emit(Hold, this.snapshot(g));
|
|
6245
|
+
}
|
|
6246
|
+
}
|
|
6247
|
+
finish(cancelled) {
|
|
6248
|
+
const g = this.gesture;
|
|
6249
|
+
if (!g) return;
|
|
6250
|
+
let event;
|
|
6251
|
+
try {
|
|
6252
|
+
event = this.snapshot(g);
|
|
6253
|
+
} finally {
|
|
6254
|
+
this.gesture = void 0;
|
|
6255
|
+
this.vector = zero;
|
|
6256
|
+
this.binding.clear();
|
|
6257
|
+
}
|
|
6258
|
+
this.events.emit(cancelled ? Cancel : End, event);
|
|
6259
|
+
}
|
|
6260
|
+
dispose() {
|
|
6261
|
+
if (this.disposed) return;
|
|
6262
|
+
cleanupAll([
|
|
6263
|
+
() => this.life.dispose(),
|
|
6264
|
+
() => this.interrupt(),
|
|
6265
|
+
() => this.binding.dispose(),
|
|
6266
|
+
() => this.events.dispose(),
|
|
6267
|
+
() => this.observers.clear(),
|
|
6268
|
+
() => this.host.remove(this)
|
|
6269
|
+
], "Virtual control cleanup failed");
|
|
6270
|
+
}
|
|
6271
|
+
};
|
|
6272
|
+
var VirtualStick = class extends VirtualControl {
|
|
6273
|
+
/** @internal Use VirtualInput.stick. */
|
|
6274
|
+
constructor(options, host, binding) {
|
|
6275
|
+
super("stick", options, host, binding);
|
|
6276
|
+
}
|
|
6277
|
+
get value() {
|
|
6278
|
+
return this.direction;
|
|
6279
|
+
}
|
|
6280
|
+
get mode() {
|
|
6281
|
+
return this.stickMode;
|
|
6282
|
+
}
|
|
6283
|
+
set mode(value) {
|
|
6284
|
+
this.stickMode = value;
|
|
6285
|
+
}
|
|
6286
|
+
};
|
|
6287
|
+
var VirtualButton = class extends VirtualControl {
|
|
6288
|
+
/** @internal Use VirtualInput.button. */
|
|
6289
|
+
constructor(options, host, binding) {
|
|
6290
|
+
super("button", options, host, binding);
|
|
6291
|
+
}
|
|
6292
|
+
get value() {
|
|
6293
|
+
return this.pressed;
|
|
6294
|
+
}
|
|
6295
|
+
/** Confirmation on normal release, including a drag outside the activation region; never on cancel. */
|
|
6296
|
+
onCommit(callback, owner) {
|
|
6297
|
+
return this.onEnd(callback, owner);
|
|
6298
|
+
}
|
|
6299
|
+
};
|
|
6300
|
+
// packages/core/src/interaction/virtual-input.ts
|
|
6301
|
+
/** Scene-scoped virtual devices; all gameplay values flow through existing ActionSources. */
|
|
6302
|
+
var VirtualInput = class {
|
|
6303
|
+
pointer;
|
|
6304
|
+
input;
|
|
6305
|
+
life = new Lifetime();
|
|
6306
|
+
list = Object.freeze([]);
|
|
6307
|
+
regions = /* @__PURE__ */ new Map();
|
|
6308
|
+
anchors = /* @__PURE__ */ new Map();
|
|
6309
|
+
view = Object.freeze({
|
|
6310
|
+
x: 0,
|
|
6311
|
+
y: 0,
|
|
6312
|
+
width: 800,
|
|
6313
|
+
height: 450
|
|
6314
|
+
});
|
|
6315
|
+
active = true;
|
|
6316
|
+
shown = true;
|
|
6317
|
+
dirty = true;
|
|
6318
|
+
host;
|
|
6319
|
+
constructor(pointer, input) {
|
|
6320
|
+
this.pointer = pointer;
|
|
6321
|
+
this.input = input;
|
|
6322
|
+
if (pointer.disposed) throw new EngineError("OWNER_DISPOSED", "Scene pointer has been disposed");
|
|
6323
|
+
this.host = {
|
|
6324
|
+
available: () => !this.disposed && this.active && input.liveInput,
|
|
6325
|
+
center: (control) => this.anchors.get(control) ?? {
|
|
6326
|
+
x: 0,
|
|
6327
|
+
y: 0
|
|
6328
|
+
},
|
|
6329
|
+
contains: (control, data) => {
|
|
6330
|
+
const region = this.regions.get(control);
|
|
6331
|
+
return region ? virtualContains(region, data.screen, this.view) : false;
|
|
6332
|
+
},
|
|
6333
|
+
region: (control, region) => {
|
|
6334
|
+
const regions = this.resolve(this.view, control, region);
|
|
6335
|
+
this.regions = regions;
|
|
6336
|
+
this.anchors = this.centers(regions, this.view);
|
|
6337
|
+
this.dirty = true;
|
|
6338
|
+
},
|
|
6339
|
+
remove: (control) => {
|
|
6340
|
+
this.list = Object.freeze(this.list.filter((c) => c !== control));
|
|
6341
|
+
this.regions.delete(control);
|
|
6342
|
+
this.anchors.delete(control);
|
|
6343
|
+
}
|
|
6344
|
+
};
|
|
6345
|
+
try {
|
|
6346
|
+
this.life.onDispose(pointer.attachTargets(this));
|
|
6347
|
+
this.life.onDispose(input.addAdapter(this));
|
|
6348
|
+
this.life.onDispose(pointer.onDispose(() => this.dispose()));
|
|
6349
|
+
} catch (error) {
|
|
6350
|
+
this.life.dispose();
|
|
6351
|
+
throw error;
|
|
6352
|
+
}
|
|
6353
|
+
}
|
|
6354
|
+
get disposed() {
|
|
6355
|
+
return this.life.disposed;
|
|
6356
|
+
}
|
|
6357
|
+
get controls() {
|
|
6358
|
+
return this.list;
|
|
6359
|
+
}
|
|
6360
|
+
get viewport() {
|
|
6361
|
+
return this.view;
|
|
6362
|
+
}
|
|
6363
|
+
get enabled() {
|
|
6364
|
+
return this.active;
|
|
6365
|
+
}
|
|
6366
|
+
set enabled(value) {
|
|
6367
|
+
this.alive();
|
|
6368
|
+
this.flag(value);
|
|
6369
|
+
if (this.active === value) return;
|
|
6370
|
+
this.active = value;
|
|
6371
|
+
this.reset();
|
|
6372
|
+
}
|
|
6373
|
+
get visible() {
|
|
6374
|
+
return this.shown;
|
|
6375
|
+
}
|
|
6376
|
+
set visible(value) {
|
|
6377
|
+
this.alive();
|
|
6378
|
+
this.flag(value);
|
|
6379
|
+
this.shown = value;
|
|
6380
|
+
}
|
|
6381
|
+
onDispose(callback) {
|
|
6382
|
+
return this.life.onDispose(callback);
|
|
6383
|
+
}
|
|
6384
|
+
flag(value) {
|
|
6385
|
+
if (typeof value !== "boolean") throw new EngineError("INVALID_VIRTUAL_INPUT", "Virtual input flags must be boolean");
|
|
6386
|
+
}
|
|
6387
|
+
alive() {
|
|
6388
|
+
if (this.disposed) throw new EngineError("OWNER_DISPOSED", "Virtual input has been disposed");
|
|
6389
|
+
}
|
|
6390
|
+
validate(controller, action, kind, options) {
|
|
6391
|
+
this.alive();
|
|
6392
|
+
checkVirtualOptions(options);
|
|
6393
|
+
if (!this.input.hasController(controller) || controller.reader(action).scene !== this.pointer.scene || action.kind !== kind) throw new EngineError("INVALID_ACTION", "Virtual input requires a matching Action kind and a controller in its own Scene/InputSystem");
|
|
6394
|
+
const region = resolveVirtualRegion(options.region, this.view);
|
|
6395
|
+
const peers = this.resolve(this.view);
|
|
6396
|
+
for (const control of this.list) {
|
|
6397
|
+
const other = peers.get(control);
|
|
6398
|
+
if (other && control.kind === (kind === "axis2d" ? "stick" : "button") && virtualOverlap(region, other)) throw new EngineError("VIRTUAL_REGION_OVERLAP", "Same-priority virtual activation regions overlap");
|
|
6399
|
+
}
|
|
6400
|
+
if (options.position) virtualPosition(options.position, this.view);
|
|
6401
|
+
}
|
|
6402
|
+
binding(controller, action) {
|
|
6403
|
+
const source = controller.createSource();
|
|
6404
|
+
return {
|
|
6405
|
+
get disposed() {
|
|
6406
|
+
return source.disposed;
|
|
6407
|
+
},
|
|
6408
|
+
onDispose: (callback) => controller.onDispose(callback),
|
|
6409
|
+
clear: () => source.clear(),
|
|
6410
|
+
dispose: () => source.dispose(),
|
|
6411
|
+
active: () => controller.active,
|
|
6412
|
+
write: (direction, pressed) => source.set(action, action.kind === "axis2d" ? direction : pressed)
|
|
6413
|
+
};
|
|
6414
|
+
}
|
|
6415
|
+
stick(controller, action, options) {
|
|
6416
|
+
this.validate(controller, action, "axis2d", options);
|
|
6417
|
+
const binding = this.binding(controller, action);
|
|
6418
|
+
try {
|
|
6419
|
+
return this.add(new VirtualStick(options, this.host, binding));
|
|
6420
|
+
} catch (error) {
|
|
6421
|
+
binding.dispose();
|
|
6422
|
+
throw error;
|
|
6423
|
+
}
|
|
6424
|
+
}
|
|
6425
|
+
button(controller, action, options) {
|
|
6426
|
+
this.validate(controller, action, "button", options);
|
|
6427
|
+
const binding = this.binding(controller, action);
|
|
6428
|
+
try {
|
|
6429
|
+
return this.add(new VirtualButton(options, this.host, binding));
|
|
6430
|
+
} catch (error) {
|
|
6431
|
+
binding.dispose();
|
|
6432
|
+
throw error;
|
|
6433
|
+
}
|
|
6434
|
+
}
|
|
6435
|
+
add(control) {
|
|
6436
|
+
this.list = Object.freeze([...this.list, control]);
|
|
6437
|
+
this.dirty = true;
|
|
6438
|
+
try {
|
|
6439
|
+
this.refresh();
|
|
6440
|
+
} catch (error) {
|
|
6441
|
+
control.dispose();
|
|
6442
|
+
throw error;
|
|
6443
|
+
}
|
|
6444
|
+
return control;
|
|
6445
|
+
}
|
|
6446
|
+
resolve(view, replaced, region) {
|
|
6447
|
+
const result = /* @__PURE__ */ new Map();
|
|
6448
|
+
for (const control of this.list) result.set(control, resolveVirtualRegion(control === replaced && region ? region : control.region, view));
|
|
6449
|
+
for (let i = 0; i < this.list.length; i++) {
|
|
6450
|
+
const a = this.list[i];
|
|
6451
|
+
if (!a) continue;
|
|
6452
|
+
for (let j = i + 1; j < this.list.length; j++) {
|
|
6453
|
+
const b = this.list[j], x = result.get(a), y = b ? result.get(b) : void 0;
|
|
6454
|
+
if (b && x && y && a.kind === b.kind && virtualOverlap(x, y)) throw new EngineError("VIRTUAL_REGION_OVERLAP", "Same-priority virtual activation regions overlap", {
|
|
6455
|
+
first: i,
|
|
6456
|
+
second: j
|
|
6457
|
+
});
|
|
6458
|
+
}
|
|
6459
|
+
}
|
|
6460
|
+
return result;
|
|
6461
|
+
}
|
|
6462
|
+
centers(regions, view) {
|
|
6463
|
+
return new Map([...regions].map(([control, region]) => {
|
|
6464
|
+
const b = region.bounds;
|
|
6465
|
+
return [control, control.position ? virtualPosition(control.position, view) : Object.freeze({
|
|
6466
|
+
x: b.x + b.width / 2,
|
|
6467
|
+
y: b.y + b.height / 2
|
|
6468
|
+
})];
|
|
6469
|
+
}));
|
|
6470
|
+
}
|
|
6471
|
+
/** Resolve after Canvas layout and before routing/rendering. Layout changes cancel active gestures. */
|
|
6472
|
+
refresh(viewport = this.view) {
|
|
6473
|
+
this.alive();
|
|
6474
|
+
const view = virtualBounds(viewport);
|
|
6475
|
+
const resized = Object.keys(view).some((key) => view[key] !== this.view[key]);
|
|
6476
|
+
if (!this.dirty && !resized && !this.list.some((c) => c.region.kind === "custom")) return;
|
|
6477
|
+
const regions = this.resolve(view), centers = this.centers(regions, view);
|
|
6478
|
+
const changed = this.list.filter((c) => resized || JSON.stringify(regions.get(c)) !== JSON.stringify(this.regions.get(c)) || JSON.stringify(centers.get(c)) !== JSON.stringify(this.anchors.get(c)));
|
|
6479
|
+
this.view = view;
|
|
6480
|
+
this.regions = regions;
|
|
6481
|
+
this.anchors = centers;
|
|
6482
|
+
this.dirty = false;
|
|
6483
|
+
cleanupAll(changed.map((c) => () => c.invalidate()), "Virtual layout cancellation failed");
|
|
6484
|
+
}
|
|
6485
|
+
pick(data) {
|
|
6486
|
+
if (this.disposed || !this.active || !this.input.liveInput || data.button !== 0) return;
|
|
6487
|
+
this.refresh();
|
|
6488
|
+
for (const kind of ["button", "stick"]) for (const control of this.list) if (control.kind === kind && control.available && control.contains(data)) return control;
|
|
6489
|
+
}
|
|
6490
|
+
sample() {
|
|
6491
|
+
if (this.disposed) return;
|
|
6492
|
+
this.refresh();
|
|
6493
|
+
cleanupAll(this.list.filter((c) => c.pressed && !c.available).map((c) => () => c.invalidate()), "Virtual availability cancellation failed");
|
|
6494
|
+
}
|
|
6495
|
+
advance(seconds) {
|
|
6496
|
+
this.alive();
|
|
6497
|
+
if (virtualFinite(seconds, "seconds") < 0) throw new EngineError("INVALID_VIRTUAL_INPUT", "Presentation seconds must be nonnegative");
|
|
6498
|
+
cleanupAll(this.list.map((c) => () => {
|
|
6499
|
+
if (!c.disposed) c.advance(seconds);
|
|
6500
|
+
}), "Virtual hold callbacks failed");
|
|
6501
|
+
}
|
|
6502
|
+
reset() {
|
|
6503
|
+
cleanupAll(this.list.map((c) => () => c.invalidate()), "Virtual input reset failed");
|
|
6504
|
+
}
|
|
6505
|
+
dispose() {
|
|
6506
|
+
if (this.disposed) return;
|
|
6507
|
+
cleanupAll([
|
|
6508
|
+
() => this.life.dispose(),
|
|
6509
|
+
...this.list.map((c) => () => c.dispose()),
|
|
6510
|
+
() => {
|
|
6511
|
+
this.regions.clear();
|
|
6512
|
+
this.anchors.clear();
|
|
6513
|
+
this.list = Object.freeze([]);
|
|
6514
|
+
}
|
|
6515
|
+
], "Virtual input cleanup failed");
|
|
6516
|
+
}
|
|
6517
|
+
};
|
|
5705
6518
|
// packages/core/src/runtime/engine.ts
|
|
5706
6519
|
var Engine = class {
|
|
5707
6520
|
input = new InputSystem();
|
|
@@ -6269,12 +7082,12 @@ var ReplayPlayer = class {
|
|
|
6269
7082
|
this.engine = engine;
|
|
6270
7083
|
this.data = parseReplayData(data);
|
|
6271
7084
|
this.bound = bindings(engine, options.controllers);
|
|
6272
|
-
if (this.data.engineVersion !== "0.1.
|
|
7085
|
+
if (this.data.engineVersion !== "0.1.5" || engine.random.seed !== this.data.random.seed) throw new EngineError("REPLAY_INCOMPATIBLE", "Engine version or initial seed does not match Replay");
|
|
6273
7086
|
if (!compatible(this.bound.map(([id, c]) => ({
|
|
6274
7087
|
id,
|
|
6275
7088
|
actions: c.schema()
|
|
6276
7089
|
})), this.data.controllers)) throw new EngineError("REPLAY_INCOMPATIBLE", "Controller IDs or Action names/kinds/normalization do not match Replay");
|
|
6277
|
-
for (const [key, value] of Object.entries(options.expectedMetadata ?? {})) if (!compatible(copyJson(value), this.data.metadata[key])) throw new EngineError("REPLAY_INCOMPATIBLE", `Metadata mismatch: ${key}`);
|
|
7090
|
+
for (const [key, value] of Object.entries(options.expectedMetadata ?? {})) if (!Object.hasOwn(this.data.metadata, key) || !compatible(copyJson(value), this.data.metadata[key])) throw new EngineError("REPLAY_INCOMPATIBLE", `Metadata mismatch: ${key}`);
|
|
6278
7091
|
this.detach = engine.attachExecution(this);
|
|
6279
7092
|
try {
|
|
6280
7093
|
engine.input.lockPlayback();
|
|
@@ -6515,7 +7328,7 @@ function checkedStyle(value) {
|
|
|
6515
7328
|
"highlight"
|
|
6516
7329
|
]
|
|
6517
7330
|
};
|
|
6518
|
-
if (typeof v !== "string" || (choices
|
|
7331
|
+
if (typeof v !== "string" || (Object.hasOwn(choices, key) ? !choices[key]?.includes(v) : !validFontFamily(v))) throw new EngineError("INVALID_UI", `Invalid UI ${key}`);
|
|
6519
7332
|
}
|
|
6520
7333
|
}
|
|
6521
7334
|
return Object.freeze(result);
|
|
@@ -6947,6 +7760,10 @@ function CanvasScroll(children = [], style = {}) {
|
|
|
6947
7760
|
clip: true
|
|
6948
7761
|
});
|
|
6949
7762
|
}
|
|
7763
|
+
function validFontFamily(value) {
|
|
7764
|
+
const syntax = value.replace(/"(?:\\[^\r\n]|[^"\\])*"|'(?:\\[^\r\n]|[^'\\])*'/g, "family").replace(/\\(?:[0-9a-f]{1,6}[ \t]?|[^\r\n])/gi, "x");
|
|
7765
|
+
return value.trim().length > 0 && !/[\r\n\0]/.test(value) && !/[;{}<>\\"']|url\s*\(/i.test(syntax);
|
|
7766
|
+
}
|
|
6950
7767
|
// packages/core/src/ui/canvas-input.ts
|
|
6951
7768
|
var boolean = (value) => {
|
|
6952
7769
|
if (typeof value !== "boolean") throw new EngineError("INVALID_UI", "Control flag must be boolean");
|
|
@@ -8163,6 +8980,6 @@ var CanvasUI = class {
|
|
|
8163
8980
|
], "Canvas UI cleanup failed");
|
|
8164
8981
|
}
|
|
8165
8982
|
};
|
|
8166
|
-
export { Action, ActionController, ActionMovement, ActionSource, Actor, ActorState, Aspect, BodyType, Camera, CameraFollow, Cancellation, CanvasButton, CanvasCheckbox, CanvasCheckboxNode, CanvasColumn, CanvasHoldButton, CanvasInputNode, CanvasLabel, CanvasNode, CanvasNumberInput, CanvasNumberInputNode, CanvasPanel, CanvasRow, CanvasScroll, CanvasSelect, CanvasSelectNode, CanvasSlider, CanvasSliderNode, CanvasTextEditorNode, CanvasTextInput, CanvasTextInputNode, CanvasUI, Character, CharacterActor, Collider, CollisionBody, CollisionResponse, CollisionShape, CollisionWorld, Component, ComponentPhase, ContactEnter, ContactExit, ContactStay, DEFAULT_RANDOM_STREAM, ENGINE_VERSION, Easing, Engine, EngineError, EventBus, FixedStepRunner, GeometryTilemap, GeometryTilemapLayer, Grid, HexOrientation, InputSystem, KeyCode, KeyboardBindings, KeyboardSource, Lifetime, Motion, PointerCancel, PointerClick, PointerDown, PointerEnter, PointerLeave, PointerMove, PointerRouting, PointerUp, RANDOM_ALGORITHM, RandomManager, RandomStream, Renderable, ReplayPlayer, ReplayRecorder, Scene, ScenePointer, SceneState, Shape, SpriteAnimation, SpriteAnimationState, SpriteFit, SpriteSheet, TileContactEnter, TileContactExit, TileContactStay, TileGeometry, Tilemap, TilemapLayer, Transform, Tween, TweenBinding, TweenPlayback, TweenScheduler, actorTween, canvasControlText, canvasHoverHighlight, canvasNumericRange, canvasNumericValue, canvasOpacity, canvasScrollbars, canvasSliderGeometry, canvasTween, cleanupAll, containsScreenPoint, containsUI, createViewTransform, defaultCanvasStyle, defineActions, defineComponent, defineEvent, deriveStreamSeed, imageSize, intersectUI, isKeyCode, layoutCanvas, nativeSession, parseKeyCode, parseReplayData, positive, screenPoint, screenToPhysical, screenToWorld, scrollbarContains, sliderTween, spriteFrame, spriteFrameBounds, tweenFinite, worldPoint, worldToScreen, wrapCanvasText };
|
|
8983
|
+
export { Action, ActionController, ActionMovement, ActionSource, Actor, ActorState, Aspect, BodyType, Camera, CameraFollow, Cancellation, CanvasButton, CanvasCheckbox, CanvasCheckboxNode, CanvasColumn, CanvasHoldButton, CanvasInputNode, CanvasLabel, CanvasNode, CanvasNumberInput, CanvasNumberInputNode, CanvasPanel, CanvasRow, CanvasScroll, CanvasSelect, CanvasSelectNode, CanvasSlider, CanvasSliderNode, CanvasTextEditorNode, CanvasTextInput, CanvasTextInputNode, CanvasUI, Character, CharacterActor, Collider, CollisionBody, CollisionResponse, CollisionShape, CollisionWorld, Component, ComponentPhase, ContactEnter, ContactExit, ContactStay, DEFAULT_RANDOM_STREAM, ENGINE_VERSION, Easing, Engine, EngineError, EventBus, FixedStepRunner, GeometryTilemap, GeometryTilemapLayer, Grid, HexOrientation, InputSystem, KeyCode, KeyboardBindings, KeyboardSource, Lifetime, Motion, PointerCancel, PointerClick, PointerDown, PointerEnter, PointerLeave, PointerMove, PointerRouting, PointerUp, RANDOM_ALGORITHM, RandomManager, RandomStream, Renderable, ReplayPlayer, ReplayRecorder, Scene, ScenePointer, SceneState, Shape, SpriteAnimation, SpriteAnimationState, SpriteFit, SpriteSheet, TileContactEnter, TileContactExit, TileContactStay, TileGeometry, Tilemap, TilemapLayer, Transform, Tween, TweenBinding, TweenPlayback, TweenScheduler, VirtualButton, VirtualControl, VirtualInput, VirtualRegions, VirtualStick, VirtualStickMode, actorTween, canvasControlText, canvasHoverHighlight, canvasNumericRange, canvasNumericValue, canvasOpacity, canvasScrollbars, canvasSliderGeometry, canvasTween, cleanupAll, containsScreenPoint, containsUI, createViewTransform, defaultCanvasStyle, defineActions, defineComponent, defineEvent, deriveStreamSeed, imageSize, intersectUI, isKeyCode, layoutCanvas, nativeSession, parseKeyCode, parseReplayData, positive, screenPoint, screenToPhysical, screenToWorld, scrollbarContains, sliderTween, spriteFrame, spriteFrameBounds, tweenFinite, worldPoint, worldToScreen, wrapCanvasText };
|
|
8167
8984
|
|
|
8168
8985
|
//# sourceMappingURL=index.js.map
|