pict-section-flow 1.0.0 → 1.0.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.
@@ -15,13 +15,13 @@ const _CHIP_PER_CHAR_PX = 5;
15
15
  // badge borders so the hint bezier matches its port's affinity color.
16
16
  const PORT_TYPE_COLORS =
17
17
  {
18
- 'event-in': '#3498db',
19
- 'event-out': '#2ecc71',
20
- 'setting': '#e67e22',
21
- 'value': '#f1c40f',
22
- 'error': '#e74c3c'
18
+ 'event-in': 'var(--theme-color-status-info, #3498db)',
19
+ 'event-out': 'var(--theme-color-status-success, #2ecc71)',
20
+ 'setting': 'var(--theme-color-status-warning, #e67e22)',
21
+ 'value': 'var(--theme-color-status-warning, #f1c40f)',
22
+ 'error': 'var(--theme-color-status-error, #e74c3c)'
23
23
  };
24
- const PORT_TYPE_DEFAULT_COLOR = '#95a5a6';
24
+ const PORT_TYPE_DEFAULT_COLOR = 'var(--theme-color-border-default, #95a5a6)';
25
25
 
26
26
  class PictServiceFlowConnectionRenderer extends libFableServiceProviderBase
27
27
  {
@@ -88,13 +88,13 @@ class PictServiceFlowPortRenderer extends libFableServiceProviderBase
88
88
  {
89
89
  let tmpPortTypeColorMap =
90
90
  {
91
- 'event-in': '#3498db',
92
- 'event-out': '#2ecc71',
93
- 'setting': '#e67e22',
94
- 'value': '#f1c40f',
95
- 'error': '#e74c3c'
91
+ 'event-in': 'var(--theme-color-status-info, #3498db)',
92
+ 'event-out': 'var(--theme-color-status-success, #2ecc71)',
93
+ 'setting': 'var(--theme-color-status-warning, #e67e22)',
94
+ 'value': 'var(--theme-color-status-warning, #f1c40f)',
95
+ 'error': 'var(--theme-color-status-error, #e74c3c)'
96
96
  };
97
- let tmpBorderColor = tmpPort.PortType ? (tmpPortTypeColorMap[tmpPort.PortType] || '#95a5a6') : '#95a5a6';
97
+ let tmpBorderColor = tmpPort.PortType ? (tmpPortTypeColorMap[tmpPort.PortType] || 'var(--theme-color-border-default, #95a5a6)') : 'var(--theme-color-border-default, #95a5a6)';
98
98
 
99
99
  let tmpBadgeHeight = 12;
100
100
  let tmpBadgePadH = 5;
@@ -239,7 +239,7 @@ class PictViewFlowNode extends libPictView
239
239
  tmpCodeText.setAttribute('class', 'pict-flow-node-card-code');
240
240
  tmpCodeText.setAttribute('font-size', '10');
241
241
  tmpCodeText.setAttribute('font-family', 'monospace');
242
- tmpCodeText.setAttribute('fill', '#7f8c8d');
242
+ tmpCodeText.setAttribute('fill', 'var(--theme-color-text-secondary, #7f8c8d)');
243
243
  tmpCodeText.setAttribute('text-anchor', 'middle');
244
244
  tmpCodeText.setAttribute('dominant-baseline', 'central');
245
245
  tmpCodeText.setAttribute('pointer-events', 'none');
@@ -547,7 +547,7 @@ class PictViewFlowPropertiesPanel extends libPictView
547
547
 
548
548
  // Resolve default colors from the node type config or CSS token defaults
549
549
  let tmpNodeTypeConfig = this._FlowView._NodeTypeProvider.getNodeType(tmpNodeData.Type);
550
- let tmpDefaultTitleBarColor = '#2c3e50';
550
+ let tmpDefaultTitleBarColor = 'var(--theme-color-text-primary, #2c3e50)';
551
551
  let tmpDefaultBodyFill = '#ffffff';
552
552
  let tmpDefaultBodyStroke = '#d0d4d8';
553
553
  if (tmpNodeTypeConfig)
@@ -1500,30 +1500,36 @@ class PictViewFlowToolbar extends libPictView
1500
1500
  */
1501
1501
  _buildSettingsPopup(pContainer)
1502
1502
  {
1503
- if (!this._FlowView || !this._FlowView._ThemeProvider) return;
1503
+ if (!this._FlowView) return;
1504
1504
 
1505
1505
  let tmpFlowViewIdentifier = this.options.FlowViewIdentifier;
1506
- let tmpThemeProvider = this._FlowView._ThemeProvider;
1507
- let tmpThemeKeys = tmpThemeProvider.getThemeKeys();
1508
- let tmpActiveKey = tmpThemeProvider.getActiveThemeKey();
1506
+ let tmpPresetsProvider = this._FlowView._StylePresetsProvider;
1507
+ let tmpRendererProvider = this._FlowView._RendererProvider;
1508
+ if (!tmpPresetsProvider || !tmpRendererProvider) return;
1509
+
1510
+ // The single "Style" picker surfaces curated presets — each one bundles
1511
+ // a ColorTheme + Renderer + EdgeTheme. Per-axis overrides live behind
1512
+ // future "Customize" UI; the toolbar shows the preset list only.
1513
+ let tmpPresets = tmpPresetsProvider.listPresets();
1514
+ let tmpActivePreset = tmpPresetsProvider.getActivePresetHash();
1509
1515
 
1510
1516
  let tmpThemeOptions = [];
1511
- for (let i = 0; i < tmpThemeKeys.length; i++)
1517
+ for (let i = 0; i < tmpPresets.length; i++)
1512
1518
  {
1513
- let tmpTheme = tmpThemeProvider._Themes[tmpThemeKeys[i]];
1519
+ let tmpPreset = tmpPresets[i];
1514
1520
  tmpThemeOptions.push(
1515
1521
  {
1516
- Value: tmpThemeKeys[i],
1517
- Label: tmpTheme.Label || tmpThemeKeys[i],
1518
- SelectedAttr: (tmpThemeKeys[i] === tmpActiveKey) ? ' selected="selected"' : ''
1522
+ Value: tmpPreset.Hash,
1523
+ Label: tmpPreset.Label || tmpPreset.Hash,
1524
+ SelectedAttr: (tmpPreset.Hash === tmpActivePreset) ? ' selected="selected"' : ''
1519
1525
  });
1520
1526
  }
1521
1527
 
1522
1528
  let tmpThemeOptionsHTML = this.pict.parseTemplateByHash('Flow-Layout-OptionList', { Options: tmpThemeOptions });
1523
1529
 
1524
- let tmpNoiseLevel = Math.round(tmpThemeProvider.getNoiseLevel() * 100);
1525
- let tmpActiveTheme = tmpThemeProvider.getActiveTheme();
1526
- let tmpNoiseEnabled = !!(tmpActiveTheme && tmpActiveTheme.NoiseConfig && tmpActiveTheme.NoiseConfig.Enabled);
1530
+ let tmpNoiseLevel = Math.round(tmpRendererProvider.getNoiseLevel() * 100);
1531
+ let tmpActiveRenderer = tmpRendererProvider.getActiveRenderer();
1532
+ let tmpNoiseEnabled = !!(tmpActiveRenderer && tmpActiveRenderer.NoiseConfig && tmpActiveRenderer.NoiseConfig.Enabled);
1527
1533
  let tmpNoiseDisplay = tmpNoiseEnabled ? '' : 'display:none;';
1528
1534
 
1529
1535
  this.pict.ContentAssignment.assignContent(pContainer,
@@ -1585,16 +1591,18 @@ class PictViewFlowToolbar extends libPictView
1585
1591
  let tmpNoiseSection = pContainer.querySelector('[data-settings-type="noise"]');
1586
1592
  if (!tmpNoiseSection) return;
1587
1593
 
1588
- let tmpTheme = this._FlowView._ThemeProvider.getActiveTheme();
1589
- if (tmpTheme && tmpTheme.NoiseConfig && tmpTheme.NoiseConfig.Enabled)
1594
+ let tmpRendererProvider = this._FlowView._RendererProvider;
1595
+ if (!tmpRendererProvider) { tmpNoiseSection.style.display = 'none'; return; }
1596
+ let tmpRenderer = tmpRendererProvider.getActiveRenderer();
1597
+ if (tmpRenderer && tmpRenderer.NoiseConfig && tmpRenderer.NoiseConfig.Enabled)
1590
1598
  {
1591
1599
  tmpNoiseSection.style.display = '';
1592
- // Update slider value to reflect theme default
1600
+ // Update slider value to reflect renderer default
1593
1601
  let tmpSlider = tmpNoiseSection.querySelector('.pict-flow-popup-settings-slider');
1594
1602
  let tmpValueLabel = tmpNoiseSection.querySelector('.pict-flow-popup-settings-slider-value');
1595
1603
  if (tmpSlider)
1596
1604
  {
1597
- let tmpLevel = Math.round(this._FlowView._ThemeProvider.getNoiseLevel() * 100);
1605
+ let tmpLevel = Math.round(tmpRendererProvider.getNoiseLevel() * 100);
1598
1606
  tmpSlider.value = String(tmpLevel);
1599
1607
  if (tmpValueLabel) tmpValueLabel.textContent = tmpLevel + '%';
1600
1608
  }
@@ -23,6 +23,8 @@ const libPictProviderFlowCSS = require('../providers/PictProvider-Flow-CSS.js');
23
23
  const libPictProviderFlowIcons = require('../providers/PictProvider-Flow-Icons.js');
24
24
  const libPictProviderFlowConnectorShapes = require('../providers/PictProvider-Flow-ConnectorShapes.js');
25
25
  const libPictProviderFlowTheme = require('../providers/PictProvider-Flow-Theme.js');
26
+ const libPictProviderFlowRenderer = require('../providers/PictProvider-Flow-Renderer.js');
27
+ const libPictProviderFlowStylePresets = require('../providers/PictProvider-Flow-StylePresets.js');
26
28
  const libPictProviderFlowNoise = require('../providers/PictProvider-Flow-Noise.js');
27
29
 
28
30
  const libPictViewFlowNode = require('./PictView-Flow-Node.js');
@@ -165,6 +167,11 @@ class PictViewFlow extends libPictView
165
167
  { ServiceType: 'PictProviderFlowNoise', Library: libPictProviderFlowNoise, Property: '_NoiseProvider', NoFlowView: true },
166
168
 
167
169
  // Providers (need FlowView)
170
+ // Renderer + StylePresets must be created before the legacy Theme
171
+ // shim (which delegates to them) and before CSS PostInit so that
172
+ // registerCSS() sees an initialized renderer.
173
+ { ServiceType: 'PictProviderFlowRenderer', Library: libPictProviderFlowRenderer, Property: '_RendererProvider' },
174
+ { ServiceType: 'PictProviderFlowStylePresets', Library: libPictProviderFlowStylePresets, Property: '_StylePresetsProvider' },
168
175
  { ServiceType: 'PictProviderFlowTheme', Library: libPictProviderFlowTheme, Property: '_ThemeProvider' },
169
176
  { ServiceType: 'PictProviderFlowCSS', Library: libPictProviderFlowCSS, Property: '_CSSProvider', PostInit: 'registerCSS' },
170
177
  { ServiceType: 'PictProviderFlowIcons', Library: libPictProviderFlowIcons, Property: '_IconProvider', PostInit: 'registerIconTemplates' },
@@ -255,6 +262,8 @@ class PictViewFlow extends libPictView
255
262
  this._IconProvider = null;
256
263
  this._ConnectorShapesProvider = null;
257
264
  this._ThemeProvider = null;
265
+ this._RendererProvider = null;
266
+ this._StylePresetsProvider = null;
258
267
  this._NoiseProvider = null;
259
268
  this._SVGHelperProvider = null;
260
269
  this._GeometryProvider = null;
@@ -343,23 +352,40 @@ class PictViewFlow extends libPictView
343
352
  {
344
353
  super.onBeforeInitialize();
345
354
 
346
- // Theme + Noise must be created first (CSS PostInit depends on theme state)
347
- this._ThemeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowTheme', { FlowView: this });
348
- this._NoiseProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowNoise');
349
-
350
- // Apply initial theme from options
351
- if (this.options.Theme)
355
+ // Noise + Renderer + StylePresets + Theme shim must be created before
356
+ // CSS PostInit so registerCSS() sees an initialized renderer + presets.
357
+ this._NoiseProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowNoise');
358
+ this._RendererProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowRenderer', { FlowView: this });
359
+ this._StylePresetsProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowStylePresets', { FlowView: this });
360
+ this._ThemeProvider = this.fable.instantiateServiceProviderWithoutRegistration('PictProviderFlowTheme', { FlowView: this });
361
+
362
+ // Apply initial style preset / per-axis overrides from options.
363
+ // `Theme` and `StylePreset` are aliases for the same preset-by-hash apply.
364
+ let tmpInitialPreset = this.options.StylePreset || this.options.Theme;
365
+ if (tmpInitialPreset)
366
+ {
367
+ this._StylePresetsProvider.applyPreset(tmpInitialPreset);
368
+ }
369
+ if (this.options.Renderer)
352
370
  {
353
- this._ThemeProvider.setTheme(this.options.Theme);
371
+ this._RendererProvider.setRenderer(this.options.Renderer);
372
+ this._StylePresetsProvider.markCustomized();
354
373
  }
355
374
  if (typeof this.options.NoiseLevel === 'number')
356
375
  {
357
- this._ThemeProvider.setNoiseLevel(this.options.NoiseLevel);
376
+ this._RendererProvider.setNoiseLevel(this.options.NoiseLevel);
358
377
  }
359
378
 
360
- // Instantiate all remaining services (skips Theme + Noise since already set)
379
+ // Instantiate all remaining services (skips Noise/Renderer/StylePresets/Theme
380
+ // since already set above)
361
381
  this._instantiateServices();
362
382
 
383
+ // Now that CSSProvider exists, inject the active renderer's GeometryCSS.
384
+ if (this._CSSProvider && typeof this._CSSProvider.registerRendererCSS === 'function')
385
+ {
386
+ this._CSSProvider.registerRendererCSS(this._RendererProvider.getActiveRenderer());
387
+ }
388
+
363
389
  // Subscribe to the host application's pict-provider-theme so the flow
364
390
  // editor's marker arrowhead colors and shape overrides update when the
365
391
  // host swaps light/dark or palette themes. CSS variables (--theme-*)
@@ -902,89 +928,188 @@ class PictViewFlow extends libPictView
902
928
  return this._ViewportManager.exitFullscreen();
903
929
  }
904
930
 
905
- // ── Theme API ────────────────────────────────────────────────────────
931
+ // ── Theme / Renderer / Style-Preset API ─────────────────────────────
932
+ //
933
+ // Three axes you can drive independently:
934
+ // - ColorTheme — delegates to pict-provider-theme (a pict-section-theme
935
+ // catalog hash like 'flow-sketch' or 'pict-default')
936
+ // - Renderer — delegates to PictProviderFlowRenderer (controls
937
+ // bracket/rect node body, jitter, shadows, fonts)
938
+ // - EdgeTheme — delegates to PictService-Flow-Layout (Bezier /
939
+ // Straight / Orthogonal / Perimeter / …; see
940
+ // PictView-Flow.setEdgeTheme below)
941
+ //
942
+ // Most users pick a curated combo via `setStylePreset()` — the preset
943
+ // applies all three axes in order. Per-axis overrides mark the active
944
+ // preset as 'customized' (getStylePreset returns null afterward).
945
+ //
946
+ // For backwards-compatibility, `setTheme()` / `getThemeKey()` continue
947
+ // to work as aliases for `setStylePreset()` / `getStylePreset()`.
906
948
 
907
949
  /**
908
- * Switch the active theme and re-render.
909
- * @param {string} pThemeKey - Theme key (e.g. 'default', 'sketch', 'blueprint', 'mono', 'retro-80s', 'retro-90s')
950
+ * Apply a named style preset — sets ColorTheme, Renderer, EdgeTheme
951
+ * (and optional NoiseLevel) in one call.
952
+ * @param {string} pPresetHash
910
953
  */
911
- setTheme(pThemeKey)
954
+ setStylePreset(pPresetHash)
912
955
  {
913
- if (!this._ThemeProvider)
956
+ if (!this._StylePresetsProvider)
914
957
  {
915
- this.log.warn('PictSectionFlow setTheme: ThemeProvider not available');
958
+ this.log.warn('PictSectionFlow setStylePreset: StylePresets provider not available');
916
959
  return;
917
960
  }
961
+ let tmpApplied = this._StylePresetsProvider.applyPreset(pPresetHash);
962
+ if (!tmpApplied) { return; }
963
+ this._refreshAfterStyleChange();
964
+ if (this._EventHandlerProvider)
965
+ {
966
+ this._EventHandlerProvider.fireEvent('onStylePresetChanged', pPresetHash);
967
+ // Back-compat — old code listens for 'onThemeChanged'
968
+ this._EventHandlerProvider.fireEvent('onThemeChanged', pPresetHash);
969
+ }
970
+ }
918
971
 
919
- let tmpApplied = this._ThemeProvider.setTheme(pThemeKey);
920
- if (!tmpApplied) return;
972
+ /**
973
+ * Hash of the active style preset, or null when in customized state.
974
+ * @returns {string|null}
975
+ */
976
+ getStylePreset()
977
+ {
978
+ return this._StylePresetsProvider ? this._StylePresetsProvider.getActivePresetHash() : null;
979
+ }
921
980
 
922
- // Re-register CSS with the new theme overrides
923
- if (this._CSSProvider)
981
+ /**
982
+ * Override just the color theme — delegates to pict-provider-theme.
983
+ * @param {string} pThemeHash - a pict-section-theme catalog hash
984
+ */
985
+ setColorTheme(pThemeHash)
986
+ {
987
+ if (this.fable.providers && this.fable.providers.Theme)
924
988
  {
925
- this._CSSProvider.registerCSS();
989
+ try { this.fable.providers.Theme.applyTheme(pThemeHash); }
990
+ catch (pErr) { this.log.warn(`PictSectionFlow setColorTheme: applyTheme failed — ${pErr.message}`); return; }
926
991
  }
992
+ else
993
+ {
994
+ this.log.warn('PictSectionFlow setColorTheme: pict-provider-theme not available in host');
995
+ return;
996
+ }
997
+ if (this._StylePresetsProvider) { this._StylePresetsProvider.markCustomized(); }
998
+ this._refreshAfterStyleChange();
999
+ if (this._EventHandlerProvider)
1000
+ {
1001
+ this._EventHandlerProvider.fireEvent('onColorThemeChanged', pThemeHash);
1002
+ }
1003
+ }
927
1004
 
928
- // Re-inject marker defs (arrowhead colors may have changed)
929
- this._reinjectMarkerDefs();
930
-
931
- // Full re-render
932
- if (this.initialRenderComplete)
1005
+ /**
1006
+ * The active color theme hash (from pict-provider-theme).
1007
+ * @returns {string|null}
1008
+ */
1009
+ getColorThemeKey()
1010
+ {
1011
+ if (this.fable.providers && this.fable.providers.Theme && typeof this.fable.providers.Theme.getActiveTheme === 'function')
933
1012
  {
934
- this.renderFlow();
1013
+ let tmpActive = this.fable.providers.Theme.getActiveTheme();
1014
+ if (tmpActive && tmpActive.Hash) { return tmpActive.Hash; }
935
1015
  }
1016
+ return null;
1017
+ }
936
1018
 
1019
+ /**
1020
+ * Override just the renderer — controls node body shape, jitter, shadows.
1021
+ * @param {string} pRendererKey
1022
+ */
1023
+ setRenderer(pRendererKey)
1024
+ {
1025
+ if (!this._RendererProvider)
1026
+ {
1027
+ this.log.warn('PictSectionFlow setRenderer: Renderer provider not available');
1028
+ return;
1029
+ }
1030
+ let tmpApplied = this._RendererProvider.setRenderer(pRendererKey);
1031
+ if (!tmpApplied) { return; }
1032
+ if (this._StylePresetsProvider) { this._StylePresetsProvider.markCustomized(); }
1033
+ this._refreshAfterStyleChange();
937
1034
  if (this._EventHandlerProvider)
938
1035
  {
939
- this._EventHandlerProvider.fireEvent('onThemeChanged', pThemeKey);
1036
+ this._EventHandlerProvider.fireEvent('onRendererChanged', pRendererKey);
940
1037
  }
941
1038
  }
942
1039
 
943
1040
  /**
944
- * Set the noise level (0 to 1) and re-render.
1041
+ * The active renderer key.
1042
+ * @returns {string}
1043
+ */
1044
+ getRendererKey()
1045
+ {
1046
+ return this._RendererProvider ? this._RendererProvider.getActiveRendererKey() : 'clean';
1047
+ }
1048
+
1049
+ /**
1050
+ * Set the noise level (0 to 1) and re-render. Noise applies only when
1051
+ * the active renderer enables it (see Renderer.NoiseConfig).
945
1052
  * @param {number} pLevel - 0 = precise, 1 = maximum wobble
946
1053
  */
947
1054
  setNoiseLevel(pLevel)
948
1055
  {
949
- if (!this._ThemeProvider)
1056
+ if (this._RendererProvider)
950
1057
  {
951
- this.log.warn('PictSectionFlow setNoiseLevel: ThemeProvider not available');
952
- return;
1058
+ this._RendererProvider.setNoiseLevel(pLevel);
953
1059
  }
954
-
955
- this._ThemeProvider.setNoiseLevel(pLevel);
956
-
957
- // Full re-render to apply new noise
958
- if (this.initialRenderComplete)
1060
+ else if (this._ThemeProvider)
959
1061
  {
960
- this.renderFlow();
1062
+ this._ThemeProvider.setNoiseLevel(pLevel);
961
1063
  }
1064
+ if (this.initialRenderComplete) { this.renderFlow(); }
962
1065
  }
963
1066
 
964
1067
  /**
965
- * Get the current noise level (0 to 1).
1068
+ * Current noise level (0 to 1).
966
1069
  * @returns {number}
967
1070
  */
968
1071
  getNoiseLevel()
969
1072
  {
970
- if (this._ThemeProvider)
971
- {
972
- return this._ThemeProvider.getNoiseLevel();
973
- }
1073
+ if (this._RendererProvider) { return this._RendererProvider.getNoiseLevel(); }
1074
+ if (this._ThemeProvider) { return this._ThemeProvider.getNoiseLevel(); }
974
1075
  return 0;
975
1076
  }
976
1077
 
977
1078
  /**
978
- * Get the active theme key.
979
- * @returns {string}
1079
+ * @deprecated since the 3-axis refactor — use setStylePreset() instead.
1080
+ * Kept as an alias for back-compat with existing host apps and views.
1081
+ * @param {string} pPresetHash
1082
+ */
1083
+ setTheme(pPresetHash)
1084
+ {
1085
+ this.setStylePreset(pPresetHash);
1086
+ }
1087
+
1088
+ /**
1089
+ * @deprecated since the 3-axis refactor — use getStylePreset() instead.
1090
+ * Returns the active preset hash (or null if customized).
1091
+ * @returns {string|null}
980
1092
  */
981
1093
  getThemeKey()
982
1094
  {
983
- if (this._ThemeProvider)
1095
+ return this.getStylePreset();
1096
+ }
1097
+
1098
+ /**
1099
+ * Common refresh path used by all axis-change methods.
1100
+ * Re-registers the renderer CSS, re-injects marker defs (arrowhead colors
1101
+ * may have shifted with the new theme), and full-renders the flow.
1102
+ * @private
1103
+ */
1104
+ _refreshAfterStyleChange()
1105
+ {
1106
+ if (this._CSSProvider && typeof this._CSSProvider.registerRendererCSS === 'function' && this._RendererProvider)
984
1107
  {
985
- return this._ThemeProvider.getActiveThemeKey();
1108
+ this._CSSProvider.registerRendererCSS(this._RendererProvider.getActiveRenderer());
986
1109
  }
987
- return 'default';
1110
+ if (this._CSSProvider) { this._CSSProvider.registerCSS(); }
1111
+ this._reinjectMarkerDefs();
1112
+ if (this.initialRenderComplete) { this.renderFlow(); }
988
1113
  }
989
1114
 
990
1115
  _reinjectMarkerDefs() { return this._RenderManager.reinjectMarkerDefs(); }
@@ -0,0 +1,133 @@
1
+ const libFable = require('fable');
2
+ const libChai = require('chai');
3
+ const libExpect = libChai.expect;
4
+
5
+ const libRenderer = require('../source/providers/PictProvider-Flow-Renderer.js');
6
+
7
+ suite('PictProvider-Flow-Renderer',
8
+ function ()
9
+ {
10
+ let _Fable;
11
+ let _Renderer;
12
+
13
+ setup(function ()
14
+ {
15
+ _Fable = new libFable({});
16
+ _Renderer = new libRenderer(_Fable, {}, 'Renderer-Test');
17
+ });
18
+
19
+ suite('Built-in renderer registry', function ()
20
+ {
21
+ test('registers clean, bracket, sketch, crt, workstation by default', function ()
22
+ {
23
+ let tmpKeys = _Renderer.getRendererKeys();
24
+ libExpect(tmpKeys).to.include('clean');
25
+ libExpect(tmpKeys).to.include('bracket');
26
+ libExpect(tmpKeys).to.include('sketch');
27
+ libExpect(tmpKeys).to.include('crt');
28
+ libExpect(tmpKeys).to.include('workstation');
29
+ });
30
+
31
+ test('clean is the default active renderer', function ()
32
+ {
33
+ libExpect(_Renderer.getActiveRendererKey()).to.equal('clean');
34
+ });
35
+
36
+ test('each built-in renderer has the required shape', function ()
37
+ {
38
+ let tmpKeys = _Renderer.getRendererKeys();
39
+ for (let i = 0; i < tmpKeys.length; i++)
40
+ {
41
+ let tmpR = _Renderer.getActiveRenderer.call(
42
+ { _Renderers: _Renderer._Renderers, _ActiveRendererKey: tmpKeys[i] });
43
+ libExpect(tmpR, `renderer ${tmpKeys[i]}`).to.have.property('Key', tmpKeys[i]);
44
+ libExpect(tmpR).to.have.property('NodeBodyMode');
45
+ libExpect(tmpR).to.have.property('NoiseConfig');
46
+ libExpect(tmpR).to.have.property('ConnectionConfig');
47
+ libExpect(tmpR).to.have.property('GeometryCSS');
48
+ }
49
+ });
50
+ });
51
+
52
+ suite('setRenderer()', function ()
53
+ {
54
+ test('switches the active renderer + updates noise default', function ()
55
+ {
56
+ _Renderer.setRenderer('sketch');
57
+ libExpect(_Renderer.getActiveRendererKey()).to.equal('sketch');
58
+ let tmpActive = _Renderer.getActiveRenderer();
59
+ libExpect(tmpActive.NodeBodyMode).to.equal('bracket');
60
+ libExpect(_Renderer.getNoiseLevel()).to.equal(0.4);
61
+ });
62
+
63
+ test('returns false for unknown renderer + leaves state unchanged', function ()
64
+ {
65
+ _Renderer.setRenderer('sketch');
66
+ let tmpResult = _Renderer.setRenderer('nonexistent');
67
+ libExpect(tmpResult).to.equal(false);
68
+ libExpect(_Renderer.getActiveRendererKey()).to.equal('sketch');
69
+ });
70
+
71
+ test('switching to a renderer with noise disabled resets noise to 0', function ()
72
+ {
73
+ _Renderer.setRenderer('sketch'); // 0.4
74
+ _Renderer.setRenderer('clean'); // 0
75
+ libExpect(_Renderer.getNoiseLevel()).to.equal(0);
76
+ });
77
+ });
78
+
79
+ suite('register()', function ()
80
+ {
81
+ test('registers a custom renderer + reads back through getActiveRenderer', function ()
82
+ {
83
+ _Renderer.register('custom', {
84
+ Label: 'Custom',
85
+ NodeBodyMode: 'rect',
86
+ NoiseConfig: { Enabled: false, DefaultLevel: 0, MaxJitterPx: 0, AffectsNodes: false, AffectsConnections: false },
87
+ ConnectionConfig: { StrokeWidth: 3, ArrowheadStyle: 'triangle' },
88
+ GeometryCSS: '',
89
+ AdditionalCSS: ''
90
+ });
91
+ let tmpResult = _Renderer.setRenderer('custom');
92
+ libExpect(tmpResult).to.equal(true);
93
+ let tmpActive = _Renderer.getActiveRenderer();
94
+ libExpect(tmpActive.Key).to.equal('custom');
95
+ libExpect(tmpActive.Label).to.equal('Custom');
96
+ });
97
+ });
98
+
99
+ suite('Noise APIs', function ()
100
+ {
101
+ test('setNoiseLevel clamps to [0,1]', function ()
102
+ {
103
+ _Renderer.setNoiseLevel(-1);
104
+ libExpect(_Renderer.getNoiseLevel()).to.equal(0);
105
+ _Renderer.setNoiseLevel(2);
106
+ libExpect(_Renderer.getNoiseLevel()).to.equal(1);
107
+ _Renderer.setNoiseLevel(0.5);
108
+ libExpect(_Renderer.getNoiseLevel()).to.equal(0.5);
109
+ });
110
+
111
+ test('getNodeNoiseAmplitude is 0 when active renderer disables noise', function ()
112
+ {
113
+ _Renderer.setRenderer('clean');
114
+ _Renderer.setNoiseLevel(1);
115
+ libExpect(_Renderer.getNodeNoiseAmplitude()).to.equal(0);
116
+ });
117
+
118
+ test('getNodeNoiseAmplitude scales with noise level when sketch is active', function ()
119
+ {
120
+ _Renderer.setRenderer('sketch');
121
+ _Renderer.setNoiseLevel(0.5);
122
+ // sketch.MaxJitterPx is 4 — 0.5 * 4 = 2
123
+ libExpect(_Renderer.getNodeNoiseAmplitude()).to.equal(2);
124
+ });
125
+
126
+ test('processPathString is a no-op when noise is disabled', function ()
127
+ {
128
+ _Renderer.setRenderer('clean');
129
+ let tmpResult = _Renderer.processPathString('M 0 0 L 10 10', 'seed');
130
+ libExpect(tmpResult).to.equal('M 0 0 L 10 10');
131
+ });
132
+ });
133
+ });