pixifact 0.1.5 → 0.2.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.
Files changed (75) hide show
  1. package/README.md +13 -13
  2. package/dist/compiler/builtinScenes.d.ts +2 -2
  3. package/dist/compiler/builtinScenes.js +1 -9
  4. package/dist/compiler/compileScenes.js +16 -0
  5. package/dist/compiler/pixiNodeSchema.d.ts +17 -7
  6. package/dist/compiler/pixiNodeSchema.js +219 -0
  7. package/dist/compiler/sceneValidation.js +13 -2
  8. package/dist/compiler/spec.d.ts +5 -1
  9. package/dist/compiler/templateParser.js +18 -10
  10. package/dist/compiler/templateSerializer.js +1 -0
  11. package/dist/compiler/typescriptCompiler.js +53 -8
  12. package/dist/project/index.d.ts +5 -0
  13. package/dist/project/index.js +22 -0
  14. package/dist/runtime/Control.d.ts +18 -0
  15. package/dist/runtime/Control.js +49 -0
  16. package/dist/runtime/GridContainer.d.ts +25 -0
  17. package/dist/runtime/GridContainer.js +115 -0
  18. package/dist/runtime/Group.d.ts +2 -0
  19. package/dist/runtime/Group.js +30 -10
  20. package/dist/runtime/HBoxContainer.d.ts +21 -0
  21. package/dist/runtime/HBoxContainer.js +74 -0
  22. package/dist/runtime/Image.d.ts +47 -0
  23. package/dist/runtime/Image.js +220 -0
  24. package/dist/runtime/NineImage.d.ts +22 -0
  25. package/dist/runtime/NineImage.js +54 -0
  26. package/dist/runtime/Rect.d.ts +48 -0
  27. package/dist/runtime/Rect.js +162 -0
  28. package/dist/runtime/ScrollContainer.d.ts +28 -0
  29. package/dist/runtime/ScrollContainer.js +370 -0
  30. package/dist/runtime/TileImage.d.ts +27 -0
  31. package/dist/runtime/TileImage.js +57 -0
  32. package/dist/runtime/VBoxContainer.d.ts +21 -0
  33. package/dist/runtime/VBoxContainer.js +74 -0
  34. package/dist/runtime/frameLayout.d.ts +11 -0
  35. package/dist/runtime/frameLayout.js +116 -0
  36. package/dist/runtime/frameLayoutAccessors.d.ts +4 -0
  37. package/dist/runtime/frameLayoutAccessors.js +7 -0
  38. package/dist/runtime/index.d.ts +12 -0
  39. package/dist/runtime/index.js +12 -0
  40. package/dist/runtime/stackLayout.d.ts +16 -0
  41. package/dist/runtime/stackLayout.js +59 -0
  42. package/dist/viewport.d.ts +43 -0
  43. package/dist/viewport.js +93 -0
  44. package/package.json +2 -2
  45. package/dist/builtin-scenes/CenterContainer.d.ts +0 -13
  46. package/dist/builtin-scenes/CenterContainer.js +0 -71
  47. package/dist/builtin-scenes/CenterContainer.scene +0 -3
  48. package/dist/builtin-scenes/CenterContainer.ts +0 -77
  49. package/dist/builtin-scenes/Control.d.ts +0 -44
  50. package/dist/builtin-scenes/Control.js +0 -206
  51. package/dist/builtin-scenes/Control.scene +0 -3
  52. package/dist/builtin-scenes/Control.ts +0 -233
  53. package/dist/builtin-scenes/FlexItem.d.ts +0 -45
  54. package/dist/builtin-scenes/FlexItem.js +0 -164
  55. package/dist/builtin-scenes/FlexItem.scene +0 -3
  56. package/dist/builtin-scenes/FlexItem.ts +0 -180
  57. package/dist/builtin-scenes/FlexLayout.d.ts +0 -29
  58. package/dist/builtin-scenes/FlexLayout.js +0 -358
  59. package/dist/builtin-scenes/FlexLayout.scene +0 -3
  60. package/dist/builtin-scenes/FlexLayout.ts +0 -409
  61. package/dist/builtin-scenes/HBoxContainer.d.ts +0 -15
  62. package/dist/builtin-scenes/HBoxContainer.js +0 -70
  63. package/dist/builtin-scenes/HBoxContainer.scene +0 -3
  64. package/dist/builtin-scenes/HBoxContainer.ts +0 -75
  65. package/dist/builtin-scenes/MarginContainer.d.ts +0 -23
  66. package/dist/builtin-scenes/MarginContainer.js +0 -131
  67. package/dist/builtin-scenes/MarginContainer.scene +0 -3
  68. package/dist/builtin-scenes/MarginContainer.ts +0 -139
  69. package/dist/builtin-scenes/VBoxContainer.d.ts +0 -15
  70. package/dist/builtin-scenes/VBoxContainer.js +0 -70
  71. package/dist/builtin-scenes/VBoxContainer.scene +0 -3
  72. package/dist/builtin-scenes/VBoxContainer.ts +0 -75
  73. package/dist/builtin-scenes/controlLayout.d.ts +0 -57
  74. package/dist/builtin-scenes/controlLayout.js +0 -202
  75. package/dist/builtin-scenes/controlLayout.ts +0 -270
@@ -1,9 +1,13 @@
1
- import { pixiSceneDisplayProps, pixiSceneGraphicsProps, pixiSceneSpriteLikeProps, pixiSceneTextStyleProps, pixiSceneTransformProps, } from './pixiNodeSchema.js';
1
+ import { pixiSceneDisplayProps, pixiSceneGraphicsProps, pixiSceneLayoutProps, pixiSceneRectProps, pixiSceneSpriteLikeProps, pixiSceneTextStyleProps, pixiSceneTransformProps, } from './pixiNodeSchema.js';
2
2
  const transformProps = new Set(pixiSceneTransformProps);
3
+ const layoutProps = new Set(pixiSceneLayoutProps);
3
4
  const pixiProps = new Set(pixiSceneDisplayProps);
4
5
  const spriteProps = new Set(pixiSceneSpriteLikeProps);
5
6
  const graphicsProps = new Set(pixiSceneGraphicsProps);
7
+ const rectProps = new Set(pixiSceneRectProps);
6
8
  const textStyleProps = new Set(pixiSceneTextStyleProps);
9
+ const runtimeNodeTypes = new Set(['GridContainer', 'HBoxContainer', 'ScrollContainer', 'VBoxContainer', 'Rect', 'Image', 'NineImage', 'TileImage']);
10
+ const runtimeNodeProps = new Set(['columns', 'gap', 'gapX', 'gapY', 'alignX', 'alignY', 'justify', 'direction', 'scrollX', 'scrollY']);
7
11
  export function compileSceneTemplateToTs(template, options = {}) {
8
12
  const context = new CompileContext(template, options);
9
13
  return context.compile();
@@ -13,6 +17,7 @@ class CompileContext {
13
17
  options;
14
18
  #imports = new Set();
15
19
  #pixiImports = new Set();
20
+ #pixiRuntimeImports = new Set();
16
21
  #runtimeImports = new Set();
17
22
  #lines = [];
18
23
  #parts = [];
@@ -72,7 +77,8 @@ class CompileContext {
72
77
  ...[...this.#imports].filter((item) => item !== 'Container').sort(),
73
78
  ...[...this.#pixiImports].sort(),
74
79
  ])];
75
- const lines = [`import { ${imports.join(', ')} } from 'pixi.js';`, `import { Group } from 'pixifact/runtime';`];
80
+ const runtimeImports = ['Group', ...[...this.#pixiRuntimeImports].sort()];
81
+ const lines = [`import { ${imports.join(', ')} } from 'pixi.js';`, `import { ${runtimeImports.join(', ')} } from 'pixifact/runtime';`];
76
82
  for (const [texture, variable] of this.#textureImportEntries()) {
77
83
  lines.push(`import ${variable} from ${JSON.stringify(this.options.textureImports?.[texture])};`);
78
84
  }
@@ -109,7 +115,12 @@ class CompileContext {
109
115
  }
110
116
  #collectImports(node) {
111
117
  if (node.kind === 'pixi') {
112
- this.#imports.add(node.type);
118
+ if (runtimeNodeTypes.has(node.type)) {
119
+ this.#pixiRuntimeImports.add(node.type);
120
+ }
121
+ else {
122
+ this.#imports.add(node.type);
123
+ }
113
124
  for (const child of node.children) {
114
125
  this.#collectImports(child);
115
126
  }
@@ -189,7 +200,7 @@ class CompileContext {
189
200
  const variable = node.id || this.#anonymousName(node.type);
190
201
  this.#lines.push(` const ${variable} = ${this.#constructPixiNode(node)};`);
191
202
  this.#applyNodeId(variable, node.id);
192
- this.#applyPixiProps(variable, node.props);
203
+ this.#applyPixiProps(variable, node.props, false, undefined, node.type);
193
204
  this.#applyParentSorting(parent, node.props);
194
205
  this.#lines.push(` ${parent}.addChild(${variable});`);
195
206
  for (const child of node.children) {
@@ -240,7 +251,7 @@ class CompileContext {
240
251
  const variable = node.id || this.#anonymousName(node.type);
241
252
  this.#lines.push(` const ${variable} = ${this.#constructPixiNode(node)};`);
242
253
  this.#applyNodeId(variable, node.id);
243
- this.#applyPixiProps(variable, node.props);
254
+ this.#applyPixiProps(variable, node.props, false, undefined, node.type);
244
255
  for (const child of node.children) {
245
256
  this.#compileNode(child, variable, actionsParameter);
246
257
  }
@@ -263,6 +274,9 @@ class CompileContext {
263
274
  if (node.type === 'TilingSprite') {
264
275
  return `new TilingSprite(${this.#spriteTextureOptions(node.props)})`;
265
276
  }
277
+ if (node.type === 'Image' || node.type === 'NineImage' || node.type === 'TileImage') {
278
+ return `new ${node.type}(${this.#spriteTextureOptions(node.props)})`;
279
+ }
266
280
  if (node.type === 'Graphics') {
267
281
  return 'new Graphics()';
268
282
  }
@@ -340,7 +354,7 @@ class CompileContext {
340
354
  #structContractSourceScene(instanceScene, contract) {
341
355
  return contract.sourceScene ?? instanceScene;
342
356
  }
343
- #applyPixiProps(variable, props, instance = false, sceneInstance) {
357
+ #applyPixiProps(variable, props, instance = false, sceneInstance, pixiType) {
344
358
  const x = props.x;
345
359
  const y = props.y;
346
360
  if (x !== undefined || y !== undefined) {
@@ -352,6 +366,7 @@ class CompileContext {
352
366
  if (props.height !== undefined && props.shape === undefined) {
353
367
  this.#lines.push(` ${variable}.height = ${this.#value(props.height)};`);
354
368
  }
369
+ this.#applyLayoutProps(variable, props);
355
370
  const scaleX = props.scaleX;
356
371
  const scaleY = props.scaleY;
357
372
  if (scaleX !== undefined || scaleY !== undefined) {
@@ -375,7 +390,16 @@ class CompileContext {
375
390
  this.#drawGraphics(variable, props);
376
391
  }
377
392
  for (const [key, value] of Object.entries(props)) {
393
+ if (!instance && runtimeNodeProps.has(key)) {
394
+ this.#lines.push(` ${variable}.${key} = ${this.#value(value)};`);
395
+ continue;
396
+ }
397
+ if (!instance && pixiType === 'Rect' && this.#isRectProp(key)) {
398
+ this.#lines.push(` ${variable}.${key} = ${this.#value(value)};`);
399
+ continue;
400
+ }
378
401
  const nativePixiNodeProp = transformProps.has(key)
402
+ || layoutProps.has(key)
379
403
  || spriteProps.has(key)
380
404
  || graphicsProps.has(key)
381
405
  || key === 'text'
@@ -386,6 +410,9 @@ class CompileContext {
386
410
  if (instance && transformProps.has(key)) {
387
411
  continue;
388
412
  }
413
+ if (instance && layoutProps.has(key)) {
414
+ continue;
415
+ }
389
416
  if (instance && value && typeof value === 'object') {
390
417
  this.#applyStructProp(variable, key, value, sceneInstance);
391
418
  continue;
@@ -397,8 +424,9 @@ class CompileContext {
397
424
  }
398
425
  #applyRootProps() {
399
426
  const props = { ...this.template.props };
400
- const width = props.width;
401
- const height = props.height;
427
+ const hasExplicitSize = props.width !== undefined || props.height !== undefined;
428
+ const width = hasExplicitSize ? props.width : this.options.defaultRootSize?.width;
429
+ const height = hasExplicitSize ? props.height : this.options.defaultRootSize?.height;
402
430
  delete props.width;
403
431
  delete props.height;
404
432
  if (width !== undefined || height !== undefined) {
@@ -406,6 +434,17 @@ class CompileContext {
406
434
  }
407
435
  this.#applyPixiProps('root', props);
408
436
  }
437
+ #applyLayoutProps(variable, props) {
438
+ const entries = pixiSceneLayoutProps.flatMap((key) => {
439
+ const value = props[key];
440
+ return value === undefined ? [] : [`${key}: ${this.#value(value)}`];
441
+ });
442
+ if (entries.length === 0) {
443
+ return;
444
+ }
445
+ this.#pixiRuntimeImports.add('setFrameLayout');
446
+ this.#lines.push(` setFrameLayout(${variable}, { ${entries.join(', ')} });`);
447
+ }
409
448
  #applyStructProp(variable, key, value, sceneInstance) {
410
449
  const contract = this.#sceneInstanceStructContract(sceneInstance, key);
411
450
  if (!contract) {
@@ -432,6 +471,9 @@ class CompileContext {
432
471
  if (props.tint !== undefined) {
433
472
  this.#lines.push(` ${variable}.tint = ${this.#value(props.tint)};`);
434
473
  }
474
+ if (props.fit !== undefined) {
475
+ this.#lines.push(` ${variable}.fit = ${this.#value(props.fit)};`);
476
+ }
435
477
  for (const key of ['leftWidth', 'rightWidth', 'topHeight', 'bottomHeight', 'tileRotation']) {
436
478
  const value = props[key];
437
479
  if (value !== undefined) {
@@ -519,6 +561,9 @@ class CompileContext {
519
561
  #isTextStyleProp(key) {
520
562
  return textStyleProps.has(key);
521
563
  }
564
+ #isRectProp(key) {
565
+ return rectProps.has(key);
566
+ }
522
567
  #anonymousName(type) {
523
568
  this.#nextId += 1;
524
569
  return `${type.charAt(0).toLowerCase()}${type.slice(1)}${this.#nextId}`;
@@ -1,3 +1,4 @@
1
+ import { type PixifactViewportConfig } from '../viewport.js';
1
2
  export declare const pixifactProjectConfigFileName = "pixifact.project.json";
2
3
  export interface PixifactProjectRunConfig {
3
4
  command: string;
@@ -13,15 +14,19 @@ export interface PixifactProjectConfig {
13
14
  version: 1;
14
15
  name: string;
15
16
  resolution: PixifactProjectResolution;
17
+ viewport: PixifactProjectViewport;
16
18
  scenes: Record<string, string>;
17
19
  run?: PixifactProjectRunConfig;
18
20
  }
21
+ export type PixifactProjectViewport = PixifactViewportConfig;
19
22
  export interface PixifactProjectSummary {
20
23
  name: string;
21
24
  resolution: PixifactProjectResolution;
25
+ viewport: PixifactProjectViewport;
22
26
  scenes: Record<string, string>;
23
27
  run?: PixifactProjectRunConfig;
24
28
  }
25
29
  export declare const defaultPixifactProjectResolution: PixifactProjectResolution;
30
+ export declare const defaultPixifactProjectViewport: PixifactProjectViewport;
26
31
  export declare function parsePixifactProjectConfig(value: unknown): PixifactProjectConfig;
27
32
  export declare function summarizePixifactProjectConfig(config: PixifactProjectConfig): PixifactProjectSummary;
@@ -1,8 +1,12 @@
1
+ import { pixifactViewportModes, } from '../viewport.js';
1
2
  export const pixifactProjectConfigFileName = 'pixifact.project.json';
2
3
  export const defaultPixifactProjectResolution = {
3
4
  width: 750,
4
5
  height: 1334,
5
6
  };
7
+ export const defaultPixifactProjectViewport = {
8
+ mode: 'showAll',
9
+ };
6
10
  function isRecord(value) {
7
11
  return typeof value === 'object' && value !== null && !Array.isArray(value);
8
12
  }
@@ -62,6 +66,22 @@ function parseResolution(value) {
62
66
  height: assertPositiveNumber(resolution.height, 'resolution.height'),
63
67
  };
64
68
  }
69
+ function parseViewport(value) {
70
+ if (value === undefined) {
71
+ return defaultPixifactProjectViewport;
72
+ }
73
+ const viewport = assertRecord(value, 'viewport');
74
+ const mode = assertString(viewport.mode, 'viewport.mode');
75
+ if (!isPixifactViewportMode(mode)) {
76
+ throw new Error(`viewport.mode must be one of ${pixifactViewportModes.join(', ')}`);
77
+ }
78
+ return {
79
+ mode,
80
+ };
81
+ }
82
+ function isPixifactViewportMode(value) {
83
+ return pixifactViewportModes.includes(value);
84
+ }
65
85
  function parseRun(value) {
66
86
  if (value === undefined) {
67
87
  return undefined;
@@ -86,6 +106,7 @@ export function parsePixifactProjectConfig(value) {
86
106
  version: 1,
87
107
  name: assertString(config.name, 'name'),
88
108
  resolution: parseResolution(config.resolution),
109
+ viewport: parseViewport(config.viewport),
89
110
  scenes: parseScenes(config.scenes),
90
111
  run: parseRun(config.run),
91
112
  };
@@ -94,6 +115,7 @@ export function summarizePixifactProjectConfig(config) {
94
115
  return {
95
116
  name: config.name,
96
117
  resolution: config.resolution,
118
+ viewport: config.viewport,
97
119
  scenes: config.scenes,
98
120
  ...(config.run === undefined ? {} : { run: config.run }),
99
121
  };
@@ -0,0 +1,18 @@
1
+ import type { GroupOptions } from './Group.js';
2
+ import { Group } from './Group.js';
3
+ export declare class Control extends Group {
4
+ #private;
5
+ constructor(options?: GroupOptions);
6
+ get left(): number | undefined;
7
+ set left(value: number | undefined);
8
+ get right(): number | undefined;
9
+ set right(value: number | undefined);
10
+ get top(): number | undefined;
11
+ set top(value: number | undefined);
12
+ get bottom(): number | undefined;
13
+ set bottom(value: number | undefined);
14
+ get horizontal(): number | undefined;
15
+ set horizontal(value: number | undefined);
16
+ get vertical(): number | undefined;
17
+ set vertical(value: number | undefined);
18
+ }
@@ -0,0 +1,49 @@
1
+ import { Group } from './Group.js';
2
+ import { getFrameLayout, setFrameLayout, } from './frameLayout.js';
3
+ export class Control extends Group {
4
+ constructor(options = {}) {
5
+ super(options);
6
+ }
7
+ get left() {
8
+ return this.#layoutProp('left');
9
+ }
10
+ set left(value) {
11
+ this.#setLayoutProp('left', value);
12
+ }
13
+ get right() {
14
+ return this.#layoutProp('right');
15
+ }
16
+ set right(value) {
17
+ this.#setLayoutProp('right', value);
18
+ }
19
+ get top() {
20
+ return this.#layoutProp('top');
21
+ }
22
+ set top(value) {
23
+ this.#setLayoutProp('top', value);
24
+ }
25
+ get bottom() {
26
+ return this.#layoutProp('bottom');
27
+ }
28
+ set bottom(value) {
29
+ this.#setLayoutProp('bottom', value);
30
+ }
31
+ get horizontal() {
32
+ return this.#layoutProp('horizontal');
33
+ }
34
+ set horizontal(value) {
35
+ this.#setLayoutProp('horizontal', value);
36
+ }
37
+ get vertical() {
38
+ return this.#layoutProp('vertical');
39
+ }
40
+ set vertical(value) {
41
+ this.#setLayoutProp('vertical', value);
42
+ }
43
+ #layoutProp(prop) {
44
+ return getFrameLayout(this)[prop];
45
+ }
46
+ #setLayoutProp(prop, value) {
47
+ setFrameLayout(this, { [prop]: value });
48
+ }
49
+ }
@@ -0,0 +1,25 @@
1
+ import { Control } from './Control.js';
2
+ import type { GroupOptions } from './Group.js';
3
+ export declare class GridContainer extends Control {
4
+ #private;
5
+ constructor(options?: GroupOptions);
6
+ get width(): number;
7
+ set width(value: number);
8
+ get height(): number;
9
+ set height(value: number);
10
+ setSize(value: number | {
11
+ width: number;
12
+ height?: number;
13
+ }, height?: number): void;
14
+ get columns(): number;
15
+ set columns(value: number);
16
+ get gapX(): number;
17
+ set gapX(value: number);
18
+ get gapY(): number;
19
+ set gapY(value: number);
20
+ get alignX(): string;
21
+ set alignX(value: string);
22
+ get alignY(): string;
23
+ set alignY(value: string);
24
+ layout(): void;
25
+ }
@@ -0,0 +1,115 @@
1
+ import { Control } from './Control.js';
2
+ import { finiteNumber, layoutChild, parseStackAlign, stackAlignOffset, stackChildSize, } from './stackLayout.js';
3
+ export class GridContainer extends Control {
4
+ #columns = 1;
5
+ #gapX = 0;
6
+ #gapY = 0;
7
+ #alignX = 'start';
8
+ #alignY = 'start';
9
+ #explicitWidth = false;
10
+ #explicitHeight = false;
11
+ constructor(options = {}) {
12
+ super(options);
13
+ this.#explicitWidth = options.width !== undefined;
14
+ this.#explicitHeight = options.height !== undefined;
15
+ }
16
+ get width() {
17
+ return super.width;
18
+ }
19
+ set width(value) {
20
+ this.#explicitWidth = true;
21
+ super.width = value;
22
+ }
23
+ get height() {
24
+ return super.height;
25
+ }
26
+ set height(value) {
27
+ this.#explicitHeight = true;
28
+ super.height = value;
29
+ }
30
+ setSize(value, height) {
31
+ this.#explicitWidth = true;
32
+ this.#explicitHeight = true;
33
+ super.setSize(value, height);
34
+ }
35
+ get columns() {
36
+ return this.#columns;
37
+ }
38
+ set columns(value) {
39
+ this.#columns = Math.max(1, Math.floor(finiteNumber(value, 1)));
40
+ this.layout();
41
+ }
42
+ get gapX() {
43
+ return this.#gapX;
44
+ }
45
+ set gapX(value) {
46
+ this.#gapX = Math.max(0, finiteNumber(value, 0));
47
+ this.layout();
48
+ }
49
+ get gapY() {
50
+ return this.#gapY;
51
+ }
52
+ set gapY(value) {
53
+ this.#gapY = Math.max(0, finiteNumber(value, 0));
54
+ this.layout();
55
+ }
56
+ get alignX() {
57
+ return this.#alignX;
58
+ }
59
+ set alignX(value) {
60
+ this.#alignX = parseStackAlign(value);
61
+ this.layout();
62
+ }
63
+ get alignY() {
64
+ return this.#alignY;
65
+ }
66
+ set alignY(value) {
67
+ this.#alignY = parseStackAlign(value);
68
+ this.layout();
69
+ }
70
+ layout() {
71
+ const children = this.children;
72
+ const metrics = measureGrid(children, this.#columns, this.#gapX, this.#gapY);
73
+ const width = this.#explicitWidth ? this.width : metrics.width;
74
+ const height = this.#explicitHeight ? this.height : metrics.height;
75
+ if (width !== this.width || height !== this.height) {
76
+ this.setBoxSize(width, height);
77
+ }
78
+ let rowY = 0;
79
+ for (let row = 0; row < metrics.rowHeights.length; row++) {
80
+ let columnX = 0;
81
+ for (let column = 0; column < metrics.columnWidths.length; column++) {
82
+ const child = children[row * this.#columns + column];
83
+ if (child) {
84
+ const size = stackChildSize(child);
85
+ child.position.set(columnX + stackAlignOffset(this.#alignX, Math.max(0, metrics.columnWidths[column] - size.width)), rowY + stackAlignOffset(this.#alignY, Math.max(0, metrics.rowHeights[row] - size.height)));
86
+ layoutChild(child);
87
+ }
88
+ columnX += metrics.columnWidths[column] + this.#gapX;
89
+ }
90
+ rowY += metrics.rowHeights[row] + this.#gapY;
91
+ }
92
+ }
93
+ }
94
+ function measureGrid(children, columns, gapX, gapY) {
95
+ if (children.length === 0) {
96
+ return { width: 0, height: 0, columnWidths: [], rowHeights: [] };
97
+ }
98
+ const columnCount = Math.min(columns, children.length);
99
+ const rowCount = Math.ceil(children.length / columns);
100
+ const columnWidths = Array.from({ length: columnCount }, () => 0);
101
+ const rowHeights = Array.from({ length: rowCount }, () => 0);
102
+ for (let index = 0; index < children.length; index++) {
103
+ const child = children[index];
104
+ const column = index % columns;
105
+ const row = Math.floor(index / columns);
106
+ columnWidths[column] = Math.max(columnWidths[column], child.width);
107
+ rowHeights[row] = Math.max(rowHeights[row], child.height);
108
+ }
109
+ return {
110
+ width: columnWidths.reduce((sum, width) => sum + width, 0) + gapX * Math.max(0, columnWidths.length - 1),
111
+ height: rowHeights.reduce((sum, height) => sum + height, 0) + gapY * Math.max(0, rowHeights.length - 1),
112
+ columnWidths,
113
+ rowHeights,
114
+ };
115
+ }
@@ -15,4 +15,6 @@ export declare class Group extends Container {
15
15
  width: number;
16
16
  height?: number;
17
17
  }, height?: number): void;
18
+ layout(): void;
19
+ protected setBoxSize(width: number, height: number): void;
18
20
  }
@@ -1,27 +1,30 @@
1
1
  import { Container, Rectangle } from 'pixi.js';
2
+ import { layoutFrameChildren } from './frameLayout.js';
2
3
  export class Group extends Container {
3
4
  #width = 0;
4
5
  #height = 0;
5
6
  constructor(options = {}) {
6
7
  const { width, height, ...containerOptions } = options;
7
8
  super(containerOptions);
9
+ this.on('childAdded', this.#layoutChildren, this);
10
+ this.on('childRemoved', this.#layoutChildren, this);
8
11
  if (width !== undefined || height !== undefined) {
9
- this.setSize(width ?? 0, height ?? 0);
12
+ this.#setBoxSize(width ?? 0, height ?? 0);
10
13
  }
11
14
  }
12
15
  get width() {
13
16
  return this.#width;
14
17
  }
15
18
  set width(value) {
16
- this.#width = value;
17
- this.#syncHitArea();
19
+ this.#setBoxSize(value, this.#height);
20
+ this.layout();
18
21
  }
19
22
  get height() {
20
23
  return this.#height;
21
24
  }
22
25
  set height(value) {
23
- this.#height = value;
24
- this.#syncHitArea();
26
+ this.#setBoxSize(this.#width, value);
27
+ this.layout();
25
28
  }
26
29
  getSize(out = { width: 0, height: 0 }) {
27
30
  out.width = this.#width;
@@ -30,16 +33,33 @@ export class Group extends Container {
30
33
  }
31
34
  setSize(value, height) {
32
35
  if (typeof value === 'number') {
33
- this.#width = value;
34
- this.#height = height ?? value;
36
+ this.#setBoxSize(value, height ?? value);
35
37
  }
36
38
  else {
37
- this.#width = value.width;
38
- this.#height = value.height ?? value.width;
39
+ this.#setBoxSize(value.width, value.height ?? value.width);
39
40
  }
41
+ this.layout();
42
+ }
43
+ layout() {
44
+ this.#applyFrameLayout();
45
+ }
46
+ setBoxSize(width, height) {
47
+ this.#setBoxSize(width, height);
48
+ }
49
+ #setBoxSize(width, height) {
50
+ this.#width = width;
51
+ this.#height = height;
40
52
  this.#syncHitArea();
41
53
  }
54
+ #layoutChildren() {
55
+ this.layout();
56
+ }
57
+ #applyFrameLayout() {
58
+ layoutFrameChildren(this);
59
+ }
42
60
  #syncHitArea() {
43
- this.hitArea = new Rectangle(0, 0, this.#width, this.#height);
61
+ const bounds = new Rectangle(0, 0, this.#width, this.#height);
62
+ this.hitArea = bounds;
63
+ this.boundsArea = bounds;
44
64
  }
45
65
  }
@@ -0,0 +1,21 @@
1
+ import { Control } from './Control.js';
2
+ import type { GroupOptions } from './Group.js';
3
+ export declare class HBoxContainer extends Control {
4
+ #private;
5
+ constructor(options?: GroupOptions);
6
+ get width(): number;
7
+ set width(value: number);
8
+ get height(): number;
9
+ set height(value: number);
10
+ setSize(value: number | {
11
+ width: number;
12
+ height?: number;
13
+ }, height?: number): void;
14
+ get gap(): number;
15
+ set gap(value: number);
16
+ get alignY(): string;
17
+ set alignY(value: string);
18
+ get justify(): string;
19
+ set justify(value: string);
20
+ layout(): void;
21
+ }
@@ -0,0 +1,74 @@
1
+ import { Control } from './Control.js';
2
+ import { finiteNumber, layoutChild, measureStackNaturalSize, parseStackAlign, parseStackJustify, stackAlignOffset, stackChildSize, stackJustifyOffset, } from './stackLayout.js';
3
+ export class HBoxContainer extends Control {
4
+ #gap = 0;
5
+ #alignY = 'start';
6
+ #justify = 'start';
7
+ #explicitWidth = false;
8
+ #explicitHeight = false;
9
+ constructor(options = {}) {
10
+ super(options);
11
+ this.#explicitWidth = options.width !== undefined;
12
+ this.#explicitHeight = options.height !== undefined;
13
+ }
14
+ get width() {
15
+ return super.width;
16
+ }
17
+ set width(value) {
18
+ this.#explicitWidth = true;
19
+ super.width = value;
20
+ }
21
+ get height() {
22
+ return super.height;
23
+ }
24
+ set height(value) {
25
+ this.#explicitHeight = true;
26
+ super.height = value;
27
+ }
28
+ setSize(value, height) {
29
+ this.#explicitWidth = true;
30
+ this.#explicitHeight = true;
31
+ super.setSize(value, height);
32
+ }
33
+ get gap() {
34
+ return this.#gap;
35
+ }
36
+ set gap(value) {
37
+ this.#gap = Math.max(0, finiteNumber(value, 0));
38
+ this.layout();
39
+ }
40
+ get alignY() {
41
+ return this.#alignY;
42
+ }
43
+ set alignY(value) {
44
+ this.#alignY = parseStackAlign(value);
45
+ this.layout();
46
+ }
47
+ get justify() {
48
+ return this.#justify;
49
+ }
50
+ set justify(value) {
51
+ this.#justify = parseStackJustify(value);
52
+ this.layout();
53
+ }
54
+ layout() {
55
+ const children = this.children;
56
+ const natural = measureStackNaturalSize('horizontal', children, this.#gap);
57
+ const width = this.#explicitWidth ? this.width : natural.width;
58
+ const height = this.#explicitHeight ? this.height : natural.height;
59
+ if (width !== this.width || height !== this.height) {
60
+ this.setBoxSize(width, height);
61
+ }
62
+ const remaining = Math.max(0, width - natural.width);
63
+ const gap = this.#justify === 'space-between' && children.length > 1
64
+ ? this.#gap + remaining / (children.length - 1)
65
+ : this.#gap;
66
+ let cursor = stackJustifyOffset(this.#justify, remaining);
67
+ for (const child of children) {
68
+ const size = stackChildSize(child);
69
+ child.position.set(cursor, stackAlignOffset(this.#alignY, Math.max(0, height - size.height)));
70
+ layoutChild(child);
71
+ cursor += size.width + gap;
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,47 @@
1
+ import { Mesh, Texture, type MeshOptions, type Size } from 'pixi.js';
2
+ export type ImageFit = 'stretch' | 'contain' | 'cover' | 'none';
3
+ export type ImageOptions = Omit<MeshOptions, 'geometry' | 'texture'> & {
4
+ texture?: Texture;
5
+ width?: number;
6
+ height?: number;
7
+ fit?: ImageFit;
8
+ anchorX?: number;
9
+ anchorY?: number;
10
+ };
11
+ export declare class Image extends Mesh {
12
+ #private;
13
+ constructor(options?: ImageOptions);
14
+ get width(): number;
15
+ set width(value: number);
16
+ get height(): number;
17
+ set height(value: number);
18
+ getSize(out?: Size): Size;
19
+ setSize(value: number | {
20
+ width: number;
21
+ height?: number;
22
+ }, height?: number): void;
23
+ get texture(): Texture;
24
+ set texture(value: Texture);
25
+ get fit(): ImageFit;
26
+ set fit(value: ImageFit);
27
+ get anchor(): {
28
+ x: number;
29
+ y: number;
30
+ } | number;
31
+ set anchor(value: {
32
+ x: number;
33
+ y: number;
34
+ } | number);
35
+ get left(): number | undefined;
36
+ set left(value: number | undefined);
37
+ get right(): number | undefined;
38
+ set right(value: number | undefined);
39
+ get top(): number | undefined;
40
+ set top(value: number | undefined);
41
+ get bottom(): number | undefined;
42
+ set bottom(value: number | undefined);
43
+ get horizontal(): number | undefined;
44
+ set horizontal(value: number | undefined);
45
+ get vertical(): number | undefined;
46
+ set vertical(value: number | undefined);
47
+ }