@datamoc/mw_games 0.38.0 → 0.39.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 (90) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/api-surface.json +500 -2
  3. package/dist/assets/SvgParts.d.ts +31 -0
  4. package/dist/assets/SvgParts.d.ts.map +1 -0
  5. package/dist/assets/SvgParts.js +54 -0
  6. package/dist/assets/SvgParts.js.map +1 -0
  7. package/dist/assets/SvgSource.d.ts +3 -0
  8. package/dist/assets/SvgSource.d.ts.map +1 -0
  9. package/dist/assets/SvgSource.js +22 -0
  10. package/dist/assets/SvgSource.js.map +1 -0
  11. package/dist/assets/index.d.ts +2 -0
  12. package/dist/assets/index.d.ts.map +1 -1
  13. package/dist/assets/index.js +1 -0
  14. package/dist/assets/index.js.map +1 -1
  15. package/dist/assets/svg.d.ts.map +1 -1
  16. package/dist/assets/svg.js +9 -5
  17. package/dist/assets/svg.js.map +1 -1
  18. package/dist/mw_games.global.js +110 -110
  19. package/dist/mwl/presentation.d.ts.map +1 -1
  20. package/dist/mwl/presentation.js +25 -18
  21. package/dist/mwl/presentation.js.map +1 -1
  22. package/dist/physics/PhysicsWorld.d.ts +49 -17
  23. package/dist/physics/PhysicsWorld.d.ts.map +1 -1
  24. package/dist/physics/PhysicsWorld.js +297 -59
  25. package/dist/physics/PhysicsWorld.js.map +1 -1
  26. package/dist/physics/XpbdWorld.d.ts +1 -1
  27. package/dist/physics/XpbdWorld.js +1 -1
  28. package/dist/physics/cloth/index.d.ts +98 -0
  29. package/dist/physics/cloth/index.d.ts.map +1 -0
  30. package/dist/physics/cloth/index.js +207 -0
  31. package/dist/physics/cloth/index.js.map +1 -0
  32. package/dist/physics/index.d.ts +4 -0
  33. package/dist/physics/index.d.ts.map +1 -1
  34. package/dist/physics/index.js +2 -0
  35. package/dist/physics/index.js.map +1 -1
  36. package/dist/physics/spring/index.d.ts +90 -0
  37. package/dist/physics/spring/index.d.ts.map +1 -0
  38. package/dist/physics/spring/index.js +97 -0
  39. package/dist/physics/spring/index.js.map +1 -0
  40. package/dist/skeleton/index.d.ts +94 -13
  41. package/dist/skeleton/index.d.ts.map +1 -1
  42. package/dist/skeleton/index.js +192 -73
  43. package/dist/skeleton/index.js.map +1 -1
  44. package/dist/two-d/presentation/MwlPresentation.d.ts +12 -0
  45. package/dist/two-d/presentation/MwlPresentation.d.ts.map +1 -1
  46. package/dist/two-d/presentation/MwlPresentation.js +204 -153
  47. package/dist/two-d/presentation/MwlPresentation.js.map +1 -1
  48. package/dist/two-d/render/ClothSprite.d.ts +83 -0
  49. package/dist/two-d/render/ClothSprite.d.ts.map +1 -0
  50. package/dist/two-d/render/ClothSprite.js +105 -0
  51. package/dist/two-d/render/ClothSprite.js.map +1 -0
  52. package/dist/two-d/render/HazeLayer.d.ts +112 -0
  53. package/dist/two-d/render/HazeLayer.d.ts.map +1 -0
  54. package/dist/two-d/render/HazeLayer.js +185 -0
  55. package/dist/two-d/render/HazeLayer.js.map +1 -0
  56. package/dist/two-d/render/NeonGlow.d.ts +100 -0
  57. package/dist/two-d/render/NeonGlow.d.ts.map +1 -0
  58. package/dist/two-d/render/NeonGlow.js +183 -0
  59. package/dist/two-d/render/NeonGlow.js.map +1 -0
  60. package/dist/two-d/render/SkeletalSprite.d.ts +5 -2
  61. package/dist/two-d/render/SkeletalSprite.d.ts.map +1 -1
  62. package/dist/two-d/render/SkeletalSprite.js +27 -3
  63. package/dist/two-d/render/SkeletalSprite.js.map +1 -1
  64. package/dist/two-d/render/SpringSprite.d.ts +40 -0
  65. package/dist/two-d/render/SpringSprite.d.ts.map +1 -0
  66. package/dist/two-d/render/SpringSprite.js +39 -0
  67. package/dist/two-d/render/SpringSprite.js.map +1 -0
  68. package/dist/two-d/render/SpriteDeform.d.ts +156 -0
  69. package/dist/two-d/render/SpriteDeform.d.ts.map +1 -0
  70. package/dist/two-d/render/SpriteDeform.js +278 -0
  71. package/dist/two-d/render/SpriteDeform.js.map +1 -0
  72. package/dist/two-d/render/SvgSkeleton.d.ts +54 -0
  73. package/dist/two-d/render/SvgSkeleton.d.ts.map +1 -0
  74. package/dist/two-d/render/SvgSkeleton.js +112 -0
  75. package/dist/two-d/render/SvgSkeleton.js.map +1 -0
  76. package/dist/two-d/render/index.d.ts +12 -0
  77. package/dist/two-d/render/index.d.ts.map +1 -1
  78. package/dist/two-d/render/index.js +6 -0
  79. package/dist/two-d/render/index.js.map +1 -1
  80. package/dist/version.d.ts +1 -1
  81. package/dist/version.js +1 -1
  82. package/package.json +9 -7
  83. package/sbom.cdx.json +6 -58
  84. package/tools/asset-report.d.mts +37 -0
  85. package/tools/asset-report.mjs +157 -0
  86. package/tools/compile-resources.d.mts +2 -0
  87. package/tools/compile-resources.mjs +23 -2
  88. package/tools/emit-page.d.mts +2 -0
  89. package/tools/emit-page.mjs +11 -1
  90. package/tools/vite.mjs +4 -1
@@ -9,6 +9,12 @@ import { Window } from '../ui/Window.js';
9
9
  import { WindowStack } from '../ui/WindowStack.js';
10
10
  import { Grid, flowLayout, resolveAnchor } from '../ui/Layout.js';
11
11
  import { setTheme, theme } from '../ui/theme.js';
12
+ /** Fallback widget sizes, shared by construction and layout so the two cannot drift. */
13
+ const DEFAULT_BUTTON_WIDTH = 160;
14
+ const DEFAULT_BUTTON_HEIGHT = 32;
15
+ const DEFAULT_BAR_HEIGHT = 12;
16
+ const DEFAULT_WINDOW_WIDTH = 300;
17
+ const DEFAULT_WINDOW_HEIGHT = 200;
12
18
  /**
13
19
  * Instantiates compiled MWL presentation using existing Pixi widgets. The controller owns state
14
20
  * and actions; context switches restyle the same views without rebuilding procedural adapters.
@@ -109,15 +115,15 @@ export class MwlPresentation {
109
115
  const context = this.data.contexts[this.context];
110
116
  const definition = this.data.themes[context?.theme ?? this.baseTheme];
111
117
  this.tokens = { ...definition.tokens, ...context?.tokens };
112
- const p = definition.properties;
118
+ const props = definition.properties;
113
119
  setTheme({
114
120
  ...this.previousTheme,
115
- padding: Number(p.padding ?? this.previousTheme.padding),
116
- spacing: Number(p.spacing ?? this.previousTheme.spacing),
121
+ padding: Number(props.padding ?? this.previousTheme.padding),
122
+ spacing: Number(props.spacing ?? this.previousTheme.spacing),
117
123
  font: {
118
124
  ...this.previousTheme.font,
119
- family: p.font_family ?? this.previousTheme.font.family,
120
- size: Number(p.font_size ?? this.previousTheme.font.size),
125
+ family: props.font_family ?? this.previousTheme.font.family,
126
+ size: Number(props.font_size ?? this.previousTheme.font.size),
121
127
  },
122
128
  color: {
123
129
  ...this.previousTheme.color,
@@ -224,45 +230,19 @@ export class MwlPresentation {
224
230
  this.mount(node);
225
231
  changed = true;
226
232
  }
227
- if (node.view.visible !== visible) {
228
- node.view.visible = visible;
233
+ const view = this.viewOf(node);
234
+ if (view.visible !== visible) {
235
+ view.visible = visible;
229
236
  changed = true;
230
237
  }
231
- if (node.view instanceof Button) {
232
- const disabled = this.read(node, 'disabled', false);
233
- const selected = this.read(node, 'selected', false);
234
- if (disabled !== node.view.disabled) {
235
- node.view.setDisabled(disabled);
236
- changed = true;
237
- }
238
- if (selected !== node.view.selected) {
239
- node.view.setSelected(selected);
240
- changed = true;
241
- }
242
- if (disabled)
243
- node.state = 'disabled';
244
- else if (selected)
245
- node.state = 'selected';
246
- else if (node === this.focused)
247
- node.state = 'focused';
248
- else if (['disabled', 'selected', 'focused'].includes(node.state))
249
- node.state = 'idle';
250
- }
238
+ if (view instanceof Button)
239
+ changed = this.refreshButton(node, view) || changed;
251
240
  const properties = this.resolveProperties(node);
252
241
  if (!equalProperties(node.properties, properties)) {
253
242
  node.properties = properties;
254
243
  changed = true;
255
244
  }
256
- const values = { visible, alpha: Number(properties.alpha ?? 1) };
257
- if (node.view instanceof Button || node.view instanceof Label)
258
- values.text = this.read(node, 'text', '');
259
- if (node.view instanceof Window)
260
- values.title = this.read(node, 'title', '');
261
- if (node.view instanceof Bar) {
262
- values.value = this.read(node, 'value', 1);
263
- values.max = this.read(node, 'max', 1);
264
- values.color = this.read(node, 'color', this.token(properties.color_token, 'accent'));
265
- }
245
+ const values = this.readValues(node, properties, visible);
266
246
  if (!equalProperties(node.values, values)) {
267
247
  node.values = values;
268
248
  changed = true;
@@ -277,34 +257,80 @@ export class MwlPresentation {
277
257
  }
278
258
  return changed;
279
259
  }
260
+ refreshButton(node, view) {
261
+ let changed = false;
262
+ const disabled = this.read(node, 'disabled', false);
263
+ const selected = this.read(node, 'selected', false);
264
+ if (disabled !== view.disabled) {
265
+ view.setDisabled(disabled);
266
+ changed = true;
267
+ }
268
+ if (selected !== view.selected) {
269
+ view.setSelected(selected);
270
+ changed = true;
271
+ }
272
+ if (disabled)
273
+ node.state = 'disabled';
274
+ else if (selected)
275
+ node.state = 'selected';
276
+ else if (node === this.focused)
277
+ node.state = 'focused';
278
+ else if (['disabled', 'selected', 'focused'].includes(node.state))
279
+ node.state = 'idle';
280
+ return changed;
281
+ }
282
+ readValues(node, properties, visible) {
283
+ const view = this.viewOf(node);
284
+ const values = { visible, alpha: Number(properties.alpha ?? 1) };
285
+ if (view instanceof Button || view instanceof Label)
286
+ values.text = this.read(node, 'text', '');
287
+ if (view instanceof Window)
288
+ values.title = this.read(node, 'title', '');
289
+ if (view instanceof Bar) {
290
+ values.value = this.read(node, 'value', 1);
291
+ values.max = this.read(node, 'max', 1);
292
+ values.color = this.read(node, 'color', this.token(properties.color_token, 'accent'));
293
+ }
294
+ return values;
295
+ }
296
+ viewOf(node) {
297
+ const view = node.view;
298
+ if (!view)
299
+ throw new Error(`${node.path}: missing view`);
300
+ return view;
301
+ }
280
302
  mount(node) {
281
- const p = this.resolveProperties(node);
282
- const width = Number(p.width ?? 160), height = Number(p.height ?? 32);
303
+ const props = this.resolveProperties(node);
304
+ const width = Number(props.width ?? DEFAULT_BUTTON_WIDTH), height = Number(props.height ?? DEFAULT_BUTTON_HEIGHT);
283
305
  switch (node.definition.kind) {
284
306
  case 'ui_label':
285
- node.view = new Label({ text: '', color: this.token(p.color_token, 'text') });
307
+ node.view = new Label({ text: '', color: this.token(props.color_token, 'text') });
286
308
  break;
287
309
  case 'ui_button':
288
310
  node.view = new Button({ width, height, text: '' });
289
311
  break;
290
312
  case 'ui_bar':
291
- node.view = new Bar({ width, height: Number(p.height ?? 12) });
313
+ node.view = new Bar({ width, height: Number(props.height ?? DEFAULT_BAR_HEIGHT) });
292
314
  break;
293
315
  case 'ui_window':
294
316
  node.view = new Window({
295
- width,
296
- height: Number(p.height ?? 200),
317
+ width: Number(props.width ?? DEFAULT_WINDOW_WIDTH),
318
+ height: Number(props.height ?? DEFAULT_WINDOW_HEIGHT),
297
319
  title: '',
298
- modal: p.modal !== 'false',
299
- closable: p.closable !== 'false',
320
+ modal: props.modal !== 'false',
321
+ closable: props.closable !== 'false',
300
322
  closeButton: true,
301
323
  blocker: true,
302
- anchor: (p.anchor ?? 'center'),
324
+ anchor: (props.anchor ?? 'center'),
303
325
  });
304
326
  break;
305
327
  default: {
306
- if (p.adapter) {
307
- node.adapter = this.options.adapters[p.adapter]({ tokens: this.tokens, properties: p });
328
+ if (props.adapter) {
329
+ const adapters = this.options.adapters;
330
+ const create = adapters && Object.hasOwn(adapters, props.adapter) ? adapters[props.adapter] : undefined;
331
+ if (!create)
332
+ throw new Error(`${node.path}: missing presentation adapter "${props.adapter}"`);
333
+ node.adapter = create({ tokens: this.tokens, properties: props });
308
334
  node.view = node.adapter.view;
309
335
  }
310
336
  else
@@ -316,43 +342,45 @@ export class MwlPresentation {
316
342
  if (node.definition.kind !== 'ui_repeat')
317
343
  for (const child of node.definition.children)
318
344
  node.children.push(this.createRecord(child, `${node.path}/${child.id}`, childParent, node.item));
319
- if (node.view instanceof Button) {
320
- const button = node.view;
321
- const click = () => {
322
- this.setFocus(node);
323
- this.invoke(node.definition.properties.action, node.item);
324
- if (!this.destroyed)
325
- this.update();
326
- };
327
- button.onClick.add(click);
328
- node.cleanups.push(() => button.onClick.remove(click));
329
- for (const [event, state] of [
330
- ['pointerover', 'hover'],
331
- ['pointerout', 'idle'],
332
- ['pointerdown', 'pressed'],
333
- ['pointerup', 'hover'],
334
- ]) {
335
- const listener = () => {
336
- node.state = state;
337
- this.update();
338
- };
339
- button.on(event, listener);
340
- node.cleanups.push(() => button.off(event, listener));
341
- }
342
- }
343
- if (node.view instanceof Window) {
344
- const window = node.view;
345
- window.delegate = { handleAction: (action) => this.navigate(action, node) };
346
- const close = () => {
347
- node.closed = true;
348
- this.unmount(node, false);
349
- this.invoke(node.definition.properties.close_action, node.item);
345
+ if (node.view instanceof Button)
346
+ this.wireButton(node, node.view);
347
+ if (node.view instanceof Window)
348
+ this.wireWindow(node, node.view);
349
+ }
350
+ wireButton(node, button) {
351
+ const click = () => {
352
+ this.setFocus(node);
353
+ this.invoke(node.definition.properties.action, node.item);
354
+ if (!this.destroyed)
355
+ this.update();
356
+ };
357
+ button.onClick.add(click);
358
+ node.cleanups.push(() => button.onClick.remove(click));
359
+ for (const [event, state] of [
360
+ ['pointerover', 'hover'],
361
+ ['pointerout', 'idle'],
362
+ ['pointerdown', 'pressed'],
363
+ ['pointerup', 'hover'],
364
+ ]) {
365
+ const listener = () => {
366
+ node.state = state;
367
+ this.update();
350
368
  };
351
- window.onClose.add(close);
352
- node.cleanups.push(() => window.onClose.remove(close));
353
- this.windows.push(window);
369
+ button.on(event, listener);
370
+ node.cleanups.push(() => button.off(event, listener));
354
371
  }
355
372
  }
373
+ wireWindow(node, window) {
374
+ window.delegate = { handleAction: (action) => this.navigate(action, node) };
375
+ const close = () => {
376
+ node.closed = true;
377
+ this.unmount(node, false);
378
+ this.invoke(node.definition.properties.close_action, node.item);
379
+ };
380
+ window.onClose.add(close);
381
+ node.cleanups.push(() => window.onClose.remove(close));
382
+ this.windows.push(window);
383
+ }
356
384
  reconcile(node) {
357
385
  const p = node.definition.properties;
358
386
  const items = this.state[p.items_binding];
@@ -375,7 +403,8 @@ export class MwlPresentation {
375
403
  for (const item of items) {
376
404
  const prefix = `${node.path}/${encodeURIComponent(String(item[p.key]))}`;
377
405
  const template = node.definition.children[0];
378
- const child = existing.get(prefix) ?? this.createRecord(template, `${prefix}/${template.id}`, node.view, item);
406
+ const child = existing.get(prefix) ??
407
+ this.createRecord(template, `${prefix}/${template.id}`, this.viewOf(node), item);
379
408
  if (!existing.has(prefix))
380
409
  changed = true;
381
410
  existing.delete(prefix);
@@ -395,16 +424,16 @@ export class MwlPresentation {
395
424
  return this.tokens[name ?? fallback];
396
425
  }
397
426
  applyProperties(node) {
398
- const view = node.view, p = node.properties, values = node.values;
427
+ const view = this.viewOf(node), props = node.properties, values = node.values;
399
428
  view.alpha = values.alpha;
400
429
  if (node.definition.kind === 'scene_layer' && !node.adapter) {
401
- if (p.image) {
430
+ if (props.image) {
402
431
  if (!node.image) {
403
- node.image = new Sprite(texture(p.image));
432
+ node.image = new Sprite(texture(props.image));
404
433
  view.addChildAt(node.image, 0);
405
434
  }
406
435
  else
407
- node.image.texture = texture(p.image);
436
+ node.image.texture = texture(props.image);
408
437
  }
409
438
  else {
410
439
  node.image?.destroy();
@@ -412,11 +441,11 @@ export class MwlPresentation {
412
441
  }
413
442
  }
414
443
  if (['ui_container', 'ui_row', 'ui_column', 'ui_grid', 'ui_mount', 'ui_repeat'].includes(node.definition.kind)) {
415
- if (p.background_token && !node.background) {
444
+ if (props.background_token && !node.background) {
416
445
  node.background = new Graphics();
417
446
  view.addChildAt(node.background, 0);
418
447
  }
419
- else if (!p.background_token && node.background) {
448
+ else if (!props.background_token && node.background) {
420
449
  node.background.destroy();
421
450
  node.background = undefined;
422
451
  }
@@ -424,34 +453,37 @@ export class MwlPresentation {
424
453
  const translate = this.options.translate ?? ((text) => text);
425
454
  if (view instanceof Label) {
426
455
  view.setText(translate(values.text));
427
- view.setColor(this.token(p.color_token, 'text'));
456
+ view.setColor(this.token(props.color_token, 'text'));
428
457
  }
429
458
  if (view instanceof Button) {
430
459
  view.setText(translate(values.text));
431
460
  view.setColors({
432
- text: this.token(p.color_token, 'text'),
433
- disabledText: this.token(p.color_token, 'text'),
434
- background: this.token(p.background_token, 'panel'),
435
- border: this.token(p.border_token, 'accent'),
461
+ text: this.token(props.color_token, 'text'),
462
+ disabledText: this.token(props.color_token, 'text'),
463
+ background: this.token(props.background_token, 'panel'),
464
+ border: this.token(props.border_token, 'accent'),
436
465
  selection: node.definition.states[node.state]?.background_token
437
- ? this.token(p.background_token, 'selection')
466
+ ? this.token(props.background_token, 'selection')
438
467
  : this.tokens.selection,
439
468
  });
440
469
  }
441
470
  if (view instanceof Bar) {
442
471
  view.setValue(values.value, values.max);
443
472
  view.setColor(values.color);
444
- view.setBackground(this.token(p.background_token, 'panel'));
473
+ view.setBackground(this.token(props.background_token, 'panel'));
445
474
  }
446
475
  if (view instanceof Window) {
447
476
  view.setTitle(translate(values.title));
448
477
  view.setColors({
449
- background: this.token(p.background_token, 'panel'),
450
- border: this.token(p.border_token, 'accent'),
451
- title: this.token(p.color_token, 'accent'),
478
+ background: this.token(props.background_token, 'panel'),
479
+ border: this.token(props.border_token, 'accent'),
480
+ title: this.token(props.color_token, 'accent'),
452
481
  });
453
482
  }
454
- node.adapter?.restyle?.({ tokens: this.tokens, properties: p });
483
+ node.adapter?.restyle?.({ tokens: this.tokens, properties: props });
484
+ }
485
+ isVisible(node) {
486
+ return Boolean(node.view?.visible);
455
487
  }
456
488
  layout() {
457
489
  const bounds = { x: 0, y: 0, ...this.viewport };
@@ -465,63 +497,79 @@ export class MwlPresentation {
465
497
  const view = node.view;
466
498
  if (!view || !view.visible)
467
499
  return;
468
- const p = node.properties;
469
- const kind = node.definition.kind;
470
- const defaultWidth = view instanceof Button || view instanceof Bar ? 160 : bounds.width;
471
- let width = Math.min(Number(p.width ?? defaultWidth), bounds.width);
472
- let height = Number(p.height ??
473
- (view instanceof Button
474
- ? 32
475
- : view instanceof Bar
476
- ? 12
477
- : view instanceof Label
478
- ? view.height
479
- : bounds.height));
480
- if (view instanceof Window) {
481
- width = Math.max(0, Math.min(Number(p.width ?? 300), this.viewport.width - theme().padding * 2));
482
- height = Math.max(0, Math.min(Number(p.height ?? 200), this.viewport.height - theme().padding * 2));
483
- view.resize(width, height);
484
- }
485
- else if (view instanceof Button || view instanceof Bar)
486
- view.resize(width, height);
487
- else if (view instanceof Label) {
488
- view.setWrapWidth(width);
489
- height = view.height;
490
- }
491
- const padding = Number(p.padding ?? 0), gap = Number(p.gap ?? 0);
500
+ const { width, height: measured } = this.measureNode(node, view, bounds);
501
+ const props = node.properties;
502
+ const padding = Number(props.padding ?? 0), gap = Number(props.gap ?? 0);
492
503
  const inner = {
493
504
  x: 0,
494
505
  y: 0,
495
506
  width: Math.max(0, width - padding * 2),
496
- height: Math.max(0, height - padding * 2),
507
+ height: Math.max(0, measured - padding * 2),
497
508
  };
498
509
  if (view instanceof Window) {
499
510
  inner.width = view.contentWidth;
500
511
  inner.height = view.contentHeight;
501
512
  }
502
- const visible = node.children.filter((child) => child.view?.visible);
513
+ const visible = node.children.filter((child) => this.isVisible(child));
503
514
  for (const child of visible)
504
515
  this.layoutNode(child, inner);
516
+ const auto = this.placeChildren(node, visible, inner, padding, gap);
517
+ const height = auto ?? measured;
518
+ node.width = width;
519
+ node.height = height;
520
+ this.paintNode(node, width, height);
521
+ this.anchorNode(node, view, bounds, width, height);
522
+ node.adapter?.resize?.(width, height);
523
+ }
524
+ measureNode(node, view, bounds) {
525
+ const props = node.properties;
526
+ const defaultWidth = view instanceof Button || view instanceof Bar ? DEFAULT_BUTTON_WIDTH : bounds.width;
527
+ let width = Math.min(Number(props.width ?? defaultWidth), bounds.width);
528
+ let height = Number(props.height ?? this.defaultHeight(view, bounds));
529
+ if (view instanceof Window) {
530
+ width = Math.max(0, Math.min(Number(props.width ?? DEFAULT_WINDOW_WIDTH), this.viewport.width - theme().padding * 2));
531
+ height = Math.max(0, Math.min(Number(props.height ?? DEFAULT_WINDOW_HEIGHT), this.viewport.height - theme().padding * 2));
532
+ view.resize(width, height);
533
+ }
534
+ else if (view instanceof Button || view instanceof Bar)
535
+ view.resize(width, height);
536
+ else if (view instanceof Label) {
537
+ view.setWrapWidth(width);
538
+ height = view.height;
539
+ }
540
+ return { width, height };
541
+ }
542
+ defaultHeight(view, bounds) {
543
+ if (view instanceof Button)
544
+ return DEFAULT_BUTTON_HEIGHT;
545
+ if (view instanceof Bar)
546
+ return DEFAULT_BAR_HEIGHT;
547
+ if (view instanceof Label)
548
+ return view.height;
549
+ return bounds.height;
550
+ }
551
+ /** Positions visible children; returns the content height when the node sizes to fit. */
552
+ placeChildren(node, visible, inner, padding, gap) {
553
+ const props = node.properties;
554
+ const kind = node.definition.kind;
505
555
  if (['ui_row', 'ui_repeat'].includes(kind)) {
506
556
  const total = visible.reduce((sum, child) => sum + child.width, 0) + gap * Math.max(0, visible.length - 1);
507
- const result = flowLayout(visible, kind === 'ui_row' && p.wrap === 'false' ? Math.max(total, inner.width) : inner.width, { gap, align: p.align });
557
+ const result = flowLayout(visible, kind === 'ui_row' && props.wrap === 'false' ? Math.max(total, inner.width) : inner.width, { gap, align: props.align });
508
558
  visible.forEach((child, i) => {
509
559
  child.view.position.set(padding + result.positions[i].x, padding + result.positions[i].y);
510
560
  });
511
- if (p.height === undefined)
512
- height = result.height + padding * 2;
561
+ return props.height === undefined ? result.height + padding * 2 : undefined;
513
562
  }
514
- else if (kind === 'ui_column') {
563
+ if (kind === 'ui_column') {
515
564
  let y = padding;
516
565
  for (const child of visible) {
517
566
  child.view.position.set(padding, y);
518
567
  y += child.height + gap;
519
568
  }
520
- if (p.height === undefined)
521
- height = Math.max(0, y - gap + padding);
569
+ return props.height === undefined ? Math.max(0, y - gap + padding) : undefined;
522
570
  }
523
- else if (kind === 'ui_grid') {
524
- const columns = Number(p.columns), rows = Math.ceil(visible.length / columns);
571
+ if (kind === 'ui_grid') {
572
+ const columns = Number(props.columns), rows = Math.ceil(visible.length / columns);
525
573
  const grid = new Grid({
526
574
  columns: Array.from({ length: columns }, () => ({ grow: 1 })),
527
575
  rows: Array.from({ length: rows }, () => ({
@@ -533,35 +581,38 @@ export class MwlPresentation {
533
581
  const cell = grid.rect(Math.floor(i / columns), i % columns, inner);
534
582
  this.layoutNode(child, { ...cell, x: cell.x + padding, y: cell.y + padding });
535
583
  });
536
- if (p.height === undefined)
537
- height =
538
- grid.rowSizes(inner.height).reduce((sum, size) => sum + size, 0) +
539
- gap * Math.max(0, rows - 1) +
540
- padding * 2;
541
- }
542
- else if (['ui_container', 'ui_mount'].includes(kind) && p.height === undefined) {
543
- height = Math.max(0, ...visible.map((child) => child.view.y + child.height)) + padding * 2;
544
- }
545
- node.width = width;
546
- node.height = height;
584
+ if (props.height === undefined)
585
+ return (grid.rowSizes(inner.height).reduce((sum, size) => sum + size, 0) +
586
+ gap * Math.max(0, rows - 1) +
587
+ padding * 2);
588
+ return undefined;
589
+ }
590
+ if (['ui_container', 'ui_mount'].includes(kind) && props.height === undefined)
591
+ return Math.max(0, ...visible.map((child) => child.view.y + child.height)) + padding * 2;
592
+ return undefined;
593
+ }
594
+ paintNode(node, width, height) {
595
+ const props = node.properties;
547
596
  if (node.background) {
548
- node.background.clear().rect(0, 0, width, height).fill(this.token(p.background_token, 'panel'));
549
- if (p.border_token)
550
- node.background.stroke({ color: this.token(p.border_token, 'accent'), width: 1 });
597
+ node.background.clear().rect(0, 0, width, height).fill(this.token(props.background_token, 'panel'));
598
+ if (props.border_token)
599
+ node.background.stroke({ color: this.token(props.border_token, 'accent'), width: 1 });
551
600
  }
601
+ }
602
+ anchorNode(node, view, bounds, width, height) {
603
+ const props = node.properties;
552
604
  if (node.parent === this.camera.world)
553
- view.position.set(Number(p.x ?? 0), Number(p.y ?? 0));
605
+ view.position.set(Number(props.x ?? 0), Number(props.y ?? 0));
554
606
  else if (!(view instanceof Window)) {
555
607
  const placed = resolveAnchor({
556
- anchor: (p.anchor ?? 'top-left'),
608
+ anchor: (props.anchor ?? 'top-left'),
557
609
  width,
558
610
  height,
559
- offsetX: Number(p.x ?? 0),
560
- offsetY: Number(p.y ?? 0),
611
+ offsetX: Number(props.x ?? 0),
612
+ offsetY: Number(props.y ?? 0),
561
613
  }, bounds);
562
614
  view.position.set(placed.x, placed.y);
563
615
  }
564
- node.adapter?.resize?.(width, height);
565
616
  }
566
617
  invoke(action, item) {
567
618
  if (action)