@realitycollective/iwsdk-uiextensions 0.1.0-preview.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/Examples/README.md +13 -0
  3. package/Examples/basic-window/main.ts +32 -0
  4. package/Examples/basic-window/window.uikitml +58 -0
  5. package/Examples/controls/controls.uikitml +49 -0
  6. package/Examples/controls/main.ts +48 -0
  7. package/Examples/dock-regions/main.ts +55 -0
  8. package/LICENSE +21 -0
  9. package/README.md +155 -0
  10. package/dist/component-set.d.ts +28 -0
  11. package/dist/component-set.js +108 -0
  12. package/dist/component-set.js.map +1 -0
  13. package/dist/components.d.ts +189 -0
  14. package/dist/components.js +97 -0
  15. package/dist/components.js.map +1 -0
  16. package/dist/factory.d.ts +44 -0
  17. package/dist/factory.js +63 -0
  18. package/dist/factory.js.map +1 -0
  19. package/dist/index.d.ts +25 -0
  20. package/dist/index.js +28 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/manager-registry.d.ts +8 -0
  23. package/dist/manager-registry.js +11 -0
  24. package/dist/manager-registry.js.map +1 -0
  25. package/dist/region-registry-store.d.ts +7 -0
  26. package/dist/region-registry-store.js +11 -0
  27. package/dist/region-registry-store.js.map +1 -0
  28. package/dist/register.d.ts +19 -0
  29. package/dist/register.js +20 -0
  30. package/dist/register.js.map +1 -0
  31. package/dist/scene-host.d.ts +44 -0
  32. package/dist/scene-host.js +190 -0
  33. package/dist/scene-host.js.map +1 -0
  34. package/dist/systems/controls-system.d.ts +24 -0
  35. package/dist/systems/controls-system.js +24 -0
  36. package/dist/systems/controls-system.js.map +1 -0
  37. package/dist/systems/dock-region-system.d.ts +230 -0
  38. package/dist/systems/dock-region-system.js +102 -0
  39. package/dist/systems/dock-region-system.js.map +1 -0
  40. package/dist/systems/dock-system.d.ts +205 -0
  41. package/dist/systems/dock-system.js +135 -0
  42. package/dist/systems/dock-system.js.map +1 -0
  43. package/dist/systems/drag-system.d.ts +280 -0
  44. package/dist/systems/drag-system.js +167 -0
  45. package/dist/systems/drag-system.js.map +1 -0
  46. package/dist/systems/window-system.d.ts +439 -0
  47. package/dist/systems/window-system.js +244 -0
  48. package/dist/systems/window-system.js.map +1 -0
  49. package/package.json +67 -0
@@ -0,0 +1,167 @@
1
+ /**
2
+ * UIDragSystem - drag windows by the title bar.
3
+ *
4
+ * Reuses `@pmndrs/handle` (the exact library behind the IWSDK's grab
5
+ * systems): a `HandleStore` targets the window entity's object while being
6
+ * *bound* to the title-bar uikit element, so grabbing the title bar with the
7
+ * ray (or a pinch) moves the whole window with the same feel as native IWSDK
8
+ * grabbing. The system pumps its own stores, so it works even when the app
9
+ * doesn't enable `features.grabbing`.
10
+ *
11
+ * Dragging implies placing: on grab the window leaves any dock region and
12
+ * switches to `world-locked`; on release near a region it docks there.
13
+ */
14
+ import { HandleStore } from '@pmndrs/handle';
15
+ import { PanelDocument, Vector3, createSystem, } from '@iwsdk/core';
16
+ import { UIDockRegion, UIDockedTo, UIWindow, UIWindowState } from '../components.js';
17
+ import { WINDOW_CHROME_IDS } from '@realitycollective/webxr-uiextensions';
18
+ import { DockMode } from '@realitycollective/webxr-uiextensions';
19
+ import { faceViewerYaw } from '@realitycollective/webxr-uiextensions';
20
+ import { HoldToDrag } from '@realitycollective/webxr-uiextensions';
21
+ import { regionRegistryFor } from '../region-registry-store.js';
22
+ import { windowManagerFor } from '../manager-registry.js';
23
+ export class UIDragSystem extends createSystem({
24
+ windows: { required: [UIWindow, UIWindowState, PanelDocument] },
25
+ regions: { required: [UIDockRegion] },
26
+ }) {
27
+ states = new Map();
28
+ worldPosition = new Vector3();
29
+ viewerPosition = new Vector3();
30
+ init() {
31
+ this.cleanupFuncs.push(this.queries.windows.subscribe('qualify', (entity) => {
32
+ this.attach(entity);
33
+ }), this.queries.windows.subscribe('disqualify', (entity) => {
34
+ this.detach(entity);
35
+ }));
36
+ this.queries.windows.entities.forEach((entity) => this.attach(entity));
37
+ }
38
+ update(delta) {
39
+ for (const [entity, state] of this.states) {
40
+ const held = state.store.inputState.size > 0;
41
+ const { phase, began, ended } = state.hold.update(held, delta);
42
+ if (began) {
43
+ this.onDragStart(entity);
44
+ }
45
+ if (phase === 'dragging') {
46
+ // The store only runs while an actual drag is in progress: presses
47
+ // shorter than the hold delay never move the window (they're
48
+ // clicks), and skipping the release-frame update stops the handle
49
+ // from restoring its grab-time rotation over our billboard yaw.
50
+ state.store.update(delta);
51
+ this.whileDragging(entity);
52
+ }
53
+ if (ended) {
54
+ this.onDragEnd(entity);
55
+ }
56
+ }
57
+ }
58
+ attach(entity) {
59
+ if (this.states.has(entity) || !entity.getValue(UIWindow, 'movable')) {
60
+ return;
61
+ }
62
+ const document = PanelDocument.data.document[entity.index];
63
+ const titlebar = document?.getElementById(WINDOW_CHROME_IDS.titlebar);
64
+ const object = entity.object3D;
65
+ if (!titlebar || !object) {
66
+ return;
67
+ }
68
+ const store = new HandleStore(object, () => ({
69
+ translate: true,
70
+ rotate: false,
71
+ scale: false,
72
+ multitouch: false,
73
+ projectRays: false,
74
+ }));
75
+ // The title-bar uikit element is an Object3D - bind it as the grab
76
+ // surface while the store's target stays the window root.
77
+ store.bind(titlebar);
78
+ this.states.set(entity, {
79
+ store,
80
+ hold: new HoldToDrag(Math.max(0, entity.getValue(UIWindow, 'dragDelay'))),
81
+ });
82
+ }
83
+ detach(entity) {
84
+ const state = this.states.get(entity);
85
+ if (!state) {
86
+ return;
87
+ }
88
+ try {
89
+ state.store.cancel();
90
+ }
91
+ catch {
92
+ // Handle stores throw when cancelled with no active input; ignore.
93
+ }
94
+ this.states.delete(entity);
95
+ }
96
+ onDragStart(entity) {
97
+ const id = entity.getValue(UIWindow, 'windowId');
98
+ const manager = windowManagerFor(this.world);
99
+ if (id && manager.has(id)) {
100
+ manager.focus(id);
101
+ // Mark dragging BEFORE the dock change so the pin label reads "PIN"
102
+ // (loose in hand) throughout the drag, not "UNPIN".
103
+ manager.setDragging(id, true);
104
+ }
105
+ // Grabbing takes the window out of any region and out of follow mode -
106
+ // the user is placing it by hand.
107
+ if (entity.hasComponent(UIDockedTo)) {
108
+ entity.removeComponent(UIDockedTo);
109
+ }
110
+ if (entity.getValue(UIWindow, 'dockMode') !== DockMode.WorldLocked) {
111
+ entity.setValue(UIWindow, 'dockMode', DockMode.WorldLocked);
112
+ }
113
+ }
114
+ whileDragging(entity) {
115
+ if (!entity.getValue(UIWindow, 'billboardWhileDragging')) {
116
+ return;
117
+ }
118
+ const object = entity.object3D;
119
+ if (!object) {
120
+ return;
121
+ }
122
+ object.getWorldPosition(this.worldPosition);
123
+ this.camera.getWorldPosition(this.viewerPosition);
124
+ const yaw = faceViewerYaw([this.worldPosition.x, this.worldPosition.y, this.worldPosition.z], [this.viewerPosition.x, this.viewerPosition.y, this.viewerPosition.z], object.rotation.y);
125
+ object.rotation.set(0, yaw, 0);
126
+ }
127
+ onDragEnd(entity) {
128
+ const id = entity.getValue(UIWindow, 'windowId');
129
+ const manager = windowManagerFor(this.world);
130
+ if (id && manager.has(id)) {
131
+ manager.setDragging(id, false); // released → placed → label "UNPIN"
132
+ }
133
+ // Settle facing the viewer at the drop spot - the window keeps the
134
+ // orientation it was dragged in, not its pre-drag rotation.
135
+ this.whileDragging(entity);
136
+ const object = entity.object3D;
137
+ if (!object) {
138
+ return;
139
+ }
140
+ object.getWorldPosition(this.worldPosition);
141
+ const dropPoint = [
142
+ this.worldPosition.x,
143
+ this.worldPosition.y,
144
+ this.worldPosition.z,
145
+ ];
146
+ const registry = regionRegistryFor(this.world);
147
+ const origins = new Map();
148
+ this.queries.regions.entities.forEach((regionEntity) => {
149
+ const regionId = regionEntity.getValue(UIDockRegion, 'regionId');
150
+ const regionObject = regionEntity.object3D;
151
+ if (!regionId || !regionObject) {
152
+ return;
153
+ }
154
+ regionObject.getWorldPosition(this.viewerPosition);
155
+ origins.set(regionId, [
156
+ this.viewerPosition.x,
157
+ this.viewerPosition.y,
158
+ this.viewerPosition.z,
159
+ ]);
160
+ });
161
+ const captured = registry.capture(dropPoint, origins);
162
+ if (captured !== undefined) {
163
+ entity.addComponent(UIDockedTo, { regionId: captured });
164
+ }
165
+ }
166
+ }
167
+ //# sourceMappingURL=drag-system.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"drag-system.js","sourceRoot":"","sources":["../../src/systems/drag-system.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * UIDragSystem - drag windows by the title bar.\n *\n * Reuses `@pmndrs/handle` (the exact library behind the IWSDK's grab\n * systems): a `HandleStore` targets the window entity's object while being\n * *bound* to the title-bar uikit element, so grabbing the title bar with the\n * ray (or a pinch) moves the whole window with the same feel as native IWSDK\n * grabbing. The system pumps its own stores, so it works even when the app\n * doesn't enable `features.grabbing`.\n *\n * Dragging implies placing: on grab the window leaves any dock region and\n * switches to `world-locked`; on release near a region it docks there.\n */\nimport { HandleStore } from '@pmndrs/handle';\nimport {\n PanelDocument,\n UIKitDocument,\n Object3D,\n Vector3,\n createSystem,\n type Entity,\n} from '@iwsdk/core';\nimport { UIDockRegion, UIDockedTo, UIWindow, UIWindowState } from '../components.js';\nimport { WINDOW_CHROME_IDS } from '@realitycollective/webxr-uiextensions';\nimport { DockMode } from '@realitycollective/webxr-uiextensions';\nimport { faceViewerYaw } from '@realitycollective/webxr-uiextensions';\nimport { HoldToDrag } from '@realitycollective/webxr-uiextensions';\nimport type { Vec3 } from '@realitycollective/webxr-uiextensions';\nimport { regionRegistryFor } from '../region-registry-store.js';\nimport { windowManagerFor } from '../manager-registry.js';\n\ninterface DragState {\n store: HandleStore<unknown>;\n hold: HoldToDrag;\n}\n\nexport class UIDragSystem extends createSystem({\n windows: { required: [UIWindow, UIWindowState, PanelDocument] },\n regions: { required: [UIDockRegion] },\n}) {\n private states = new Map<Entity, DragState>();\n private worldPosition = new Vector3();\n private viewerPosition = new Vector3();\n\n override init(): void {\n this.cleanupFuncs.push(\n this.queries.windows.subscribe('qualify', (entity) => {\n this.attach(entity);\n }),\n this.queries.windows.subscribe('disqualify', (entity) => {\n this.detach(entity);\n }),\n );\n this.queries.windows.entities.forEach((entity) => this.attach(entity));\n }\n\n override update(delta: number): void {\n for (const [entity, state] of this.states) {\n const held = state.store.inputState.size > 0;\n const { phase, began, ended } = state.hold.update(held, delta);\n\n if (began) {\n this.onDragStart(entity);\n }\n if (phase === 'dragging') {\n // The store only runs while an actual drag is in progress: presses\n // shorter than the hold delay never move the window (they're\n // clicks), and skipping the release-frame update stops the handle\n // from restoring its grab-time rotation over our billboard yaw.\n state.store.update(delta);\n this.whileDragging(entity);\n }\n if (ended) {\n this.onDragEnd(entity);\n }\n }\n }\n\n private attach(entity: Entity): void {\n if (this.states.has(entity) || !entity.getValue(UIWindow, 'movable')) {\n return;\n }\n const document = PanelDocument.data.document[entity.index] as\n | UIKitDocument\n | undefined;\n const titlebar = document?.getElementById(WINDOW_CHROME_IDS.titlebar);\n const object = entity.object3D;\n if (!titlebar || !object) {\n return;\n }\n const store = new HandleStore(object, () => ({\n translate: true as const,\n rotate: false as const,\n scale: false as const,\n multitouch: false,\n projectRays: false,\n }));\n // The title-bar uikit element is an Object3D - bind it as the grab\n // surface while the store's target stays the window root.\n store.bind(titlebar as unknown as Object3D<never>);\n this.states.set(entity, {\n store,\n hold: new HoldToDrag(Math.max(0, entity.getValue(UIWindow, 'dragDelay') as number)),\n });\n }\n\n private detach(entity: Entity): void {\n const state = this.states.get(entity);\n if (!state) {\n return;\n }\n try {\n state.store.cancel();\n } catch {\n // Handle stores throw when cancelled with no active input; ignore.\n }\n this.states.delete(entity);\n }\n\n private onDragStart(entity: Entity): void {\n const id = entity.getValue(UIWindow, 'windowId') as string;\n const manager = windowManagerFor(this.world);\n if (id && manager.has(id)) {\n manager.focus(id);\n // Mark dragging BEFORE the dock change so the pin label reads \"PIN\"\n // (loose in hand) throughout the drag, not \"UNPIN\".\n manager.setDragging(id, true);\n }\n // Grabbing takes the window out of any region and out of follow mode -\n // the user is placing it by hand.\n if (entity.hasComponent(UIDockedTo)) {\n entity.removeComponent(UIDockedTo);\n }\n if (entity.getValue(UIWindow, 'dockMode') !== DockMode.WorldLocked) {\n entity.setValue(UIWindow, 'dockMode', DockMode.WorldLocked);\n }\n }\n\n private whileDragging(entity: Entity): void {\n if (!entity.getValue(UIWindow, 'billboardWhileDragging')) {\n return;\n }\n const object = entity.object3D;\n if (!object) {\n return;\n }\n object.getWorldPosition(this.worldPosition);\n this.camera.getWorldPosition(this.viewerPosition);\n const yaw = faceViewerYaw(\n [this.worldPosition.x, this.worldPosition.y, this.worldPosition.z],\n [this.viewerPosition.x, this.viewerPosition.y, this.viewerPosition.z],\n object.rotation.y,\n );\n object.rotation.set(0, yaw, 0);\n }\n\n private onDragEnd(entity: Entity): void {\n const id = entity.getValue(UIWindow, 'windowId') as string;\n const manager = windowManagerFor(this.world);\n if (id && manager.has(id)) {\n manager.setDragging(id, false); // released → placed → label \"UNPIN\"\n }\n // Settle facing the viewer at the drop spot - the window keeps the\n // orientation it was dragged in, not its pre-drag rotation.\n this.whileDragging(entity);\n const object = entity.object3D;\n if (!object) {\n return;\n }\n object.getWorldPosition(this.worldPosition);\n const dropPoint: Vec3 = [\n this.worldPosition.x,\n this.worldPosition.y,\n this.worldPosition.z,\n ];\n\n const registry = regionRegistryFor(this.world);\n const origins = new Map<string, Vec3>();\n this.queries.regions.entities.forEach((regionEntity) => {\n const regionId = regionEntity.getValue(UIDockRegion, 'regionId') as string;\n const regionObject = regionEntity.object3D;\n if (!regionId || !regionObject) {\n return;\n }\n regionObject.getWorldPosition(this.viewerPosition);\n origins.set(regionId, [\n this.viewerPosition.x,\n this.viewerPosition.y,\n this.viewerPosition.z,\n ]);\n });\n\n const captured = registry.capture(dropPoint, origins);\n if (captured !== undefined) {\n entity.addComponent(UIDockedTo, { regionId: captured });\n }\n }\n}\n"]}
@@ -0,0 +1,439 @@
1
+ /**
2
+ * UIWindowSystem - wires window chrome, keeps the `WindowManager` in sync
3
+ * with entities, applies focus ordering and minimize state.
4
+ *
5
+ * Reuse over recreation: the panel itself is loaded/rendered by the IWSDK's
6
+ * `PanelUISystem`; this system only discovers the chrome elements by their
7
+ * well-known ids and drives them.
8
+ */
9
+ import { type Entity } from '@iwsdk/core';
10
+ declare const UIWindowSystem_base: import("elics").SystemConstructor<import("elics").SystemSchema, {
11
+ pendingWindows: {
12
+ required: (import("elics").Component<{
13
+ windowId: {
14
+ type: import("@iwsdk/core").Types.String;
15
+ default: string;
16
+ };
17
+ title: {
18
+ type: import("@iwsdk/core").Types.String;
19
+ default: string;
20
+ };
21
+ dockMode: {
22
+ type: import("@iwsdk/core").Types.Enum;
23
+ enum: {
24
+ readonly WorldLocked: "world-locked";
25
+ readonly BodyFollow: "body-follow";
26
+ readonly HeadLocked: "head-locked";
27
+ };
28
+ default: "world-locked";
29
+ };
30
+ movable: {
31
+ type: import("@iwsdk/core").Types.Boolean;
32
+ default: true;
33
+ };
34
+ closable: {
35
+ type: import("@iwsdk/core").Types.Boolean;
36
+ default: true;
37
+ };
38
+ minimizable: {
39
+ type: import("@iwsdk/core").Types.Boolean;
40
+ default: true;
41
+ };
42
+ pinnable: {
43
+ type: import("@iwsdk/core").Types.Boolean;
44
+ default: true;
45
+ };
46
+ billboardWhileDragging: {
47
+ type: import("@iwsdk/core").Types.Boolean;
48
+ default: true;
49
+ };
50
+ dragDelay: {
51
+ type: import("@iwsdk/core").Types.Float32;
52
+ default: number;
53
+ };
54
+ followOffset: {
55
+ type: import("@iwsdk/core").Types.Vec3;
56
+ default: [number, number, number];
57
+ };
58
+ followSpeed: {
59
+ type: import("@iwsdk/core").Types.Float32;
60
+ default: number;
61
+ };
62
+ followTolerance: {
63
+ type: import("@iwsdk/core").Types.Float32;
64
+ default: number;
65
+ };
66
+ focusBias: {
67
+ type: import("@iwsdk/core").Types.Float32;
68
+ default: number;
69
+ };
70
+ targetWidth: {
71
+ type: import("@iwsdk/core").Types.Float32;
72
+ default: number;
73
+ };
74
+ targetHeight: {
75
+ type: import("@iwsdk/core").Types.Float32;
76
+ default: number;
77
+ };
78
+ }> | import("elics").Component<{
79
+ config: {
80
+ type: import("@iwsdk/core").Types.String;
81
+ default: string;
82
+ };
83
+ }> | import("elics").Component<{
84
+ document: {
85
+ type: import("@iwsdk/core").Types.Object;
86
+ default: undefined;
87
+ };
88
+ }>)[];
89
+ excluded: import("elics").Component<{
90
+ appliedDockMode: {
91
+ type: import("@iwsdk/core").Types.String;
92
+ default: string;
93
+ };
94
+ chromeWired: {
95
+ type: import("@iwsdk/core").Types.Boolean;
96
+ default: false;
97
+ };
98
+ homeDockMode: {
99
+ type: import("@iwsdk/core").Types.String;
100
+ default: string;
101
+ };
102
+ homeRegion: {
103
+ type: import("@iwsdk/core").Types.String;
104
+ default: string;
105
+ };
106
+ homePosition: {
107
+ type: import("@iwsdk/core").Types.Vec3;
108
+ default: [number, number, number];
109
+ };
110
+ homeYaw: {
111
+ type: import("@iwsdk/core").Types.Float32;
112
+ default: number;
113
+ };
114
+ }>[];
115
+ };
116
+ windows: {
117
+ required: (import("elics").Component<{
118
+ windowId: {
119
+ type: import("@iwsdk/core").Types.String;
120
+ default: string;
121
+ };
122
+ title: {
123
+ type: import("@iwsdk/core").Types.String;
124
+ default: string;
125
+ };
126
+ dockMode: {
127
+ type: import("@iwsdk/core").Types.Enum;
128
+ enum: {
129
+ readonly WorldLocked: "world-locked";
130
+ readonly BodyFollow: "body-follow";
131
+ readonly HeadLocked: "head-locked";
132
+ };
133
+ default: "world-locked";
134
+ };
135
+ movable: {
136
+ type: import("@iwsdk/core").Types.Boolean;
137
+ default: true;
138
+ };
139
+ closable: {
140
+ type: import("@iwsdk/core").Types.Boolean;
141
+ default: true;
142
+ };
143
+ minimizable: {
144
+ type: import("@iwsdk/core").Types.Boolean;
145
+ default: true;
146
+ };
147
+ pinnable: {
148
+ type: import("@iwsdk/core").Types.Boolean;
149
+ default: true;
150
+ };
151
+ billboardWhileDragging: {
152
+ type: import("@iwsdk/core").Types.Boolean;
153
+ default: true;
154
+ };
155
+ dragDelay: {
156
+ type: import("@iwsdk/core").Types.Float32;
157
+ default: number;
158
+ };
159
+ followOffset: {
160
+ type: import("@iwsdk/core").Types.Vec3;
161
+ default: [number, number, number];
162
+ };
163
+ followSpeed: {
164
+ type: import("@iwsdk/core").Types.Float32;
165
+ default: number;
166
+ };
167
+ followTolerance: {
168
+ type: import("@iwsdk/core").Types.Float32;
169
+ default: number;
170
+ };
171
+ focusBias: {
172
+ type: import("@iwsdk/core").Types.Float32;
173
+ default: number;
174
+ };
175
+ targetWidth: {
176
+ type: import("@iwsdk/core").Types.Float32;
177
+ default: number;
178
+ };
179
+ targetHeight: {
180
+ type: import("@iwsdk/core").Types.Float32;
181
+ default: number;
182
+ };
183
+ }> | import("elics").Component<{
184
+ appliedDockMode: {
185
+ type: import("@iwsdk/core").Types.String;
186
+ default: string;
187
+ };
188
+ chromeWired: {
189
+ type: import("@iwsdk/core").Types.Boolean;
190
+ default: false;
191
+ };
192
+ homeDockMode: {
193
+ type: import("@iwsdk/core").Types.String;
194
+ default: string;
195
+ };
196
+ homeRegion: {
197
+ type: import("@iwsdk/core").Types.String;
198
+ default: string;
199
+ };
200
+ homePosition: {
201
+ type: import("@iwsdk/core").Types.Vec3;
202
+ default: [number, number, number];
203
+ };
204
+ homeYaw: {
205
+ type: import("@iwsdk/core").Types.Float32;
206
+ default: number;
207
+ };
208
+ }> | import("elics").Component<{
209
+ document: {
210
+ type: import("@iwsdk/core").Types.Object;
211
+ default: undefined;
212
+ };
213
+ }>)[];
214
+ };
215
+ }, World, import("@iwsdk/core").System<import("elics").SystemSchema, {
216
+ pendingWindows: {
217
+ required: (import("elics").Component<{
218
+ windowId: {
219
+ type: import("@iwsdk/core").Types.String;
220
+ default: string;
221
+ };
222
+ title: {
223
+ type: import("@iwsdk/core").Types.String;
224
+ default: string;
225
+ };
226
+ dockMode: {
227
+ type: import("@iwsdk/core").Types.Enum;
228
+ enum: {
229
+ readonly WorldLocked: "world-locked";
230
+ readonly BodyFollow: "body-follow";
231
+ readonly HeadLocked: "head-locked";
232
+ };
233
+ default: "world-locked";
234
+ };
235
+ movable: {
236
+ type: import("@iwsdk/core").Types.Boolean;
237
+ default: true;
238
+ };
239
+ closable: {
240
+ type: import("@iwsdk/core").Types.Boolean;
241
+ default: true;
242
+ };
243
+ minimizable: {
244
+ type: import("@iwsdk/core").Types.Boolean;
245
+ default: true;
246
+ };
247
+ pinnable: {
248
+ type: import("@iwsdk/core").Types.Boolean;
249
+ default: true;
250
+ };
251
+ billboardWhileDragging: {
252
+ type: import("@iwsdk/core").Types.Boolean;
253
+ default: true;
254
+ };
255
+ dragDelay: {
256
+ type: import("@iwsdk/core").Types.Float32;
257
+ default: number;
258
+ };
259
+ followOffset: {
260
+ type: import("@iwsdk/core").Types.Vec3;
261
+ default: [number, number, number];
262
+ };
263
+ followSpeed: {
264
+ type: import("@iwsdk/core").Types.Float32;
265
+ default: number;
266
+ };
267
+ followTolerance: {
268
+ type: import("@iwsdk/core").Types.Float32;
269
+ default: number;
270
+ };
271
+ focusBias: {
272
+ type: import("@iwsdk/core").Types.Float32;
273
+ default: number;
274
+ };
275
+ targetWidth: {
276
+ type: import("@iwsdk/core").Types.Float32;
277
+ default: number;
278
+ };
279
+ targetHeight: {
280
+ type: import("@iwsdk/core").Types.Float32;
281
+ default: number;
282
+ };
283
+ }> | import("elics").Component<{
284
+ config: {
285
+ type: import("@iwsdk/core").Types.String;
286
+ default: string;
287
+ };
288
+ }> | import("elics").Component<{
289
+ document: {
290
+ type: import("@iwsdk/core").Types.Object;
291
+ default: undefined;
292
+ };
293
+ }>)[];
294
+ excluded: import("elics").Component<{
295
+ appliedDockMode: {
296
+ type: import("@iwsdk/core").Types.String;
297
+ default: string;
298
+ };
299
+ chromeWired: {
300
+ type: import("@iwsdk/core").Types.Boolean;
301
+ default: false;
302
+ };
303
+ homeDockMode: {
304
+ type: import("@iwsdk/core").Types.String;
305
+ default: string;
306
+ };
307
+ homeRegion: {
308
+ type: import("@iwsdk/core").Types.String;
309
+ default: string;
310
+ };
311
+ homePosition: {
312
+ type: import("@iwsdk/core").Types.Vec3;
313
+ default: [number, number, number];
314
+ };
315
+ homeYaw: {
316
+ type: import("@iwsdk/core").Types.Float32;
317
+ default: number;
318
+ };
319
+ }>[];
320
+ };
321
+ windows: {
322
+ required: (import("elics").Component<{
323
+ windowId: {
324
+ type: import("@iwsdk/core").Types.String;
325
+ default: string;
326
+ };
327
+ title: {
328
+ type: import("@iwsdk/core").Types.String;
329
+ default: string;
330
+ };
331
+ dockMode: {
332
+ type: import("@iwsdk/core").Types.Enum;
333
+ enum: {
334
+ readonly WorldLocked: "world-locked";
335
+ readonly BodyFollow: "body-follow";
336
+ readonly HeadLocked: "head-locked";
337
+ };
338
+ default: "world-locked";
339
+ };
340
+ movable: {
341
+ type: import("@iwsdk/core").Types.Boolean;
342
+ default: true;
343
+ };
344
+ closable: {
345
+ type: import("@iwsdk/core").Types.Boolean;
346
+ default: true;
347
+ };
348
+ minimizable: {
349
+ type: import("@iwsdk/core").Types.Boolean;
350
+ default: true;
351
+ };
352
+ pinnable: {
353
+ type: import("@iwsdk/core").Types.Boolean;
354
+ default: true;
355
+ };
356
+ billboardWhileDragging: {
357
+ type: import("@iwsdk/core").Types.Boolean;
358
+ default: true;
359
+ };
360
+ dragDelay: {
361
+ type: import("@iwsdk/core").Types.Float32;
362
+ default: number;
363
+ };
364
+ followOffset: {
365
+ type: import("@iwsdk/core").Types.Vec3;
366
+ default: [number, number, number];
367
+ };
368
+ followSpeed: {
369
+ type: import("@iwsdk/core").Types.Float32;
370
+ default: number;
371
+ };
372
+ followTolerance: {
373
+ type: import("@iwsdk/core").Types.Float32;
374
+ default: number;
375
+ };
376
+ focusBias: {
377
+ type: import("@iwsdk/core").Types.Float32;
378
+ default: number;
379
+ };
380
+ targetWidth: {
381
+ type: import("@iwsdk/core").Types.Float32;
382
+ default: number;
383
+ };
384
+ targetHeight: {
385
+ type: import("@iwsdk/core").Types.Float32;
386
+ default: number;
387
+ };
388
+ }> | import("elics").Component<{
389
+ appliedDockMode: {
390
+ type: import("@iwsdk/core").Types.String;
391
+ default: string;
392
+ };
393
+ chromeWired: {
394
+ type: import("@iwsdk/core").Types.Boolean;
395
+ default: false;
396
+ };
397
+ homeDockMode: {
398
+ type: import("@iwsdk/core").Types.String;
399
+ default: string;
400
+ };
401
+ homeRegion: {
402
+ type: import("@iwsdk/core").Types.String;
403
+ default: string;
404
+ };
405
+ homePosition: {
406
+ type: import("@iwsdk/core").Types.Vec3;
407
+ default: [number, number, number];
408
+ };
409
+ homeYaw: {
410
+ type: import("@iwsdk/core").Types.Float32;
411
+ default: number;
412
+ };
413
+ }> | import("elics").Component<{
414
+ document: {
415
+ type: import("@iwsdk/core").Types.Object;
416
+ default: undefined;
417
+ };
418
+ }>)[];
419
+ };
420
+ }>>;
421
+ export declare class UIWindowSystem extends UIWindowSystem_base {
422
+ private manager;
423
+ private entitiesById;
424
+ private localCamera;
425
+ init(): void;
426
+ update(): void;
427
+ entityFor(windowId: string): Entity | undefined;
428
+ private adoptWindow;
429
+ /** Return a window to its home: spawn region, or original placement/mode. */
430
+ returnHome(entity: Entity): void;
431
+ /** MIN when open, MAX when minimized - the label names the next action. */
432
+ private syncMinimizeLabel;
433
+ private syncPinLabel;
434
+ private releaseWindow;
435
+ private wireChrome;
436
+ private applyMinimized;
437
+ private documentOf;
438
+ }
439
+ export {};