canvasengine 2.0.1-beta.1 → 2.0.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.
- 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/Container.d.ts +25 -0
- package/dist/components/Container.d.ts.map +1 -0
- package/dist/components/DOMContainer.d.ts +28 -0
- package/dist/components/DOMContainer.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/DisplayObject.d.ts +18 -0
- package/dist/components/DisplayObject.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 +37 -0
- package/dist/components/Joystick.d.ts.map +1 -0
- package/dist/components/Mesh.d.ts +138 -0
- package/dist/components/Mesh.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/Sprite.d.ts +182 -0
- package/dist/components/Sprite.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/Viewport.d.ts +52 -0
- package/dist/components/Viewport.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 +127 -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/SpriteEffects.d.ts +70 -0
- package/dist/directives/SpriteEffects.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 +15 -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 +139 -0
- package/dist/engine/reactive.d.ts.map +1 -0
- package/dist/engine/signal.d.ts +74 -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 +53 -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 -844
- package/dist/index.d.ts.map +1 -0
- package/dist/index.global.js +189 -0
- package/dist/index.global.js.map +1 -0
- package/dist/index.js +15156 -3287
- 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/index.d.ts +1 -3
- package/package.json +19 -52
- package/src/components/Button.ts +406 -0
- package/src/components/Canvas.ts +82 -51
- package/src/components/Container.ts +31 -4
- package/src/components/DOMContainer.ts +387 -0
- package/src/components/DOMElement.ts +556 -0
- package/src/components/DOMSprite.ts +1044 -0
- package/src/components/DisplayObject.ts +423 -202
- package/src/components/FocusContainer.ts +368 -0
- package/src/components/Graphic.ts +255 -68
- package/src/components/Joystick.ts +269 -0
- package/src/components/Mesh.ts +239 -0
- package/src/components/NineSliceSprite.ts +4 -1
- package/src/components/ParticleEmitter.ts +12 -8
- package/src/components/Sprite.ts +441 -54
- package/src/components/Text.ts +270 -26
- package/src/components/Video.ts +110 -0
- package/src/components/Viewport.ts +122 -63
- package/src/components/index.ts +12 -5
- package/src/components/types/DisplayObject.ts +64 -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 +86 -431
- package/src/directives/Scheduler.ts +21 -16
- package/src/directives/Shake.ts +298 -0
- package/src/directives/Sound.ts +94 -31
- package/src/directives/SpriteEffects.ts +461 -0
- package/src/directives/ViewportFollow.ts +40 -9
- package/src/directives/index.ts +14 -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 +1266 -167
- package/src/engine/signal.ts +335 -47
- package/src/engine/trigger.ts +95 -12
- package/src/engine/utils.ts +101 -9
- package/src/hooks/useFocus.ts +91 -0
- package/src/hooks/useProps.ts +92 -7
- package/src/index.ts +13 -6
- package/src/types/pixi-cull.d.ts +7 -0
- package/src/utils/GlobalAssetLoader.ts +257 -0
- package/src/utils/RadialGradient.ts +29 -0
- package/src/utils/functions.ts +7 -0
- package/src/utils/tabindex.ts +70 -0
- package/testing/index.ts +42 -0
- package/tsconfig.json +15 -25
- package/vite.config.ts +39 -0
- package/.cursorrules +0 -0
- package/.github/workflows/ci.yml +0 -29
- package/README.md +0 -79
- package/dist/compiler/vite.js +0 -119
- package/dist/compiler/vite.js.map +0 -1
- package/logo.png +0 -0
- package/src/compiler/grammar.pegjs +0 -180
- package/src/compiler/vite.ts +0 -166
- package/src/components/DrawMap/index.ts +0 -65
- package/src/components/Tilemap/Tile.ts +0 -79
- package/src/components/Tilemap/TileGroup.ts +0 -207
- package/src/components/Tilemap/TileLayer.ts +0 -163
- package/src/components/Tilemap/TileSet.ts +0 -41
- package/src/components/Tilemap/index.ts +0 -80
- package/src/presets/Bar.ts +0 -89
- package/src/presets/Button.ts +0 -0
- package/src/presets/Joystick.ts +0 -286
- package/src/presets/NightAmbiant.ts +0 -122
- package/src/presets/Particle.ts +0 -53
- package/starter/assets/logo.png +0 -0
- package/starter/components/app.ce +0 -18
- package/starter/components/hello.ce +0 -35
- package/starter/index.html +0 -21
- package/starter/main.ts +0 -6
- package/starter/package.json +0 -20
- package/starter/tsconfig.json +0 -32
- package/starter/vite.config.ts +0 -12
- package/tsconfig.node.json +0 -10
- package/tsup.config.ts +0 -28
- package/vitest.config.ts +0 -12
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
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { Texture } from "pixi.js";
|
|
2
|
+
import { h, mount } from "../engine/signal";
|
|
3
|
+
import { useDefineProps } from "../hooks/useProps";
|
|
4
|
+
import { Sprite } from "./Sprite";
|
|
5
|
+
import { effect, Signal, signal } from "@signe/reactive";
|
|
6
|
+
|
|
7
|
+
interface VideoProps {
|
|
8
|
+
src: string;
|
|
9
|
+
paused?: boolean;
|
|
10
|
+
loop?: boolean;
|
|
11
|
+
muted?: boolean;
|
|
12
|
+
loader?: {
|
|
13
|
+
onComplete?: (texture: Texture) => void;
|
|
14
|
+
onProgress?: (progress: number) => void;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function Video(props: VideoProps) {
|
|
19
|
+
const eventsMap = {
|
|
20
|
+
audioprocess: null,
|
|
21
|
+
canplay: null,
|
|
22
|
+
canplaythrough: null,
|
|
23
|
+
complete: null,
|
|
24
|
+
durationchange: null,
|
|
25
|
+
emptied: null,
|
|
26
|
+
ended: null,
|
|
27
|
+
loadeddata: null,
|
|
28
|
+
loadedmetadata: null,
|
|
29
|
+
pause: null,
|
|
30
|
+
play: null,
|
|
31
|
+
playing: null,
|
|
32
|
+
progress: null,
|
|
33
|
+
ratechange: null,
|
|
34
|
+
seeked: null,
|
|
35
|
+
seeking: null,
|
|
36
|
+
stalled: null,
|
|
37
|
+
suspend: null,
|
|
38
|
+
timeupdate: null,
|
|
39
|
+
volumechange: null,
|
|
40
|
+
waiting: null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const video: Signal<HTMLVideoElement | null> = signal(null)
|
|
44
|
+
const defineProps = useDefineProps(props)
|
|
45
|
+
const { play, loop, muted } = defineProps({
|
|
46
|
+
play: {
|
|
47
|
+
type: Boolean,
|
|
48
|
+
default: true
|
|
49
|
+
},
|
|
50
|
+
loop: {
|
|
51
|
+
type: Boolean,
|
|
52
|
+
default: false
|
|
53
|
+
},
|
|
54
|
+
muted: {
|
|
55
|
+
type: Boolean,
|
|
56
|
+
default: false
|
|
57
|
+
}
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
effect(() => {
|
|
61
|
+
const _video = video()
|
|
62
|
+
const state = play()
|
|
63
|
+
if (_video && state !== undefined) {
|
|
64
|
+
if (state) {
|
|
65
|
+
_video.play()
|
|
66
|
+
} else {
|
|
67
|
+
_video.pause()
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (_video && loop()) {
|
|
71
|
+
_video.loop = loop()
|
|
72
|
+
}
|
|
73
|
+
if (_video && muted()) {
|
|
74
|
+
_video.muted = muted()
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
mount(() => {
|
|
79
|
+
return () => {
|
|
80
|
+
for (let event in eventsMap) {
|
|
81
|
+
if (eventsMap[event]) {
|
|
82
|
+
video().removeEventListener(event, eventsMap[event])
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return h(Sprite, {
|
|
89
|
+
...props,
|
|
90
|
+
image: props.src,
|
|
91
|
+
loader: {
|
|
92
|
+
onComplete: (texture) => {
|
|
93
|
+
const source = texture.source.resource
|
|
94
|
+
video.set(source)
|
|
95
|
+
if (props?.loader?.onComplete) {
|
|
96
|
+
props.loader.onComplete(texture)
|
|
97
|
+
}
|
|
98
|
+
for (let event in eventsMap) {
|
|
99
|
+
if (props[event]) {
|
|
100
|
+
const cb = (ev) => {
|
|
101
|
+
props[event](ev)
|
|
102
|
+
}
|
|
103
|
+
eventsMap[event] = cb
|
|
104
|
+
source.addEventListener(event, cb)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
}
|