canvasengine 2.0.0-beta.6 → 2.0.0-beta.61
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/DebugRenderer-DkjTAc48.js +1384 -0
- package/dist/DebugRenderer-DkjTAc48.js.map +1 -0
- package/dist/components/Button.d.ts +185 -0
- package/dist/components/Button.d.ts.map +1 -0
- package/dist/components/Canvas.d.ts +17 -0
- package/dist/components/Canvas.d.ts.map +1 -0
- package/dist/components/DOMElement.d.ts +54 -0
- package/dist/components/DOMElement.d.ts.map +1 -0
- package/dist/components/DOMSprite.d.ts +127 -0
- package/dist/components/DOMSprite.d.ts.map +1 -0
- package/dist/components/FocusContainer.d.ts +129 -0
- package/dist/components/FocusContainer.d.ts.map +1 -0
- package/dist/components/Graphic.d.ts +64 -0
- package/dist/components/Graphic.d.ts.map +1 -0
- package/dist/components/Joystick.d.ts +36 -0
- package/dist/components/Joystick.d.ts.map +1 -0
- package/dist/components/NineSliceSprite.d.ts +16 -0
- package/dist/components/NineSliceSprite.d.ts.map +1 -0
- package/dist/components/ParticleEmitter.d.ts +4 -0
- package/dist/components/ParticleEmitter.d.ts.map +1 -0
- package/dist/components/Scene.d.ts +2 -0
- package/dist/components/Scene.d.ts.map +1 -0
- package/dist/components/Text.d.ts +24 -0
- package/dist/components/Text.d.ts.map +1 -0
- package/dist/components/TilingSprite.d.ts +17 -0
- package/dist/components/TilingSprite.d.ts.map +1 -0
- package/dist/components/Video.d.ts +14 -0
- package/dist/components/Video.d.ts.map +1 -0
- package/dist/components/index.d.ts +20 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/types/DisplayObject.d.ts +118 -0
- package/dist/components/types/DisplayObject.d.ts.map +1 -0
- package/dist/components/types/MouseEvent.d.ts +4 -0
- package/dist/components/types/MouseEvent.d.ts.map +1 -0
- package/dist/components/types/Spritesheet.d.ts +248 -0
- package/dist/components/types/Spritesheet.d.ts.map +1 -0
- package/dist/components/types/index.d.ts +4 -0
- package/dist/components/types/index.d.ts.map +1 -0
- package/dist/directives/Controls.d.ts +112 -0
- package/dist/directives/Controls.d.ts.map +1 -0
- package/dist/directives/ControlsBase.d.ts +199 -0
- package/dist/directives/ControlsBase.d.ts.map +1 -0
- package/dist/directives/Drag.d.ts +69 -0
- package/dist/directives/Drag.d.ts.map +1 -0
- package/dist/directives/Flash.d.ts +116 -0
- package/dist/directives/Flash.d.ts.map +1 -0
- package/dist/directives/FocusNavigation.d.ts +52 -0
- package/dist/directives/FocusNavigation.d.ts.map +1 -0
- package/dist/directives/FogVisibility.d.ts +47 -0
- package/dist/directives/FogVisibility.d.ts.map +1 -0
- package/dist/directives/GamepadControls.d.ts +224 -0
- package/dist/directives/GamepadControls.d.ts.map +1 -0
- package/dist/directives/JoystickControls.d.ts +171 -0
- package/dist/directives/JoystickControls.d.ts.map +1 -0
- package/dist/directives/KeyboardControls.d.ts +219 -0
- package/dist/directives/KeyboardControls.d.ts.map +1 -0
- package/dist/directives/Scheduler.d.ts +36 -0
- package/dist/directives/Scheduler.d.ts.map +1 -0
- package/dist/directives/Shake.d.ts +98 -0
- package/dist/directives/Shake.d.ts.map +1 -0
- package/dist/directives/Sound.d.ts +25 -0
- package/dist/directives/Sound.d.ts.map +1 -0
- package/dist/directives/Transition.d.ts +10 -0
- package/dist/directives/Transition.d.ts.map +1 -0
- package/dist/directives/ViewportCull.d.ts +11 -0
- package/dist/directives/ViewportCull.d.ts.map +1 -0
- package/dist/directives/ViewportFollow.d.ts +18 -0
- package/dist/directives/ViewportFollow.d.ts.map +1 -0
- package/dist/directives/index.d.ts +14 -0
- package/dist/directives/index.d.ts.map +1 -0
- package/dist/dist-BOOc43Qm.js +778 -0
- package/dist/dist-BOOc43Qm.js.map +1 -0
- package/dist/engine/FocusManager.d.ts +174 -0
- package/dist/engine/FocusManager.d.ts.map +1 -0
- package/dist/engine/animation.d.ts +72 -0
- package/dist/engine/animation.d.ts.map +1 -0
- package/dist/engine/bootstrap.d.ts +52 -0
- package/dist/engine/bootstrap.d.ts.map +1 -0
- package/dist/engine/directive.d.ts +13 -0
- package/dist/engine/directive.d.ts.map +1 -0
- package/dist/engine/reactive.d.ts +135 -0
- package/dist/engine/reactive.d.ts.map +1 -0
- package/dist/engine/signal.d.ts +73 -0
- package/dist/engine/signal.d.ts.map +1 -0
- package/dist/engine/trigger.d.ts +54 -0
- package/dist/engine/trigger.d.ts.map +1 -0
- package/dist/engine/utils.d.ts +89 -0
- package/dist/engine/utils.d.ts.map +1 -0
- package/dist/hooks/addContext.d.ts +2 -0
- package/dist/hooks/addContext.d.ts.map +1 -0
- package/dist/hooks/useFocus.d.ts +60 -0
- package/dist/hooks/useFocus.d.ts.map +1 -0
- package/dist/hooks/useProps.d.ts +42 -0
- package/dist/hooks/useProps.d.ts.map +1 -0
- package/dist/hooks/useRef.d.ts +4 -0
- package/dist/hooks/useRef.d.ts.map +1 -0
- package/dist/index.d.ts +19 -1107
- package/dist/index.d.ts.map +1 -0
- package/dist/index.global.js +8 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +14708 -3135
- package/dist/index.js.map +1 -1
- package/dist/utils/Ease.d.ts +17 -0
- package/dist/utils/Ease.d.ts.map +1 -0
- package/dist/utils/GlobalAssetLoader.d.ts +141 -0
- package/dist/utils/GlobalAssetLoader.d.ts.map +1 -0
- package/dist/utils/RadialGradient.d.ts +57 -0
- package/dist/utils/RadialGradient.d.ts.map +1 -0
- package/dist/utils/functions.d.ts +2 -0
- package/dist/utils/functions.d.ts.map +1 -0
- package/dist/utils/tabindex.d.ts +16 -0
- package/dist/utils/tabindex.d.ts.map +1 -0
- package/package.json +16 -9
- package/src/components/Button.ts +399 -0
- package/src/components/Canvas.ts +82 -51
- package/src/components/Container.ts +21 -2
- package/src/components/DOMContainer.ts +379 -0
- package/src/components/DOMElement.ts +556 -0
- package/src/components/DOMSprite.ts +1040 -0
- package/src/components/DisplayObject.ts +422 -201
- package/src/components/FocusContainer.ts +368 -0
- package/src/components/Graphic.ts +239 -73
- package/src/components/Joystick.ts +363 -0
- package/src/components/Mesh.ts +222 -0
- package/src/components/NineSliceSprite.ts +4 -1
- package/src/components/ParticleEmitter.ts +12 -8
- package/src/components/Sprite.ts +418 -52
- package/src/components/Text.ts +270 -26
- package/src/components/Viewport.ts +122 -63
- package/src/components/index.ts +9 -2
- package/src/components/types/DisplayObject.ts +53 -5
- package/src/components/types/Spritesheet.ts +0 -118
- package/src/directives/Controls.ts +254 -0
- package/src/directives/ControlsBase.ts +267 -0
- package/src/directives/Drag.ts +357 -52
- package/src/directives/Flash.ts +419 -0
- package/src/directives/FocusNavigation.ts +113 -0
- package/src/directives/FogVisibility.ts +273 -0
- package/src/directives/GamepadControls.ts +537 -0
- package/src/directives/JoystickControls.ts +396 -0
- package/src/directives/KeyboardControls.ts +85 -430
- package/src/directives/Scheduler.ts +21 -5
- package/src/directives/Shake.ts +298 -0
- package/src/directives/Sound.ts +94 -31
- package/src/directives/ViewportFollow.ts +40 -9
- package/src/directives/index.ts +13 -6
- package/src/engine/FocusManager.ts +510 -0
- package/src/engine/animation.ts +175 -21
- package/src/engine/bootstrap.ts +140 -6
- package/src/engine/directive.ts +4 -4
- package/src/engine/reactive.ts +980 -177
- package/src/engine/signal.ts +241 -47
- package/src/engine/trigger.ts +34 -7
- package/src/engine/utils.ts +19 -3
- package/src/hooks/useFocus.ts +91 -0
- package/src/hooks/useProps.ts +1 -1
- package/src/index.ts +8 -2
- package/src/types/pixi-cull.d.ts +7 -0
- package/src/utils/GlobalAssetLoader.ts +257 -0
- package/src/utils/functions.ts +7 -0
- package/src/utils/tabindex.ts +70 -0
- package/testing/index.ts +35 -4
- package/tsconfig.json +18 -0
- package/vite.config.ts +39 -0
package/src/components/Text.ts
CHANGED
|
@@ -1,28 +1,52 @@
|
|
|
1
|
+
import { layout as layoutPretext, prepare as preparePretext, type PreparedText, type PrepareOptions } from "@chenglou/pretext";
|
|
1
2
|
import { Text as PixiText, TextStyle } from "pixi.js";
|
|
2
|
-
import { createComponent, registerComponent } from "../engine/reactive";
|
|
3
|
+
import { createComponent, registerComponent, Element } from "../engine/reactive";
|
|
3
4
|
import { DisplayObject } from "./DisplayObject";
|
|
4
5
|
import { DisplayObjectProps } from "./types/DisplayObject";
|
|
5
6
|
import { Signal } from "@signe/reactive";
|
|
6
|
-
import { on } from "../engine/trigger";
|
|
7
|
+
import { on, isTrigger } from "../engine/trigger";
|
|
8
|
+
import { Howl } from "howler";
|
|
7
9
|
|
|
8
10
|
enum TextEffect {
|
|
9
11
|
Typewriter = "typewriter",
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
interface TextProps extends DisplayObjectProps {
|
|
14
|
+
export interface TextProps extends DisplayObjectProps {
|
|
13
15
|
text?: string;
|
|
14
16
|
style?: Partial<TextStyle>;
|
|
15
17
|
color?: string;
|
|
16
|
-
size?: string;
|
|
18
|
+
size?: string | number;
|
|
17
19
|
fontFamily?: string;
|
|
18
20
|
typewriter?: {
|
|
19
21
|
speed?: number;
|
|
20
22
|
start?: () => void;
|
|
21
23
|
onComplete?: () => void;
|
|
22
24
|
skip?: () => void;
|
|
25
|
+
sound?: {
|
|
26
|
+
src: string;
|
|
27
|
+
volume?: number;
|
|
28
|
+
rate?: number;
|
|
29
|
+
};
|
|
23
30
|
};
|
|
31
|
+
context?: any; // Ensure context is available, ideally typed from a base prop or injected
|
|
24
32
|
}
|
|
25
33
|
|
|
34
|
+
type PretextMeasurement = {
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const toFiniteNumber = (value: unknown): number | null => {
|
|
40
|
+
if (typeof value === "number") {
|
|
41
|
+
return Number.isFinite(value) ? value : null;
|
|
42
|
+
}
|
|
43
|
+
if (typeof value === "string") {
|
|
44
|
+
const parsed = Number.parseFloat(value);
|
|
45
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
};
|
|
49
|
+
|
|
26
50
|
class CanvasText extends DisplayObject(PixiText) {
|
|
27
51
|
private subscriptionTick: any;
|
|
28
52
|
private fullText: string = "";
|
|
@@ -31,10 +55,22 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
31
55
|
private _wordWrapWidth: number = 0;
|
|
32
56
|
private typewriterOptions: any = {};
|
|
33
57
|
private skipSignal?: () => void;
|
|
58
|
+
private typewriterSound?: Howl;
|
|
59
|
+
private lastSoundTime: number = 0;
|
|
60
|
+
private soundDuration: number = 0; // Duration of the sound in milliseconds
|
|
61
|
+
private pretextPrepared: PreparedText | null = null;
|
|
62
|
+
private pretextPrepareKey: string = "";
|
|
63
|
+
private measuredLayout: PretextMeasurement | null = null;
|
|
34
64
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
65
|
+
/**
|
|
66
|
+
* Called when the component is mounted to the scene graph.
|
|
67
|
+
* Initializes the typewriter effect if configured.
|
|
68
|
+
* @param {Element<CanvasText>} element - The element being mounted with parent and props.
|
|
69
|
+
* @param {number} [index] - The index of the component among its siblings.
|
|
70
|
+
*/
|
|
71
|
+
async onMount(element: Element<any>, index?: number): Promise<void> {
|
|
72
|
+
const { props } = element;
|
|
73
|
+
await super.onMount(element, index);
|
|
38
74
|
const tick: Signal = props.context.tick;
|
|
39
75
|
|
|
40
76
|
if (props.text && props.typewriter) {
|
|
@@ -44,12 +80,18 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
44
80
|
// Set typewriter options
|
|
45
81
|
if (props.typewriter) {
|
|
46
82
|
this.typewriterOptions = props.typewriter;
|
|
47
|
-
if (this.typewriterOptions.skip) {
|
|
83
|
+
if (this.typewriterOptions.skip && isTrigger(this.typewriterOptions.skip)) {
|
|
48
84
|
on(this.typewriterOptions.skip, () => {
|
|
49
85
|
this.skipTypewriter();
|
|
50
86
|
});
|
|
51
87
|
}
|
|
88
|
+
// Initialize typewriter sound if configured
|
|
89
|
+
if (this.typewriterOptions.sound) {
|
|
90
|
+
this.initializeTypewriterSound();
|
|
91
|
+
}
|
|
52
92
|
}
|
|
93
|
+
// Update layout after initializing typewriter
|
|
94
|
+
this.updateLayout();
|
|
53
95
|
}
|
|
54
96
|
this.subscriptionTick = tick.observable.subscribe(() => {
|
|
55
97
|
if (props.typewriter) {
|
|
@@ -63,23 +105,24 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
63
105
|
if (props.typewriter) {
|
|
64
106
|
if (props.typewriter) {
|
|
65
107
|
this.typewriterOptions = props.typewriter;
|
|
108
|
+
// Reinitialize sound if sound configuration changed
|
|
109
|
+
if (props.typewriter.sound) {
|
|
110
|
+
this.initializeTypewriterSound();
|
|
111
|
+
}
|
|
66
112
|
}
|
|
67
113
|
}
|
|
68
|
-
if (props.text) {
|
|
69
|
-
this.text = props.text;
|
|
114
|
+
if (props.text !== undefined) {
|
|
115
|
+
this.text = ''+props.text;
|
|
70
116
|
}
|
|
71
117
|
if (props.text !== undefined && props.text !== this.fullText && this.fullProps.typewriter) {
|
|
72
118
|
this.text = "";
|
|
73
119
|
this.currentIndex = 0;
|
|
74
120
|
this.fullText = props.text;
|
|
121
|
+
// Update layout after resetting typewriter
|
|
122
|
+
this.updateLayout();
|
|
75
123
|
}
|
|
76
124
|
if (props.style) {
|
|
77
|
-
|
|
78
|
-
this.style[key] = props.style[key];
|
|
79
|
-
}
|
|
80
|
-
if (props.style.wordWrapWidth) {
|
|
81
|
-
this._wordWrapWidth = props.style.wordWrapWidth;
|
|
82
|
-
}
|
|
125
|
+
this.applyTextStyle(props.style);
|
|
83
126
|
}
|
|
84
127
|
if (props.color) {
|
|
85
128
|
this.style.fill = props.color;
|
|
@@ -90,18 +133,186 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
90
133
|
if (props.fontFamily) {
|
|
91
134
|
this.style.fontFamily = props.fontFamily;
|
|
92
135
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
this.setHeight(this.height);
|
|
136
|
+
this.updateWordWrapWidth();
|
|
137
|
+
|
|
138
|
+
// Use the centralized layout update method
|
|
139
|
+
this.updateLayout();
|
|
99
140
|
}
|
|
100
141
|
|
|
101
142
|
get onCompleteCallback() {
|
|
102
143
|
return this.typewriterOptions.onComplete;
|
|
103
144
|
}
|
|
104
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Initializes the typewriter sound effect using Howler.
|
|
148
|
+
* Creates a Howl instance with the configured sound settings.
|
|
149
|
+
* Calculates the sound duration to prevent overlapping sounds.
|
|
150
|
+
*/
|
|
151
|
+
private initializeTypewriterSound() {
|
|
152
|
+
if (!this.typewriterOptions.sound?.src) return;
|
|
153
|
+
|
|
154
|
+
this.typewriterSound = new Howl({
|
|
155
|
+
src: [this.typewriterOptions.sound.src],
|
|
156
|
+
volume: this.typewriterOptions.sound.volume ?? 0.5,
|
|
157
|
+
rate: this.typewriterOptions.sound.rate ?? 1.0,
|
|
158
|
+
preload: true,
|
|
159
|
+
onload: () => {
|
|
160
|
+
// Calculate sound duration in milliseconds
|
|
161
|
+
if (this.typewriterSound) {
|
|
162
|
+
const duration = this.typewriterSound.duration();
|
|
163
|
+
const rate = this.typewriterOptions.sound?.rate ?? 1.0;
|
|
164
|
+
this.soundDuration = (duration / rate) * 1000;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Plays the typewriter sound with duration-based cooldown to prevent overlapping sounds.
|
|
172
|
+
* @param {number} currentTime - The current timestamp to check against sound duration.
|
|
173
|
+
*/
|
|
174
|
+
private playTypewriterSound(currentTime: number) {
|
|
175
|
+
if (!this.typewriterSound || !this.typewriterOptions.sound) return;
|
|
176
|
+
|
|
177
|
+
// Check if enough time has passed since the last sound play
|
|
178
|
+
// Use the actual sound duration to prevent overlap
|
|
179
|
+
if (this.soundDuration > 0 && currentTime - this.lastSoundTime < this.soundDuration) return;
|
|
180
|
+
|
|
181
|
+
this.typewriterSound.play();
|
|
182
|
+
this.lastSoundTime = currentTime;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Updates the layout properties of the text component.
|
|
187
|
+
* This method ensures consistent width, height and word wrap behavior.
|
|
188
|
+
*/
|
|
189
|
+
private updateLayout() {
|
|
190
|
+
const measured = this.measurePretextLayout();
|
|
191
|
+
const width = measured?.width ?? this.width;
|
|
192
|
+
const height = measured?.height ?? this.height;
|
|
193
|
+
|
|
194
|
+
this.measuredLayout = measured ?? { width, height };
|
|
195
|
+
this.setMeasuredLayout(width, height);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private applyTextStyle(style: Partial<TextStyle>) {
|
|
199
|
+
const assign = (this.style as TextStyle & { assign?: (values: any) => TextStyle }).assign;
|
|
200
|
+
if (typeof assign === "function") {
|
|
201
|
+
assign.call(this.style, style);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
for (const key in style) {
|
|
206
|
+
(this.style as any)[key] = (style as any)[key];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
private updateWordWrapWidth() {
|
|
211
|
+
if (!this.style.wordWrap) {
|
|
212
|
+
this._wordWrapWidth = 0;
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const wordWrapWidth = toFiniteNumber(this.style.wordWrapWidth);
|
|
216
|
+
this._wordWrapWidth = wordWrapWidth !== null && wordWrapWidth > 0 ? wordWrapWidth : 0;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private measurePretextLayout(): PretextMeasurement | null {
|
|
220
|
+
if (!this.style.wordWrap || this._wordWrapWidth <= 0) {
|
|
221
|
+
this.pretextPrepared = null;
|
|
222
|
+
this.pretextPrepareKey = "";
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const text = `${this.text ?? ""}`;
|
|
227
|
+
const font = this.resolvePretextFont();
|
|
228
|
+
const lineHeight = this.resolveLineHeight();
|
|
229
|
+
const options = this.resolvePretextOptions();
|
|
230
|
+
const prepareKey = JSON.stringify([text, font, options.whiteSpace, options.wordBreak, options.letterSpacing]);
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
if (this.pretextPrepareKey !== prepareKey || !this.pretextPrepared) {
|
|
234
|
+
this.pretextPrepared = preparePretext(text, font, options);
|
|
235
|
+
this.pretextPrepareKey = prepareKey;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const result = layoutPretext(this.pretextPrepared, this._wordWrapWidth, lineHeight);
|
|
239
|
+
return {
|
|
240
|
+
width: this._wordWrapWidth,
|
|
241
|
+
height: result.height,
|
|
242
|
+
};
|
|
243
|
+
} catch {
|
|
244
|
+
this.pretextPrepared = null;
|
|
245
|
+
this.pretextPrepareKey = "";
|
|
246
|
+
return null;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
private resolvePretextFont(): string {
|
|
251
|
+
const fontString = (this.style as TextStyle & { _fontString?: string })._fontString;
|
|
252
|
+
if (fontString) return fontString;
|
|
253
|
+
|
|
254
|
+
const fontSize = this.resolveFontSize();
|
|
255
|
+
const fontFamily = Array.isArray(this.style.fontFamily)
|
|
256
|
+
? this.style.fontFamily.join(",")
|
|
257
|
+
: this.style.fontFamily;
|
|
258
|
+
|
|
259
|
+
return `${this.style.fontStyle} ${this.style.fontVariant} ${this.style.fontWeight} ${fontSize}px ${fontFamily}`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private resolvePretextOptions(): PrepareOptions {
|
|
263
|
+
return {
|
|
264
|
+
whiteSpace: this.style.whiteSpace === "normal" ? "normal" : "pre-wrap",
|
|
265
|
+
letterSpacing: this.style.letterSpacing || undefined,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
private resolveLineHeight(): number {
|
|
270
|
+
const lineHeight = toFiniteNumber(this.style.lineHeight);
|
|
271
|
+
if (lineHeight !== null && lineHeight > 0) return lineHeight;
|
|
272
|
+
return this.resolveFontSize();
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
private resolveFontSize(): number {
|
|
276
|
+
const fontSize = toFiniteNumber(this.style.fontSize);
|
|
277
|
+
return fontSize !== null && fontSize > 0 ? fontSize : 16;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
private setMeasuredLayout(width: number, height: number) {
|
|
281
|
+
const layout: { width?: number; height?: number } = {};
|
|
282
|
+
|
|
283
|
+
if (this.fullProps.width === undefined) {
|
|
284
|
+
this.displayWidth.set(width);
|
|
285
|
+
if (this.parentIsFlex) {
|
|
286
|
+
layout.width = width;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (this.fullProps.height === undefined) {
|
|
291
|
+
this.displayHeight.set(height);
|
|
292
|
+
if (this.parentIsFlex) {
|
|
293
|
+
layout.height = height;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (this.parentIsFlex && (layout.width !== undefined || layout.height !== undefined)) {
|
|
298
|
+
(this as any).layout = layout;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
getWidth(): number {
|
|
303
|
+
if (this.fullProps.width === undefined && this.measuredLayout) {
|
|
304
|
+
return this.measuredLayout.width;
|
|
305
|
+
}
|
|
306
|
+
return super.getWidth();
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
getHeight(): number {
|
|
310
|
+
if (this.fullProps.height === undefined && this.measuredLayout) {
|
|
311
|
+
return this.measuredLayout.height;
|
|
312
|
+
}
|
|
313
|
+
return super.getHeight();
|
|
314
|
+
}
|
|
315
|
+
|
|
105
316
|
private typewriterEffect() {
|
|
106
317
|
if (this.currentIndex < this.fullText.length) {
|
|
107
318
|
const nextIndex = Math.min(
|
|
@@ -111,6 +322,14 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
111
322
|
this.text = this.fullText.slice(0, nextIndex);
|
|
112
323
|
this.currentIndex = nextIndex;
|
|
113
324
|
|
|
325
|
+
// Play typewriter sound if configured
|
|
326
|
+
if (this.typewriterOptions.sound) {
|
|
327
|
+
this.playTypewriterSound(Date.now());
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Update layout after text change to maintain proper word wrap and dimensions
|
|
331
|
+
this.updateLayout();
|
|
332
|
+
|
|
114
333
|
// Check if typewriter effect is complete
|
|
115
334
|
if (
|
|
116
335
|
this.currentIndex === this.fullText.length &&
|
|
@@ -128,15 +347,40 @@ class CanvasText extends DisplayObject(PixiText) {
|
|
|
128
347
|
}
|
|
129
348
|
this.text = this.fullText;
|
|
130
349
|
this.currentIndex = this.fullText.length;
|
|
350
|
+
|
|
351
|
+
// Update layout after setting full text to maintain proper word wrap and dimensions
|
|
352
|
+
this.updateLayout();
|
|
131
353
|
}
|
|
132
354
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
355
|
+
/**
|
|
356
|
+
* Called when the component is about to be destroyed.
|
|
357
|
+
* Unsubscribes from the tick observable and cleans up sound resources.
|
|
358
|
+
* @param {Element<any>} parent - The parent element.
|
|
359
|
+
* @param {() => void} [afterDestroy] - An optional callback function to be executed after the component's own destruction logic.
|
|
360
|
+
*/
|
|
361
|
+
async onDestroy(parent: Element<any>, afterDestroy?: () => void): Promise<void> {
|
|
362
|
+
const _afterDestroy = async () => {
|
|
363
|
+
if (this.subscriptionTick) {
|
|
364
|
+
this.subscriptionTick.unsubscribe();
|
|
365
|
+
}
|
|
366
|
+
// Clean up typewriter sound
|
|
367
|
+
if (this.typewriterSound) {
|
|
368
|
+
this.typewriterSound.stop();
|
|
369
|
+
this.typewriterSound.unload();
|
|
370
|
+
this.typewriterSound = undefined;
|
|
371
|
+
}
|
|
372
|
+
this.pretextPrepared = null;
|
|
373
|
+
this.pretextPrepareKey = "";
|
|
374
|
+
this.measuredLayout = null;
|
|
375
|
+
if (afterDestroy) {
|
|
376
|
+
afterDestroy();
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
await super.onDestroy(parent, _afterDestroy);
|
|
136
380
|
}
|
|
137
381
|
}
|
|
138
382
|
|
|
139
|
-
interface CanvasText extends PixiText {}
|
|
383
|
+
// interface CanvasText extends PixiText {} // Removed as it's redundant and causes type conflicts
|
|
140
384
|
|
|
141
385
|
registerComponent("Text", CanvasText);
|
|
142
386
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Viewport as PixiViewport } from 'pixi-viewport';
|
|
2
2
|
import { Subscription } from 'rxjs';
|
|
3
|
-
import { createComponent, registerComponent } from '../engine/reactive';
|
|
4
|
-
import { DisplayObject } from './DisplayObject';
|
|
5
|
-
import { effect } from '@signe/reactive';
|
|
3
|
+
import { createComponent, registerComponent, Element, Props } from '../engine/reactive';
|
|
4
|
+
import { DisplayObject, ComponentInstance } from './DisplayObject';
|
|
5
|
+
import { effect, Signal } from '@signe/reactive';
|
|
6
|
+
import { Graphics, Container, ContainerChild, IRenderLayer } from 'pixi.js';
|
|
6
7
|
|
|
7
8
|
const EVENTS = [
|
|
8
9
|
'bounce-x-end',
|
|
@@ -28,55 +29,103 @@ const EVENTS = [
|
|
|
28
29
|
'zoomed-end'
|
|
29
30
|
]
|
|
30
31
|
|
|
31
|
-
export
|
|
32
|
+
export interface ViewportProps extends Props {
|
|
33
|
+
screenWidth?: number;
|
|
34
|
+
screenHeight?: number;
|
|
35
|
+
worldWidth?: number;
|
|
36
|
+
worldHeight?: number;
|
|
37
|
+
sortableChildren?: boolean;
|
|
38
|
+
clamp?: boolean | {
|
|
39
|
+
left?: number;
|
|
40
|
+
right?: number;
|
|
41
|
+
top?: number;
|
|
42
|
+
bottom?: number;
|
|
43
|
+
};
|
|
44
|
+
context?: any;
|
|
45
|
+
[key: string]: any;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export class CanvasViewport extends DisplayObject(Container) {
|
|
32
49
|
private tickSubscription: Subscription
|
|
33
50
|
overrideProps = ['wheel']
|
|
51
|
+
#mask: Graphics
|
|
52
|
+
public viewport: PixiViewport
|
|
34
53
|
|
|
35
54
|
constructor() {
|
|
55
|
+
super()
|
|
36
56
|
const defaultOptions = {
|
|
37
57
|
noTicker: true,
|
|
38
58
|
events: {
|
|
39
59
|
domElement: {
|
|
40
|
-
addEventListener: () => {}
|
|
60
|
+
addEventListener: () => { }
|
|
41
61
|
}
|
|
42
62
|
},
|
|
43
63
|
}
|
|
44
64
|
// @ts-ignore
|
|
45
|
-
|
|
65
|
+
this.viewport = new PixiViewport(defaultOptions)
|
|
66
|
+
super.addChild(this.viewport)
|
|
67
|
+
|
|
68
|
+
this.#mask = new Graphics()
|
|
69
|
+
super.addChild(this.#mask)
|
|
70
|
+
this.mask = this.#mask
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
addChild<U extends any[]>(...children: U): U[0] {
|
|
74
|
+
return this.viewport.addChild(...children)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
addChildAt<T extends ContainerChild | IRenderLayer>(child: T, index: number): T {
|
|
78
|
+
return this.viewport.addChildAt(child, index) as T
|
|
46
79
|
}
|
|
47
80
|
|
|
48
81
|
onInit(props) {
|
|
49
82
|
super.onInit(props)
|
|
50
83
|
for (let event of EVENTS) {
|
|
51
|
-
|
|
52
|
-
if (props[camelCaseEvent]) {
|
|
53
|
-
this.on(event, props[camelCaseEvent])
|
|
54
|
-
}
|
|
84
|
+
if (props[event]) this.viewport.on(event, props[event])
|
|
55
85
|
}
|
|
56
86
|
}
|
|
57
87
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Called when the component is mounted to the scene graph.
|
|
90
|
+
* Initializes viewport settings and subscriptions.
|
|
91
|
+
* @param {Element<CanvasViewport>} element - The element being mounted. Its `props` property (of type ViewportProps) contains component properties and context.
|
|
92
|
+
* @param {number} [index] - The index of the component among its siblings.
|
|
93
|
+
*/
|
|
94
|
+
async onMount(element: Element<any>, index?: number): Promise<void> {
|
|
95
|
+
element.props.context.viewport = this.viewport
|
|
96
|
+
await super.onMount(element, index);
|
|
97
|
+
const { props } = element;
|
|
98
|
+
const { tick, app, canvasSize } = props.context;
|
|
99
|
+
|
|
63
100
|
effect(() => {
|
|
64
|
-
|
|
65
|
-
|
|
101
|
+
if (props.screenWidth === undefined) {
|
|
102
|
+
this.viewport.screenWidth = canvasSize().width
|
|
103
|
+
}
|
|
104
|
+
if (props.screenHeight === undefined) {
|
|
105
|
+
this.viewport.screenHeight = canvasSize().height
|
|
106
|
+
}
|
|
107
|
+
this.updateMask()
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
effect(() => {
|
|
111
|
+
const _app = app()
|
|
112
|
+
if (!_app) return
|
|
113
|
+
|
|
114
|
+
const renderer = _app.renderer
|
|
115
|
+
|
|
116
|
+
renderer.events.domElement.addEventListener(
|
|
117
|
+
'wheel',
|
|
118
|
+
this.viewport.input.wheelFunction
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
this.viewport.options.events = renderer.events
|
|
66
122
|
})
|
|
67
123
|
|
|
68
|
-
renderer.events.domElement.addEventListener(
|
|
69
|
-
'wheel',
|
|
70
|
-
this.input.wheelFunction
|
|
71
|
-
);
|
|
72
|
-
this.options.events = renderer.events
|
|
73
|
-
|
|
74
124
|
this.tickSubscription = tick.observable.subscribe(({ value }) => {
|
|
75
|
-
this.update(value.
|
|
125
|
+
this.viewport.update(value.deltaTime)
|
|
76
126
|
})
|
|
77
127
|
|
|
78
|
-
|
|
79
|
-
this.updateViewportSettings(element.props)
|
|
128
|
+
this.updateViewportSettings(props)
|
|
80
129
|
}
|
|
81
130
|
|
|
82
131
|
onUpdate(props) {
|
|
@@ -86,74 +135,84 @@ export class CanvasViewport extends DisplayObject(PixiViewport) {
|
|
|
86
135
|
|
|
87
136
|
private updateViewportSettings(props) {
|
|
88
137
|
if (props.screenWidth !== undefined) {
|
|
89
|
-
this.screenWidth = props.screenWidth
|
|
138
|
+
this.viewport.screenWidth = props.screenWidth
|
|
90
139
|
}
|
|
91
140
|
if (props.screenHeight !== undefined) {
|
|
92
|
-
this.screenHeight = props.screenHeight
|
|
141
|
+
this.viewport.screenHeight = props.screenHeight
|
|
93
142
|
}
|
|
143
|
+
this.updateMask()
|
|
94
144
|
if (props.worldWidth !== undefined) {
|
|
95
|
-
this.worldWidth = props.worldWidth
|
|
145
|
+
this.viewport.worldWidth = props.worldWidth
|
|
96
146
|
}
|
|
97
147
|
if (props.worldHeight !== undefined) {
|
|
98
|
-
this.worldHeight = props.worldHeight
|
|
148
|
+
this.viewport.worldHeight = props.worldHeight
|
|
149
|
+
}
|
|
150
|
+
if (props.sortableChildren !== undefined) {
|
|
151
|
+
this.viewport.sortableChildren = props.sortableChildren
|
|
152
|
+
}
|
|
153
|
+
if (props.drag) {
|
|
154
|
+
this.viewport.drag(props.drag)
|
|
99
155
|
}
|
|
100
|
-
// if (props.drag) {
|
|
101
|
-
// if (props.drag === true) {
|
|
102
|
-
|
|
103
|
-
// } else {
|
|
104
|
-
// this.drag(props.drag)
|
|
105
|
-
// }
|
|
106
|
-
// }
|
|
107
156
|
if (props.clamp) {
|
|
108
|
-
this.clamp(props.clamp)
|
|
157
|
+
this.viewport.clamp(props.clamp.value ?? props.clamp)
|
|
109
158
|
}
|
|
110
159
|
if (props.wheel) {
|
|
111
160
|
if (props.wheel === true) {
|
|
112
|
-
this.wheel()
|
|
161
|
+
this.viewport.wheel()
|
|
113
162
|
} else {
|
|
114
|
-
this.wheel(props.wheel)
|
|
163
|
+
this.viewport.wheel(props.wheel)
|
|
115
164
|
}
|
|
116
165
|
}
|
|
117
166
|
if (props.decelerate) {
|
|
118
167
|
if (props.decelerate === true) {
|
|
119
|
-
this.decelerate()
|
|
168
|
+
this.viewport.decelerate()
|
|
120
169
|
} else {
|
|
121
|
-
this.decelerate(props.decelerate)
|
|
170
|
+
this.viewport.decelerate(props.decelerate)
|
|
122
171
|
}
|
|
123
172
|
}
|
|
124
173
|
if (props.pinch) {
|
|
125
174
|
if (props.pinch === true) {
|
|
126
|
-
this.pinch()
|
|
175
|
+
this.viewport.pinch()
|
|
127
176
|
} else {
|
|
128
|
-
this.pinch(props.pinch)
|
|
177
|
+
this.viewport.pinch(props.pinch)
|
|
129
178
|
}
|
|
130
179
|
}
|
|
131
180
|
}
|
|
132
181
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
this.
|
|
182
|
+
private updateMask() {
|
|
183
|
+
if (!this.#mask) return
|
|
184
|
+
this.#mask.clear()
|
|
185
|
+
this.#mask.beginFill(0xffffff)
|
|
186
|
+
this.#mask.drawRect(0, 0, this.viewport.screenWidth, this.viewport.screenHeight)
|
|
187
|
+
this.#mask.endFill()
|
|
136
188
|
}
|
|
137
|
-
}
|
|
138
189
|
|
|
139
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Called when the component is about to be destroyed.
|
|
192
|
+
* Unsubscribes from the tick observable.
|
|
193
|
+
* @param {Element<any>} parent - The parent element.
|
|
194
|
+
* @param {() => void} [afterDestroy] - An optional callback function to be executed after the component's own destruction logic.
|
|
195
|
+
*/
|
|
196
|
+
async onDestroy(parent: Element<any>, afterDestroy?: () => void): Promise<void> {
|
|
197
|
+
const _afterDestroy = async () => {
|
|
198
|
+
this.tickSubscription.unsubscribe()
|
|
199
|
+
afterDestroy()
|
|
200
|
+
}
|
|
201
|
+
await super.onDestroy(parent, _afterDestroy);
|
|
202
|
+
}
|
|
140
203
|
|
|
141
|
-
|
|
204
|
+
// Proxy methods for viewport plugins
|
|
205
|
+
follow(...args: any[]) {
|
|
206
|
+
return (this.viewport.follow as any)(...args)
|
|
207
|
+
}
|
|
142
208
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
worldWidth?: number;
|
|
147
|
-
worldHeight?: number;
|
|
148
|
-
clamp?: boolean | {
|
|
149
|
-
left?: number;
|
|
150
|
-
right?: number;
|
|
151
|
-
top?: number;
|
|
152
|
-
bottom?: number;
|
|
153
|
-
};
|
|
154
|
-
[key: string]: any;
|
|
209
|
+
get plugins() {
|
|
210
|
+
return this.viewport.plugins
|
|
211
|
+
}
|
|
155
212
|
}
|
|
156
213
|
|
|
214
|
+
registerComponent('Viewport', CanvasViewport)
|
|
215
|
+
|
|
157
216
|
export function Viewport(props: ViewportProps) {
|
|
158
217
|
return createComponent('Viewport', props);
|
|
159
|
-
}
|
|
218
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { Canvas } from './Canvas'
|
|
2
2
|
export { Container } from './Container'
|
|
3
|
-
export { Graphics, Rect, Circle, Ellipse, Triangle, Svg
|
|
3
|
+
export { Graphics, Rect, Circle, Ellipse, Triangle, Svg } from './Graphic'
|
|
4
|
+
export { Mesh } from './Mesh'
|
|
4
5
|
export { Scene } from './Scene'
|
|
5
6
|
export { ParticlesEmitter } from './ParticleEmitter'
|
|
6
7
|
export { Sprite } from './Sprite'
|
|
@@ -9,4 +10,10 @@ export { Text } from './Text'
|
|
|
9
10
|
export { TilingSprite } from './TilingSprite'
|
|
10
11
|
export { Viewport } from './Viewport'
|
|
11
12
|
export { NineSliceSprite } from './NineSliceSprite'
|
|
12
|
-
export { type ComponentInstance } from './DisplayObject'
|
|
13
|
+
export { type ComponentInstance } from './DisplayObject'
|
|
14
|
+
export { DOMContainer } from './DOMContainer'
|
|
15
|
+
export { DOMElement } from './DOMElement'
|
|
16
|
+
export { DOMSprite } from './DOMSprite'
|
|
17
|
+
export { Button, ButtonState, type ButtonProps, type ButtonStyle } from './Button'
|
|
18
|
+
export { Joystick, type JoystickSettings } from './Joystick'
|
|
19
|
+
export { FocusContainer, Navigation, type FocusContainerProps } from './FocusContainer'
|