pixifact 0.1.3 → 0.1.5

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 (185) hide show
  1. package/README.md +3 -3
  2. package/dist/builtin-scenes/CenterContainer.d.ts +13 -0
  3. package/dist/builtin-scenes/CenterContainer.js +71 -0
  4. package/dist/builtin-scenes/CenterContainer.scene +3 -0
  5. package/dist/builtin-scenes/CenterContainer.ts +77 -0
  6. package/dist/builtin-scenes/Control.d.ts +44 -0
  7. package/dist/builtin-scenes/Control.js +206 -0
  8. package/dist/builtin-scenes/Control.scene +3 -0
  9. package/dist/builtin-scenes/Control.ts +233 -0
  10. package/dist/builtin-scenes/FlexItem.d.ts +45 -0
  11. package/dist/builtin-scenes/FlexItem.js +164 -0
  12. package/dist/builtin-scenes/FlexItem.scene +3 -0
  13. package/dist/builtin-scenes/FlexItem.ts +180 -0
  14. package/dist/builtin-scenes/FlexLayout.d.ts +29 -0
  15. package/dist/builtin-scenes/FlexLayout.js +358 -0
  16. package/dist/builtin-scenes/FlexLayout.scene +3 -0
  17. package/dist/builtin-scenes/FlexLayout.ts +409 -0
  18. package/dist/builtin-scenes/HBoxContainer.d.ts +15 -0
  19. package/dist/builtin-scenes/HBoxContainer.js +70 -0
  20. package/dist/builtin-scenes/HBoxContainer.scene +3 -0
  21. package/dist/builtin-scenes/HBoxContainer.ts +75 -0
  22. package/dist/builtin-scenes/MarginContainer.d.ts +23 -0
  23. package/dist/builtin-scenes/MarginContainer.js +131 -0
  24. package/dist/builtin-scenes/MarginContainer.scene +3 -0
  25. package/dist/builtin-scenes/MarginContainer.ts +139 -0
  26. package/dist/builtin-scenes/VBoxContainer.d.ts +15 -0
  27. package/dist/builtin-scenes/VBoxContainer.js +70 -0
  28. package/dist/builtin-scenes/VBoxContainer.scene +3 -0
  29. package/dist/builtin-scenes/VBoxContainer.ts +75 -0
  30. package/dist/builtin-scenes/controlLayout.d.ts +57 -0
  31. package/dist/builtin-scenes/controlLayout.js +202 -0
  32. package/dist/builtin-scenes/controlLayout.ts +270 -0
  33. package/dist/compiler/builtinScenes.d.ts +14 -0
  34. package/dist/compiler/builtinScenes.js +62 -0
  35. package/dist/compiler/compileScenes.js +126 -19
  36. package/dist/compiler/decorators.js +3 -0
  37. package/dist/compiler/index.d.ts +1 -0
  38. package/dist/compiler/index.js +1 -0
  39. package/dist/compiler/sceneAssetPair.js +19 -0
  40. package/dist/compiler/sceneRuntime.d.ts +5 -4
  41. package/dist/compiler/scriptInterfaceExtractor.d.ts +5 -0
  42. package/dist/compiler/scriptInterfaceExtractor.js +214 -8
  43. package/dist/compiler/spec.d.ts +2 -0
  44. package/dist/compiler/templateParser.js +4 -3
  45. package/dist/compiler/templateSerializer.js +2 -1
  46. package/dist/compiler/typescriptCompiler.js +87 -13
  47. package/dist/compiler-node/builtinSceneAssets.d.ts +8 -0
  48. package/dist/compiler-node/builtinSceneAssets.js +57 -0
  49. package/dist/compiler-node/index.d.ts +1 -0
  50. package/dist/compiler-node/index.js +1 -0
  51. package/dist/errors.d.ts +1 -0
  52. package/dist/errors.js +24 -0
  53. package/dist/index.d.ts +2 -5
  54. package/dist/index.js +2 -5
  55. package/dist/{authoring/ProjectConfig.d.ts → project/index.d.ts} +7 -0
  56. package/dist/{authoring/ProjectConfig.js → project/index.js} +22 -0
  57. package/dist/runtime/Group.d.ts +18 -0
  58. package/dist/runtime/Group.js +45 -0
  59. package/dist/runtime/index.d.ts +1 -9
  60. package/dist/runtime/index.js +1 -9
  61. package/package.json +4 -26
  62. package/dist/authoring/ActionRegistry.d.ts +0 -10
  63. package/dist/authoring/ActionRegistry.js +0 -26
  64. package/dist/authoring/ComponentPalette.d.ts +0 -16
  65. package/dist/authoring/ComponentPalette.js +0 -50
  66. package/dist/authoring/DesignToken.d.ts +0 -16
  67. package/dist/authoring/DesignToken.js +0 -33
  68. package/dist/authoring/DiffModel.d.ts +0 -10
  69. package/dist/authoring/DiffModel.js +0 -104
  70. package/dist/authoring/InspectorModel.d.ts +0 -27
  71. package/dist/authoring/InspectorModel.js +0 -139
  72. package/dist/authoring/LogicGraph.d.ts +0 -66
  73. package/dist/authoring/LogicGraph.js +0 -184
  74. package/dist/authoring/OverrideJournal.d.ts +0 -18
  75. package/dist/authoring/OverrideJournal.js +0 -23
  76. package/dist/authoring/PreferenceMemory.d.ts +0 -21
  77. package/dist/authoring/PreferenceMemory.js +0 -39
  78. package/dist/authoring/RuntimePreview.d.ts +0 -11
  79. package/dist/authoring/RuntimePreview.js +0 -25
  80. package/dist/authoring/SceneDocument.d.ts +0 -74
  81. package/dist/authoring/SceneDocument.js +0 -332
  82. package/dist/authoring/SceneProjectState.d.ts +0 -20
  83. package/dist/authoring/SceneProjectState.js +0 -8
  84. package/dist/authoring/SceneSelection.d.ts +0 -11
  85. package/dist/authoring/SceneSelection.js +0 -1
  86. package/dist/authoring/index.d.ts +0 -13
  87. package/dist/authoring/index.js +0 -13
  88. package/dist/commands/Command.d.ts +0 -63
  89. package/dist/commands/Command.js +0 -1
  90. package/dist/commands/CommandStack.d.ts +0 -14
  91. package/dist/commands/CommandStack.js +0 -45
  92. package/dist/commands/applyCommand.d.ts +0 -23
  93. package/dist/commands/applyCommand.js +0 -316
  94. package/dist/commands/index.d.ts +0 -4
  95. package/dist/commands/index.js +0 -4
  96. package/dist/commands/utils.d.ts +0 -17
  97. package/dist/commands/utils.js +0 -77
  98. package/dist/commands/validateCommand.d.ts +0 -12
  99. package/dist/commands/validateCommand.js +0 -285
  100. package/dist/nodes/Input.d.ts +0 -91
  101. package/dist/nodes/Input.js +0 -352
  102. package/dist/nodes/Textarea.d.ts +0 -10
  103. package/dist/nodes/Textarea.js +0 -38
  104. package/dist/nodes/components/Button.d.ts +0 -8
  105. package/dist/nodes/components/Button.js +0 -45
  106. package/dist/nodes/components/InputField.d.ts +0 -12
  107. package/dist/nodes/components/InputField.js +0 -53
  108. package/dist/nodes/components/ProgressBar.d.ts +0 -14
  109. package/dist/nodes/components/ProgressBar.js +0 -66
  110. package/dist/nodes/components/ScrollRect.d.ts +0 -23
  111. package/dist/nodes/components/ScrollRect.js +0 -108
  112. package/dist/nodes/components/Selectable.d.ts +0 -32
  113. package/dist/nodes/components/Selectable.js +0 -153
  114. package/dist/nodes/components/index.d.ts +0 -5
  115. package/dist/nodes/components/index.js +0 -5
  116. package/dist/nodes/graphics/Graphic.d.ts +0 -14
  117. package/dist/nodes/graphics/Graphic.js +0 -47
  118. package/dist/nodes/graphics/ImageGraphic.d.ts +0 -9
  119. package/dist/nodes/graphics/ImageGraphic.js +0 -42
  120. package/dist/nodes/graphics/RoundedRectGraphic.d.ts +0 -13
  121. package/dist/nodes/graphics/RoundedRectGraphic.js +0 -65
  122. package/dist/nodes/graphics/TextGraphic.d.ts +0 -13
  123. package/dist/nodes/graphics/TextGraphic.js +0 -70
  124. package/dist/nodes/graphics/index.d.ts +0 -4
  125. package/dist/nodes/graphics/index.js +0 -4
  126. package/dist/nodes/index.d.ts +0 -3
  127. package/dist/nodes/index.js +0 -3
  128. package/dist/runtime/Application.d.ts +0 -15
  129. package/dist/runtime/Application.js +0 -67
  130. package/dist/runtime/GameObject.d.ts +0 -100
  131. package/dist/runtime/GameObject.js +0 -297
  132. package/dist/runtime/GameObjectEvent.d.ts +0 -52
  133. package/dist/runtime/GameObjectEvent.js +0 -38
  134. package/dist/runtime/Transform.d.ts +0 -32
  135. package/dist/runtime/Transform.js +0 -157
  136. package/dist/runtime/component/Component.d.ts +0 -13
  137. package/dist/runtime/component/Component.js +0 -13
  138. package/dist/runtime/component/ComponentRegistry.d.ts +0 -49
  139. package/dist/runtime/component/ComponentRegistry.js +0 -25
  140. package/dist/runtime/component/Flex.d.ts +0 -6
  141. package/dist/runtime/component/Flex.js +0 -15
  142. package/dist/runtime/component/FlexGroup.d.ts +0 -26
  143. package/dist/runtime/component/FlexGroup.js +0 -136
  144. package/dist/runtime/component/GridLayout.d.ts +0 -47
  145. package/dist/runtime/component/GridLayout.js +0 -129
  146. package/dist/runtime/component/Layout.d.ts +0 -104
  147. package/dist/runtime/component/Layout.js +0 -248
  148. package/dist/runtime/component/decorators.d.ts +0 -4
  149. package/dist/runtime/component/decorators.js +0 -52
  150. package/dist/runtime/component/index.d.ts +0 -7
  151. package/dist/runtime/component/index.js +0 -7
  152. package/dist/runtime/graphics/index.d.ts +0 -11
  153. package/dist/runtime/graphics/index.js +0 -29
  154. package/dist/runtime/group/index.d.ts +0 -39
  155. package/dist/runtime/group/index.js +0 -102
  156. package/dist/runtime/image/Image.d.ts +0 -19
  157. package/dist/runtime/image/Image.js +0 -60
  158. package/dist/runtime/image/NineSliceImage.d.ts +0 -23
  159. package/dist/runtime/image/NineSliceImage.js +0 -64
  160. package/dist/runtime/image/index.d.ts +0 -2
  161. package/dist/runtime/image/index.js +0 -2
  162. package/dist/runtime/label/Label.d.ts +0 -11
  163. package/dist/runtime/label/Label.js +0 -26
  164. package/dist/runtime/label/LabelStyle.d.ts +0 -3
  165. package/dist/runtime/label/LabelStyle.js +0 -3
  166. package/dist/runtime/label/index.d.ts +0 -2
  167. package/dist/runtime/label/index.js +0 -2
  168. package/dist/runtime/utils/index.d.ts +0 -1
  169. package/dist/runtime/utils/index.js +0 -1
  170. package/dist/runtime/utils/onlyOnceQueueMicrotask.d.ts +0 -1
  171. package/dist/runtime/utils/onlyOnceQueueMicrotask.js +0 -12
  172. package/dist/runtime/utils/setProps.d.ts +0 -10
  173. package/dist/runtime/utils/setProps.js +0 -17
  174. package/dist/scene/dsl.d.ts +0 -25
  175. package/dist/scene/dsl.js +0 -112
  176. package/dist/scene/index.d.ts +0 -5
  177. package/dist/scene/index.js +0 -5
  178. package/dist/scene/instantiate.d.ts +0 -8
  179. package/dist/scene/instantiate.js +0 -208
  180. package/dist/scene/nodesDsl.d.ts +0 -42
  181. package/dist/scene/nodesDsl.js +0 -169
  182. package/dist/scene/spec.d.ts +0 -100
  183. package/dist/scene/spec.js +0 -1
  184. package/dist/scene/templates.d.ts +0 -21
  185. package/dist/scene/templates.js +0 -239
@@ -0,0 +1,202 @@
1
+ export const defaultControlLayoutProps = {
2
+ minWidth: 0,
3
+ minHeight: 0,
4
+ hSize: 'content',
5
+ vSize: 'content',
6
+ stretch: 1,
7
+ };
8
+ export function measureChildren(container) {
9
+ const bounds = measureChildrenBounds(container);
10
+ return {
11
+ width: bounds.width,
12
+ height: bounds.height,
13
+ };
14
+ }
15
+ export function measureChildrenBounds(container) {
16
+ if (container.children.length === 0) {
17
+ return { x: 0, y: 0, width: 0, height: 0 };
18
+ }
19
+ let minX = Number.POSITIVE_INFINITY;
20
+ let minY = Number.POSITIVE_INFINITY;
21
+ let maxX = Number.NEGATIVE_INFINITY;
22
+ let maxY = Number.NEGATIVE_INFINITY;
23
+ for (const child of container.children) {
24
+ minX = Math.min(minX, child.x);
25
+ minY = Math.min(minY, child.y);
26
+ maxX = Math.max(maxX, child.x + child.width);
27
+ maxY = Math.max(maxY, child.y + child.height);
28
+ }
29
+ return {
30
+ x: minX,
31
+ y: minY,
32
+ width: maxX - minX,
33
+ height: maxY - minY,
34
+ };
35
+ }
36
+ export function measureControlChild(child, baseSizes) {
37
+ const measured = child.measureControlNaturalSize?.();
38
+ if (measured) {
39
+ return measured;
40
+ }
41
+ if (!baseSizes.has(child)) {
42
+ baseSizes.set(child, {
43
+ width: child.width,
44
+ height: child.height,
45
+ });
46
+ }
47
+ return baseSizes.get(child) ?? { width: child.width, height: child.height };
48
+ }
49
+ export function controlChildProps(child) {
50
+ return child.getControlLayoutProps?.() ?? defaultControlLayoutProps;
51
+ }
52
+ export function controlMinSize(child, baseSizes) {
53
+ const props = controlChildProps(child);
54
+ const natural = measureControlChild(child, baseSizes);
55
+ return {
56
+ width: Math.max(natural.width, props.minWidth),
57
+ height: Math.max(natural.height, props.minHeight),
58
+ };
59
+ }
60
+ export function setControlChildBox(child, rect) {
61
+ if (child.setControlLayoutBox) {
62
+ child.setControlLayoutBox(rect.x, rect.y, rect.width, rect.height);
63
+ return;
64
+ }
65
+ child.position.set(rect.x, rect.y);
66
+ }
67
+ export function layoutStack(options) {
68
+ const children = options.children;
69
+ if (!children || children.length === 0) {
70
+ return;
71
+ }
72
+ const mainAxis = options.axis;
73
+ const cross = crossAxis(mainAxis);
74
+ const naturalContainer = options.naturalSize();
75
+ const containerWidth = options.width ?? naturalContainer.width;
76
+ const containerHeight = options.height ?? naturalContainer.height;
77
+ const containerMain = mainAxis === 'horizontal' ? containerWidth : containerHeight;
78
+ const containerCross = cross === 'horizontal' ? containerWidth : containerHeight;
79
+ const gapTotal = options.gap * Math.max(0, children.length - 1);
80
+ const items = children.map((child) => {
81
+ const props = controlChildProps(child);
82
+ const natural = controlMinSize(child, options.baseSizes);
83
+ const mainMode = mainAxis === 'horizontal' ? props.hSize : props.vSize;
84
+ const crossMode = cross === 'horizontal' ? props.hSize : props.vSize;
85
+ return {
86
+ child,
87
+ props,
88
+ mainMode,
89
+ crossMode,
90
+ baseMain: mainAxis === 'horizontal' ? natural.width : natural.height,
91
+ baseCross: cross === 'horizontal' ? natural.width : natural.height,
92
+ mainSize: mainAxis === 'horizontal' ? natural.width : natural.height,
93
+ crossSize: cross === 'horizontal' ? natural.width : natural.height,
94
+ };
95
+ });
96
+ const naturalMain = items.reduce((sum, item) => sum + item.baseMain, 0);
97
+ const freeMain = Math.max(0, containerMain - naturalMain - gapTotal);
98
+ const expandable = items.filter((item) => item.mainMode === 'expand');
99
+ const totalStretch = expandable.reduce((sum, item) => sum + item.props.stretch, 0);
100
+ if (totalStretch > 0) {
101
+ for (const item of expandable) {
102
+ item.mainSize = item.baseMain + freeMain * (item.props.stretch / totalStretch);
103
+ }
104
+ }
105
+ for (const item of items) {
106
+ if (fillOrExpand(item.crossMode)) {
107
+ item.crossSize = containerCross;
108
+ }
109
+ }
110
+ const usedMain = items.reduce((sum, item) => sum + item.mainSize, 0) + gapTotal;
111
+ const remainingMain = Math.max(0, containerMain - usedMain);
112
+ const actualGap = options.justify === 'space-between' && items.length > 1
113
+ ? options.gap + remainingMain / (items.length - 1)
114
+ : options.gap;
115
+ let cursor = justifyOffset(options.justify, remainingMain);
116
+ for (const item of items) {
117
+ const crossFree = Math.max(0, containerCross - item.crossSize);
118
+ const main = cursor;
119
+ const crossPosition = alignOffset(options.alignCross, crossFree);
120
+ setControlChildBox(item.child, {
121
+ x: mainAxis === 'horizontal' ? main : crossPosition,
122
+ y: mainAxis === 'horizontal' ? crossPosition : main,
123
+ width: mainAxis === 'horizontal' ? item.mainSize : item.crossSize,
124
+ height: mainAxis === 'horizontal' ? item.crossSize : item.mainSize,
125
+ });
126
+ cursor += item.mainSize + actualGap;
127
+ }
128
+ }
129
+ export function measureStackNaturalSize(axis, children, gap, baseSizes) {
130
+ if (!children || children.length === 0) {
131
+ return { width: 0, height: 0 };
132
+ }
133
+ const gapTotal = gap * Math.max(0, children.length - 1);
134
+ const sizes = children.map((child) => controlMinSize(child, baseSizes));
135
+ if (axis === 'horizontal') {
136
+ return {
137
+ width: sizes.reduce((sum, size) => sum + size.width, 0) + gapTotal,
138
+ height: sizes.reduce((max, size) => Math.max(max, size.height), 0),
139
+ };
140
+ }
141
+ return {
142
+ width: sizes.reduce((max, size) => Math.max(max, size.width), 0),
143
+ height: sizes.reduce((sum, size) => sum + size.height, 0) + gapTotal,
144
+ };
145
+ }
146
+ export function fillOrExpand(mode) {
147
+ return mode === 'fill' || mode === 'expand';
148
+ }
149
+ export function crossAxis(axis) {
150
+ return axis === 'horizontal' ? 'vertical' : 'horizontal';
151
+ }
152
+ export function alignOffset(align, available) {
153
+ if (align === 'center') {
154
+ return available / 2;
155
+ }
156
+ if (align === 'end') {
157
+ return available;
158
+ }
159
+ return 0;
160
+ }
161
+ export function justifyOffset(justify, available) {
162
+ if (justify === 'center') {
163
+ return available / 2;
164
+ }
165
+ if (justify === 'end') {
166
+ return available;
167
+ }
168
+ return 0;
169
+ }
170
+ export function parseSizeMode(value) {
171
+ if (value === 'fill' || value === 'expand') {
172
+ return value;
173
+ }
174
+ return 'content';
175
+ }
176
+ export function parseAlign(value) {
177
+ if (value === 'center' || value === 'end') {
178
+ return value;
179
+ }
180
+ return 'start';
181
+ }
182
+ export function parseJustify(value) {
183
+ if (value === 'center' || value === 'end' || value === 'space-between') {
184
+ return value;
185
+ }
186
+ return 'start';
187
+ }
188
+ export function finiteNumber(value, fallback) {
189
+ return Number.isFinite(value) ? value : fallback;
190
+ }
191
+ export function boxSize(value, height) {
192
+ if (typeof value === 'number') {
193
+ return {
194
+ width: Math.max(0, finiteNumber(value, 0)),
195
+ height: Math.max(0, finiteNumber(height ?? value, 0)),
196
+ };
197
+ }
198
+ return {
199
+ width: Math.max(0, finiteNumber(value.width, 0)),
200
+ height: Math.max(0, finiteNumber(value.height ?? value.width, 0)),
201
+ };
202
+ }
@@ -0,0 +1,270 @@
1
+ import { Container, type ContainerChild } from 'pixi.js';
2
+
3
+ export type ControlSizeMode = 'content' | 'fill' | 'expand';
4
+ export type ControlAlign = 'start' | 'center' | 'end';
5
+ export type ControlJustify = 'start' | 'center' | 'end' | 'space-between';
6
+ export type ControlAxis = 'horizontal' | 'vertical';
7
+
8
+ export interface Size {
9
+ width: number;
10
+ height: number;
11
+ }
12
+
13
+ export interface Rect extends Size {
14
+ x: number;
15
+ y: number;
16
+ }
17
+
18
+ export interface ControlLayoutProps {
19
+ minWidth: number;
20
+ minHeight: number;
21
+ hSize: ControlSizeMode;
22
+ vSize: ControlSizeMode;
23
+ stretch: number;
24
+ }
25
+
26
+ export type ControlLayoutChild = ContainerChild & {
27
+ getControlLayoutProps?: () => ControlLayoutProps;
28
+ measureControlNaturalSize?: () => Size;
29
+ setControlLayoutBox?: (x: number, y: number, width: number, height: number) => void;
30
+ };
31
+
32
+ export const defaultControlLayoutProps: ControlLayoutProps = {
33
+ minWidth: 0,
34
+ minHeight: 0,
35
+ hSize: 'content',
36
+ vSize: 'content',
37
+ stretch: 1,
38
+ };
39
+
40
+ export function measureChildren(container: Container): Size {
41
+ const bounds = measureChildrenBounds(container);
42
+ return {
43
+ width: bounds.width,
44
+ height: bounds.height,
45
+ };
46
+ }
47
+
48
+ export function measureChildrenBounds(container: Container): Rect {
49
+ if (container.children.length === 0) {
50
+ return { x: 0, y: 0, width: 0, height: 0 };
51
+ }
52
+
53
+ let minX = Number.POSITIVE_INFINITY;
54
+ let minY = Number.POSITIVE_INFINITY;
55
+ let maxX = Number.NEGATIVE_INFINITY;
56
+ let maxY = Number.NEGATIVE_INFINITY;
57
+
58
+ for (const child of container.children) {
59
+ minX = Math.min(minX, child.x);
60
+ minY = Math.min(minY, child.y);
61
+ maxX = Math.max(maxX, child.x + child.width);
62
+ maxY = Math.max(maxY, child.y + child.height);
63
+ }
64
+
65
+ return {
66
+ x: minX,
67
+ y: minY,
68
+ width: maxX - minX,
69
+ height: maxY - minY,
70
+ };
71
+ }
72
+
73
+ export function measureControlChild(child: ControlLayoutChild, baseSizes: WeakMap<object, Size>): Size {
74
+ const measured = child.measureControlNaturalSize?.();
75
+ if (measured) {
76
+ return measured;
77
+ }
78
+ if (!baseSizes.has(child)) {
79
+ baseSizes.set(child, {
80
+ width: child.width,
81
+ height: child.height,
82
+ });
83
+ }
84
+ return baseSizes.get(child) ?? { width: child.width, height: child.height };
85
+ }
86
+
87
+ export function controlChildProps(child: ControlLayoutChild): ControlLayoutProps {
88
+ return child.getControlLayoutProps?.() ?? defaultControlLayoutProps;
89
+ }
90
+
91
+ export function controlMinSize(child: ControlLayoutChild, baseSizes: WeakMap<object, Size>): Size {
92
+ const props = controlChildProps(child);
93
+ const natural = measureControlChild(child, baseSizes);
94
+ return {
95
+ width: Math.max(natural.width, props.minWidth),
96
+ height: Math.max(natural.height, props.minHeight),
97
+ };
98
+ }
99
+
100
+ export function setControlChildBox(child: ControlLayoutChild, rect: Rect) {
101
+ if (child.setControlLayoutBox) {
102
+ child.setControlLayoutBox(rect.x, rect.y, rect.width, rect.height);
103
+ return;
104
+ }
105
+ child.position.set(rect.x, rect.y);
106
+ }
107
+
108
+ export interface StackLayoutOptions {
109
+ axis: ControlAxis;
110
+ children: ControlLayoutChild[] | undefined;
111
+ gap: number;
112
+ alignCross: ControlAlign;
113
+ justify: ControlJustify;
114
+ baseSizes: WeakMap<object, Size>;
115
+ width: number | undefined;
116
+ height: number | undefined;
117
+ naturalSize: () => Size;
118
+ }
119
+
120
+ export function layoutStack(options: StackLayoutOptions) {
121
+ const children = options.children;
122
+ if (!children || children.length === 0) {
123
+ return;
124
+ }
125
+
126
+ const mainAxis = options.axis;
127
+ const cross = crossAxis(mainAxis);
128
+ const naturalContainer = options.naturalSize();
129
+ const containerWidth = options.width ?? naturalContainer.width;
130
+ const containerHeight = options.height ?? naturalContainer.height;
131
+ const containerMain = mainAxis === 'horizontal' ? containerWidth : containerHeight;
132
+ const containerCross = cross === 'horizontal' ? containerWidth : containerHeight;
133
+ const gapTotal = options.gap * Math.max(0, children.length - 1);
134
+ const items = children.map((child) => {
135
+ const props = controlChildProps(child);
136
+ const natural = controlMinSize(child, options.baseSizes);
137
+ const mainMode = mainAxis === 'horizontal' ? props.hSize : props.vSize;
138
+ const crossMode = cross === 'horizontal' ? props.hSize : props.vSize;
139
+ return {
140
+ child,
141
+ props,
142
+ mainMode,
143
+ crossMode,
144
+ baseMain: mainAxis === 'horizontal' ? natural.width : natural.height,
145
+ baseCross: cross === 'horizontal' ? natural.width : natural.height,
146
+ mainSize: mainAxis === 'horizontal' ? natural.width : natural.height,
147
+ crossSize: cross === 'horizontal' ? natural.width : natural.height,
148
+ };
149
+ });
150
+ const naturalMain = items.reduce((sum, item) => sum + item.baseMain, 0);
151
+ const freeMain = Math.max(0, containerMain - naturalMain - gapTotal);
152
+ const expandable = items.filter((item) => item.mainMode === 'expand');
153
+ const totalStretch = expandable.reduce((sum, item) => sum + item.props.stretch, 0);
154
+
155
+ if (totalStretch > 0) {
156
+ for (const item of expandable) {
157
+ item.mainSize = item.baseMain + freeMain * (item.props.stretch / totalStretch);
158
+ }
159
+ }
160
+
161
+ for (const item of items) {
162
+ if (fillOrExpand(item.crossMode)) {
163
+ item.crossSize = containerCross;
164
+ }
165
+ }
166
+
167
+ const usedMain = items.reduce((sum, item) => sum + item.mainSize, 0) + gapTotal;
168
+ const remainingMain = Math.max(0, containerMain - usedMain);
169
+ const actualGap = options.justify === 'space-between' && items.length > 1
170
+ ? options.gap + remainingMain / (items.length - 1)
171
+ : options.gap;
172
+ let cursor = justifyOffset(options.justify, remainingMain);
173
+
174
+ for (const item of items) {
175
+ const crossFree = Math.max(0, containerCross - item.crossSize);
176
+ const main = cursor;
177
+ const crossPosition = alignOffset(options.alignCross, crossFree);
178
+ setControlChildBox(item.child, {
179
+ x: mainAxis === 'horizontal' ? main : crossPosition,
180
+ y: mainAxis === 'horizontal' ? crossPosition : main,
181
+ width: mainAxis === 'horizontal' ? item.mainSize : item.crossSize,
182
+ height: mainAxis === 'horizontal' ? item.crossSize : item.mainSize,
183
+ });
184
+ cursor += item.mainSize + actualGap;
185
+ }
186
+ }
187
+
188
+ export function measureStackNaturalSize(axis: ControlAxis, children: ControlLayoutChild[] | undefined, gap: number, baseSizes: WeakMap<object, Size>): Size {
189
+ if (!children || children.length === 0) {
190
+ return { width: 0, height: 0 };
191
+ }
192
+ const gapTotal = gap * Math.max(0, children.length - 1);
193
+ const sizes = children.map((child) => controlMinSize(child, baseSizes));
194
+ if (axis === 'horizontal') {
195
+ return {
196
+ width: sizes.reduce((sum, size) => sum + size.width, 0) + gapTotal,
197
+ height: sizes.reduce((max, size) => Math.max(max, size.height), 0),
198
+ };
199
+ }
200
+ return {
201
+ width: sizes.reduce((max, size) => Math.max(max, size.width), 0),
202
+ height: sizes.reduce((sum, size) => sum + size.height, 0) + gapTotal,
203
+ };
204
+ }
205
+
206
+ export function fillOrExpand(mode: ControlSizeMode) {
207
+ return mode === 'fill' || mode === 'expand';
208
+ }
209
+
210
+ export function crossAxis(axis: ControlAxis): ControlAxis {
211
+ return axis === 'horizontal' ? 'vertical' : 'horizontal';
212
+ }
213
+
214
+ export function alignOffset(align: ControlAlign, available: number) {
215
+ if (align === 'center') {
216
+ return available / 2;
217
+ }
218
+ if (align === 'end') {
219
+ return available;
220
+ }
221
+ return 0;
222
+ }
223
+
224
+ export function justifyOffset(justify: ControlJustify, available: number) {
225
+ if (justify === 'center') {
226
+ return available / 2;
227
+ }
228
+ if (justify === 'end') {
229
+ return available;
230
+ }
231
+ return 0;
232
+ }
233
+
234
+ export function parseSizeMode(value: string): ControlSizeMode {
235
+ if (value === 'fill' || value === 'expand') {
236
+ return value;
237
+ }
238
+ return 'content';
239
+ }
240
+
241
+ export function parseAlign(value: string): ControlAlign {
242
+ if (value === 'center' || value === 'end') {
243
+ return value;
244
+ }
245
+ return 'start';
246
+ }
247
+
248
+ export function parseJustify(value: string): ControlJustify {
249
+ if (value === 'center' || value === 'end' || value === 'space-between') {
250
+ return value;
251
+ }
252
+ return 'start';
253
+ }
254
+
255
+ export function finiteNumber(value: number, fallback: number) {
256
+ return Number.isFinite(value) ? value : fallback;
257
+ }
258
+
259
+ export function boxSize(value: number | { width: number; height?: number }, height?: number): Size {
260
+ if (typeof value === 'number') {
261
+ return {
262
+ width: Math.max(0, finiteNumber(value, 0)),
263
+ height: Math.max(0, finiteNumber(height ?? value, 0)),
264
+ };
265
+ }
266
+ return {
267
+ width: Math.max(0, finiteNumber(value.width, 0)),
268
+ height: Math.max(0, finiteNumber(value.height ?? value.width, 0)),
269
+ };
270
+ }
@@ -0,0 +1,14 @@
1
+ import type { SceneTemplateInterface } from './spec.js';
2
+ export declare const builtinScenePrefix = "pixifact:";
3
+ export declare const builtinSceneNames: readonly ["CenterContainer", "Control", "FlexItem", "FlexLayout", "HBoxContainer", "MarginContainer", "VBoxContainer"];
4
+ export type BuiltinSceneName = typeof builtinSceneNames[number];
5
+ export type BuiltinSceneScriptSources = Record<BuiltinSceneName, string>;
6
+ export declare function isBuiltinSceneName(value: string): value is BuiltinSceneName;
7
+ export declare function builtinSceneAssetId(name: string): string;
8
+ export declare function isBuiltinSceneAssetId(value: string): boolean;
9
+ export declare function builtinSceneNameFromAssetId(assetId: string): "CenterContainer" | "Control" | "FlexItem" | "FlexLayout" | "HBoxContainer" | "MarginContainer" | "VBoxContainer";
10
+ export declare function builtinSceneAssetIds(): string[];
11
+ export declare function builtinSceneInterface(assetId: string, scriptSources: BuiltinSceneScriptSources): SceneTemplateInterface;
12
+ export declare function builtinSceneInterfaces(scriptSources: BuiltinSceneScriptSources): {
13
+ [k: string]: SceneTemplateInterface;
14
+ };
@@ -0,0 +1,62 @@
1
+ import { extractSceneScriptInterfaces } from './scriptInterfaceExtractor.js';
2
+ export const builtinScenePrefix = 'pixifact:';
3
+ export const builtinSceneNames = [
4
+ 'CenterContainer',
5
+ 'Control',
6
+ 'FlexItem',
7
+ 'FlexLayout',
8
+ 'HBoxContainer',
9
+ 'MarginContainer',
10
+ 'VBoxContainer',
11
+ ];
12
+ const builtinSceneNameSet = new Set(builtinSceneNames);
13
+ export function isBuiltinSceneName(value) {
14
+ return builtinSceneNameSet.has(value);
15
+ }
16
+ export function builtinSceneAssetId(name) {
17
+ if (!isBuiltinSceneName(name)) {
18
+ throw new Error(`Unknown built-in Scene "${name}".`);
19
+ }
20
+ return `${builtinScenePrefix}${name}.scene`;
21
+ }
22
+ export function isBuiltinSceneAssetId(value) {
23
+ return value.startsWith(builtinScenePrefix);
24
+ }
25
+ export function builtinSceneNameFromAssetId(assetId) {
26
+ if (!isBuiltinSceneAssetId(assetId) || !assetId.endsWith('.scene')) {
27
+ throw new Error(`Built-in Scene id "${assetId}" must use ${builtinScenePrefix}<Name>.scene.`);
28
+ }
29
+ const name = assetId.slice(builtinScenePrefix.length, -'.scene'.length);
30
+ if (!isBuiltinSceneName(name)) {
31
+ throw new Error(`Unknown built-in Scene "${name}".`);
32
+ }
33
+ return name;
34
+ }
35
+ export function builtinSceneAssetIds() {
36
+ return builtinSceneNames.map(builtinSceneAssetId);
37
+ }
38
+ export function builtinSceneInterface(assetId, scriptSources) {
39
+ return builtinSceneInterfaceDescriptors(scriptSources)[assetId].interface;
40
+ }
41
+ export function builtinSceneInterfaces(scriptSources) {
42
+ const descriptors = builtinSceneInterfaceDescriptors(scriptSources);
43
+ return Object.fromEntries(builtinSceneAssetIds().map((assetId) => [assetId, descriptors[assetId].interface]));
44
+ }
45
+ function builtinSceneInterfaceDescriptors(scriptSources) {
46
+ const descriptors = extractSceneScriptInterfaces(builtinSceneNames.map((name) => ({
47
+ scene: builtinSceneAssetId(name),
48
+ fileName: `${name}.ts`,
49
+ source: scriptSources[name],
50
+ })));
51
+ for (const assetId of builtinSceneAssetIds()) {
52
+ const name = builtinSceneNameFromAssetId(assetId);
53
+ const descriptor = descriptors[assetId];
54
+ if (!descriptor) {
55
+ throw new Error(`Built-in Scene "${assetId}" is missing a @scene script.`);
56
+ }
57
+ if (descriptor.className !== name) {
58
+ throw new Error(`Built-in Scene "${assetId}" name must match @scene class "${descriptor.className}".`);
59
+ }
60
+ }
61
+ return descriptors;
62
+ }