@caperjs/core 0.2.1 → 0.2.3
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/build/index.mjs +18 -1
- package/build/plugins/pwa.mjs +114 -8
- package/build/plugins/viewport.mjs +52 -0
- package/lib/{CaptionsPlugin-DKhbmJvH.js → CaptionsPlugin-WPAcfAkx.js} +13 -12
- package/lib/CaptionsPlugin-WPAcfAkx.js.map +1 -0
- package/lib/{DataAdapter-BZFTkFQ4.js → DataAdapter-DS-twowe.js} +11 -11
- package/lib/{DataAdapter-BZFTkFQ4.js.map → DataAdapter-DS-twowe.js.map} +1 -1
- package/lib/{DebugRenderer-C7X-nlXE.js → DebugRenderer-D1ryrRYx.js} +2 -2
- package/lib/{DebugRenderer-C7X-nlXE.js.map → DebugRenderer-D1ryrRYx.js.map} +1 -1
- package/lib/{DevToolsPlugin-P-atJ-5U.js → DevToolsPlugin-DH7zX7HU.js} +2 -2
- package/lib/{DevToolsPlugin-P-atJ-5U.js.map → DevToolsPlugin-DH7zX7HU.js.map} +1 -1
- package/lib/{GSAPPlugin-CeH9275z.js → GSAPPlugin-BBtRjUqe.js} +2 -2
- package/lib/{GSAPPlugin-CeH9275z.js.map → GSAPPlugin-BBtRjUqe.js.map} +1 -1
- package/lib/{LayoutPlugin-iZ3k6RsK.js → LayoutPlugin-Co6vcoV4.js} +4 -4
- package/lib/{LayoutPlugin-iZ3k6RsK.js.map → LayoutPlugin-Co6vcoV4.js.map} +1 -1
- package/lib/{SpinePlugin-BWiavCpO.js → SpinePlugin-uN641Hmk.js} +2 -2
- package/lib/{SpinePlugin-BWiavCpO.js.map → SpinePlugin-uN641Hmk.js.map} +1 -1
- package/lib/{StatsPlugin-CBpzDJL0.js → StatsPlugin-vZQRm6wh.js} +4 -4
- package/lib/{StatsPlugin-CBpzDJL0.js.map → StatsPlugin-vZQRm6wh.js.map} +1 -1
- package/lib/{VoiceOverPlugin-YaiKJtJ6.js → VoiceOverPlugin-KlZdGj1G.js} +32 -32
- package/lib/{VoiceOverPlugin-YaiKJtJ6.js.map → VoiceOverPlugin-KlZdGj1G.js.map} +1 -1
- package/lib/caper.mjs +2107 -353
- package/lib/caper.mjs.map +1 -1
- package/lib/core/Application.d.ts +37 -2
- package/lib/core/Application.d.ts.map +1 -1
- package/lib/core/create.d.ts +28 -1
- package/lib/core/create.d.ts.map +1 -1
- package/lib/core/index.d.ts +1 -1
- package/lib/core/index.d.ts.map +1 -1
- package/lib/core/interfaces/IApplicationOptions.d.ts +1 -1
- package/lib/core/interfaces/IApplicationOptions.d.ts.map +1 -1
- package/lib/core/interfaces/ICoreFunctions.d.ts +1 -0
- package/lib/core/interfaces/ICoreFunctions.d.ts.map +1 -1
- package/lib/display/AnimatedSprite.d.ts +1 -1
- package/lib/display/AnimatedSprite.d.ts.map +1 -1
- package/lib/display/Container.d.ts.map +1 -1
- package/lib/display/SpineAnimation.d.ts +1 -1
- package/lib/display/SpineAnimation.d.ts.map +1 -1
- package/lib/importOrder.containerFirst.test.d.ts +2 -0
- package/lib/importOrder.containerFirst.test.d.ts.map +1 -0
- package/lib/importOrder.displayFirst.test.d.ts +2 -0
- package/lib/importOrder.displayFirst.test.d.ts.map +1 -0
- package/lib/importOrder.mixinsFirst.test.d.ts +2 -0
- package/lib/importOrder.mixinsFirst.test.d.ts.map +1 -0
- package/lib/importOrder.sceneFirst.test.d.ts +2 -0
- package/lib/importOrder.sceneFirst.test.d.ts.map +1 -0
- package/lib/importOrder.toastFirst.test.d.ts +2 -0
- package/lib/importOrder.toastFirst.test.d.ts.map +1 -0
- package/lib/importOrder.uiFirst.test.d.ts +2 -0
- package/lib/importOrder.uiFirst.test.d.ts.map +1 -0
- package/lib/mixins/factory/Factory.d.ts +1 -1
- package/lib/mixins/factory/Factory.d.ts.map +1 -1
- package/lib/mixins/factory/Factory.test.d.ts +1 -0
- package/lib/mixins/factory/Factory.test.d.ts.map +1 -0
- package/lib/mixins/factory/const.d.ts +9 -2
- package/lib/mixins/factory/const.d.ts.map +1 -1
- package/lib/mixins/factory/defaults.d.ts +5 -0
- package/lib/mixins/factory/defaults.d.ts.map +1 -0
- package/lib/mixins/factory/methods.d.ts +1 -1
- package/lib/mixins/factory/methods.d.ts.map +1 -1
- package/lib/mixins/factory/props.d.ts +4 -2
- package/lib/mixins/factory/props.d.ts.map +1 -1
- package/lib/plugins/PopupManagerPlugin.d.ts +1 -1
- package/lib/plugins/PopupManagerPlugin.d.ts.map +1 -1
- package/lib/plugins/ResizerPlugin.d.ts +29 -0
- package/lib/plugins/ResizerPlugin.d.ts.map +1 -1
- package/lib/plugins/ResizerPlugin.test.d.ts +2 -0
- package/lib/plugins/ResizerPlugin.test.d.ts.map +1 -0
- package/lib/plugins/WebEventsPlugin.d.ts +5 -0
- package/lib/plugins/WebEventsPlugin.d.ts.map +1 -1
- package/lib/plugins/WebEventsPlugin.test.d.ts +2 -0
- package/lib/plugins/WebEventsPlugin.test.d.ts.map +1 -0
- package/lib/plugins/captions/CaptionsRenderer.d.ts +1 -1
- package/lib/plugins/captions/CaptionsRenderer.d.ts.map +1 -1
- package/lib/plugins/i18nPlugin.d.ts +29 -3
- package/lib/plugins/i18nPlugin.d.ts.map +1 -1
- package/lib/plugins/i18nPlugin.test.d.ts +2 -0
- package/lib/plugins/i18nPlugin.test.d.ts.map +1 -0
- package/lib/plugins/input/touch/VirtualControls.d.ts.map +1 -1
- package/lib/plugins/spine/pixi-spine/require-shim.d.ts +0 -1
- package/lib/plugins/spine/pixi-spine/require-shim.d.ts.map +1 -1
- package/lib/{registries-qQmLeN0b.js → registries-BLrxALZ0.js} +1138 -2800
- package/lib/registries-BLrxALZ0.js.map +1 -0
- package/lib/ui/Button.d.ts +1 -1
- package/lib/ui/Button.d.ts.map +1 -1
- package/lib/ui/FlexContainer.d.ts +1 -1
- package/lib/ui/FlexContainer.d.ts.map +1 -1
- package/lib/ui/Input.d.ts +2 -2
- package/lib/ui/Input.d.ts.map +1 -1
- package/lib/ui/Joystick.d.ts +1 -1
- package/lib/ui/Joystick.d.ts.map +1 -1
- package/lib/ui/Toast.d.ts +1 -1
- package/lib/ui/Toast.d.ts.map +1 -1
- package/lib/ui/UICanvas.d.ts +11 -1
- package/lib/ui/UICanvas.d.ts.map +1 -1
- package/lib/ui/UICanvas.test.d.ts +2 -0
- package/lib/ui/UICanvas.test.d.ts.map +1 -0
- package/lib/utils/types.d.ts +1 -2
- package/lib/utils/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/Application.ts +66 -2
- package/src/core/create.ts +30 -1
- package/src/core/index.ts +1 -1
- package/src/core/interfaces/IApplicationOptions.ts +1 -1
- package/src/core/interfaces/ICoreFunctions.ts +1 -0
- package/src/display/AnimatedSprite.ts +2 -1
- package/src/display/Container.ts +3 -1
- package/src/display/SpineAnimation.ts +3 -2
- package/src/importOrder.containerFirst.test.ts +32 -0
- package/src/importOrder.displayFirst.test.ts +19 -0
- package/src/importOrder.mixinsFirst.test.ts +18 -0
- package/src/importOrder.sceneFirst.test.ts +21 -0
- package/src/importOrder.toastFirst.test.ts +20 -0
- package/src/importOrder.uiFirst.test.ts +24 -0
- package/src/mixins/factory/Factory.test.ts +22 -0
- package/src/mixins/factory/Factory.ts +9 -2
- package/src/mixins/factory/const.ts +18 -11
- package/src/mixins/factory/defaults.ts +28 -0
- package/src/mixins/factory/methods.ts +1 -1
- package/src/mixins/factory/props.ts +5 -3
- package/src/plugins/LookupPlugin.ts +1 -1
- package/src/plugins/PopupManagerPlugin.ts +1 -1
- package/src/plugins/ResizerPlugin.test.ts +84 -0
- package/src/plugins/ResizerPlugin.ts +59 -1
- package/src/plugins/WebEventsPlugin.test.ts +75 -0
- package/src/plugins/WebEventsPlugin.ts +15 -0
- package/src/plugins/captions/CaptionsRenderer.ts +1 -1
- package/src/plugins/i18nPlugin.test.ts +124 -0
- package/src/plugins/i18nPlugin.ts +57 -18
- package/src/plugins/input/keyboard/KeyboardContols.ts +1 -1
- package/src/plugins/input/touch/VirtualControls.ts +2 -2
- package/src/plugins/spine/pixi-spine/require-shim.ts +3 -1
- package/src/ui/Button.ts +5 -1
- package/src/ui/FlexContainer.ts +3 -2
- package/src/ui/Input.ts +5 -2
- package/src/ui/Joystick.ts +1 -1
- package/src/ui/Toast.ts +2 -1
- package/src/ui/Toaster.ts +1 -1
- package/src/ui/UICanvas.test.ts +35 -0
- package/src/ui/UICanvas.ts +46 -8
- package/src/utils/types.ts +1 -1
- package/src/version.ts +1 -1
- package/lib/CaptionsPlugin-DKhbmJvH.js.map +0 -1
- package/lib/registries-qQmLeN0b.js.map +0 -1
|
@@ -17,6 +17,17 @@ export type LocaleKey = AppTypeOverrides extends { LocaleKeys: infer K extends s
|
|
|
17
17
|
? K | (string & {})
|
|
18
18
|
: string;
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Base keys that have plural leaves (i.e. a `<base>.other` entry exists in the
|
|
22
|
+
* reference locale). These are the keys `tCount()` accepts.
|
|
23
|
+
*
|
|
24
|
+
* `(string & {})` preserves autocomplete for known keys while still
|
|
25
|
+
* accepting dynamic strings.
|
|
26
|
+
*/
|
|
27
|
+
export type PluralLocaleKey = AppTypeOverrides extends { LocaleKeys: infer K extends string }
|
|
28
|
+
? (K extends `${infer B}.other` ? B : never) | (string & {})
|
|
29
|
+
: string;
|
|
30
|
+
|
|
20
31
|
/**
|
|
21
32
|
* Type definition for i18n dictionary.
|
|
22
33
|
*/
|
|
@@ -73,6 +84,8 @@ export interface Ii18nPlugin extends IPlugin {
|
|
|
73
84
|
|
|
74
85
|
t(key: LocaleKey, params?: i18nTParams, locale?: string): string;
|
|
75
86
|
|
|
87
|
+
tCount(key: PluralLocaleKey, count: number, params?: i18nTParams, locale?: string): string;
|
|
88
|
+
|
|
76
89
|
parse(input: string, locale?: string): string;
|
|
77
90
|
}
|
|
78
91
|
|
|
@@ -152,10 +165,13 @@ export class i18nPlugin extends Plugin<i18nOptions> implements Ii18nPlugin {
|
|
|
152
165
|
|
|
153
166
|
/**
|
|
154
167
|
* Translates a key into a string.
|
|
155
|
-
* If the key is not found, it
|
|
168
|
+
* If the key is not found (or no dictionary is loaded), it returns the key itself, so a typo
|
|
169
|
+
* renders visibly on screen instead of blanking the UI.
|
|
156
170
|
* If the key is found, it will replace any placeholders in the string with the values from the params object.
|
|
157
|
-
*
|
|
158
|
-
*
|
|
171
|
+
* Every `[a|b|c]` group in the string is resolved, whether or not params were passed:
|
|
172
|
+
* `variant: 'random'` picks a random item per group, `variant: <number>` picks that index for every
|
|
173
|
+
* group (clamped to the group's last item), and with no variant param index 0 is used. Brackets never
|
|
174
|
+
* survive into the output.
|
|
159
175
|
* @param key The key to translate.
|
|
160
176
|
* @param params The parameters to replace in the string.
|
|
161
177
|
* @param locale The locale to use for translation.
|
|
@@ -166,7 +182,7 @@ export class i18nPlugin extends Plugin<i18nOptions> implements Ii18nPlugin {
|
|
|
166
182
|
const dict = this._dicts[locale];
|
|
167
183
|
if (!dict) {
|
|
168
184
|
Logger.error(`i18n:: No dictionary loaded for current locale: ${locale}`);
|
|
169
|
-
return
|
|
185
|
+
return key as string;
|
|
170
186
|
}
|
|
171
187
|
// Dot-path resolution: `t('obj.nested')` walks the dict tree. Flat keys
|
|
172
188
|
// still work (including keys that literally contain dots, via the
|
|
@@ -176,24 +192,26 @@ export class i18nPlugin extends Plugin<i18nOptions> implements Ii18nPlugin {
|
|
|
176
192
|
|
|
177
193
|
if (!str) {
|
|
178
194
|
Logger.error(`i18n:: No result found for the key ${key} in the locale: ${this._locale}`);
|
|
179
|
-
return
|
|
195
|
+
return key as string;
|
|
180
196
|
}
|
|
181
197
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
// Get the selected variant based on the "variation" param.
|
|
190
|
-
const num = params.variant === 'random' ? Math.floor(Math.random() * items.length) : params.variant;
|
|
198
|
+
// Resolve every `[a|b|c]` group, whether or not params were passed, so brackets
|
|
199
|
+
// never leak into the rendered string.
|
|
200
|
+
const variant = params?.variant;
|
|
201
|
+
str = str.replace(/\[(.*?)\]/g, (_match, group: string) => {
|
|
202
|
+
// Split the group by the "|" character to get an array of variations.
|
|
203
|
+
const items = group.split('|');
|
|
191
204
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
205
|
+
if (variant === 'random') {
|
|
206
|
+
return items[Math.floor(Math.random() * items.length)];
|
|
195
207
|
}
|
|
196
208
|
|
|
209
|
+
// A number picks that index for every group, clamped to the group's last item.
|
|
210
|
+
const num = typeof variant === 'number' ? Math.min(Math.max(variant, 0), items.length - 1) : 0;
|
|
211
|
+
return items[num];
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
if (params) {
|
|
197
215
|
// Iterate over all params to replace placeholders in the string.
|
|
198
216
|
for (const f in params) {
|
|
199
217
|
// Create a regular expression to match the placeholder for the current param.
|
|
@@ -221,6 +239,27 @@ export class i18nPlugin extends Plugin<i18nOptions> implements Ii18nPlugin {
|
|
|
221
239
|
return this.t(key, params, locale);
|
|
222
240
|
}
|
|
223
241
|
|
|
242
|
+
/**
|
|
243
|
+
* Translates a pluralised key using the locale's own plural rules.
|
|
244
|
+
* The CLDR plural category for `count` (`one`, `other`, `few`, ...) is resolved against
|
|
245
|
+
* `<key>.<category>`, falling back to `<key>.other` when that leaf does not exist.
|
|
246
|
+
* `count` is passed through as a param, so `{count}` interpolates and variants still apply.
|
|
247
|
+
* @param key The base key holding the plural leaves.
|
|
248
|
+
* @param count The count used to pick the plural category.
|
|
249
|
+
* @param params Additional parameters to replace in the string.
|
|
250
|
+
* @param locale The locale to use for translation.
|
|
251
|
+
* @returns The translated string.
|
|
252
|
+
*/
|
|
253
|
+
tCount(key: PluralLocaleKey, count: number, params?: i18nTParams, locale: string = this._locale): string {
|
|
254
|
+
const category = new Intl.PluralRules(locale).select(count);
|
|
255
|
+
const dict = this._dicts[locale];
|
|
256
|
+
let pluralKey = `${key}.${category}`;
|
|
257
|
+
if (!dict || typeof resolveLocalePath(dict, pluralKey) !== 'string') {
|
|
258
|
+
pluralKey = `${key}.other`;
|
|
259
|
+
}
|
|
260
|
+
return this.t(pluralKey, { count, ...params }, locale);
|
|
261
|
+
}
|
|
262
|
+
|
|
224
263
|
/**
|
|
225
264
|
* Parses the input string and replaces anything in between {} braces, assuming it is a key in the dictionary.
|
|
226
265
|
* @param {string} input
|
|
@@ -263,7 +302,7 @@ export class i18nPlugin extends Plugin<i18nOptions> implements Ii18nPlugin {
|
|
|
263
302
|
}
|
|
264
303
|
|
|
265
304
|
protected getCoreFunctions(): string[] {
|
|
266
|
-
return ['t', 'translate', 'setLocale'];
|
|
305
|
+
return ['t', 'translate', 'tCount', 'setLocale'];
|
|
267
306
|
}
|
|
268
307
|
|
|
269
308
|
protected getCoreSignals(): string[] {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Application } from '../../../core/Application';
|
|
2
|
-
import { WithSignals } from '../../../mixins';
|
|
2
|
+
import { WithSignals } from '../../../mixins/signals';
|
|
3
3
|
import { bindAllMethods } from '../../../utils';
|
|
4
4
|
import type { Action } from '../../actions';
|
|
5
5
|
import { normalizeKey, type KeyboardEventDetail } from '../../KeyboardPlugin';
|
|
@@ -3,8 +3,8 @@ import type { ControlsActionMap, JoystickControlsScheme, TouchControlsMap, Touch
|
|
|
3
3
|
|
|
4
4
|
import { JoystickDirection } from '..';
|
|
5
5
|
import { Application } from '../../../core/Application';
|
|
6
|
-
import { WithSignals } from '../../../mixins';
|
|
7
|
-
import {
|
|
6
|
+
import { WithSignals } from '../../../mixins/signals';
|
|
7
|
+
import type { IButton, IJoystick } from '../../../ui';
|
|
8
8
|
import type { Action } from '../../actions';
|
|
9
9
|
import { AbstractControls } from '../AbstractControls';
|
|
10
10
|
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
*****************************************************************************/
|
|
29
29
|
|
|
30
30
|
declare global {
|
|
31
|
-
|
|
31
|
+
// @types/node (present via typeRoots) already declares `require: Require`;
|
|
32
|
+
// redeclaring it as `any` is a TS2403 conflict, so declare only what node
|
|
33
|
+
// doesn't, and keep the shim's loose typing at the use sites below.
|
|
32
34
|
var PIXI: any;
|
|
33
35
|
}
|
|
34
36
|
|
package/src/ui/Button.ts
CHANGED
|
@@ -12,7 +12,11 @@ import {
|
|
|
12
12
|
import { type LayoutOptions } from '@pixi/layout';
|
|
13
13
|
import type { IApplication } from '../core';
|
|
14
14
|
import { Application } from '../core/Application';
|
|
15
|
-
import { Factory
|
|
15
|
+
import { Factory } from '../mixins/factory/Factory';
|
|
16
|
+
import type { HTMLTextProps, TextProps } from '../mixins/factory/props';
|
|
17
|
+
import { Focusable } from '../mixins/focus';
|
|
18
|
+
import { Interactive } from '../mixins/interaction';
|
|
19
|
+
import { WithSignals } from '../mixins/signals';
|
|
16
20
|
import { Signal } from '../signals';
|
|
17
21
|
import { AudioAsset, bindAllMethods, type SpritesheetAsset, type TextureAsset } from '../utils';
|
|
18
22
|
|
package/src/ui/FlexContainer.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { LayoutOptions, NumberValue } from '@pixi/layout';
|
|
2
2
|
import { BitmapText, Graphics, Container as PIXIContainer, Text } from 'pixi.js';
|
|
3
3
|
import { Application } from '../core/Application';
|
|
4
|
-
import { Container } from '../display';
|
|
5
|
-
import { Factory
|
|
4
|
+
import { Container } from '../display/Container';
|
|
5
|
+
import { Factory } from '../mixins/factory/Factory';
|
|
6
|
+
import { WithSignals } from '../mixins/signals';
|
|
6
7
|
import { Signal } from '../signals';
|
|
7
8
|
import {
|
|
8
9
|
AppTypeOverrides,
|
package/src/ui/Input.ts
CHANGED
|
@@ -11,7 +11,10 @@ import {
|
|
|
11
11
|
Texture,
|
|
12
12
|
} from 'pixi.js';
|
|
13
13
|
|
|
14
|
-
import {
|
|
14
|
+
import type { TextProps } from '../mixins/factory/props';
|
|
15
|
+
import { Focusable } from '../mixins/focus';
|
|
16
|
+
import { Interactive } from '../mixins/interaction';
|
|
17
|
+
import { WithSignals } from '../mixins/signals';
|
|
15
18
|
|
|
16
19
|
import {
|
|
17
20
|
EaseString,
|
|
@@ -27,7 +30,7 @@ import {
|
|
|
27
30
|
} from '../utils';
|
|
28
31
|
|
|
29
32
|
import { gsap } from 'gsap';
|
|
30
|
-
import { Container } from '../display';
|
|
33
|
+
import { Container } from '../display/Container';
|
|
31
34
|
import { Signal } from '../signals';
|
|
32
35
|
|
|
33
36
|
/**
|
package/src/ui/Joystick.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* ported to TypeScript and adapted for Caper
|
|
5
5
|
*/
|
|
6
6
|
import { DestroyOptions, FederatedPointerEvent, Graphics, Point, Sprite } from 'pixi.js';
|
|
7
|
-
import { Container } from '../display';
|
|
7
|
+
import { Container } from '../display/Container';
|
|
8
8
|
import { JoystickDirection } from '../plugins';
|
|
9
9
|
import { Signal } from '../signals';
|
|
10
10
|
|
package/src/ui/Toast.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { gsap } from 'gsap';
|
|
2
2
|
import { Graphics, Text } from 'pixi.js';
|
|
3
3
|
import { Container } from '../display/Container';
|
|
4
|
-
import {
|
|
4
|
+
import type { TextStyle } from '../mixins/factory/props';
|
|
5
|
+
import { WithSignals } from '../mixins/signals';
|
|
5
6
|
import { Signal } from '../signals';
|
|
6
7
|
import { Button } from './Button';
|
|
7
8
|
import { Toaster } from './Toaster';
|
package/src/ui/Toaster.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { gsap } from 'gsap';
|
|
2
2
|
import { Container } from '../display/Container';
|
|
3
|
-
import { WithSignals } from '../mixins';
|
|
3
|
+
import { WithSignals } from '../mixins/signals';
|
|
4
4
|
import { Signal } from '../signals';
|
|
5
5
|
import { defaultToastConfig, Toast, ToastConfig } from './Toast';
|
|
6
6
|
import { UICanvasEdge } from './UICanvas';
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// UICanvas transitively imports Application → Pixi display graph. Stub it — the
|
|
4
|
+
// pure padding helper needs none of it.
|
|
5
|
+
vi.mock('../core/Application', () => ({
|
|
6
|
+
Application: { getInstance: () => ({}) },
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
import { computeEffectivePadding } from './UICanvas';
|
|
10
|
+
|
|
11
|
+
const zero = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
12
|
+
|
|
13
|
+
describe('computeEffectivePadding', () => {
|
|
14
|
+
it('adds the safe area to the configured padding', () => {
|
|
15
|
+
expect(
|
|
16
|
+
computeEffectivePadding({ top: 10, right: 10, bottom: 10, left: 10 }, { ...zero, top: 44, bottom: 34 }),
|
|
17
|
+
).toEqual({ top: 54, right: 10, bottom: 44, left: 10 });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('returns the configured padding when there is no safe area', () => {
|
|
21
|
+
expect(computeEffectivePadding({ top: 10, right: 20, bottom: 30, left: 40 }, zero)).toEqual({
|
|
22
|
+
top: 10,
|
|
23
|
+
right: 20,
|
|
24
|
+
bottom: 30,
|
|
25
|
+
left: 40,
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('does not compound when applied repeatedly to the same base padding', () => {
|
|
30
|
+
const base = { top: 10, right: 10, bottom: 10, left: 10 };
|
|
31
|
+
const safeArea = { ...zero, top: 44 };
|
|
32
|
+
computeEffectivePadding(base, safeArea);
|
|
33
|
+
expect(computeEffectivePadding(base, safeArea).top).toBe(54);
|
|
34
|
+
});
|
|
35
|
+
});
|
package/src/ui/UICanvas.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { LayoutOptions } from '@pixi/layout';
|
|
2
2
|
import { ContainerChild, Graphics, RenderLayer, Container as PIXIContainer, Text } from 'pixi.js';
|
|
3
3
|
import { Application } from '../core/Application';
|
|
4
|
-
import { Container } from '../display';
|
|
5
|
-
import { Factory
|
|
4
|
+
import { Container } from '../display/Container';
|
|
5
|
+
import { Factory } from '../mixins/factory/Factory';
|
|
6
|
+
import { WithSignals } from '../mixins/signals';
|
|
6
7
|
import type { AppTypeOverrides, Padding, PointLike, Size, SizeLike } from '../utils';
|
|
7
8
|
import {
|
|
8
9
|
bindAllMethods,
|
|
@@ -72,21 +73,46 @@ export type UICanvasConfig = {
|
|
|
72
73
|
padding: Padding;
|
|
73
74
|
size: Size;
|
|
74
75
|
useAppSize: boolean;
|
|
76
|
+
useSafeArea: boolean;
|
|
75
77
|
layout?: Omit<LayoutOptions, 'target'> | null | boolean;
|
|
76
78
|
autoLayoutChildren?: boolean;
|
|
77
79
|
};
|
|
78
80
|
|
|
79
|
-
export const UICanvasConfigKeys: (keyof UICanvasConfig)[] = [
|
|
81
|
+
export const UICanvasConfigKeys: (keyof UICanvasConfig)[] = [
|
|
82
|
+
'debug',
|
|
83
|
+
'padding',
|
|
84
|
+
'size',
|
|
85
|
+
'useAppSize',
|
|
86
|
+
'useSafeArea',
|
|
87
|
+
'layout',
|
|
88
|
+
];
|
|
80
89
|
|
|
81
90
|
export type UICanvasProps = {
|
|
82
91
|
debug: boolean;
|
|
83
92
|
padding: Partial<Padding> | PointLike;
|
|
84
93
|
size?: SizeLike;
|
|
85
94
|
useAppSize?: boolean;
|
|
95
|
+
useSafeArea?: boolean;
|
|
86
96
|
layout?: Omit<LayoutOptions, 'target'> | null | boolean;
|
|
87
97
|
autoLayoutChildren?: boolean;
|
|
88
98
|
};
|
|
89
99
|
|
|
100
|
+
const zeroPadding: Padding = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The padding the canvas actually lays out with: the configured padding plus the
|
|
104
|
+
* device's safe-area insets. Kept separate from `config.padding` so it can be
|
|
105
|
+
* re-derived on every resize without compounding.
|
|
106
|
+
*/
|
|
107
|
+
export function computeEffectivePadding(padding: Padding, safeArea: Padding): Padding {
|
|
108
|
+
return {
|
|
109
|
+
top: padding.top + safeArea.top,
|
|
110
|
+
right: padding.right + safeArea.right,
|
|
111
|
+
bottom: padding.bottom + safeArea.bottom,
|
|
112
|
+
left: padding.left + safeArea.left,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
|
|
90
116
|
const defaultLayout = {
|
|
91
117
|
flexGrow: 0,
|
|
92
118
|
flexShrink: 0,
|
|
@@ -124,6 +150,7 @@ export class UICanvas extends _UICanvas {
|
|
|
124
150
|
padding: ensurePadding(config?.padding ?? 0),
|
|
125
151
|
size: config.size !== undefined ? resolveSizeLike(config.size) : { width: 0, height: 0 },
|
|
126
152
|
useAppSize: config.useAppSize === true,
|
|
153
|
+
useSafeArea: config.useSafeArea ?? true,
|
|
127
154
|
autoLayoutChildren: config.autoLayoutChildren ?? true,
|
|
128
155
|
};
|
|
129
156
|
|
|
@@ -414,13 +441,22 @@ export class UICanvas extends _UICanvas {
|
|
|
414
441
|
set padding(value: Partial<Padding> | PointLike) {
|
|
415
442
|
this.config.padding = ensurePadding(value);
|
|
416
443
|
// Update position to account for padding
|
|
444
|
+
this._applyPadding();
|
|
445
|
+
this._updateLayout();
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/** Writes the effective padding (configured padding + safe-area insets) into the layout. */
|
|
449
|
+
private _applyPadding() {
|
|
450
|
+
const padding = computeEffectivePadding(
|
|
451
|
+
this.config.padding,
|
|
452
|
+
this.config.useSafeArea ? this.app.safeArea : zeroPadding,
|
|
453
|
+
);
|
|
417
454
|
this.layout = {
|
|
418
|
-
paddingLeft:
|
|
419
|
-
paddingTop:
|
|
420
|
-
paddingRight:
|
|
421
|
-
paddingBottom:
|
|
455
|
+
paddingLeft: padding.left,
|
|
456
|
+
paddingTop: padding.top,
|
|
457
|
+
paddingRight: padding.right,
|
|
458
|
+
paddingBottom: padding.bottom,
|
|
422
459
|
};
|
|
423
|
-
this._updateLayout();
|
|
424
460
|
}
|
|
425
461
|
|
|
426
462
|
private static isFlexContainer(child: PIXIContainer): boolean {
|
|
@@ -494,6 +530,8 @@ export class UICanvas extends _UICanvas {
|
|
|
494
530
|
}
|
|
495
531
|
|
|
496
532
|
public resize() {
|
|
533
|
+
// Re-derived rather than cached: the safe area changes with the viewport.
|
|
534
|
+
this._applyPadding();
|
|
497
535
|
if (this.config.useAppSize) {
|
|
498
536
|
this.size = { width: this.app.size.width, height: this.app.size.height };
|
|
499
537
|
this.position.set(-this.app.size.width * 0.5, -this.app.size.height * 0.5);
|
package/src/utils/types.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AssetInitOptions, AssetsManifest, AssetsPreferences, Texture, UnresolvedAsset } from 'pixi.js';
|
|
2
2
|
import { Point } from 'pixi.js';
|
|
3
3
|
import type { Scene } from '../display/Scene';
|
|
4
|
-
import { SceneAssets } from '../display';
|
|
4
|
+
import type { SceneAssets } from '../display/Scene';
|
|
5
5
|
import type { FilterBitmapFontNames, FilterCleanAssetNames, FilterSpineAssetNames } from './typefilters';
|
|
6
6
|
|
|
7
7
|
/**
|
package/src/version.ts
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"CaptionsPlugin-DKhbmJvH.js","names":[],"sources":["../src/plugins/captions/CaptionsRenderer.ts","../src/plugins/captions/CaptionsPlugin.ts"],"sourcesContent":["import { gsap } from 'gsap';\nimport { BitmapText, Container as PIXIContainer, Sprite, Texture } from 'pixi.js';\nimport { Container } from '../../display';\nimport { type Size } from '../../utils';\nimport { CaptionsPlugin } from './CaptionsPlugin';\n\nexport type CaptionLine = {\n id: string;\n start: number;\n end: number;\n content: string;\n speaker?: string;\n};\n\nexport interface ICaptionRenderer extends PIXIContainer {\n visible: boolean;\n\n start(): void;\n\n stop(): void;\n\n lineBegin(line: CaptionLine): void;\n\n lineEnd(line: CaptionLine): void;\n\n resize(size?: Size): void;\n\n updateSettings(): void;\n\n destroy(): void;\n}\n\nexport class CaptionsRenderer extends Container implements ICaptionRenderer {\n private readonly _bg: Sprite;\n private readonly _text: BitmapText;\n private readonly _size: Size = { width: 0, height: 0 };\n private readonly fontSize = 48;\n\n constructor(private plugin: CaptionsPlugin) {\n super({ autoResize: false });\n\n this._bg = this.add.sprite({ asset: Texture.WHITE, anchor: [0.5, 0] });\n this._bg.tint = this.plugin.options.backgroundColor;\n this._bg.alpha = 0;\n\n this._text = this.add.bitmapText({\n font: plugin.options.fontName,\n resolution: 2,\n roundPixels: true,\n style: {\n fill: this.plugin.options.textColor,\n fontSize: this.fontSize * this.getSizeMultiplier(),\n fontWeight: 'bold',\n align: 'center',\n wordWrapWidth: this.plugin.maxWidth,\n wordWrap: true,\n lineHeight: this.fontSize * this.getSizeMultiplier() * 2 + 20,\n },\n });\n this._text.style.wordWrap = true;\n this._text.alpha = 0;\n this._text.visible = false;\n this._text.anchor.x = 0.5;\n this._text.anchor.y = 0;\n this.addChild(this._text);\n\n if (this.plugin.debug) {\n let isTapping = false;\n this._bg.eventMode = 'static';\n this._bg.on('pointertap', () => {\n if (isTapping) {\n return;\n }\n isTapping = true;\n setTimeout(() => {\n isTapping = false;\n void this.app.voiceover.stopVO();\n }, 100);\n });\n }\n }\n\n public start(): void {\n this._text.visible = true;\n this._bg.visible = true;\n\n gsap.killTweensOf([this._bg, this._text]);\n this.animate({ alpha: this.plugin.options.backgroundAlpha, visible: true, duration: 0.2 }, this._bg);\n this.animate({ alpha: 1, visible: true, duration: 0.2 }, this._text);\n }\n\n public stop(): void {\n gsap.killTweensOf([this._bg, this._text]);\n this.animate({ alpha: 0, visible: false, duration: 0.2 }, [this._bg, this._text]);\n }\n\n public lineBegin(line: CaptionLine): void {\n let text = line.content\n .replace(/<[^>]*>/gi, '') // remove html tags\n .replace(/\\[.*]\\W*/gi, ''); // remove brackets\n if (line.start === 0) {\n text = text.charAt(0).toUpperCase() + text.slice(1);\n }\n this._text.text = text;\n this.resize();\n }\n\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n public lineEnd(_line: CaptionLine): void {}\n\n public resize(): void {\n const size = this.app.size;\n const opts = this.plugin.options;\n\n this._size.width = size.width;\n this._size.height = size.height;\n\n this._text.style.fontSize = this.fontSize * this.getSizeMultiplier();\n this._text.style.wordWrapWidth = opts.maxWidth * 2;\n this._text.scale.set(0.5);\n this._text.position.set(this._size.width * 0.5, 0);\n\n this._bg.position.set(this._size.width * 0.5, 0);\n\n let h = this._text.height;\n if (opts.padding.top) {\n h += opts.padding.top;\n }\n if (opts.padding.bottom) {\n h += opts.padding.bottom;\n }\n this._bg.height = h;\n\n this._text.y += opts.padding.top;\n\n if (opts.floating) {\n let w = this._text.width;\n\n if (opts.padding.left) {\n w += opts.padding.left;\n }\n if (opts.padding.right) {\n w += opts.padding.right;\n }\n\n this._bg.x += opts.padding.right * 0.5 - opts.padding.left * 0.5;\n this._bg.width = w;\n } else {\n this._bg.width = size.width;\n }\n\n if (opts.position === 'top') {\n this.position.set(-size.width * 0.5, Math.round(-size.height * 0.5));\n if (opts.floating) {\n this.position.y += Math.round(opts.distance - this._bg.y);\n }\n } else {\n this.position.set(-size.width * 0.5, Math.round(size.height * 0.5 - this._bg.height));\n if (opts.floating) {\n this.position.y -= Math.round(opts.distance + this._bg.y);\n }\n }\n }\n\n updateSettings() {\n if (this.plugin.options.enabled) {\n this._text.style.fill = this.plugin.options.textColor;\n this._bg.tint = this.plugin.options.backgroundColor;\n if (this._bg.visible) {\n this._bg.alpha = this.plugin.options.backgroundAlpha;\n this.resize();\n }\n } else {\n this.stop();\n this._bg.visible = false;\n this._text.visible = false;\n this._bg.alpha = this._text.alpha = 0;\n }\n }\n\n private getSizeMultiplier(): number {\n return this.plugin.options.fontSizeMultiplier;\n }\n}\n","import { Assets, Container, Ticker } from 'pixi.js';\nimport type { ImportListItem, ImportListItemModule, Padding, PointLike } from '../../utils';\nimport { ensurePadding, getDynamicModuleFromImportListItem, isDev, Logger } from '../../utils';\nimport type { IAudioInstance } from '../audio';\nimport type { IPlugin } from '../Plugin';\nimport { Plugin } from '../Plugin';\nimport type { ICaptionRenderer } from './CaptionsRenderer';\nimport { CaptionsRenderer } from './CaptionsRenderer';\n\nconst isDebug = isDev;\n\n/**\n * Type definition for i18n dictionary.\n */\ntype CaptionsDict = Record<string, any>;\n\n/**\n * Type definition for i18n import list item.\n */\ntype CaptionsImportListItem<T> = {\n id: string;\n namedExport?: string;\n options?: any;\n module?: ImportListItemModule<T>;\n} & {\n json?: string;\n};\n\ntype CaptionRendererConstructor = new (owner: CaptionsPlugin) => ICaptionRenderer;\n\n/**\n * Type definition for i18n options.\n */\nexport type CaptionsOptions = {\n renderer: CaptionRendererConstructor;\n defaultLocale: string;\n files: CaptionsImportListItem<CaptionsDict>[];\n fontFile: string;\n fontName: string;\n fontSizeMultiplier: number;\n maxWidth: number;\n textColor: number;\n backgroundColor: number;\n backgroundAlpha: number;\n enabled: boolean;\n floating: boolean;\n distance: number;\n padding: Padding;\n position: 'top' | 'bottom';\n};\n\n/**\n * Default options for i18n module.\n */\nconst defaultOptions: Partial<CaptionsOptions> = {\n renderer: CaptionsRenderer,\n defaultLocale: 'en',\n fontFile: './caper/font/Sans.fnt',\n fontName: 'Sans',\n fontSizeMultiplier: 1,\n maxWidth: 0.8,\n textColor: 0xffffff,\n backgroundColor: 0x0,\n backgroundAlpha: 0.4,\n enabled: true,\n floating: false,\n distance: 0,\n padding: { top: 20, left: 0, bottom: 20, right: 0 },\n position: 'top',\n};\n\nfunction resolveMaxWidth(value: number, appWidth: number) {\n if (value <= 0) {\n return 0;\n }\n if (value > 0 && value <= 1) {\n return appWidth * value;\n }\n return value;\n}\n\n/**\n * Interface for i18n module.\n */\nexport interface ICaptionsPlugin extends IPlugin {\n readonly locale: string;\n view: Container;\n enabled: boolean;\n floating: boolean;\n distance: number;\n padding: Padding;\n position: 'top' | 'bottom';\n backgroundColor: number;\n textColor: number;\n backgroundAlpha: number;\n fontSizeMultiplier: number;\n maxWidth: number;\n options: Partial<CaptionsOptions>;\n\n setLocale(localeId: string): Promise<string>;\n\n loadLocale(localeId: string): Promise<void>;\n\n render(): void;\n}\n\n/**\n * i18n module class.\n */\nexport class CaptionsPlugin extends Plugin<CaptionsOptions> implements ICaptionsPlugin {\n public readonly id = 'captions';\n public view: Container = new Container();\n public renderer: ICaptionRenderer;\n private _dicts: Record<string, CaptionsDict> = {};\n private _locales: string[];\n private _activeCaptionLine = -1;\n private _activeCaptionTime = 0;\n\n private _locale: string;\n\n /**\n * Getter for locale.\n */\n get locale(): string {\n return this._locale;\n }\n\n private _originalOptions: CaptionsOptions;\n\n get options(): CaptionsOptions {\n return this._options;\n }\n\n set options(value: Partial<CaptionsOptions>) {\n this._options = { ...this._options, ...value };\n\n if (value.padding) {\n this._options.padding = ensurePadding(value.padding);\n }\n }\n\n private _activeCaptionId?: string;\n\n get activeCaptionId() {\n return this._activeCaptionId;\n }\n\n private _paused: boolean = false;\n\n get paused(): boolean {\n return this._paused;\n }\n\n set paused(value: boolean) {\n this._paused = value;\n }\n\n get floating(): boolean {\n return this.options.floating;\n }\n\n set floating(value: boolean) {\n this.options.floating = value;\n this.updateRenderer();\n }\n\n public get enabled(): boolean {\n return this.options.enabled;\n }\n\n set enabled(value: boolean) {\n this.options.enabled = value;\n if (this.enabled) {\n this.view.visible = true;\n } else {\n this.view.visible = false;\n }\n }\n\n public get position(): 'top' | 'bottom' {\n return this.options.position;\n }\n\n public set position(value: 'top' | 'bottom') {\n this.options.position = value;\n this.renderer.updateSettings();\n }\n\n set backgroundColor(value: number) {\n this.options.backgroundColor = value;\n this.updateRenderer();\n }\n\n set textColor(value: number) {\n this.options.textColor = value;\n this.updateRenderer();\n }\n\n set backgroundAlpha(value: number) {\n this.options.backgroundAlpha = value;\n this.updateRenderer();\n }\n\n get padding(): Padding {\n return this.options.padding;\n }\n\n set padding(value: Partial<Padding> | PointLike) {\n this.options.padding = ensurePadding(value);\n this.updateRenderer();\n }\n\n get distance(): number {\n return this.options.distance;\n }\n\n set distance(value: number) {\n this.options.distance = value;\n this.updateRenderer();\n }\n\n public get fontSizeMultiplier(): number {\n return this.options.fontSizeMultiplier;\n }\n\n public set fontSizeMultiplier(value: number) {\n this.options.fontSizeMultiplier = value;\n this.renderer.resize();\n }\n\n public get maxWidth(): number {\n return this.options.maxWidth;\n }\n\n public set maxWidth(value: number) {\n this.options.maxWidth = resolveMaxWidth(value, this.app.size.width);\n this.renderer?.resize();\n }\n\n get list(): CaptionsDict {\n return this._dicts[this._locale];\n }\n\n get debug(): boolean {\n return isDebug;\n }\n\n /**\n * Initializes the i18n module.\n * sets the default locale and loads the locale files.\n * @param _app\n * @param options The i18n options.\n * @returns Promise<void>\n */\n public async initialize(options: Partial<CaptionsOptions>): Promise<void> {\n this._options = {\n ...defaultOptions,\n ...options,\n } as CaptionsOptions;\n\n this._originalOptions = { ...this._options };\n\n if (options?.padding) {\n this._options.padding = ensurePadding(options.padding);\n }\n\n this._locale = this.app.i18n.locale;\n this._locales = this.app.i18n.locales;\n\n if (this._options.fontFile) {\n await Assets.load(this._options.fontFile);\n }\n\n if (this._options?.files?.length > 0) {\n const files = this._options.files.filter((file) => this._locales.includes(file.id));\n for (const file of files) {\n await this.loadLocale(file.id);\n }\n }\n\n this.addSignalConnection(this.app.onResize.connect(this.handleResize, 'highest'));\n }\n\n private handleResize() {\n this.maxWidth = this._originalOptions.maxWidth;\n }\n\n public postInitialize(): void {\n this.app.i18n.onLocaleChanged.connect(this._handleLocaleChange);\n this.app.voiceover.onVoiceOverStart.connect(this._handleVoiceOverStart);\n this.app.voiceover.onVoiceOverPaused.connect(this._handleVoiceOverPaused);\n this.app.voiceover.onVoiceOverResumed.connect(this._handleVoiceOverResumed);\n this.app.voiceover.onVoiceOverComplete.connect(this._handleVoiceoverComplete);\n this.app.voiceover.onVoiceOverStopped.connect(this._handleVoiceoverStopped);\n\n this.app.stage.addChild(this.view);\n this._options.maxWidth = resolveMaxWidth(this._originalOptions.maxWidth, this.app.size.width);\n const RendererClass: CaptionRendererConstructor = this.options.renderer;\n this.renderer = this.view.addChild(new RendererClass(this)) as CaptionsRenderer;\n\n this.app.ticker.add(this.update);\n this.app.scenes.onSceneChangeStart.connect(this.stopAllCaptions);\n }\n\n /**\n * Sets the locale.\n * If the locale is not loaded, it will load it first.\n * @param localeId The locale id to set.\n * @returns Promise<string>\n */\n async setLocale(localeId: string) {\n if (this._locale === localeId) {\n return this._locale;\n }\n if (this._activeCaptionId) {\n this.stopCaption(this._activeCaptionId);\n }\n this._activeCaptionId = undefined;\n await this._loadAndSetLocale(localeId);\n this._locale = localeId;\n return this._locale;\n }\n\n /**\n * Loads a locale.\n * @param localeId The locale id to load.\n * @returns Promise<void>\n */\n async loadLocale(localeId: string) {\n if (this._locale === 'localeId') {\n return;\n }\n const file = this._options.files.find((file) => localeId === file.id);\n if (!file) {\n Logger.error(`i18n:: Could not find locale file for ${localeId}`);\n return;\n }\n this._dicts[localeId] = file.json\n ? await Assets.load(file.json)\n : await getDynamicModuleFromImportListItem(file as ImportListItem<CaptionsDict>);\n }\n\n render() {\n this.renderer.resize();\n }\n\n update(ticker: Ticker): void {\n if (this._paused) {\n return;\n }\n const dt: number = ticker.deltaMS / 1000;\n const id = this._activeCaptionId;\n if (id) {\n this._activeCaptionTime += dt;\n const ms = this._activeCaptionTime * 1000;\n const cc = this.list[id];\n const n = cc.length;\n for (let i = 0; i < n; i++) {\n if (ms >= cc[i].start && ms < cc[i].end) {\n if (this._activeCaptionLine !== i) {\n this.playLine(id, i);\n }\n break;\n }\n }\n if (ms >= cc[n - 1].end) {\n this.playLine(id, -1);\n }\n }\n }\n\n public playCaption(id: string): void {\n // remove extension from id\n id = this._getId(id);\n this.playLine(id, 0);\n }\n\n public stopCaption(id: string): void {\n // remove extension from id\n id = this._getId(id);\n if (this._activeCaptionId === id) {\n this.playLine(id, -1);\n }\n }\n\n public stopAllCaptions(): void {\n this.renderer?.stop();\n this._activeCaptionId = undefined;\n this._activeCaptionLine = -1;\n }\n\n private _getId(id: string) {\n return id.replace(/\\.[^/.]+$/, '');\n }\n\n private updateRenderer() {\n if (!this.enabled) {\n return;\n }\n this.renderer.updateSettings();\n }\n\n private _handleLocaleChange() {\n void this.setLocale(this.app.i18n.locale);\n }\n\n private _handleVoiceOverStart(vo: IAudioInstance) {\n this.playCaption(vo.id);\n }\n\n private _handleVoiceOverPaused() {\n this.paused = true;\n }\n\n private _handleVoiceOverResumed() {\n this.paused = false;\n }\n\n private _handleVoiceoverComplete(vo: IAudioInstance) {\n const id = this._getId(vo?.id || '');\n if (this._activeCaptionId === id) {\n this.stopCaption(id);\n }\n }\n\n private _handleVoiceoverStopped() {\n this.stopAllCaptions();\n }\n\n /**\n * Loads and sets a locale.\n * If the locale is not loaded, it will load it first.\n * @param localeId The locale id to load and set.\n */\n private async _loadAndSetLocale(localeId: string) {\n if (!this._dicts[localeId]) {\n await this.loadLocale(localeId);\n }\n }\n\n private playLine(id: string, index: number) {\n const cc = this.list[id];\n const line = cc ? cc[index] : undefined;\n if (line) {\n if (this._activeCaptionId !== id) {\n this._activeCaptionId = id;\n this._activeCaptionTime = 0;\n this._activeCaptionLine = -1;\n this.renderer?.start();\n }\n if (this._activeCaptionLine !== index) {\n this._activeCaptionLine = index;\n this.renderer?.lineBegin({\n id: this._activeCaptionId,\n ...cc[index],\n });\n }\n } else if (this._activeCaptionId === id && cc) {\n this.renderer?.lineEnd({\n id: this._activeCaptionId,\n ...cc[this._activeCaptionLine],\n });\n this.renderer?.stop();\n this._activeCaptionId = undefined;\n this._activeCaptionLine = -1;\n }\n }\n}\n"],"mappings":";;;;AAgCA,IAAa,IAAb,cAAsC,EAAsC;CAM1E,YAAY,GAAgC;AA4B1C,MA3BA,MAAM,EAAE,YAAY,IAAO,CAAC,EADV,KAAA,SAAA,gBAHW;GAAE,OAAO;GAAG,QAAQ;GAAG,kBAC1B,IAK1B,KAAK,MAAM,KAAK,IAAI,OAAO;GAAE,OAAO,EAAQ;GAAO,QAAQ,CAAC,IAAK,EAAE;GAAE,CAAC,EACtE,KAAK,IAAI,OAAO,KAAK,OAAO,QAAQ,iBACpC,KAAK,IAAI,QAAQ,GAEjB,KAAK,QAAQ,KAAK,IAAI,WAAW;GAC/B,MAAM,EAAO,QAAQ;GACrB,YAAY;GACZ,aAAa;GACb,OAAO;IACL,MAAM,KAAK,OAAO,QAAQ;IAC1B,UAAU,KAAK,WAAW,KAAK,mBAAmB;IAClD,YAAY;IACZ,OAAO;IACP,eAAe,KAAK,OAAO;IAC3B,UAAU;IACV,YAAY,KAAK,WAAW,KAAK,mBAAmB,GAAG,IAAI;IAC5D;GACF,CAAC,EACF,KAAK,MAAM,MAAM,WAAW,IAC5B,KAAK,MAAM,QAAQ,GACnB,KAAK,MAAM,UAAU,IACrB,KAAK,MAAM,OAAO,IAAI,IACtB,KAAK,MAAM,OAAO,IAAI,GACtB,KAAK,SAAS,KAAK,MAAM,EAErB,KAAK,OAAO,OAAO;GACrB,IAAI,IAAY;AAEhB,GADA,KAAK,IAAI,YAAY,UACrB,KAAK,IAAI,GAAG,oBAAoB;AAC1B,UAGJ,IAAY,IACZ,iBAAiB;AAEV,KADL,IAAY,IACP,KAAK,IAAI,UAAU,QAAQ;OAC/B,IAAI;KACP;;;CAIN,QAAqB;AAMnB,EALA,KAAK,MAAM,UAAU,IACrB,KAAK,IAAI,UAAU,IAEnB,EAAK,aAAa,CAAC,KAAK,KAAK,KAAK,MAAM,CAAC,EACzC,KAAK,QAAQ;GAAE,OAAO,KAAK,OAAO,QAAQ;GAAiB,SAAS;GAAM,UAAU;GAAK,EAAE,KAAK,IAAI,EACpG,KAAK,QAAQ;GAAE,OAAO;GAAG,SAAS;GAAM,UAAU;GAAK,EAAE,KAAK,MAAM;;CAGtE,OAAoB;AAElB,EADA,EAAK,aAAa,CAAC,KAAK,KAAK,KAAK,MAAM,CAAC,EACzC,KAAK,QAAQ;GAAE,OAAO;GAAG,SAAS;GAAO,UAAU;GAAK,EAAE,CAAC,KAAK,KAAK,KAAK,MAAM,CAAC;;CAGnF,UAAiB,GAAyB;EACxC,IAAI,IAAO,EAAK,QACb,QAAQ,aAAa,GAAG,CACxB,QAAQ,cAAc,GAAG;AAK5B,EAJI,EAAK,UAAU,MACjB,IAAO,EAAK,OAAO,EAAE,CAAC,aAAa,GAAG,EAAK,MAAM,EAAE,GAErD,KAAK,MAAM,OAAO,GAClB,KAAK,QAAQ;;CAIf,QAAe,GAA0B;CAEzC,SAAsB;EACpB,IAAM,IAAO,KAAK,IAAI,MAChB,IAAO,KAAK,OAAO;AAUzB,EARA,KAAK,MAAM,QAAQ,EAAK,OACxB,KAAK,MAAM,SAAS,EAAK,QAEzB,KAAK,MAAM,MAAM,WAAW,KAAK,WAAW,KAAK,mBAAmB,EACpE,KAAK,MAAM,MAAM,gBAAgB,EAAK,WAAW,GACjD,KAAK,MAAM,MAAM,IAAI,GAAI,EACzB,KAAK,MAAM,SAAS,IAAI,KAAK,MAAM,QAAQ,IAAK,EAAE,EAElD,KAAK,IAAI,SAAS,IAAI,KAAK,MAAM,QAAQ,IAAK,EAAE;EAEhD,IAAI,IAAI,KAAK,MAAM;AAWnB,MAVI,EAAK,QAAQ,QACf,KAAK,EAAK,QAAQ,MAEhB,EAAK,QAAQ,WACf,KAAK,EAAK,QAAQ,SAEpB,KAAK,IAAI,SAAS,GAElB,KAAK,MAAM,KAAK,EAAK,QAAQ,KAEzB,EAAK,UAAU;GACjB,IAAI,IAAI,KAAK,MAAM;AAUnB,GARI,EAAK,QAAQ,SACf,KAAK,EAAK,QAAQ,OAEhB,EAAK,QAAQ,UACf,KAAK,EAAK,QAAQ,QAGpB,KAAK,IAAI,KAAK,EAAK,QAAQ,QAAQ,KAAM,EAAK,QAAQ,OAAO,IAC7D,KAAK,IAAI,QAAQ;QAEjB,MAAK,IAAI,QAAQ,EAAK;AAGxB,EAAI,EAAK,aAAa,SACpB,KAAK,SAAS,IAAI,CAAC,EAAK,QAAQ,IAAK,KAAK,MAAM,CAAC,EAAK,SAAS,GAAI,CAAC,EAChE,EAAK,aACP,KAAK,SAAS,KAAK,KAAK,MAAM,EAAK,WAAW,KAAK,IAAI,EAAE,MAG3D,KAAK,SAAS,IAAI,CAAC,EAAK,QAAQ,IAAK,KAAK,MAAM,EAAK,SAAS,KAAM,KAAK,IAAI,OAAO,CAAC,EACjF,EAAK,aACP,KAAK,SAAS,KAAK,KAAK,MAAM,EAAK,WAAW,KAAK,IAAI,EAAE;;CAK/D,iBAAiB;AACf,EAAI,KAAK,OAAO,QAAQ,WACtB,KAAK,MAAM,MAAM,OAAO,KAAK,OAAO,QAAQ,WAC5C,KAAK,IAAI,OAAO,KAAK,OAAO,QAAQ,iBAChC,KAAK,IAAI,YACX,KAAK,IAAI,QAAQ,KAAK,OAAO,QAAQ,iBACrC,KAAK,QAAQ,MAGf,KAAK,MAAM,EACX,KAAK,IAAI,UAAU,IACnB,KAAK,MAAM,UAAU,IACrB,KAAK,IAAI,QAAQ,KAAK,MAAM,QAAQ;;CAIxC,oBAAoC;AAClC,SAAO,KAAK,OAAO,QAAQ;;GC5KzB,IAAU,GA6CV,IAA2C;CAC/C,UAAU;CACV,eAAe;CACf,UAAU;CACV,UAAU;CACV,oBAAoB;CACpB,UAAU;CACV,WAAW;CACX,iBAAiB;CACjB,iBAAiB;CACjB,SAAS;CACT,UAAU;CACV,UAAU;CACV,SAAS;EAAE,KAAK;EAAI,MAAM;EAAG,QAAQ;EAAI,OAAO;EAAG;CACnD,UAAU;CACX;AAED,SAAS,EAAgB,GAAe,GAAkB;AAOxD,QANI,KAAS,IACJ,IAEL,IAAQ,KAAK,KAAS,IACjB,IAAW,IAEb;;AA+BT,IAAa,IAAb,cAAoC,EAAmD;;yBAChE,wBACI,IAAI,GAAW,gBAEO,EAAE,4BAEpB,8BACA,kBA+BF;;CAxB3B,IAAI,SAAiB;AACnB,SAAO,KAAK;;CAKd,IAAI,UAA2B;AAC7B,SAAO,KAAK;;CAGd,IAAI,QAAQ,GAAiC;AAG3C,EAFA,KAAK,WAAW;GAAE,GAAG,KAAK;GAAU,GAAG;GAAO,EAE1C,EAAM,YACR,KAAK,SAAS,UAAU,EAAc,EAAM,QAAQ;;CAMxD,IAAI,kBAAkB;AACpB,SAAO,KAAK;;CAKd,IAAI,SAAkB;AACpB,SAAO,KAAK;;CAGd,IAAI,OAAO,GAAgB;AACzB,OAAK,UAAU;;CAGjB,IAAI,WAAoB;AACtB,SAAO,KAAK,QAAQ;;CAGtB,IAAI,SAAS,GAAgB;AAE3B,EADA,KAAK,QAAQ,WAAW,GACxB,KAAK,gBAAgB;;CAGvB,IAAW,UAAmB;AAC5B,SAAO,KAAK,QAAQ;;CAGtB,IAAI,QAAQ,GAAgB;AAE1B,EADA,KAAK,QAAQ,UAAU,GACnB,KAAK,UACP,KAAK,KAAK,UAAU,KAEpB,KAAK,KAAK,UAAU;;CAIxB,IAAW,WAA6B;AACtC,SAAO,KAAK,QAAQ;;CAGtB,IAAW,SAAS,GAAyB;AAE3C,EADA,KAAK,QAAQ,WAAW,GACxB,KAAK,SAAS,gBAAgB;;CAGhC,IAAI,gBAAgB,GAAe;AAEjC,EADA,KAAK,QAAQ,kBAAkB,GAC/B,KAAK,gBAAgB;;CAGvB,IAAI,UAAU,GAAe;AAE3B,EADA,KAAK,QAAQ,YAAY,GACzB,KAAK,gBAAgB;;CAGvB,IAAI,gBAAgB,GAAe;AAEjC,EADA,KAAK,QAAQ,kBAAkB,GAC/B,KAAK,gBAAgB;;CAGvB,IAAI,UAAmB;AACrB,SAAO,KAAK,QAAQ;;CAGtB,IAAI,QAAQ,GAAqC;AAE/C,EADA,KAAK,QAAQ,UAAU,EAAc,EAAM,EAC3C,KAAK,gBAAgB;;CAGvB,IAAI,WAAmB;AACrB,SAAO,KAAK,QAAQ;;CAGtB,IAAI,SAAS,GAAe;AAE1B,EADA,KAAK,QAAQ,WAAW,GACxB,KAAK,gBAAgB;;CAGvB,IAAW,qBAA6B;AACtC,SAAO,KAAK,QAAQ;;CAGtB,IAAW,mBAAmB,GAAe;AAE3C,EADA,KAAK,QAAQ,qBAAqB,GAClC,KAAK,SAAS,QAAQ;;CAGxB,IAAW,WAAmB;AAC5B,SAAO,KAAK,QAAQ;;CAGtB,IAAW,SAAS,GAAe;AAEjC,EADA,KAAK,QAAQ,WAAW,EAAgB,GAAO,KAAK,IAAI,KAAK,MAAM,EACnE,KAAK,UAAU,QAAQ;;CAGzB,IAAI,OAAqB;AACvB,SAAO,KAAK,OAAO,KAAK;;CAG1B,IAAI,QAAiB;AACnB,SAAO;;CAUT,MAAa,WAAW,GAAkD;AAmBxE,MAlBA,KAAK,WAAW;GACd,GAAG;GACH,GAAG;GACJ,EAED,KAAK,mBAAmB,EAAE,GAAG,KAAK,UAAU,EAExC,GAAS,YACX,KAAK,SAAS,UAAU,EAAc,EAAQ,QAAQ,GAGxD,KAAK,UAAU,KAAK,IAAI,KAAK,QAC7B,KAAK,WAAW,KAAK,IAAI,KAAK,SAE1B,KAAK,SAAS,YAChB,MAAM,EAAO,KAAK,KAAK,SAAS,SAAS,EAGvC,KAAK,UAAU,OAAO,SAAS,GAAG;GACpC,IAAM,IAAQ,KAAK,SAAS,MAAM,QAAQ,MAAS,KAAK,SAAS,SAAS,EAAK,GAAG,CAAC;AACnF,QAAK,IAAM,KAAQ,EACjB,OAAM,KAAK,WAAW,EAAK,GAAG;;AAIlC,OAAK,oBAAoB,KAAK,IAAI,SAAS,QAAQ,KAAK,cAAc,UAAU,CAAC;;CAGnF,eAAuB;AACrB,OAAK,WAAW,KAAK,iBAAiB;;CAGxC,iBAA8B;AAS5B,EARA,KAAK,IAAI,KAAK,gBAAgB,QAAQ,KAAK,oBAAoB,EAC/D,KAAK,IAAI,UAAU,iBAAiB,QAAQ,KAAK,sBAAsB,EACvE,KAAK,IAAI,UAAU,kBAAkB,QAAQ,KAAK,uBAAuB,EACzE,KAAK,IAAI,UAAU,mBAAmB,QAAQ,KAAK,wBAAwB,EAC3E,KAAK,IAAI,UAAU,oBAAoB,QAAQ,KAAK,yBAAyB,EAC7E,KAAK,IAAI,UAAU,mBAAmB,QAAQ,KAAK,wBAAwB,EAE3E,KAAK,IAAI,MAAM,SAAS,KAAK,KAAK,EAClC,KAAK,SAAS,WAAW,EAAgB,KAAK,iBAAiB,UAAU,KAAK,IAAI,KAAK,MAAM;EAC7F,IAAM,IAA4C,KAAK,QAAQ;AAI/D,EAHA,KAAK,WAAW,KAAK,KAAK,SAAS,IAAI,EAAc,KAAK,CAAC,EAE3D,KAAK,IAAI,OAAO,IAAI,KAAK,OAAO,EAChC,KAAK,IAAI,OAAO,mBAAmB,QAAQ,KAAK,gBAAgB;;CASlE,MAAM,UAAU,GAAkB;AAUhC,SATI,KAAK,YAAY,IACZ,KAAK,WAEV,KAAK,oBACP,KAAK,YAAY,KAAK,iBAAiB,EAEzC,KAAK,mBAAmB,KAAA,GACxB,MAAM,KAAK,kBAAkB,EAAS,EACtC,KAAK,UAAU,GACR,KAAK;;CAQd,MAAM,WAAW,GAAkB;AACjC,MAAI,KAAK,YAAY,WACnB;EAEF,IAAM,IAAO,KAAK,SAAS,MAAM,MAAM,MAAS,MAAa,EAAK,GAAG;AACrE,MAAI,CAAC,GAAM;AACT,KAAO,MAAM,yCAAyC,IAAW;AACjE;;AAEF,OAAK,OAAO,KAAY,EAAK,OACzB,MAAM,EAAO,KAAK,EAAK,KAAK,GAC5B,MAAM,EAAmC,EAAqC;;CAGpF,SAAS;AACP,OAAK,SAAS,QAAQ;;CAGxB,OAAO,GAAsB;AAC3B,MAAI,KAAK,QACP;EAEF,IAAM,IAAa,EAAO,UAAU,KAC9B,IAAK,KAAK;AAChB,MAAI,GAAI;AACN,QAAK,sBAAsB;GAC3B,IAAM,IAAK,KAAK,qBAAqB,KAC/B,IAAK,KAAK,KAAK,IACf,IAAI,EAAG;AACb,QAAK,IAAI,IAAI,GAAG,IAAI,GAAG,IACrB,KAAI,KAAM,EAAG,GAAG,SAAS,IAAK,EAAG,GAAG,KAAK;AACvC,IAAI,KAAK,uBAAuB,KAC9B,KAAK,SAAS,GAAI,EAAE;AAEtB;;AAGJ,GAAI,KAAM,EAAG,IAAI,GAAG,OAClB,KAAK,SAAS,GAAI,GAAG;;;CAK3B,YAAmB,GAAkB;AAGnC,EADA,IAAK,KAAK,OAAO,EAAG,EACpB,KAAK,SAAS,GAAI,EAAE;;CAGtB,YAAmB,GAAkB;AAGnC,EADA,IAAK,KAAK,OAAO,EAAG,EAChB,KAAK,qBAAqB,KAC5B,KAAK,SAAS,GAAI,GAAG;;CAIzB,kBAA+B;AAG7B,EAFA,KAAK,UAAU,MAAM,EACrB,KAAK,mBAAmB,KAAA,GACxB,KAAK,qBAAqB;;CAG5B,OAAe,GAAY;AACzB,SAAO,EAAG,QAAQ,aAAa,GAAG;;CAGpC,iBAAyB;AAClB,OAAK,WAGV,KAAK,SAAS,gBAAgB;;CAGhC,sBAA8B;AACvB,OAAK,UAAU,KAAK,IAAI,KAAK,OAAO;;CAG3C,sBAA8B,GAAoB;AAChD,OAAK,YAAY,EAAG,GAAG;;CAGzB,yBAAiC;AAC/B,OAAK,SAAS;;CAGhB,0BAAkC;AAChC,OAAK,SAAS;;CAGhB,yBAAiC,GAAoB;EACnD,IAAM,IAAK,KAAK,OAAO,GAAI,MAAM,GAAG;AACpC,EAAI,KAAK,qBAAqB,KAC5B,KAAK,YAAY,EAAG;;CAIxB,0BAAkC;AAChC,OAAK,iBAAiB;;CAQxB,MAAc,kBAAkB,GAAkB;AAChD,EAAK,KAAK,OAAO,MACf,MAAM,KAAK,WAAW,EAAS;;CAInC,SAAiB,GAAY,GAAe;EAC1C,IAAM,IAAK,KAAK,KAAK;AAErB,EADa,KAAK,EAAG,MAEf,KAAK,qBAAqB,MAC5B,KAAK,mBAAmB,GACxB,KAAK,qBAAqB,GAC1B,KAAK,qBAAqB,IAC1B,KAAK,UAAU,OAAO,GAEpB,KAAK,uBAAuB,MAC9B,KAAK,qBAAqB,GAC1B,KAAK,UAAU,UAAU;GACvB,IAAI,KAAK;GACT,GAAG,EAAG;GACP,CAAC,KAEK,KAAK,qBAAqB,KAAM,MACzC,KAAK,UAAU,QAAQ;GACrB,IAAI,KAAK;GACT,GAAG,EAAG,KAAK;GACZ,CAAC,EACF,KAAK,UAAU,MAAM,EACrB,KAAK,mBAAmB,KAAA,GACxB,KAAK,qBAAqB"}
|