canvasengine 2.0.0-beta.9 → 2.0.0-rc.1

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 (167) hide show
  1. package/dist/DebugRenderer-DkjTAc48.js +1384 -0
  2. package/dist/DebugRenderer-DkjTAc48.js.map +1 -0
  3. package/dist/components/Button.d.ts +185 -0
  4. package/dist/components/Button.d.ts.map +1 -0
  5. package/dist/components/Canvas.d.ts +17 -0
  6. package/dist/components/Canvas.d.ts.map +1 -0
  7. package/dist/components/DOMElement.d.ts +54 -0
  8. package/dist/components/DOMElement.d.ts.map +1 -0
  9. package/dist/components/DOMSprite.d.ts +127 -0
  10. package/dist/components/DOMSprite.d.ts.map +1 -0
  11. package/dist/components/FocusContainer.d.ts +129 -0
  12. package/dist/components/FocusContainer.d.ts.map +1 -0
  13. package/dist/components/Graphic.d.ts +64 -0
  14. package/dist/components/Graphic.d.ts.map +1 -0
  15. package/dist/components/Joystick.d.ts +36 -0
  16. package/dist/components/Joystick.d.ts.map +1 -0
  17. package/dist/components/NineSliceSprite.d.ts +16 -0
  18. package/dist/components/NineSliceSprite.d.ts.map +1 -0
  19. package/dist/components/ParticleEmitter.d.ts +4 -0
  20. package/dist/components/ParticleEmitter.d.ts.map +1 -0
  21. package/dist/components/Scene.d.ts +2 -0
  22. package/dist/components/Scene.d.ts.map +1 -0
  23. package/dist/components/Text.d.ts +24 -0
  24. package/dist/components/Text.d.ts.map +1 -0
  25. package/dist/components/TilingSprite.d.ts +17 -0
  26. package/dist/components/TilingSprite.d.ts.map +1 -0
  27. package/dist/components/Video.d.ts +14 -0
  28. package/dist/components/Video.d.ts.map +1 -0
  29. package/dist/components/index.d.ts +20 -0
  30. package/dist/components/index.d.ts.map +1 -0
  31. package/dist/components/types/DisplayObject.d.ts +122 -0
  32. package/dist/components/types/DisplayObject.d.ts.map +1 -0
  33. package/dist/components/types/MouseEvent.d.ts +4 -0
  34. package/dist/components/types/MouseEvent.d.ts.map +1 -0
  35. package/dist/components/types/Spritesheet.d.ts +248 -0
  36. package/dist/components/types/Spritesheet.d.ts.map +1 -0
  37. package/dist/components/types/index.d.ts +4 -0
  38. package/dist/components/types/index.d.ts.map +1 -0
  39. package/dist/directives/Controls.d.ts +112 -0
  40. package/dist/directives/Controls.d.ts.map +1 -0
  41. package/dist/directives/ControlsBase.d.ts +199 -0
  42. package/dist/directives/ControlsBase.d.ts.map +1 -0
  43. package/dist/directives/Drag.d.ts +69 -0
  44. package/dist/directives/Drag.d.ts.map +1 -0
  45. package/dist/directives/Flash.d.ts +116 -0
  46. package/dist/directives/Flash.d.ts.map +1 -0
  47. package/dist/directives/FocusNavigation.d.ts +52 -0
  48. package/dist/directives/FocusNavigation.d.ts.map +1 -0
  49. package/dist/directives/FogVisibility.d.ts +47 -0
  50. package/dist/directives/FogVisibility.d.ts.map +1 -0
  51. package/dist/directives/GamepadControls.d.ts +224 -0
  52. package/dist/directives/GamepadControls.d.ts.map +1 -0
  53. package/dist/directives/JoystickControls.d.ts +171 -0
  54. package/dist/directives/JoystickControls.d.ts.map +1 -0
  55. package/dist/directives/KeyboardControls.d.ts +219 -0
  56. package/dist/directives/KeyboardControls.d.ts.map +1 -0
  57. package/dist/directives/Scheduler.d.ts +36 -0
  58. package/dist/directives/Scheduler.d.ts.map +1 -0
  59. package/dist/directives/Shake.d.ts +98 -0
  60. package/dist/directives/Shake.d.ts.map +1 -0
  61. package/dist/directives/Sound.d.ts +25 -0
  62. package/dist/directives/Sound.d.ts.map +1 -0
  63. package/dist/directives/SpriteEffects.d.ts +70 -0
  64. package/dist/directives/SpriteEffects.d.ts.map +1 -0
  65. package/dist/directives/Transition.d.ts +10 -0
  66. package/dist/directives/Transition.d.ts.map +1 -0
  67. package/dist/directives/ViewportCull.d.ts +11 -0
  68. package/dist/directives/ViewportCull.d.ts.map +1 -0
  69. package/dist/directives/ViewportFollow.d.ts +18 -0
  70. package/dist/directives/ViewportFollow.d.ts.map +1 -0
  71. package/dist/directives/index.d.ts +15 -0
  72. package/dist/directives/index.d.ts.map +1 -0
  73. package/dist/dist-BOOc43Qm.js +778 -0
  74. package/dist/dist-BOOc43Qm.js.map +1 -0
  75. package/dist/engine/FocusManager.d.ts +174 -0
  76. package/dist/engine/FocusManager.d.ts.map +1 -0
  77. package/dist/engine/animation.d.ts +72 -0
  78. package/dist/engine/animation.d.ts.map +1 -0
  79. package/dist/engine/bootstrap.d.ts +52 -0
  80. package/dist/engine/bootstrap.d.ts.map +1 -0
  81. package/dist/engine/directive.d.ts +13 -0
  82. package/dist/engine/directive.d.ts.map +1 -0
  83. package/dist/engine/reactive.d.ts +135 -0
  84. package/dist/engine/reactive.d.ts.map +1 -0
  85. package/dist/engine/signal.d.ts +73 -0
  86. package/dist/engine/signal.d.ts.map +1 -0
  87. package/dist/engine/trigger.d.ts +54 -0
  88. package/dist/engine/trigger.d.ts.map +1 -0
  89. package/dist/engine/utils.d.ts +89 -0
  90. package/dist/engine/utils.d.ts.map +1 -0
  91. package/dist/hooks/addContext.d.ts +2 -0
  92. package/dist/hooks/addContext.d.ts.map +1 -0
  93. package/dist/hooks/useFocus.d.ts +60 -0
  94. package/dist/hooks/useFocus.d.ts.map +1 -0
  95. package/dist/hooks/useProps.d.ts +42 -0
  96. package/dist/hooks/useProps.d.ts.map +1 -0
  97. package/dist/hooks/useRef.d.ts +4 -0
  98. package/dist/hooks/useRef.d.ts.map +1 -0
  99. package/dist/index.d.ts +19 -1135
  100. package/dist/index.d.ts.map +1 -0
  101. package/dist/index.global.js +189 -0
  102. package/dist/index.global.js.map +1 -0
  103. package/dist/index.js +15001 -3212
  104. package/dist/index.js.map +1 -1
  105. package/dist/utils/Ease.d.ts +17 -0
  106. package/dist/utils/Ease.d.ts.map +1 -0
  107. package/dist/utils/GlobalAssetLoader.d.ts +141 -0
  108. package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
  109. package/dist/utils/RadialGradient.d.ts +57 -0
  110. package/dist/utils/RadialGradient.d.ts.map +1 -0
  111. package/dist/utils/functions.d.ts +2 -0
  112. package/dist/utils/functions.d.ts.map +1 -0
  113. package/dist/utils/tabindex.d.ts +16 -0
  114. package/dist/utils/tabindex.d.ts.map +1 -0
  115. package/package.json +16 -9
  116. package/src/components/Button.ts +399 -0
  117. package/src/components/Canvas.ts +82 -51
  118. package/src/components/Container.ts +21 -2
  119. package/src/components/DOMContainer.ts +379 -0
  120. package/src/components/DOMElement.ts +556 -0
  121. package/src/components/DOMSprite.ts +1040 -0
  122. package/src/components/DisplayObject.ts +422 -201
  123. package/src/components/FocusContainer.ts +368 -0
  124. package/src/components/Graphic.ts +239 -73
  125. package/src/components/Joystick.ts +363 -0
  126. package/src/components/Mesh.ts +222 -0
  127. package/src/components/NineSliceSprite.ts +4 -1
  128. package/src/components/ParticleEmitter.ts +12 -8
  129. package/src/components/Sprite.ts +418 -52
  130. package/src/components/Text.ts +268 -24
  131. package/src/components/Viewport.ts +122 -63
  132. package/src/components/index.ts +9 -2
  133. package/src/components/types/DisplayObject.ts +57 -5
  134. package/src/components/types/Spritesheet.ts +0 -118
  135. package/src/directives/Controls.ts +254 -0
  136. package/src/directives/ControlsBase.ts +267 -0
  137. package/src/directives/Drag.ts +357 -52
  138. package/src/directives/Flash.ts +419 -0
  139. package/src/directives/FocusNavigation.ts +113 -0
  140. package/src/directives/FogVisibility.ts +273 -0
  141. package/src/directives/GamepadControls.ts +537 -0
  142. package/src/directives/JoystickControls.ts +396 -0
  143. package/src/directives/KeyboardControls.ts +85 -430
  144. package/src/directives/Scheduler.ts +21 -5
  145. package/src/directives/Shake.ts +298 -0
  146. package/src/directives/Sound.ts +94 -31
  147. package/src/directives/SpriteEffects.ts +461 -0
  148. package/src/directives/ViewportFollow.ts +40 -9
  149. package/src/directives/index.ts +14 -6
  150. package/src/engine/FocusManager.ts +510 -0
  151. package/src/engine/animation.ts +137 -19
  152. package/src/engine/bootstrap.ts +140 -6
  153. package/src/engine/directive.ts +4 -4
  154. package/src/engine/reactive.ts +979 -236
  155. package/src/engine/signal.ts +241 -47
  156. package/src/engine/trigger.ts +34 -7
  157. package/src/engine/utils.ts +19 -3
  158. package/src/hooks/useFocus.ts +91 -0
  159. package/src/hooks/useProps.ts +1 -1
  160. package/src/index.ts +8 -2
  161. package/src/types/pixi-cull.d.ts +7 -0
  162. package/src/utils/GlobalAssetLoader.ts +257 -0
  163. package/src/utils/functions.ts +7 -0
  164. package/src/utils/tabindex.ts +70 -0
  165. package/testing/index.ts +35 -4
  166. package/tsconfig.json +18 -0
  167. package/vite.config.ts +39 -0
@@ -0,0 +1,461 @@
1
+ import { effect, isSignal } from "@signe/reactive";
2
+ import { Container, Graphics, Rectangle, Sprite as PixiSprite } from "pixi.js";
3
+ import { OutlineFilter } from "pixi-filters/outline";
4
+ import { Directive, registerDirective } from "../engine/directive";
5
+ import { isElement } from "../engine/reactive";
6
+ import type { Element } from "../engine/reactive";
7
+ import type { SignalOrPrimitive } from "../components/types";
8
+ import type { Subscription } from "rxjs";
9
+
10
+ type MaybeSignal<T> = SignalOrPrimitive<T> | T;
11
+
12
+ const valueOf = <T>(value: MaybeSignal<T> | undefined, fallback: T): T => {
13
+ if (value === undefined) return fallback;
14
+ return isSignal(value as any) ? (value as any)() : (value as T);
15
+ };
16
+
17
+ const rawValueOf = <T>(value: MaybeSignal<T> | undefined): T | undefined => {
18
+ if (value === undefined) return undefined;
19
+ return isSignal(value as any) ? (value as any)() : (value as T);
20
+ };
21
+
22
+ const asArray = <T>(value: T | T[] | undefined): T[] => {
23
+ if (value === undefined) return [];
24
+ return Array.isArray(value) ? value : [value];
25
+ };
26
+
27
+ const getFilters = (instance: Container): any[] => {
28
+ const filters = (instance as any).filters;
29
+ if (!filters) return [];
30
+ return Array.isArray(filters) ? [...filters] : [filters];
31
+ };
32
+
33
+ const setFilters = (instance: Container, filters: any[]) => {
34
+ (instance as any).filters = filters.length > 0 ? filters : [];
35
+ };
36
+
37
+ const getElementInstance = (value: unknown): Container | null => {
38
+ const resolved = rawValueOf(value as any);
39
+ if (isElement(resolved)) {
40
+ return (resolved.componentInstance as unknown as Container) ?? null;
41
+ }
42
+ if (resolved instanceof Container) return resolved;
43
+ if (resolved && typeof resolved === "object" && typeof (resolved as any).getBounds === "function") {
44
+ return resolved as Container;
45
+ }
46
+ return null;
47
+ };
48
+
49
+ const normalizeBounds = (bounds: any): Rectangle => {
50
+ return new Rectangle(bounds?.x ?? 0, bounds?.y ?? 0, bounds?.width ?? 0, bounds?.height ?? 0);
51
+ };
52
+
53
+ const getWorldBounds = (instance: any, mode: "bounds" | "hitbox" = "bounds"): Rectangle => {
54
+ const bounds = normalizeBounds(
55
+ typeof instance.getBounds === "function"
56
+ ? instance.getBounds()
57
+ : { x: instance.x ?? 0, y: instance.y ?? 0, width: instance.width ?? 0, height: instance.height ?? 0 }
58
+ );
59
+
60
+ if (mode !== "hitbox" || !instance.hitbox) {
61
+ return bounds;
62
+ }
63
+
64
+ const hitbox = instance.hitbox;
65
+ return new Rectangle(
66
+ bounds.x + Math.max(0, (bounds.width - hitbox.w) / 2),
67
+ bounds.y + Math.max(0, bounds.height - hitbox.h),
68
+ hitbox.w,
69
+ hitbox.h
70
+ );
71
+ };
72
+
73
+ const intersectRect = (a: Rectangle, b: Rectangle): Rectangle | null => {
74
+ const x = Math.max(a.x, b.x);
75
+ const y = Math.max(a.y, b.y);
76
+ const right = Math.min(a.x + a.width, b.x + b.width);
77
+ const bottom = Math.min(a.y + a.height, b.y + b.height);
78
+ const width = right - x;
79
+ const height = bottom - y;
80
+ if (width <= 0 || height <= 0) return null;
81
+ return new Rectangle(x, y, width, height);
82
+ };
83
+
84
+ const drawFilledRect = (graphics: Graphics, rect: Rectangle) => {
85
+ graphics.rect(rect.x, rect.y, rect.width, rect.height);
86
+ graphics.fill(0xffffff);
87
+ };
88
+
89
+ const addMaskToParent = (instance: Container, mask: Graphics) => {
90
+ if (mask.parent === instance.parent) return;
91
+ if (mask.parent) {
92
+ mask.parent.removeChild(mask);
93
+ }
94
+ instance.parent?.addChild(mask);
95
+ };
96
+
97
+ const setMask = (instance: any, mask: any, inverse = false) => {
98
+ if (!instance) return;
99
+ if (typeof instance.setMask === "function") {
100
+ if (mask == null) {
101
+ instance.mask = null;
102
+ }
103
+ instance.setMask({ mask: mask ?? null, inverse });
104
+ } else {
105
+ instance.mask = mask ?? null;
106
+ }
107
+ };
108
+
109
+ const clearMask = (instance: any, fallbackMask: any) => {
110
+ setMask(instance, fallbackMask ?? null);
111
+ };
112
+
113
+ const copyTransform = (target: any, source: any) => {
114
+ const position = { x: source.x ?? source.position?.x ?? 0, y: source.y ?? source.position?.y ?? 0 };
115
+ if (target.position?.copyFrom) {
116
+ target.position.copyFrom(position);
117
+ } else {
118
+ target.x = position.x;
119
+ target.y = position.y;
120
+ }
121
+ target.scale?.copyFrom?.(source.scale ?? { x: 1, y: 1 });
122
+ target.pivot?.copyFrom?.(source.pivot ?? { x: 0, y: 0 });
123
+ target.skew?.copyFrom?.(source.skew ?? { x: 0, y: 0 });
124
+ target.rotation = source.rotation ?? 0;
125
+ };
126
+
127
+ const drawDisplayRect = (graphics: Graphics, instance: any, rect: Rectangle) => {
128
+ copyTransform(graphics, instance);
129
+
130
+ const textureWidth = instance.texture?.orig?.width ?? instance.texture?.width ?? instance.width ?? rect.width;
131
+ const textureHeight = instance.texture?.orig?.height ?? instance.texture?.height ?? instance.height ?? rect.height;
132
+ const displayWidth = instance.width || textureWidth || 1;
133
+ const displayHeight = instance.height || textureHeight || 1;
134
+ const anchorX = instance.anchor?.x ?? 0;
135
+ const anchorY = instance.anchor?.y ?? 0;
136
+
137
+ drawFilledRect(
138
+ graphics,
139
+ new Rectangle(
140
+ -anchorX * textureWidth + (rect.x * textureWidth) / displayWidth,
141
+ -anchorY * textureHeight + (rect.y * textureHeight) / displayHeight,
142
+ (rect.width * textureWidth) / displayWidth,
143
+ (rect.height * textureHeight) / displayHeight
144
+ )
145
+ );
146
+ };
147
+
148
+ export type OutlineProps = {
149
+ enabled?: SignalOrPrimitive<boolean>;
150
+ color?: SignalOrPrimitive<number>;
151
+ thickness?: SignalOrPrimitive<number>;
152
+ quality?: SignalOrPrimitive<number>;
153
+ alpha?: SignalOrPrimitive<number>;
154
+ };
155
+
156
+ export type ClipShape =
157
+ | {
158
+ type: "rect";
159
+ x: SignalOrPrimitive<number>;
160
+ y: SignalOrPrimitive<number>;
161
+ width: SignalOrPrimitive<number>;
162
+ height: SignalOrPrimitive<number>;
163
+ };
164
+
165
+ export type ClipProps = {
166
+ enabled?: SignalOrPrimitive<boolean>;
167
+ mode?: SignalOrPrimitive<"keep" | "hide">;
168
+ shape: ClipShape;
169
+ };
170
+
171
+ export type OcclusionProps = {
172
+ enabled?: SignalOrPrimitive<boolean>;
173
+ obstacles: SignalOrPrimitive<Element | Element[] | Container | Container[]>;
174
+ bounds?: SignalOrPrimitive<"bounds" | "hitbox">;
175
+ padding?: SignalOrPrimitive<number>;
176
+ alpha?: SignalOrPrimitive<number>;
177
+ zIndex?: SignalOrPrimitive<number>;
178
+ };
179
+
180
+ export class Outline extends Directive {
181
+ private elementRef: Element<Container> | null = null;
182
+ private filter: OutlineFilter | null = null;
183
+ private updateEffect: ReturnType<typeof effect> | null = null;
184
+
185
+ onInit(element: Element<Container>) {
186
+ this.elementRef = element;
187
+ }
188
+
189
+ onMount() {
190
+ this.updateEffect = effect(() => {
191
+ this.apply();
192
+ });
193
+ }
194
+
195
+ onUpdate() {
196
+ this.apply();
197
+ }
198
+
199
+ onDestroy() {
200
+ this.removeFilter();
201
+ this.updateEffect?.subscription.unsubscribe();
202
+ this.updateEffect = null;
203
+ this.elementRef = null;
204
+ }
205
+
206
+ private get props(): OutlineProps {
207
+ const props = rawValueOf(this.elementRef?.props.outline);
208
+ if (props === true) return {};
209
+ if (props === false) return { enabled: false };
210
+ return (props as OutlineProps | undefined) ?? {};
211
+ }
212
+
213
+ private apply() {
214
+ const instance = this.elementRef?.componentInstance;
215
+ if (!instance) return;
216
+
217
+ const props = this.props;
218
+ const enabled = valueOf(props.enabled, true);
219
+
220
+ if (!enabled) {
221
+ this.removeFilter();
222
+ return;
223
+ }
224
+
225
+ if (!this.filter) {
226
+ this.filter = new OutlineFilter({
227
+ thickness: valueOf(props.thickness, 1),
228
+ color: valueOf(props.color, 0xffffff),
229
+ quality: valueOf(props.quality, 0.1),
230
+ alpha: valueOf(props.alpha, 1),
231
+ });
232
+ setFilters(instance, [...getFilters(instance), this.filter]);
233
+ }
234
+
235
+ this.filter.thickness = valueOf(props.thickness, 1);
236
+ this.filter.color = valueOf(props.color, 0xffffff);
237
+ this.filter.quality = valueOf(props.quality, 0.1);
238
+ this.filter.alpha = valueOf(props.alpha, 1);
239
+ }
240
+
241
+ private removeFilter() {
242
+ const instance = this.elementRef?.componentInstance;
243
+ if (!instance || !this.filter) return;
244
+ setFilters(instance, getFilters(instance).filter((filter) => filter !== this.filter));
245
+ this.filter = null;
246
+ }
247
+ }
248
+
249
+ export class Clip extends Directive {
250
+ private elementRef: Element<Container> | null = null;
251
+ private maskGraphics: Graphics | null = null;
252
+ private previousMask: any = null;
253
+ private tickSubscription: Subscription | null = null;
254
+ private updateEffect: ReturnType<typeof effect> | null = null;
255
+
256
+ onInit(element: Element<Container>) {
257
+ this.elementRef = element;
258
+ }
259
+
260
+ onMount(element: Element<Container>) {
261
+ this.tickSubscription = element.props.context?.tick?.observable?.subscribe(() => {
262
+ this.apply();
263
+ }) ?? null;
264
+ this.updateEffect = effect(() => {
265
+ this.apply();
266
+ });
267
+ }
268
+
269
+ onUpdate() {
270
+ this.apply();
271
+ }
272
+
273
+ onDestroy() {
274
+ const instance = this.elementRef?.componentInstance as any;
275
+ if (instance && this.maskGraphics) {
276
+ clearMask(instance, this.previousMask);
277
+ }
278
+ if (this.maskGraphics?.parent) {
279
+ this.maskGraphics.parent.removeChild(this.maskGraphics);
280
+ }
281
+ this.maskGraphics?.destroy();
282
+ this.maskGraphics = null;
283
+ this.previousMask = null;
284
+ this.tickSubscription?.unsubscribe();
285
+ this.tickSubscription = null;
286
+ this.updateEffect?.subscription.unsubscribe();
287
+ this.updateEffect = null;
288
+ this.elementRef = null;
289
+ }
290
+
291
+ private get props(): ClipProps | null {
292
+ return (rawValueOf(this.elementRef?.props.clip) as ClipProps | undefined) ?? null;
293
+ }
294
+
295
+ private apply() {
296
+ const instance = this.elementRef?.componentInstance as any;
297
+ const props = this.props;
298
+ if (!instance || !instance.parent || !props?.shape) return;
299
+
300
+ if (!valueOf(props.enabled, true)) {
301
+ if (this.maskGraphics) {
302
+ clearMask(instance, this.previousMask);
303
+ this.maskGraphics.clear();
304
+ }
305
+ return;
306
+ }
307
+
308
+ if (!this.maskGraphics) {
309
+ this.maskGraphics = new Graphics();
310
+ this.previousMask = instance.mask ?? null;
311
+ }
312
+
313
+ addMaskToParent(instance, this.maskGraphics);
314
+ this.maskGraphics.clear();
315
+
316
+ const shape = props.shape;
317
+ if (shape.type === "rect") {
318
+ const rect = new Rectangle(
319
+ valueOf(shape.x, 0),
320
+ valueOf(shape.y, 0),
321
+ valueOf(shape.width, 0),
322
+ valueOf(shape.height, 0)
323
+ );
324
+ drawDisplayRect(this.maskGraphics, instance, rect);
325
+ }
326
+
327
+ const inverse = valueOf(props.mode, "keep") === "hide";
328
+ setMask(instance, this.maskGraphics, inverse);
329
+ }
330
+ }
331
+
332
+ export class Occlusion extends Directive {
333
+ private elementRef: Element<Container> | null = null;
334
+ private maskGraphics: Graphics | null = null;
335
+ private ghostSprite: PixiSprite | null = null;
336
+ private tickSubscription: Subscription | null = null;
337
+ private updateEffect: ReturnType<typeof effect> | null = null;
338
+
339
+ onInit(element: Element<Container>) {
340
+ this.elementRef = element;
341
+ }
342
+
343
+ onMount(element: Element<Container>) {
344
+ this.tickSubscription = element.props.context?.tick?.observable?.subscribe(() => {
345
+ this.apply();
346
+ }) ?? null;
347
+ this.updateEffect = effect(() => {
348
+ this.apply();
349
+ });
350
+ }
351
+
352
+ onUpdate() {
353
+ this.apply();
354
+ }
355
+
356
+ onDestroy() {
357
+ clearMask(this.ghostSprite, null);
358
+ if (this.ghostSprite?.parent) {
359
+ this.ghostSprite.parent.removeChild(this.ghostSprite);
360
+ }
361
+ this.ghostSprite?.destroy();
362
+ if (this.maskGraphics?.parent) {
363
+ this.maskGraphics.parent.removeChild(this.maskGraphics);
364
+ }
365
+ this.maskGraphics?.destroy();
366
+ this.maskGraphics = null;
367
+ this.ghostSprite = null;
368
+ this.tickSubscription?.unsubscribe();
369
+ this.tickSubscription = null;
370
+ this.updateEffect?.subscription.unsubscribe();
371
+ this.updateEffect = null;
372
+ this.elementRef = null;
373
+ }
374
+
375
+ private get props(): OcclusionProps | null {
376
+ return (rawValueOf(this.elementRef?.props.occlusion) as OcclusionProps | undefined) ?? null;
377
+ }
378
+
379
+ private apply() {
380
+ const instance = this.elementRef?.componentInstance as any;
381
+ const props = this.props;
382
+ if (!instance || !props) return;
383
+
384
+ if (!valueOf(props.enabled, true)) {
385
+ if (this.maskGraphics) {
386
+ this.maskGraphics.clear();
387
+ }
388
+ if (this.ghostSprite) {
389
+ this.ghostSprite.visible = false;
390
+ }
391
+ return;
392
+ }
393
+
394
+ if (!this.maskGraphics || !this.ghostSprite) {
395
+ if (!instance.texture) return;
396
+ this.maskGraphics = new Graphics();
397
+ this.ghostSprite = new PixiSprite(instance.texture);
398
+ }
399
+
400
+ addMaskToParent(instance, this.maskGraphics);
401
+ if (this.ghostSprite.parent !== instance.parent) {
402
+ if (this.ghostSprite.parent) {
403
+ this.ghostSprite.parent.removeChild(this.ghostSprite);
404
+ }
405
+ instance.parent?.addChild(this.ghostSprite);
406
+ }
407
+
408
+ this.maskGraphics.clear();
409
+ this.ghostSprite.texture = instance.texture;
410
+ this.ghostSprite.width = instance.width;
411
+ this.ghostSprite.height = instance.height;
412
+ this.ghostSprite.visible = false;
413
+ this.ghostSprite.alpha = valueOf(props.alpha, 0.35);
414
+ copyTransform(this.ghostSprite, instance);
415
+ if (instance.anchor && this.ghostSprite.anchor) {
416
+ this.ghostSprite.anchor.copyFrom(instance.anchor);
417
+ }
418
+
419
+ const boundsMode = valueOf(props.bounds, "bounds");
420
+ const padding = valueOf(props.padding, 0);
421
+ const targetBounds = getWorldBounds(instance, boundsMode);
422
+ let hasIntersection = false;
423
+ let maxObstacleZIndex = instance.zIndex ?? 0;
424
+
425
+ for (const obstacle of asArray(rawValueOf(props.obstacles) as any)) {
426
+ const obstacleInstance = getElementInstance(obstacle);
427
+ if (!obstacleInstance) continue;
428
+
429
+ maxObstacleZIndex = Math.max(maxObstacleZIndex, (obstacleInstance as any).zIndex ?? 0);
430
+ const obstacleBounds = getWorldBounds(obstacleInstance, boundsMode);
431
+ const intersection = intersectRect(targetBounds, obstacleBounds);
432
+ if (!intersection) continue;
433
+
434
+ hasIntersection = true;
435
+ const parentLocalTopLeft = instance.parent?.toLocal?.({ x: intersection.x - padding, y: intersection.y - padding })
436
+ ?? { x: intersection.x - padding, y: intersection.y - padding };
437
+ drawFilledRect(
438
+ this.maskGraphics,
439
+ new Rectangle(
440
+ parentLocalTopLeft.x,
441
+ parentLocalTopLeft.y,
442
+ intersection.width + padding * 2,
443
+ intersection.height + padding * 2
444
+ )
445
+ );
446
+ }
447
+
448
+ if (hasIntersection) {
449
+ this.ghostSprite.visible = true;
450
+ this.ghostSprite.zIndex = valueOf(props.zIndex, maxObstacleZIndex + 1);
451
+ setMask(this.ghostSprite, this.maskGraphics, false);
452
+ } else {
453
+ this.ghostSprite.visible = false;
454
+ clearMask(this.ghostSprite, null);
455
+ }
456
+ }
457
+ }
458
+
459
+ registerDirective("outline", Outline);
460
+ registerDirective("clip", Clip);
461
+ registerDirective("occlusion", Occlusion);
@@ -1,25 +1,56 @@
1
1
  import { ComponentInstance } from '../components/DisplayObject';
2
+ import { SignalOrPrimitive } from '../components/types';
2
3
  import { Directive, registerDirective } from '../engine/directive';
3
4
  import { Element } from '../engine/reactive';
4
5
  import { error } from '../engine/utils';
6
+ import { useProps } from '../hooks/useProps';
7
+
8
+ export type ViewportFollowProps = {
9
+ viewportFollow?: boolean | {
10
+ speed?: SignalOrPrimitive<number>;
11
+ acceleration?: SignalOrPrimitive<number>;
12
+ radius?: SignalOrPrimitive<number>;
13
+ };
14
+ }
5
15
 
6
16
  export class ViewportFollow extends Directive {
7
17
  onInit(element: Element<ComponentInstance>) {
8
18
 
9
19
  }
10
20
  onMount(element: Element) {
11
- const { viewportFollow } = element.props
12
- const { viewport } = element.props.context
21
+ this.onUpdate(element.props.viewportFollow, element)
22
+ }
23
+ onUpdate(viewportFollow: any, element: Element) {
24
+ const viewport = element.props.context?.viewport
13
25
  if (!viewport) {
14
- throw error('ViewportFollow directive requires a Viewport component to be mounted in the same context')
26
+ if (viewportFollow) {
27
+ throw error('ViewportFollow directive requires a Viewport component to be mounted in the same context')
28
+ }
29
+ return
30
+ }
31
+ if (viewportFollow) {
32
+ if (viewportFollow === true) {
33
+ viewport.follow(element.componentInstance)
34
+ } else {
35
+ const options = useProps(viewportFollow, {
36
+ speed: undefined,
37
+ acceleration: undefined,
38
+ radius: undefined
39
+ })
40
+ viewport.follow(element.componentInstance, {
41
+ speed: options.speed(),
42
+ acceleration: options.acceleration(),
43
+ radius: options.radius()
44
+ })
45
+ }
46
+ } else if (viewportFollow === null) {
47
+ viewport.plugins.remove('follow')
15
48
  }
16
- viewport.follow(element.componentInstance)
17
- }
18
- onUpdate(props: any) {
19
-
20
49
  }
21
- onDestroy() {
22
-
50
+ onDestroy(element: Element) {
51
+ const { viewportFollow } = element.props
52
+ const viewport = element.props.context?.viewport
53
+ if (viewportFollow && viewport) viewport.plugins.remove('follow')
23
54
  }
24
55
  }
25
56
 
@@ -1,7 +1,15 @@
1
- import './KeyboardControls'
2
- import './Scheduler'
3
- import './ViewportFollow'
1
+ export * from './ControlsBase'
2
+ export * from './KeyboardControls'
3
+ export * from './GamepadControls'
4
+ export * from './JoystickControls'
5
+ export * from './Controls'
6
+ export * from './Scheduler'
7
+ export * from './ViewportFollow'
4
8
  //import './ViewportCull'
5
- import './Sound'
6
- import './Drag'
7
- import './Transition'
9
+ export * from './Sound'
10
+ export * from './Drag'
11
+ export * from './Transition'
12
+ export * from './Shake'
13
+ export * from './Flash'
14
+ export * from './FogVisibility'
15
+ export * from './SpriteEffects'