canvasengine 2.0.0-beta.61 → 2.0.0-beta.63
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/dist/components/types/DisplayObject.d.ts +4 -0
- package/dist/components/types/DisplayObject.d.ts.map +1 -1
- package/dist/directives/SpriteEffects.d.ts +70 -0
- package/dist/directives/SpriteEffects.d.ts.map +1 -0
- package/dist/directives/index.d.ts +1 -0
- package/dist/directives/index.d.ts.map +1 -1
- package/dist/engine/reactive.d.ts.map +1 -1
- package/dist/index.global.js +185 -4
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +1920 -1627
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/types/DisplayObject.ts +4 -0
- package/src/directives/SpriteEffects.ts +461 -0
- package/src/directives/index.ts +1 -0
- package/src/engine/reactive.ts +16 -0
package/package.json
CHANGED
|
@@ -5,6 +5,7 @@ import { ViewportFollowProps } from "../../directives/ViewportFollow";
|
|
|
5
5
|
import { ShakeProps } from "../../directives/Shake";
|
|
6
6
|
import { FlashProps } from "../../directives/Flash";
|
|
7
7
|
import { FogVisibilityProps } from "../../directives/FogVisibility";
|
|
8
|
+
import type { ClipProps, OcclusionProps, OutlineProps } from "../../directives/SpriteEffects";
|
|
8
9
|
|
|
9
10
|
export type FlexDirection = 'row' | 'column' | 'row-reverse' | 'column-reverse';
|
|
10
11
|
export type JustifyContent = 'flex-start' | 'flex-end' | 'center' | 'space-between' | 'space-around';
|
|
@@ -82,6 +83,9 @@ export interface DisplayObjectProps {
|
|
|
82
83
|
shake?: ShakeProps;
|
|
83
84
|
flash?: FlashProps;
|
|
84
85
|
fogVisibility?: FogVisibilityProps;
|
|
86
|
+
outline?: OutlineProps;
|
|
87
|
+
clip?: ClipProps;
|
|
88
|
+
occlusion?: OcclusionProps;
|
|
85
89
|
|
|
86
90
|
// Events
|
|
87
91
|
click?: PIXI.FederatedEventHandler;
|
|
@@ -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);
|
package/src/directives/index.ts
CHANGED
package/src/engine/reactive.ts
CHANGED
|
@@ -697,6 +697,19 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
697
697
|
|
|
698
698
|
if (child instanceof Observable) {
|
|
699
699
|
const mountedFlowElements = childGroup.mounted;
|
|
700
|
+
const flowEffectSubscriptions = ((child as any).effectSubscriptions ?? []) as Subscription[];
|
|
701
|
+
const flowEffectMounts = ((child as any).effectMounts ?? []) as Array<(element: Element) => any>;
|
|
702
|
+
|
|
703
|
+
const applyFlowEffects = (element: Element) => {
|
|
704
|
+
if (!flowEffectMounts.length) {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
element.effectMounts = [
|
|
709
|
+
...flowEffectMounts,
|
|
710
|
+
...(element.effectMounts ?? []),
|
|
711
|
+
];
|
|
712
|
+
};
|
|
700
713
|
|
|
701
714
|
const createFragmentOwner = (): Element => ({
|
|
702
715
|
tag: 'fragment',
|
|
@@ -724,6 +737,7 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
724
737
|
}
|
|
725
738
|
|
|
726
739
|
const routed = routeDomComponent(parent, element);
|
|
740
|
+
applyFlowEffects(routed);
|
|
727
741
|
mountedFlowElements.set(element, routed);
|
|
728
742
|
onMount(parent, routed, getInsertIndex(sourceIndex, orderedSources));
|
|
729
743
|
propagateContext(routed);
|
|
@@ -801,6 +815,7 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
801
815
|
} else if (isElement(value)) {
|
|
802
816
|
// Handle direct Element emission
|
|
803
817
|
const routed = routeDomComponent(parent, value);
|
|
818
|
+
applyFlowEffects(routed);
|
|
804
819
|
childGroup.mounted.set(value, routed);
|
|
805
820
|
onMount(parent, routed, getInsertIndex(0, [value]));
|
|
806
821
|
propagateContext(routed);
|
|
@@ -821,6 +836,7 @@ export function createComponent(tag: string, props?: Props): Element {
|
|
|
821
836
|
destroyElement(mounted);
|
|
822
837
|
});
|
|
823
838
|
mountedFlowElements.clear();
|
|
839
|
+
flowEffectSubscriptions.forEach((sub) => sub.unsubscribe());
|
|
824
840
|
});
|
|
825
841
|
|
|
826
842
|
// Store subscription for cleanup
|