pixifact 0.4.0 → 0.5.0
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/compiler/builtinScenes.d.ts +0 -5
- package/dist/compiler/builtinScenes.js +0 -26
- package/dist/compiler/commands/index.js +8 -5
- package/dist/compiler/compileScenes.d.ts +3 -1
- package/dist/compiler/compileScenes.js +32 -2
- package/dist/compiler/index.d.ts +0 -1
- package/dist/compiler/index.js +0 -1
- package/dist/compiler/pixiNodeSchema.d.ts +4 -3
- package/dist/compiler/pixiNodeSchema.js +60 -6
- package/dist/compiler/sceneValidation.d.ts +3 -1
- package/dist/compiler/sceneValidation.js +105 -6
- package/dist/compiler/spec.d.ts +18 -7
- package/dist/compiler/spec.js +3 -1
- package/dist/compiler/templateParser.js +19 -0
- package/dist/compiler/templateSerializer.js +7 -1
- package/dist/compiler/typescriptCompiler.js +66 -8
- package/dist/compiler-node/builtinSceneInterfaces.d.ts +6 -0
- package/dist/compiler-node/builtinSceneInterfaces.js +27 -0
- package/dist/compiler-node/index.d.ts +2 -0
- package/dist/compiler-node/index.js +2 -0
- package/dist/{compiler → compiler-node}/scriptInterfaceExtractor.d.ts +1 -1
- package/dist/{compiler → compiler-node}/scriptInterfaceExtractor.js +157 -25
- package/dist/runtime/BitmapLabel.d.ts +9 -0
- package/dist/runtime/BitmapLabel.js +7 -0
- package/dist/runtime/Label.d.ts +9 -0
- package/dist/runtime/Label.js +7 -0
- package/dist/runtime/ScrollContainer.d.ts +2 -0
- package/dist/runtime/ScrollContainer.js +12 -2
- package/dist/runtime/TextBoxControl.d.ts +48 -0
- package/dist/runtime/TextBoxControl.js +162 -0
- package/dist/runtime/index.d.ts +2 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/runtimeBehavior.d.ts +3 -0
- package/dist/runtime/runtimeBehavior.js +11 -0
- package/dist/scene/decorators.d.ts +2 -1
- package/dist/scene/decorators.js +12 -7
- package/dist/scene/index.d.ts +1 -0
- package/dist/scene/index.js +1 -0
- package/dist/scene/sceneBindingRuntime.d.ts +10 -0
- package/dist/scene/sceneBindingRuntime.js +124 -0
- package/package.json +1 -1
|
@@ -79,18 +79,24 @@ function serializeAttributes(attributes) {
|
|
|
79
79
|
}
|
|
80
80
|
function flattenAttributes(attributes) {
|
|
81
81
|
return attributes.flatMap(([name, value]) => {
|
|
82
|
-
if (!value || typeof value !== 'object') {
|
|
82
|
+
if (!value || typeof value !== 'object' || isBindingValue(value)) {
|
|
83
83
|
return [[name, value]];
|
|
84
84
|
}
|
|
85
85
|
return Object.entries(value).map(([field, fieldValue]) => [`${name}.${field}`, fieldValue]);
|
|
86
86
|
});
|
|
87
87
|
}
|
|
88
88
|
function formatAttributeValue(name, value) {
|
|
89
|
+
if (isBindingValue(value)) {
|
|
90
|
+
return `{${value.path.join('.')}}`;
|
|
91
|
+
}
|
|
89
92
|
if (typeof value === 'number' && colorPropNames.has(name)) {
|
|
90
93
|
return `#${value.toString(16).padStart(6, '0').slice(-6)}`;
|
|
91
94
|
}
|
|
92
95
|
return String(value);
|
|
93
96
|
}
|
|
97
|
+
function isBindingValue(value) {
|
|
98
|
+
return typeof value === 'object' && value.kind === 'binding';
|
|
99
|
+
}
|
|
94
100
|
function escapeAttribute(value) {
|
|
95
101
|
return value
|
|
96
102
|
.replaceAll('&', '&')
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import { pixiSceneDisplayProps, pixiSceneGraphicsProps, pixiSceneLayoutProps, pixiSceneRectProps, pixiSceneSpriteLikeProps, pixiSceneTextStyleProps, pixiSceneTransformProps, } from './pixiNodeSchema.js';
|
|
1
|
+
import { pixiSceneDisplayProps, pixiSceneGraphicsProps, pixiSceneLabelProps, pixiSceneLayoutProps, pixiSceneRectProps, pixiSceneSpriteLikeProps, pixiSceneTextStyleProps, pixiSceneTransformProps, } from './pixiNodeSchema.js';
|
|
2
2
|
import { compilerSceneNodeLocator } from './commands/index.js';
|
|
3
3
|
const transformProps = new Set(pixiSceneTransformProps);
|
|
4
4
|
const layoutProps = new Set(pixiSceneLayoutProps);
|
|
5
5
|
const pixiProps = new Set(pixiSceneDisplayProps);
|
|
6
6
|
const spriteProps = new Set(pixiSceneSpriteLikeProps);
|
|
7
7
|
const graphicsProps = new Set(pixiSceneGraphicsProps);
|
|
8
|
+
const labelProps = new Set(pixiSceneLabelProps);
|
|
8
9
|
const rectProps = new Set(pixiSceneRectProps);
|
|
9
10
|
const textStyleProps = new Set(pixiSceneTextStyleProps);
|
|
10
|
-
const runtimeNodeTypes = new Set(['Group', 'GridContainer', 'HBoxContainer', 'ScrollContainer', 'VBoxContainer', 'Rect', 'Image', 'NineImage', 'TileImage']);
|
|
11
|
+
const runtimeNodeTypes = new Set(['Group', 'GridContainer', 'HBoxContainer', 'ScrollContainer', 'VBoxContainer', 'Label', 'BitmapLabel', 'Rect', 'Image', 'NineImage', 'TileImage']);
|
|
11
12
|
const runtimeNodeProps = new Set(['columns', 'gap', 'gapX', 'gapY', 'alignX', 'alignY', 'justify', 'direction', 'scrollX', 'scrollY']);
|
|
13
|
+
function isSceneBindingValue(value) {
|
|
14
|
+
return !!value && typeof value === 'object' && value.kind === 'binding';
|
|
15
|
+
}
|
|
12
16
|
export function compileSceneTemplateToTs(template, options = {}) {
|
|
13
17
|
const context = new CompileContext(template, options);
|
|
14
18
|
return context.compile();
|
|
@@ -228,7 +232,8 @@ class CompileContext {
|
|
|
228
232
|
const variable = node.id || this.#anonymousName(node.type);
|
|
229
233
|
const locator = compilerSceneNodeLocator(node, path);
|
|
230
234
|
const constructorName = this.#sceneConstructorName(node);
|
|
231
|
-
|
|
235
|
+
const constructorArgs = this.#sceneInstanceConstructorArgs(variable, node);
|
|
236
|
+
this.#lines.push(` const ${variable} = new ${constructorName}(${constructorArgs});`);
|
|
232
237
|
this.#applyPixiProps(variable, node.props, true, node);
|
|
233
238
|
for (const [name, action] of Object.entries(node.events)) {
|
|
234
239
|
this.#runtimeImports.add('connectSceneEvent');
|
|
@@ -253,7 +258,8 @@ class CompileContext {
|
|
|
253
258
|
const variable = node.id || this.#anonymousName(node.type);
|
|
254
259
|
const locator = compilerSceneNodeLocator(node, path);
|
|
255
260
|
const constructorName = this.#sceneConstructorName(node);
|
|
256
|
-
|
|
261
|
+
const constructorArgs = this.#sceneInstanceConstructorArgs(variable, node);
|
|
262
|
+
this.#lines.push(` const ${variable} = new ${constructorName}(${constructorArgs});`);
|
|
257
263
|
this.#applyPixiProps(variable, node.props, true, node);
|
|
258
264
|
for (const [name, action] of Object.entries(node.events)) {
|
|
259
265
|
this.#runtimeImports.add('connectSceneEvent');
|
|
@@ -282,7 +288,7 @@ class CompileContext {
|
|
|
282
288
|
}
|
|
283
289
|
#constructPixiNode(node) {
|
|
284
290
|
if (node.type === 'Text' || node.type === 'BitmapText' || node.type === 'HTMLText') {
|
|
285
|
-
const text = this.#stringValue(node.props.text ?? '');
|
|
291
|
+
const text = this.#stringValue(isSceneBindingValue(node.props.text) ? '' : node.props.text ?? '');
|
|
286
292
|
const style = this.#styleObject(node.props);
|
|
287
293
|
return `new ${node.type}({ text: ${text}${style ? `, style: ${style}` : ''} })`;
|
|
288
294
|
}
|
|
@@ -308,6 +314,31 @@ class CompileContext {
|
|
|
308
314
|
#sceneConstructorName(node) {
|
|
309
315
|
return this.options.sceneClassAliases?.[node.scene] ?? node.type;
|
|
310
316
|
}
|
|
317
|
+
#sceneInstanceConstructorArgs(variable, node) {
|
|
318
|
+
const sceneInterface = this.options.sceneInterfaces?.[node.scene];
|
|
319
|
+
if (!sceneInterface) {
|
|
320
|
+
return '';
|
|
321
|
+
}
|
|
322
|
+
const entries = [];
|
|
323
|
+
for (const [key, value] of Object.entries(node.props)) {
|
|
324
|
+
const contract = sceneInterface.props[key];
|
|
325
|
+
if (!contract) {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
if (isSceneBindingValue(value)) {
|
|
329
|
+
this.#runtimeImports.add('readSceneBindingValue');
|
|
330
|
+
entries.push(`${key}: readSceneBindingValue(root, ${JSON.stringify(value)})`);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
if (contract.type === 'struct' && value && typeof value === 'object') {
|
|
334
|
+
const structVariable = this.#createStructValue(variable, key, value, contract);
|
|
335
|
+
entries.push(`${key}: ${structVariable}`);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
entries.push(`${key}: ${this.#value(value)}`);
|
|
339
|
+
}
|
|
340
|
+
return entries.length > 0 ? `{ ${entries.join(', ')} }` : '';
|
|
341
|
+
}
|
|
311
342
|
#sceneStructImports(sceneImport) {
|
|
312
343
|
const scene = this.#sceneImportScene(sceneImport);
|
|
313
344
|
if (!scene) {
|
|
@@ -378,6 +409,18 @@ class CompileContext {
|
|
|
378
409
|
return contract.sourceScene ?? instanceScene;
|
|
379
410
|
}
|
|
380
411
|
#applyPixiProps(variable, props, instance = false, sceneInstance, pixiType) {
|
|
412
|
+
const publicProps = new Set(Object.keys(sceneInstance ? this.options.sceneInterfaces?.[sceneInstance.scene]?.props ?? {} : {}));
|
|
413
|
+
const bindingEntries = Object.entries(props).filter((entry) => (isSceneBindingValue(entry[1]) && !publicProps.has(entry[0])));
|
|
414
|
+
props = Object.fromEntries(Object.entries(props).filter(([, value]) => !isSceneBindingValue(value)));
|
|
415
|
+
if (sceneInstance) {
|
|
416
|
+
props = Object.fromEntries(Object.entries(props).filter(([key]) => !publicProps.has(key)));
|
|
417
|
+
for (const [key, value] of Object.entries(sceneInstance.props)) {
|
|
418
|
+
if (publicProps.has(key) && isSceneBindingValue(value)) {
|
|
419
|
+
this.#runtimeImports.add('bindSceneNodeProp');
|
|
420
|
+
this.#lines.push(` bindSceneNodeProp(root, ${JSON.stringify(value)}, ${variable}, ${JSON.stringify(key)});`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
}
|
|
381
424
|
const x = props.x;
|
|
382
425
|
const y = props.y;
|
|
383
426
|
if (x !== undefined || y !== undefined) {
|
|
@@ -413,6 +456,10 @@ class CompileContext {
|
|
|
413
456
|
this.#drawGraphics(variable, props);
|
|
414
457
|
}
|
|
415
458
|
for (const [key, value] of Object.entries(props)) {
|
|
459
|
+
if (!instance && (pixiType === 'Label' || pixiType === 'BitmapLabel') && labelProps.has(key)) {
|
|
460
|
+
this.#lines.push(` ${variable}.${key} = ${this.#value(value)};`);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
416
463
|
if (!instance && runtimeNodeProps.has(key)) {
|
|
417
464
|
this.#lines.push(` ${variable}.${key} = ${this.#value(value)};`);
|
|
418
465
|
continue;
|
|
@@ -436,7 +483,7 @@ class CompileContext {
|
|
|
436
483
|
if (instance && layoutProps.has(key)) {
|
|
437
484
|
continue;
|
|
438
485
|
}
|
|
439
|
-
if (instance && value && typeof value === 'object') {
|
|
486
|
+
if (instance && value && typeof value === 'object' && !isSceneBindingValue(value)) {
|
|
440
487
|
this.#applyStructProp(variable, key, value, sceneInstance);
|
|
441
488
|
continue;
|
|
442
489
|
}
|
|
@@ -444,6 +491,10 @@ class CompileContext {
|
|
|
444
491
|
this.#lines.push(` ${variable}.${key} = ${this.#value(value)};`);
|
|
445
492
|
}
|
|
446
493
|
}
|
|
494
|
+
for (const [key, binding] of bindingEntries) {
|
|
495
|
+
this.#runtimeImports.add('bindSceneNodeProp');
|
|
496
|
+
this.#lines.push(` bindSceneNodeProp(root, ${JSON.stringify(binding)}, ${variable}, ${JSON.stringify(key)});`);
|
|
497
|
+
}
|
|
447
498
|
}
|
|
448
499
|
#applyRootProps() {
|
|
449
500
|
const props = { ...this.template.props };
|
|
@@ -474,12 +525,16 @@ class CompileContext {
|
|
|
474
525
|
this.#lines.push(` ${variable}.${key} = ${this.#value(value)};`);
|
|
475
526
|
return;
|
|
476
527
|
}
|
|
528
|
+
const structVariable = this.#createStructValue(variable, key, value, contract);
|
|
529
|
+
this.#lines.push(` ${variable}.${key} = ${structVariable};`);
|
|
530
|
+
}
|
|
531
|
+
#createStructValue(variable, key, value, contract) {
|
|
477
532
|
const structVariable = `${variable}${key.charAt(0).toUpperCase()}${key.slice(1)}`;
|
|
478
533
|
this.#lines.push(` const ${structVariable} = new ${contract.struct}();`);
|
|
479
534
|
for (const [field, fieldValue] of Object.entries(value)) {
|
|
480
535
|
this.#lines.push(` ${structVariable}.${field} = ${this.#value(fieldValue)};`);
|
|
481
536
|
}
|
|
482
|
-
|
|
537
|
+
return structVariable;
|
|
483
538
|
}
|
|
484
539
|
#sceneInstanceStructContract(sceneInstance, key) {
|
|
485
540
|
const contract = sceneInstance ? this.options.sceneInterfaces?.[sceneInstance.scene]?.props[key] : undefined;
|
|
@@ -550,7 +605,7 @@ class CompileContext {
|
|
|
550
605
|
const entries = [];
|
|
551
606
|
for (const key of pixiSceneTextStyleProps) {
|
|
552
607
|
const value = props[key];
|
|
553
|
-
if (value !== undefined) {
|
|
608
|
+
if (value !== undefined && !isSceneBindingValue(value)) {
|
|
554
609
|
entries.push(`${key}: ${key === 'fontWeight' ? JSON.stringify(String(value)) : this.#value(value)}`);
|
|
555
610
|
}
|
|
556
611
|
}
|
|
@@ -608,6 +663,9 @@ class CompileContext {
|
|
|
608
663
|
if (typeof value === 'string') {
|
|
609
664
|
return JSON.stringify(value);
|
|
610
665
|
}
|
|
666
|
+
if (isSceneBindingValue(value)) {
|
|
667
|
+
throw new Error(`Scene binding ${value.path.join('.')} must be compiled as a binding target.`);
|
|
668
|
+
}
|
|
611
669
|
if (value && typeof value === 'object') {
|
|
612
670
|
return `{ ${Object.entries(value).map(([key, fieldValue]) => `${key}: ${this.#scalarValue(fieldValue)}`).join(', ')} }`;
|
|
613
671
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { SceneTemplateInterface } from '../compiler/spec.js';
|
|
2
|
+
import { type BuiltinSceneScriptSources } from '../compiler/builtinScenes.js';
|
|
3
|
+
export declare function builtinSceneInterface(assetId: string, scriptSources: BuiltinSceneScriptSources): SceneTemplateInterface;
|
|
4
|
+
export declare function builtinSceneInterfaces(scriptSources: BuiltinSceneScriptSources): {
|
|
5
|
+
[k: string]: SceneTemplateInterface;
|
|
6
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { builtinSceneAssetId, builtinSceneAssetIds, builtinSceneNameFromAssetId, builtinSceneNames, } from '../compiler/builtinScenes.js';
|
|
2
|
+
import { extractSceneScriptInterfaces } from './scriptInterfaceExtractor.js';
|
|
3
|
+
export function builtinSceneInterface(assetId, scriptSources) {
|
|
4
|
+
return builtinSceneInterfaceDescriptors(scriptSources)[assetId].interface;
|
|
5
|
+
}
|
|
6
|
+
export function builtinSceneInterfaces(scriptSources) {
|
|
7
|
+
const descriptors = builtinSceneInterfaceDescriptors(scriptSources);
|
|
8
|
+
return Object.fromEntries(builtinSceneAssetIds().map((assetId) => [assetId, descriptors[assetId].interface]));
|
|
9
|
+
}
|
|
10
|
+
function builtinSceneInterfaceDescriptors(scriptSources) {
|
|
11
|
+
const descriptors = extractSceneScriptInterfaces(builtinSceneNames.map((name) => ({
|
|
12
|
+
scene: builtinSceneAssetId(name),
|
|
13
|
+
fileName: `${name}.ts`,
|
|
14
|
+
source: scriptSources[name],
|
|
15
|
+
})));
|
|
16
|
+
for (const assetId of builtinSceneAssetIds()) {
|
|
17
|
+
const name = builtinSceneNameFromAssetId(assetId);
|
|
18
|
+
const descriptor = descriptors[assetId];
|
|
19
|
+
if (!descriptor) {
|
|
20
|
+
throw new Error(`Built-in Scene "${assetId}" is missing a @scene script.`);
|
|
21
|
+
}
|
|
22
|
+
if (descriptor.className !== name) {
|
|
23
|
+
throw new Error(`Built-in Scene "${assetId}" name must match @scene class "${descriptor.className}".`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return descriptors;
|
|
27
|
+
}
|
|
@@ -15,6 +15,7 @@ function extractSceneScriptClasses(source, fileName, options) {
|
|
|
15
15
|
const sourceId = normalizeScriptSourceId(fileName);
|
|
16
16
|
const imports = collectNamedImports(sourceFile, sourceId);
|
|
17
17
|
const structClasses = collectStructClasses(sourceFile);
|
|
18
|
+
const variantDeclarations = collectVariantDeclarations(sourceFile);
|
|
18
19
|
const classes = [];
|
|
19
20
|
for (const statement of sourceFile.statements) {
|
|
20
21
|
if (!ts.isClassDeclaration(statement)) {
|
|
@@ -42,7 +43,8 @@ function extractSceneScriptClasses(source, fileName, options) {
|
|
|
42
43
|
}
|
|
43
44
|
const prop = memberDecoratorOptions(member, 'prop');
|
|
44
45
|
if (prop) {
|
|
45
|
-
|
|
46
|
+
assertStaticPropMember(member, name);
|
|
47
|
+
props[name] = propContract(prop, member, structClasses, variantDeclarations);
|
|
46
48
|
}
|
|
47
49
|
const event = memberDecoratorOptions(member, 'event');
|
|
48
50
|
if (event) {
|
|
@@ -318,6 +320,88 @@ function memberName(name) {
|
|
|
318
320
|
}
|
|
319
321
|
return undefined;
|
|
320
322
|
}
|
|
323
|
+
function collectVariantDeclarations(sourceFile) {
|
|
324
|
+
const variants = new Map();
|
|
325
|
+
for (const statement of sourceFile.statements) {
|
|
326
|
+
if (!ts.isVariableStatement(statement)) {
|
|
327
|
+
continue;
|
|
328
|
+
}
|
|
329
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
330
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const initializer = unwrapExpression(declaration.initializer);
|
|
334
|
+
if (!ts.isCallExpression(initializer)
|
|
335
|
+
|| !ts.isIdentifier(initializer.expression)
|
|
336
|
+
|| initializer.expression.text !== 'defineVariants') {
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
if (initializer.arguments.length !== 1) {
|
|
340
|
+
throw new Error('defineVariants accepts exactly one argument.');
|
|
341
|
+
}
|
|
342
|
+
variants.set(declaration.name.text, variantObjectValue(initializer.arguments[0], 'defineVariants argument'));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
return variants;
|
|
346
|
+
}
|
|
347
|
+
function variantObjectValue(expression, label) {
|
|
348
|
+
const object = unwrapExpression(expression);
|
|
349
|
+
if (!ts.isObjectLiteralExpression(object)) {
|
|
350
|
+
throw new Error(`${label} must be an object literal.`);
|
|
351
|
+
}
|
|
352
|
+
const variants = {};
|
|
353
|
+
let expectedFields;
|
|
354
|
+
for (const property of object.properties) {
|
|
355
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
356
|
+
throw new Error(`${label} only supports property assignments.`);
|
|
357
|
+
}
|
|
358
|
+
const name = memberName(property.name);
|
|
359
|
+
if (!name) {
|
|
360
|
+
throw new Error(`${label} only supports literal case names.`);
|
|
361
|
+
}
|
|
362
|
+
const caseExpression = unwrapExpression(property.initializer);
|
|
363
|
+
if (!ts.isObjectLiteralExpression(caseExpression)) {
|
|
364
|
+
throw new Error(`${label}.${name} must be an object literal.`);
|
|
365
|
+
}
|
|
366
|
+
const fields = {};
|
|
367
|
+
for (const field of caseExpression.properties) {
|
|
368
|
+
if (!ts.isPropertyAssignment(field)) {
|
|
369
|
+
throw new Error(`${label}.${name} only supports property assignments.`);
|
|
370
|
+
}
|
|
371
|
+
const fieldName = memberName(field.name);
|
|
372
|
+
if (!fieldName) {
|
|
373
|
+
throw new Error(`${label}.${name} only supports literal field names.`);
|
|
374
|
+
}
|
|
375
|
+
fields[fieldName] = literalValue(field.initializer, `${label}.${name}.${fieldName}`);
|
|
376
|
+
}
|
|
377
|
+
const fieldTypes = Object.fromEntries(Object.entries(fields).map(([field, value]) => [field, typeof value]));
|
|
378
|
+
if (!expectedFields) {
|
|
379
|
+
expectedFields = fieldTypes;
|
|
380
|
+
}
|
|
381
|
+
else if (!sameFieldTypes(expectedFields, fieldTypes)) {
|
|
382
|
+
throw new Error(`defineVariants case "${name}" must contain the same fields and field types as the first case.`);
|
|
383
|
+
}
|
|
384
|
+
variants[name] = fields;
|
|
385
|
+
}
|
|
386
|
+
if (Object.keys(variants).length === 0) {
|
|
387
|
+
throw new Error('defineVariants requires at least one case.');
|
|
388
|
+
}
|
|
389
|
+
return variants;
|
|
390
|
+
}
|
|
391
|
+
function sameFieldTypes(left, right) {
|
|
392
|
+
const leftEntries = Object.entries(left);
|
|
393
|
+
return leftEntries.length === Object.keys(right).length
|
|
394
|
+
&& leftEntries.every(([name, type]) => right[name] === type);
|
|
395
|
+
}
|
|
396
|
+
function unwrapExpression(expression) {
|
|
397
|
+
if (ts.isAsExpression(expression)
|
|
398
|
+
|| ts.isTypeAssertionExpression(expression)
|
|
399
|
+
|| ts.isSatisfiesExpression(expression)
|
|
400
|
+
|| ts.isParenthesizedExpression(expression)) {
|
|
401
|
+
return unwrapExpression(expression.expression);
|
|
402
|
+
}
|
|
403
|
+
return expression;
|
|
404
|
+
}
|
|
321
405
|
function collectStructClasses(sourceFile) {
|
|
322
406
|
const classes = new Map();
|
|
323
407
|
for (const statement of sourceFile.statements) {
|
|
@@ -355,65 +439,113 @@ function structFields(node) {
|
|
|
355
439
|
}
|
|
356
440
|
return fields;
|
|
357
441
|
}
|
|
358
|
-
function
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
442
|
+
function assertStaticPropMember(member, name) {
|
|
443
|
+
const isDeclareProperty = ts.isPropertyDeclaration(member)
|
|
444
|
+
&& member.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.DeclareKeyword)
|
|
445
|
+
&& !member.modifiers.some((modifier) => modifier.kind === ts.SyntaxKind.AccessorKeyword)
|
|
446
|
+
&& !member.initializer;
|
|
447
|
+
if (!isDeclareProperty) {
|
|
448
|
+
throw new Error(`@prop "${name}" must decorate a declare property without an initializer.`);
|
|
449
|
+
}
|
|
450
|
+
if (!member.type) {
|
|
451
|
+
throw new Error(`@prop "${name}" requires an explicit TypeScript property type.`);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
function propContract(prop, member, structClasses, variantDeclarations) {
|
|
455
|
+
if (!ts.isPropertyDeclaration(member) || !member.type) {
|
|
456
|
+
throw new Error('@prop requires a typed declare property.');
|
|
363
457
|
}
|
|
364
|
-
if (
|
|
365
|
-
throw new Error('@prop type
|
|
458
|
+
if (prop.type !== undefined) {
|
|
459
|
+
throw new Error('@prop type is inferred from the TypeScript property declaration; remove the type option.');
|
|
460
|
+
}
|
|
461
|
+
const defaultValue = prop.default;
|
|
462
|
+
const variantsReference = prop.variants;
|
|
463
|
+
if (variantsReference !== undefined) {
|
|
464
|
+
if (!isIdentifierValue(variantsReference)) {
|
|
465
|
+
throw new Error('@prop variants must reference a local defineVariants declaration.');
|
|
466
|
+
}
|
|
467
|
+
const variants = variantDeclarations.get(variantsReference.text);
|
|
468
|
+
if (!variants) {
|
|
469
|
+
throw new Error(`@prop variants "${variantsReference.text}" must reference a local defineVariants declaration.`);
|
|
470
|
+
}
|
|
471
|
+
if (typeof defaultValue !== 'string' || !variants[defaultValue]) {
|
|
472
|
+
throw new Error('@prop Variant default must name an existing defineVariants case.');
|
|
473
|
+
}
|
|
474
|
+
assertVariantPropertyType(member.type, variantsReference.text);
|
|
475
|
+
return {
|
|
476
|
+
type: 'variant',
|
|
477
|
+
default: defaultValue,
|
|
478
|
+
variants,
|
|
479
|
+
};
|
|
366
480
|
}
|
|
367
|
-
const primitiveType =
|
|
481
|
+
const primitiveType = primitivePropertyType(member.type);
|
|
368
482
|
if (primitiveType) {
|
|
369
483
|
if (defaultValue !== undefined && (!isSceneTemplateScalarValue(defaultValue) || sceneTemplateScalarType(defaultValue) !== primitiveType)) {
|
|
370
|
-
throw new Error(`@prop default
|
|
484
|
+
throw new Error(`@prop default must be a ${primitiveType} literal.`);
|
|
371
485
|
}
|
|
372
486
|
return {
|
|
373
487
|
type: primitiveType,
|
|
374
488
|
...(defaultValue !== undefined ? { default: defaultValue } : {}),
|
|
375
489
|
};
|
|
376
490
|
}
|
|
377
|
-
const
|
|
491
|
+
const structName = typeReferenceName(member.type);
|
|
492
|
+
if (!structName) {
|
|
493
|
+
throw new Error('@prop type must be string, number, boolean, a local struct class, or keyof a local defineVariants declaration.');
|
|
494
|
+
}
|
|
495
|
+
const structClass = structClasses.get(structName);
|
|
378
496
|
if (!structClass) {
|
|
379
|
-
throw new Error(`Struct prop type ${
|
|
497
|
+
throw new Error(`Struct prop type ${structName} was not found.`);
|
|
380
498
|
}
|
|
381
499
|
if (!structClass.exported) {
|
|
382
|
-
throw new Error(`Struct prop type ${
|
|
500
|
+
throw new Error(`Struct prop type ${structName} must be exported.`);
|
|
383
501
|
}
|
|
384
502
|
if (defaultValue !== undefined) {
|
|
385
503
|
throw new Error('@prop default is only supported for primitive props.');
|
|
386
504
|
}
|
|
387
505
|
const struct = structContractInfo(structClass.node);
|
|
388
506
|
if (struct.hasRequiredConstructorParameters) {
|
|
389
|
-
throw new Error(`Struct prop type ${
|
|
507
|
+
throw new Error(`Struct prop type ${structName} must be constructable with no required parameters.`);
|
|
390
508
|
}
|
|
391
509
|
return {
|
|
392
510
|
type: 'struct',
|
|
393
|
-
struct:
|
|
511
|
+
struct: structName,
|
|
394
512
|
fields: struct.fields,
|
|
395
513
|
};
|
|
396
514
|
}
|
|
397
|
-
function
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
515
|
+
function assertVariantPropertyType(type, variantsName) {
|
|
516
|
+
if (!ts.isTypeOperatorNode(type)
|
|
517
|
+
|| type.operator !== ts.SyntaxKind.KeyOfKeyword
|
|
518
|
+
|| !ts.isTypeQueryNode(type.type)
|
|
519
|
+
|| !ts.isIdentifier(type.type.exprName)
|
|
520
|
+
|| type.type.exprName.text !== variantsName) {
|
|
521
|
+
throw new Error(`@prop Variant type must be "keyof typeof ${variantsName}".`);
|
|
522
|
+
}
|
|
402
523
|
}
|
|
403
|
-
function
|
|
404
|
-
if (
|
|
524
|
+
function primitivePropertyType(type) {
|
|
525
|
+
if (type.kind === ts.SyntaxKind.StringKeyword) {
|
|
405
526
|
return 'string';
|
|
406
527
|
}
|
|
407
|
-
if (
|
|
528
|
+
if (type.kind === ts.SyntaxKind.NumberKeyword) {
|
|
408
529
|
return 'number';
|
|
409
530
|
}
|
|
410
|
-
if (
|
|
531
|
+
if (type.kind === ts.SyntaxKind.BooleanKeyword) {
|
|
411
532
|
return 'boolean';
|
|
412
533
|
}
|
|
413
534
|
return undefined;
|
|
414
535
|
}
|
|
536
|
+
function typeReferenceName(type) {
|
|
537
|
+
return ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName)
|
|
538
|
+
? type.typeName.text
|
|
539
|
+
: undefined;
|
|
540
|
+
}
|
|
541
|
+
function structContractInfo(node) {
|
|
542
|
+
return {
|
|
543
|
+
fields: structFields(node),
|
|
544
|
+
hasRequiredConstructorParameters: node.members.some((member) => ts.isConstructorDeclaration(member) && member.parameters.some((parameter) => !parameter.questionToken && !parameter.initializer)),
|
|
545
|
+
};
|
|
546
|
+
}
|
|
415
547
|
function isIdentifierValue(value) {
|
|
416
|
-
return ts.isIdentifier(value);
|
|
548
|
+
return typeof value === 'object' && ts.isIdentifier(value);
|
|
417
549
|
}
|
|
418
550
|
function isSceneTemplateScalarValue(value) {
|
|
419
551
|
return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { BitmapText } from 'pixi.js';
|
|
2
|
+
import { TextBoxControl, type TextBoxAlign, type TextBoxFontWeight, type TextBoxOptions, type TextBoxOverflow } from './TextBoxControl.js';
|
|
3
|
+
export type BitmapLabelAlign = TextBoxAlign;
|
|
4
|
+
export type BitmapLabelOverflow = TextBoxOverflow;
|
|
5
|
+
export type BitmapLabelFontWeight = TextBoxFontWeight;
|
|
6
|
+
export type BitmapLabelOptions = TextBoxOptions;
|
|
7
|
+
export declare class BitmapLabel extends TextBoxControl<BitmapText> {
|
|
8
|
+
constructor(options?: BitmapLabelOptions);
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Text } from 'pixi.js';
|
|
2
|
+
import { TextBoxControl, type TextBoxAlign, type TextBoxFontWeight, type TextBoxOptions, type TextBoxOverflow } from './TextBoxControl.js';
|
|
3
|
+
export type LabelAlign = TextBoxAlign;
|
|
4
|
+
export type LabelOverflow = TextBoxOverflow;
|
|
5
|
+
export type LabelFontWeight = TextBoxFontWeight;
|
|
6
|
+
export type LabelOptions = TextBoxOptions;
|
|
7
|
+
export declare class Label extends TextBoxControl<Text> {
|
|
8
|
+
constructor(options?: LabelOptions);
|
|
9
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Container, type ContainerChild, type DestroyOptions } from 'pixi.js';
|
|
2
2
|
import { Control } from './Control.js';
|
|
3
3
|
import type { GroupOptions } from './Group.js';
|
|
4
|
+
import { runtimeBehavior } from './runtimeBehavior.js';
|
|
4
5
|
export type ScrollDirection = 'vertical' | 'horizontal' | 'both';
|
|
5
6
|
export type ScrollContainerOptions = GroupOptions & {
|
|
6
7
|
direction?: ScrollDirection;
|
|
@@ -11,6 +12,7 @@ export declare class ScrollContainer extends Control {
|
|
|
11
12
|
#private;
|
|
12
13
|
readonly contentLayer: Container<ContainerChild>;
|
|
13
14
|
constructor(options?: ScrollContainerOptions);
|
|
15
|
+
[runtimeBehavior](): void;
|
|
14
16
|
get direction(): ScrollDirection;
|
|
15
17
|
set direction(value: ScrollDirection);
|
|
16
18
|
get scrollX(): number;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Container, Graphics, Ticker, } from 'pixi.js';
|
|
2
2
|
import { Control } from './Control.js';
|
|
3
|
+
import { runtimeBehavior } from './runtimeBehavior.js';
|
|
3
4
|
const elasticResistance = 0.5;
|
|
4
5
|
const inertiaFriction = 0.92;
|
|
5
6
|
const springStrength = 0.18;
|
|
@@ -26,19 +27,21 @@ export class ScrollContainer extends Control {
|
|
|
26
27
|
this.#direction = direction ?? this.#direction;
|
|
27
28
|
this.#targetScrollX = scrollX ?? this.#targetScrollX;
|
|
28
29
|
this.#targetScrollY = scrollY ?? this.#targetScrollY;
|
|
29
|
-
this.eventMode = 'static';
|
|
30
30
|
this.contentLayer.mask = this.#mask;
|
|
31
31
|
this.contentLayer.on('childAdded', this.#handleContentChildAdded, this);
|
|
32
32
|
this.contentLayer.on('childRemoved', this.#handleContentChildRemoved, this);
|
|
33
33
|
super.addChild(this.contentLayer);
|
|
34
34
|
super.addChild(this.#mask);
|
|
35
|
+
this.layout();
|
|
36
|
+
}
|
|
37
|
+
[runtimeBehavior]() {
|
|
38
|
+
this.eventMode = 'static';
|
|
35
39
|
this.on('wheel', this.#handleWheel, this);
|
|
36
40
|
this.on('pointerdown', this.#handlePointerDown, this);
|
|
37
41
|
this.on('globalpointermove', this.#handleGlobalPointerMove, this);
|
|
38
42
|
this.on('pointerup', this.#handlePointerUp, this);
|
|
39
43
|
this.on('pointerupoutside', this.#handlePointerUp, this);
|
|
40
44
|
this.on('pointercancel', this.#handlePointerUp, this);
|
|
41
|
-
this.layout();
|
|
42
45
|
}
|
|
43
46
|
get direction() {
|
|
44
47
|
return this.#direction;
|
|
@@ -116,6 +119,13 @@ export class ScrollContainer extends Control {
|
|
|
116
119
|
destroy(options) {
|
|
117
120
|
this.#destroying = true;
|
|
118
121
|
this.#stopMomentum();
|
|
122
|
+
this.#drag = undefined;
|
|
123
|
+
this.off('wheel', this.#handleWheel, this);
|
|
124
|
+
this.off('pointerdown', this.#handlePointerDown, this);
|
|
125
|
+
this.off('globalpointermove', this.#handleGlobalPointerMove, this);
|
|
126
|
+
this.off('pointerup', this.#handlePointerUp, this);
|
|
127
|
+
this.off('pointerupoutside', this.#handlePointerUp, this);
|
|
128
|
+
this.off('pointercancel', this.#handlePointerUp, this);
|
|
119
129
|
this.contentLayer.off('childAdded', this.#handleContentChildAdded, this);
|
|
120
130
|
this.contentLayer.off('childRemoved', this.#handleContentChildRemoved, this);
|
|
121
131
|
for (const child of this.#watchedContent) {
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { BitmapText, Text, type TextOptions } from 'pixi.js';
|
|
2
|
+
import { Control } from './Control.js';
|
|
3
|
+
import type { GroupOptions } from './Group.js';
|
|
4
|
+
export type TextBoxAlign = 'start' | 'center' | 'end';
|
|
5
|
+
export type TextBoxOverflow = 'visible' | 'clip';
|
|
6
|
+
export type TextBoxFontWeight = 400 | 500 | 600 | 700 | '400' | '500' | '600' | '700' | 'bold';
|
|
7
|
+
export type TextBoxOptions = GroupOptions & {
|
|
8
|
+
text?: string;
|
|
9
|
+
fontFamily?: string;
|
|
10
|
+
fontSize?: number;
|
|
11
|
+
fontWeight?: TextBoxFontWeight;
|
|
12
|
+
fill?: number;
|
|
13
|
+
lineHeight?: number;
|
|
14
|
+
letterSpacing?: number;
|
|
15
|
+
wordWrap?: boolean;
|
|
16
|
+
alignX?: TextBoxAlign;
|
|
17
|
+
alignY?: TextBoxAlign;
|
|
18
|
+
overflow?: TextBoxOverflow;
|
|
19
|
+
};
|
|
20
|
+
type TextBoxRenderer = Text | BitmapText;
|
|
21
|
+
export declare class TextBoxControl<T extends TextBoxRenderer> extends Control {
|
|
22
|
+
#private;
|
|
23
|
+
constructor(options: TextBoxOptions, createTextNode: (options: TextOptions) => T);
|
|
24
|
+
get text(): string;
|
|
25
|
+
set text(value: string);
|
|
26
|
+
get fontFamily(): string;
|
|
27
|
+
set fontFamily(value: string);
|
|
28
|
+
get fontSize(): number;
|
|
29
|
+
set fontSize(value: number);
|
|
30
|
+
get fontWeight(): TextBoxFontWeight;
|
|
31
|
+
set fontWeight(value: TextBoxFontWeight);
|
|
32
|
+
get fill(): number;
|
|
33
|
+
set fill(value: number);
|
|
34
|
+
get lineHeight(): number;
|
|
35
|
+
set lineHeight(value: number);
|
|
36
|
+
get letterSpacing(): number;
|
|
37
|
+
set letterSpacing(value: number);
|
|
38
|
+
get wordWrap(): boolean;
|
|
39
|
+
set wordWrap(value: boolean);
|
|
40
|
+
get alignX(): TextBoxAlign;
|
|
41
|
+
set alignX(value: TextBoxAlign);
|
|
42
|
+
get alignY(): TextBoxAlign;
|
|
43
|
+
set alignY(value: TextBoxAlign);
|
|
44
|
+
get overflow(): TextBoxOverflow;
|
|
45
|
+
set overflow(value: TextBoxOverflow);
|
|
46
|
+
layout(): void;
|
|
47
|
+
}
|
|
48
|
+
export {};
|