diff --git a/Docs/architecture/grid-style-configuration.md b/Docs/architecture/grid-style-configuration.md
index e345fe90..179a40bc 100644
--- a/Docs/architecture/grid-style-configuration.md
+++ b/Docs/architecture/grid-style-configuration.md
@@ -64,6 +64,29 @@ column factories, `ColumnWidthCalculator`) injects the same typed record.
`GridStyleOptions` is an immutable record with a `Default` fallback used only by tests; the `#000000`
cell-palette placeholders in `Default` are never rendered in production.
+## Record shape: nested per group (interim, slice 3)
+
+`GridStyleOptions` is no longer a flat 78-field record. It is a one-level nested root that composes ~10
+per-group records (`Fonts`, `Layout`, `Selection`, `ChangedCells`, `ReadOnlyCells`, `DisabledCells`,
+`Execution`, `StatusBar`, `ValidationPanel`, `Chrome`) plus the root-level `Orientation`. `ReadOnlyCells`
+and `DisabledCells` share one `DepthPalette` type. Consumers read short nested paths — `gridStyle.Fonts.CellFontSize`,
+`gridStyle.ReadOnlyCells.Depth1`, `gridStyle.Chrome.GridLine` — instead of the old flat fields. Colors stay
+`string` in this shape; typing them as `StyleColor` is slice 4.
+
+**The YAML file, the DTOs, and `GridStyleValidator` are unchanged** by this slice: the file on disk is
+byte-identical, the DTO layer keeps its nested snake_case shape and per-key error reporting, and the load
+mapper owns the small DTO-tree-to-group walk. `SaveThenLoad_DistinctFixture` proves the nested record still
+round-trips losslessly through the unchanged file format. Property references elsewhere in this doc that still
+show a flat path (for example `GridStyleOptions.FontFamily`) now live under their group
+(`GridStyleOptions.Fonts.FontFamily`); those spellings are corrected in the slice-5 doc rewrite.
+
+**Note for slice 4.** The error-path key `colors.grid_line` and the record path `Chrome.GridLine`
+deliberately diverge: `grid_line` is a loose field in the `colors:` DTO section (beside `grid_border` /
+`grid_background`), but the one-level record folds it into `Chrome` so the root carries no lone `string`.
+Slice 4 moves `GridStyleValidator`'s per-key checks into the load mapper; that mapper-resident validation
+**must keep emitting the `colors.grid_line` key**, not rename it to `chrome.grid_line`. The key names the
+YAML path the operator edits, not the record path.
+
## Resources projection
The typed record is projected into `Application.Resources` at startup so XAML can bind styles via
diff --git a/SemiStep/SemiStep.Core/Configuration/ChangedCellColors.cs b/SemiStep/SemiStep.Core/Configuration/ChangedCellColors.cs
new file mode 100644
index 00000000..dc7b4015
--- /dev/null
+++ b/SemiStep/SemiStep.Core/Configuration/ChangedCellColors.cs
@@ -0,0 +1,5 @@
+namespace SemiStep.Core.Configuration;
+
+public sealed record ChangedCellColors(
+ string Changed,
+ string ChangedSelected);
diff --git a/SemiStep/SemiStep.Core/Configuration/ChromeColors.cs b/SemiStep/SemiStep.Core/Configuration/ChromeColors.cs
new file mode 100644
index 00000000..d3696891
--- /dev/null
+++ b/SemiStep/SemiStep.Core/Configuration/ChromeColors.cs
@@ -0,0 +1,17 @@
+namespace SemiStep.Core.Configuration;
+
+public sealed record ChromeColors(
+ string Info,
+ string Connected,
+ string Disconnected,
+ string LocalMode,
+ string Connecting,
+ string PanelBackground,
+ string PanelHeaderBackground,
+ string SubtleBorder,
+ string Separator,
+ string SecondaryForeground,
+ string GridBorder,
+ string GridBackground,
+ string HeaderForeground,
+ string GridLine);
diff --git a/SemiStep/SemiStep.Core/Configuration/DepthPalette.cs b/SemiStep/SemiStep.Core/Configuration/DepthPalette.cs
new file mode 100644
index 00000000..ee790a5b
--- /dev/null
+++ b/SemiStep/SemiStep.Core/Configuration/DepthPalette.cs
@@ -0,0 +1,13 @@
+namespace SemiStep.Core.Configuration;
+
+public sealed record DepthPalette(
+ string Depth0,
+ string Depth1,
+ string Depth2,
+ string Depth3,
+ string Depth0Past,
+ string Depth1Past,
+ string Depth2Past,
+ string Depth3Past,
+ string Selected,
+ string Foreground);
diff --git a/SemiStep/SemiStep.Core/Configuration/ExecutionPalette.cs b/SemiStep/SemiStep.Core/Configuration/ExecutionPalette.cs
new file mode 100644
index 00000000..afd8cf69
--- /dev/null
+++ b/SemiStep/SemiStep.Core/Configuration/ExecutionPalette.cs
@@ -0,0 +1,12 @@
+namespace SemiStep.Core.Configuration;
+
+public sealed record ExecutionPalette(
+ string Depth0,
+ string Depth1,
+ string Depth2,
+ string Depth3,
+ string Depth0Past,
+ string Depth1Past,
+ string Depth2Past,
+ string Depth3Past,
+ string CurrentStepMarker);
diff --git a/SemiStep/SemiStep.Core/Configuration/GridStyleFonts.cs b/SemiStep/SemiStep.Core/Configuration/GridStyleFonts.cs
new file mode 100644
index 00000000..cca70337
--- /dev/null
+++ b/SemiStep/SemiStep.Core/Configuration/GridStyleFonts.cs
@@ -0,0 +1,10 @@
+namespace SemiStep.Core.Configuration;
+
+public sealed record GridStyleFonts(
+ string FontFamily,
+ int HeaderFontSize,
+ int HeaderFontWeight,
+ bool HeaderItalic,
+ int CellFontSize,
+ int CellFontWeight,
+ bool CellItalic);
diff --git a/SemiStep/SemiStep.Core/Configuration/GridStyleLayout.cs b/SemiStep/SemiStep.Core/Configuration/GridStyleLayout.cs
new file mode 100644
index 00000000..16d61ad8
--- /dev/null
+++ b/SemiStep/SemiStep.Core/Configuration/GridStyleLayout.cs
@@ -0,0 +1,8 @@
+namespace SemiStep.Core.Configuration;
+
+public sealed record GridStyleLayout(
+ double CellPaddingLeft,
+ double CellPaddingTop,
+ double CellPaddingRight,
+ double CellPaddingBottom,
+ double RowHeight);
diff --git a/SemiStep/SemiStep.Core/Configuration/GridStyleOptions.cs b/SemiStep/SemiStep.Core/Configuration/GridStyleOptions.cs
index fd8d6cb8..8ed6c194 100644
--- a/SemiStep/SemiStep.Core/Configuration/GridStyleOptions.cs
+++ b/SemiStep/SemiStep.Core/Configuration/GridStyleOptions.cs
@@ -1,163 +1,106 @@
namespace SemiStep.Core.Configuration;
public sealed record GridStyleOptions(
- string FontFamily,
- int HeaderFontSize,
- int HeaderFontWeight,
- bool HeaderItalic,
- int CellFontSize,
- int CellFontWeight,
- bool CellItalic,
- double CellPaddingLeft,
- double CellPaddingTop,
- double CellPaddingRight,
- double CellPaddingBottom,
- double RowHeight,
- string SelectionBackgroundColor,
- string SelectionForegroundColor,
- string CellChangedColor,
- string CellChangedSelectedColor,
- string DisabledCellDepth0Color,
- string DisabledCellDepth1Color,
- string DisabledCellDepth2Color,
- string DisabledCellDepth3Color,
- string DisabledCellDepth0PastColor,
- string DisabledCellDepth1PastColor,
- string DisabledCellDepth2PastColor,
- string DisabledCellDepth3PastColor,
- string DisabledCellSelectedColor,
- string DisabledCellForegroundColor,
- string ReadOnlyCellDepth0Color,
- string ReadOnlyCellDepth1Color,
- string ReadOnlyCellDepth2Color,
- string ReadOnlyCellDepth3Color,
- string ReadOnlyCellDepth0PastColor,
- string ReadOnlyCellDepth1PastColor,
- string ReadOnlyCellDepth2PastColor,
- string ReadOnlyCellDepth3PastColor,
- string ReadOnlyCellSelectedColor,
- string ReadOnlyCellForegroundColor,
- string GridLineColor,
- string StatusBarBackgroundColor,
- string StatusBarForegroundColor,
- double StatusBarPadding,
- double StatusBarItemSpacing,
- int StatusBarFontSize,
- int StatusBarFontWeight,
- bool StatusBarItalic,
- int StatusBarTimerLabelFontSize,
- int StatusBarTimerLabelFontWeight,
- bool StatusBarTimerLabelItalic,
- int StatusBarTimerValueFontSize,
- int StatusBarTimerValueFontWeight,
- bool StatusBarTimerValueItalic,
- string ValidationPanelBackgroundColor,
- string ValidationPanelForegroundColor,
- string ValidationPanelErrorColor,
- string ValidationPanelWarningColor,
- double ValidationPanelMaxHeight,
- string ExecutionDepth0Color,
- string ExecutionDepth1Color,
- string ExecutionDepth2Color,
- string ExecutionDepth3Color,
- string ExecutionDepth0PastColor,
- string ExecutionDepth1PastColor,
- string ExecutionDepth2PastColor,
- string ExecutionDepth3PastColor,
- string ExecutionCurrentStepMarkerColor,
- string InfoColor,
- string ConnectedColor,
- string DisconnectedColor,
- string LocalModeColor,
- string ConnectingColor,
- string PanelBackgroundColor,
- string PanelHeaderBackgroundColor,
- string SubtleBorderColor,
- string SeparatorColor,
- string SecondaryForegroundColor,
- string GridBorderColor,
- string GridBackgroundColor,
- string HeaderForegroundColor,
+ GridStyleFonts Fonts,
+ GridStyleLayout Layout,
+ SelectionColors Selection,
+ ChangedCellColors ChangedCells,
+ DepthPalette ReadOnlyCells,
+ DepthPalette DisabledCells,
+ ExecutionPalette Execution,
+ StatusBarStyle StatusBar,
+ ValidationPanelStyle ValidationPanel,
+ ChromeColors Chrome,
GridOrientation Orientation)
{
// Test-only fallback; the #000000 cell palettes are placeholders, never rendered in production.
public static GridStyleOptions Default { get; } = new(
- FontFamily: "",
- HeaderFontSize: 14,
- HeaderFontWeight: 700,
- HeaderItalic: false,
- CellFontSize: 12,
- CellFontWeight: 400,
- CellItalic: false,
- CellPaddingLeft: 6,
- CellPaddingTop: 4,
- CellPaddingRight: 6,
- CellPaddingBottom: 4,
- RowHeight: 28,
- SelectionBackgroundColor: "#CCE4F7",
- SelectionForegroundColor: "#202020",
- CellChangedColor: "#FFCC80",
- CellChangedSelectedColor: "#EFD3A4",
- DisabledCellDepth0Color: "#000000",
- DisabledCellDepth1Color: "#000000",
- DisabledCellDepth2Color: "#000000",
- DisabledCellDepth3Color: "#000000",
- DisabledCellDepth0PastColor: "#000000",
- DisabledCellDepth1PastColor: "#000000",
- DisabledCellDepth2PastColor: "#000000",
- DisabledCellDepth3PastColor: "#000000",
- DisabledCellSelectedColor: "#000000",
- DisabledCellForegroundColor: "#000000",
- ReadOnlyCellDepth0Color: "#000000",
- ReadOnlyCellDepth1Color: "#000000",
- ReadOnlyCellDepth2Color: "#000000",
- ReadOnlyCellDepth3Color: "#000000",
- ReadOnlyCellDepth0PastColor: "#000000",
- ReadOnlyCellDepth1PastColor: "#000000",
- ReadOnlyCellDepth2PastColor: "#000000",
- ReadOnlyCellDepth3PastColor: "#000000",
- ReadOnlyCellSelectedColor: "#000000",
- ReadOnlyCellForegroundColor: "#000000",
- GridLineColor: "#E0E0E0",
- StatusBarBackgroundColor: "#F0F0F0",
- StatusBarForegroundColor: "#000000",
- StatusBarPadding: 5,
- StatusBarItemSpacing: 10,
- StatusBarFontSize: 12,
- StatusBarFontWeight: 400,
- StatusBarItalic: false,
- StatusBarTimerLabelFontSize: 14,
- StatusBarTimerLabelFontWeight: 400,
- StatusBarTimerLabelItalic: false,
- StatusBarTimerValueFontSize: 24,
- StatusBarTimerValueFontWeight: 400,
- StatusBarTimerValueItalic: false,
- ValidationPanelBackgroundColor: "#FFFFFF",
- ValidationPanelForegroundColor: "#000000",
- ValidationPanelErrorColor: "#D32F2F",
- ValidationPanelWarningColor: "#F57C00",
- ValidationPanelMaxHeight: 100,
- ExecutionDepth0Color: "#000000",
- ExecutionDepth1Color: "#000000",
- ExecutionDepth2Color: "#000000",
- ExecutionDepth3Color: "#000000",
- ExecutionDepth0PastColor: "#000000",
- ExecutionDepth1PastColor: "#000000",
- ExecutionDepth2PastColor: "#000000",
- ExecutionDepth3PastColor: "#000000",
- ExecutionCurrentStepMarkerColor: "#000000",
- InfoColor: "#1976D2",
- ConnectedColor: "#44BB44",
- DisconnectedColor: "#FF4444",
- LocalModeColor: "#6C707E",
- ConnectingColor: "#FFAF0F",
- PanelBackgroundColor: "#F8F8F8",
- PanelHeaderBackgroundColor: "#EEEEEE",
- SubtleBorderColor: "#D0D0D0",
- SeparatorColor: "#C0C0C0",
- SecondaryForegroundColor: "#888888",
- GridBorderColor: "#808080",
- GridBackgroundColor: "#FFFFFF",
- HeaderForegroundColor: "#000000",
+ Fonts: new GridStyleFonts(
+ FontFamily: "",
+ HeaderFontSize: 14,
+ HeaderFontWeight: 700,
+ HeaderItalic: false,
+ CellFontSize: 12,
+ CellFontWeight: 400,
+ CellItalic: false),
+ Layout: new GridStyleLayout(
+ CellPaddingLeft: 6,
+ CellPaddingTop: 4,
+ CellPaddingRight: 6,
+ CellPaddingBottom: 4,
+ RowHeight: 28),
+ Selection: new SelectionColors(
+ Background: "#CCE4F7",
+ Foreground: "#202020"),
+ ChangedCells: new ChangedCellColors(
+ Changed: "#FFCC80",
+ ChangedSelected: "#EFD3A4"),
+ ReadOnlyCells: new DepthPalette(
+ Depth0: "#000000",
+ Depth1: "#000000",
+ Depth2: "#000000",
+ Depth3: "#000000",
+ Depth0Past: "#000000",
+ Depth1Past: "#000000",
+ Depth2Past: "#000000",
+ Depth3Past: "#000000",
+ Selected: "#000000",
+ Foreground: "#000000"),
+ DisabledCells: new DepthPalette(
+ Depth0: "#000000",
+ Depth1: "#000000",
+ Depth2: "#000000",
+ Depth3: "#000000",
+ Depth0Past: "#000000",
+ Depth1Past: "#000000",
+ Depth2Past: "#000000",
+ Depth3Past: "#000000",
+ Selected: "#000000",
+ Foreground: "#000000"),
+ Execution: new ExecutionPalette(
+ Depth0: "#000000",
+ Depth1: "#000000",
+ Depth2: "#000000",
+ Depth3: "#000000",
+ Depth0Past: "#000000",
+ Depth1Past: "#000000",
+ Depth2Past: "#000000",
+ Depth3Past: "#000000",
+ CurrentStepMarker: "#000000"),
+ StatusBar: new StatusBarStyle(
+ Background: "#F0F0F0",
+ Foreground: "#000000",
+ Padding: 5,
+ ItemSpacing: 10,
+ FontSize: 12,
+ Weight: 400,
+ Italic: false,
+ TimerLabelFontSize: 14,
+ TimerLabelWeight: 400,
+ TimerLabelItalic: false,
+ TimerValueFontSize: 24,
+ TimerValueWeight: 400,
+ TimerValueItalic: false),
+ ValidationPanel: new ValidationPanelStyle(
+ Background: "#FFFFFF",
+ Foreground: "#000000",
+ ErrorColor: "#D32F2F",
+ WarningColor: "#F57C00",
+ MaxHeight: 100),
+ Chrome: new ChromeColors(
+ Info: "#1976D2",
+ Connected: "#44BB44",
+ Disconnected: "#FF4444",
+ LocalMode: "#6C707E",
+ Connecting: "#FFAF0F",
+ PanelBackground: "#F8F8F8",
+ PanelHeaderBackground: "#EEEEEE",
+ SubtleBorder: "#D0D0D0",
+ Separator: "#C0C0C0",
+ SecondaryForeground: "#888888",
+ GridBorder: "#808080",
+ GridBackground: "#FFFFFF",
+ HeaderForeground: "#000000",
+ GridLine: "#E0E0E0"),
Orientation: GridOrientation.RowsAsSteps);
}
diff --git a/SemiStep/SemiStep.Core/Configuration/Mapping/GridStyleDtoMapper.cs b/SemiStep/SemiStep.Core/Configuration/Mapping/GridStyleDtoMapper.cs
index 398e7c8b..3632a5e9 100644
--- a/SemiStep/SemiStep.Core/Configuration/Mapping/GridStyleDtoMapper.cs
+++ b/SemiStep/SemiStep.Core/Configuration/Mapping/GridStyleDtoMapper.cs
@@ -10,113 +10,113 @@ public static GridStyleOptionsDto Map(GridStyleOptions options)
{
Fonts = new GridStyleFontsDto
{
- Family = options.FontFamily,
- HeaderSize = options.HeaderFontSize,
- HeaderWeight = options.HeaderFontWeight,
- HeaderItalic = options.HeaderItalic,
- CellSize = options.CellFontSize,
- CellWeight = options.CellFontWeight,
- CellItalic = options.CellItalic
+ Family = options.Fonts.FontFamily,
+ HeaderSize = options.Fonts.HeaderFontSize,
+ HeaderWeight = options.Fonts.HeaderFontWeight,
+ HeaderItalic = options.Fonts.HeaderItalic,
+ CellSize = options.Fonts.CellFontSize,
+ CellWeight = options.Fonts.CellFontWeight,
+ CellItalic = options.Fonts.CellItalic
},
Layout = new GridStyleLayoutDto
{
- CellPaddingLeft = options.CellPaddingLeft,
- CellPaddingTop = options.CellPaddingTop,
- CellPaddingRight = options.CellPaddingRight,
- CellPaddingBottom = options.CellPaddingBottom,
- RowHeight = options.RowHeight
+ CellPaddingLeft = options.Layout.CellPaddingLeft,
+ CellPaddingTop = options.Layout.CellPaddingTop,
+ CellPaddingRight = options.Layout.CellPaddingRight,
+ CellPaddingBottom = options.Layout.CellPaddingBottom,
+ RowHeight = options.Layout.RowHeight
},
Colors = new GridStyleColorsDto
{
Selection = new GridStyleSelectionColorsDto
{
- Background = options.SelectionBackgroundColor,
- Foreground = options.SelectionForegroundColor
+ Background = options.Selection.Background,
+ Foreground = options.Selection.Foreground
},
Cells = new GridStyleCellColorsDto
{
- Changed = options.CellChangedColor,
- ChangedSelected = options.CellChangedSelectedColor,
+ Changed = options.ChangedCells.Changed,
+ ChangedSelected = options.ChangedCells.ChangedSelected,
ReadOnly = new GridStyleReadOnlyCellColorsDto
{
- Depth0 = options.ReadOnlyCellDepth0Color,
- Depth1 = options.ReadOnlyCellDepth1Color,
- Depth2 = options.ReadOnlyCellDepth2Color,
- Depth3 = options.ReadOnlyCellDepth3Color,
- Depth0Past = options.ReadOnlyCellDepth0PastColor,
- Depth1Past = options.ReadOnlyCellDepth1PastColor,
- Depth2Past = options.ReadOnlyCellDepth2PastColor,
- Depth3Past = options.ReadOnlyCellDepth3PastColor,
- Selected = options.ReadOnlyCellSelectedColor,
- Foreground = options.ReadOnlyCellForegroundColor
+ Depth0 = options.ReadOnlyCells.Depth0,
+ Depth1 = options.ReadOnlyCells.Depth1,
+ Depth2 = options.ReadOnlyCells.Depth2,
+ Depth3 = options.ReadOnlyCells.Depth3,
+ Depth0Past = options.ReadOnlyCells.Depth0Past,
+ Depth1Past = options.ReadOnlyCells.Depth1Past,
+ Depth2Past = options.ReadOnlyCells.Depth2Past,
+ Depth3Past = options.ReadOnlyCells.Depth3Past,
+ Selected = options.ReadOnlyCells.Selected,
+ Foreground = options.ReadOnlyCells.Foreground
},
Disabled = new GridStyleDisabledCellColorsDto
{
- Depth0 = options.DisabledCellDepth0Color,
- Depth1 = options.DisabledCellDepth1Color,
- Depth2 = options.DisabledCellDepth2Color,
- Depth3 = options.DisabledCellDepth3Color,
- Depth0Past = options.DisabledCellDepth0PastColor,
- Depth1Past = options.DisabledCellDepth1PastColor,
- Depth2Past = options.DisabledCellDepth2PastColor,
- Depth3Past = options.DisabledCellDepth3PastColor,
- Selected = options.DisabledCellSelectedColor,
- Foreground = options.DisabledCellForegroundColor
+ Depth0 = options.DisabledCells.Depth0,
+ Depth1 = options.DisabledCells.Depth1,
+ Depth2 = options.DisabledCells.Depth2,
+ Depth3 = options.DisabledCells.Depth3,
+ Depth0Past = options.DisabledCells.Depth0Past,
+ Depth1Past = options.DisabledCells.Depth1Past,
+ Depth2Past = options.DisabledCells.Depth2Past,
+ Depth3Past = options.DisabledCells.Depth3Past,
+ Selected = options.DisabledCells.Selected,
+ Foreground = options.DisabledCells.Foreground
},
Execution = new GridStyleExecutionColorsDto
{
- Depth0 = options.ExecutionDepth0Color,
- Depth1 = options.ExecutionDepth1Color,
- Depth2 = options.ExecutionDepth2Color,
- Depth3 = options.ExecutionDepth3Color,
- Depth0Past = options.ExecutionDepth0PastColor,
- Depth1Past = options.ExecutionDepth1PastColor,
- Depth2Past = options.ExecutionDepth2PastColor,
- Depth3Past = options.ExecutionDepth3PastColor,
- CurrentStepMarker = options.ExecutionCurrentStepMarkerColor
+ Depth0 = options.Execution.Depth0,
+ Depth1 = options.Execution.Depth1,
+ Depth2 = options.Execution.Depth2,
+ Depth3 = options.Execution.Depth3,
+ Depth0Past = options.Execution.Depth0Past,
+ Depth1Past = options.Execution.Depth1Past,
+ Depth2Past = options.Execution.Depth2Past,
+ Depth3Past = options.Execution.Depth3Past,
+ CurrentStepMarker = options.Execution.CurrentStepMarker
}
},
- GridLine = options.GridLineColor
+ GridLine = options.Chrome.GridLine
},
StatusBar = new StatusBarStyleDto
{
- Background = options.StatusBarBackgroundColor,
- Foreground = options.StatusBarForegroundColor,
- Padding = options.StatusBarPadding,
- ItemSpacing = options.StatusBarItemSpacing,
- FontSize = options.StatusBarFontSize,
- Weight = options.StatusBarFontWeight,
- Italic = options.StatusBarItalic,
- TimerLabelFontSize = options.StatusBarTimerLabelFontSize,
- TimerLabelWeight = options.StatusBarTimerLabelFontWeight,
- TimerLabelItalic = options.StatusBarTimerLabelItalic,
- TimerValueFontSize = options.StatusBarTimerValueFontSize,
- TimerValueWeight = options.StatusBarTimerValueFontWeight,
- TimerValueItalic = options.StatusBarTimerValueItalic
+ Background = options.StatusBar.Background,
+ Foreground = options.StatusBar.Foreground,
+ Padding = options.StatusBar.Padding,
+ ItemSpacing = options.StatusBar.ItemSpacing,
+ FontSize = options.StatusBar.FontSize,
+ Weight = options.StatusBar.Weight,
+ Italic = options.StatusBar.Italic,
+ TimerLabelFontSize = options.StatusBar.TimerLabelFontSize,
+ TimerLabelWeight = options.StatusBar.TimerLabelWeight,
+ TimerLabelItalic = options.StatusBar.TimerLabelItalic,
+ TimerValueFontSize = options.StatusBar.TimerValueFontSize,
+ TimerValueWeight = options.StatusBar.TimerValueWeight,
+ TimerValueItalic = options.StatusBar.TimerValueItalic
},
ValidationPanel = new ValidationPanelStyleDto
{
- Background = options.ValidationPanelBackgroundColor,
- Foreground = options.ValidationPanelForegroundColor,
- ErrorColor = options.ValidationPanelErrorColor,
- WarningColor = options.ValidationPanelWarningColor,
- MaxHeight = options.ValidationPanelMaxHeight
+ Background = options.ValidationPanel.Background,
+ Foreground = options.ValidationPanel.Foreground,
+ ErrorColor = options.ValidationPanel.ErrorColor,
+ WarningColor = options.ValidationPanel.WarningColor,
+ MaxHeight = options.ValidationPanel.MaxHeight
},
Chrome = new GridStyleChromeColorsDto
{
- Info = options.InfoColor,
- Connected = options.ConnectedColor,
- Disconnected = options.DisconnectedColor,
- LocalMode = options.LocalModeColor,
- Connecting = options.ConnectingColor,
- PanelBackground = options.PanelBackgroundColor,
- PanelHeaderBackground = options.PanelHeaderBackgroundColor,
- SubtleBorder = options.SubtleBorderColor,
- Separator = options.SeparatorColor,
- SecondaryForeground = options.SecondaryForegroundColor,
- GridBorder = options.GridBorderColor,
- GridBackground = options.GridBackgroundColor,
- HeaderForeground = options.HeaderForegroundColor
+ Info = options.Chrome.Info,
+ Connected = options.Chrome.Connected,
+ Disconnected = options.Chrome.Disconnected,
+ LocalMode = options.Chrome.LocalMode,
+ Connecting = options.Chrome.Connecting,
+ PanelBackground = options.Chrome.PanelBackground,
+ PanelHeaderBackground = options.Chrome.PanelHeaderBackground,
+ SubtleBorder = options.Chrome.SubtleBorder,
+ Separator = options.Chrome.Separator,
+ SecondaryForeground = options.Chrome.SecondaryForeground,
+ GridBorder = options.Chrome.GridBorder,
+ GridBackground = options.Chrome.GridBackground,
+ HeaderForeground = options.Chrome.HeaderForeground
},
Orientation = GridOrientationValues.Serialize(options.Orientation)
};
diff --git a/SemiStep/SemiStep.Core/Configuration/Mapping/GridStyleMapper.cs b/SemiStep/SemiStep.Core/Configuration/Mapping/GridStyleMapper.cs
index 14d664b3..469a6b5d 100644
--- a/SemiStep/SemiStep.Core/Configuration/Mapping/GridStyleMapper.cs
+++ b/SemiStep/SemiStep.Core/Configuration/Mapping/GridStyleMapper.cs
@@ -18,83 +18,93 @@ public static GridStyleOptions Map(GridStyleOptionsDto? dto)
var executionCells = dto.Colors!.Cells!.Execution!;
return new GridStyleOptions(
- FontFamily: dto.Fonts?.Family ?? defaults.FontFamily,
- HeaderFontSize: dto.Fonts?.HeaderSize ?? defaults.HeaderFontSize,
- HeaderFontWeight: dto.Fonts?.HeaderWeight ?? defaults.HeaderFontWeight,
- HeaderItalic: dto.Fonts?.HeaderItalic ?? defaults.HeaderItalic,
- CellFontSize: dto.Fonts?.CellSize ?? defaults.CellFontSize,
- CellFontWeight: dto.Fonts?.CellWeight ?? defaults.CellFontWeight,
- CellItalic: dto.Fonts?.CellItalic ?? defaults.CellItalic,
- CellPaddingLeft: dto.Layout?.CellPaddingLeft ?? defaults.CellPaddingLeft,
- CellPaddingTop: dto.Layout?.CellPaddingTop ?? defaults.CellPaddingTop,
- CellPaddingRight: dto.Layout?.CellPaddingRight ?? defaults.CellPaddingRight,
- CellPaddingBottom: dto.Layout?.CellPaddingBottom ?? defaults.CellPaddingBottom,
- RowHeight: dto.Layout?.RowHeight ?? defaults.RowHeight,
- SelectionBackgroundColor: dto.Colors?.Selection?.Background ?? defaults.SelectionBackgroundColor,
- SelectionForegroundColor: dto.Colors?.Selection?.Foreground ?? defaults.SelectionForegroundColor,
- CellChangedColor: dto.Colors?.Cells?.Changed ?? defaults.CellChangedColor,
- CellChangedSelectedColor: dto.Colors?.Cells?.ChangedSelected ?? defaults.CellChangedSelectedColor,
- DisabledCellDepth0Color: disabledCells.Depth0!,
- DisabledCellDepth1Color: disabledCells.Depth1!,
- DisabledCellDepth2Color: disabledCells.Depth2!,
- DisabledCellDepth3Color: disabledCells.Depth3!,
- DisabledCellDepth0PastColor: disabledCells.Depth0Past!,
- DisabledCellDepth1PastColor: disabledCells.Depth1Past!,
- DisabledCellDepth2PastColor: disabledCells.Depth2Past!,
- DisabledCellDepth3PastColor: disabledCells.Depth3Past!,
- DisabledCellSelectedColor: disabledCells.Selected!,
- DisabledCellForegroundColor: disabledCells.Foreground!,
- ReadOnlyCellDepth0Color: readOnlyCells.Depth0!,
- ReadOnlyCellDepth1Color: readOnlyCells.Depth1!,
- ReadOnlyCellDepth2Color: readOnlyCells.Depth2!,
- ReadOnlyCellDepth3Color: readOnlyCells.Depth3!,
- ReadOnlyCellDepth0PastColor: readOnlyCells.Depth0Past!,
- ReadOnlyCellDepth1PastColor: readOnlyCells.Depth1Past!,
- ReadOnlyCellDepth2PastColor: readOnlyCells.Depth2Past!,
- ReadOnlyCellDepth3PastColor: readOnlyCells.Depth3Past!,
- ReadOnlyCellSelectedColor: readOnlyCells.Selected!,
- ReadOnlyCellForegroundColor: readOnlyCells.Foreground!,
- GridLineColor: dto.Colors?.GridLine ?? defaults.GridLineColor,
- StatusBarBackgroundColor: dto.StatusBar?.Background ?? defaults.StatusBarBackgroundColor,
- StatusBarForegroundColor: dto.StatusBar?.Foreground ?? defaults.StatusBarForegroundColor,
- StatusBarPadding: dto.StatusBar?.Padding ?? defaults.StatusBarPadding,
- StatusBarItemSpacing: dto.StatusBar?.ItemSpacing ?? defaults.StatusBarItemSpacing,
- StatusBarFontSize: dto.StatusBar?.FontSize ?? defaults.StatusBarFontSize,
- StatusBarFontWeight: dto.StatusBar?.Weight ?? defaults.StatusBarFontWeight,
- StatusBarItalic: dto.StatusBar?.Italic ?? defaults.StatusBarItalic,
- StatusBarTimerLabelFontSize: dto.StatusBar?.TimerLabelFontSize ?? defaults.StatusBarTimerLabelFontSize,
- StatusBarTimerLabelFontWeight: dto.StatusBar?.TimerLabelWeight ?? defaults.StatusBarTimerLabelFontWeight,
- StatusBarTimerLabelItalic: dto.StatusBar?.TimerLabelItalic ?? defaults.StatusBarTimerLabelItalic,
- StatusBarTimerValueFontSize: dto.StatusBar?.TimerValueFontSize ?? defaults.StatusBarTimerValueFontSize,
- StatusBarTimerValueFontWeight: dto.StatusBar?.TimerValueWeight ?? defaults.StatusBarTimerValueFontWeight,
- StatusBarTimerValueItalic: dto.StatusBar?.TimerValueItalic ?? defaults.StatusBarTimerValueItalic,
- ValidationPanelBackgroundColor: dto.ValidationPanel?.Background ?? defaults.ValidationPanelBackgroundColor,
- ValidationPanelForegroundColor: dto.ValidationPanel?.Foreground ?? defaults.ValidationPanelForegroundColor,
- ValidationPanelErrorColor: dto.ValidationPanel?.ErrorColor ?? defaults.ValidationPanelErrorColor,
- ValidationPanelWarningColor: dto.ValidationPanel?.WarningColor ?? defaults.ValidationPanelWarningColor,
- ValidationPanelMaxHeight: dto.ValidationPanel?.MaxHeight ?? defaults.ValidationPanelMaxHeight,
- ExecutionDepth0Color: executionCells.Depth0!,
- ExecutionDepth1Color: executionCells.Depth1!,
- ExecutionDepth2Color: executionCells.Depth2!,
- ExecutionDepth3Color: executionCells.Depth3!,
- ExecutionDepth0PastColor: executionCells.Depth0Past!,
- ExecutionDepth1PastColor: executionCells.Depth1Past!,
- ExecutionDepth2PastColor: executionCells.Depth2Past!,
- ExecutionDepth3PastColor: executionCells.Depth3Past!,
- ExecutionCurrentStepMarkerColor: executionCells.CurrentStepMarker!,
- InfoColor: dto.Chrome?.Info ?? defaults.InfoColor,
- ConnectedColor: dto.Chrome?.Connected ?? defaults.ConnectedColor,
- DisconnectedColor: dto.Chrome?.Disconnected ?? defaults.DisconnectedColor,
- LocalModeColor: dto.Chrome?.LocalMode ?? defaults.LocalModeColor,
- ConnectingColor: dto.Chrome?.Connecting ?? defaults.ConnectingColor,
- PanelBackgroundColor: dto.Chrome?.PanelBackground ?? defaults.PanelBackgroundColor,
- PanelHeaderBackgroundColor: dto.Chrome?.PanelHeaderBackground ?? defaults.PanelHeaderBackgroundColor,
- SubtleBorderColor: dto.Chrome?.SubtleBorder ?? defaults.SubtleBorderColor,
- SeparatorColor: dto.Chrome?.Separator ?? defaults.SeparatorColor,
- SecondaryForegroundColor: dto.Chrome?.SecondaryForeground ?? defaults.SecondaryForegroundColor,
- GridBorderColor: dto.Chrome?.GridBorder ?? defaults.GridBorderColor,
- GridBackgroundColor: dto.Chrome?.GridBackground ?? defaults.GridBackgroundColor,
- HeaderForegroundColor: dto.Chrome?.HeaderForeground ?? defaults.HeaderForegroundColor,
+ Fonts: new GridStyleFonts(
+ FontFamily: dto.Fonts?.Family ?? defaults.Fonts.FontFamily,
+ HeaderFontSize: dto.Fonts?.HeaderSize ?? defaults.Fonts.HeaderFontSize,
+ HeaderFontWeight: dto.Fonts?.HeaderWeight ?? defaults.Fonts.HeaderFontWeight,
+ HeaderItalic: dto.Fonts?.HeaderItalic ?? defaults.Fonts.HeaderItalic,
+ CellFontSize: dto.Fonts?.CellSize ?? defaults.Fonts.CellFontSize,
+ CellFontWeight: dto.Fonts?.CellWeight ?? defaults.Fonts.CellFontWeight,
+ CellItalic: dto.Fonts?.CellItalic ?? defaults.Fonts.CellItalic),
+ Layout: new GridStyleLayout(
+ CellPaddingLeft: dto.Layout?.CellPaddingLeft ?? defaults.Layout.CellPaddingLeft,
+ CellPaddingTop: dto.Layout?.CellPaddingTop ?? defaults.Layout.CellPaddingTop,
+ CellPaddingRight: dto.Layout?.CellPaddingRight ?? defaults.Layout.CellPaddingRight,
+ CellPaddingBottom: dto.Layout?.CellPaddingBottom ?? defaults.Layout.CellPaddingBottom,
+ RowHeight: dto.Layout?.RowHeight ?? defaults.Layout.RowHeight),
+ Selection: new SelectionColors(
+ Background: dto.Colors?.Selection?.Background ?? defaults.Selection.Background,
+ Foreground: dto.Colors?.Selection?.Foreground ?? defaults.Selection.Foreground),
+ ChangedCells: new ChangedCellColors(
+ Changed: dto.Colors?.Cells?.Changed ?? defaults.ChangedCells.Changed,
+ ChangedSelected: dto.Colors?.Cells?.ChangedSelected ?? defaults.ChangedCells.ChangedSelected),
+ ReadOnlyCells: new DepthPalette(
+ Depth0: readOnlyCells.Depth0!,
+ Depth1: readOnlyCells.Depth1!,
+ Depth2: readOnlyCells.Depth2!,
+ Depth3: readOnlyCells.Depth3!,
+ Depth0Past: readOnlyCells.Depth0Past!,
+ Depth1Past: readOnlyCells.Depth1Past!,
+ Depth2Past: readOnlyCells.Depth2Past!,
+ Depth3Past: readOnlyCells.Depth3Past!,
+ Selected: readOnlyCells.Selected!,
+ Foreground: readOnlyCells.Foreground!),
+ DisabledCells: new DepthPalette(
+ Depth0: disabledCells.Depth0!,
+ Depth1: disabledCells.Depth1!,
+ Depth2: disabledCells.Depth2!,
+ Depth3: disabledCells.Depth3!,
+ Depth0Past: disabledCells.Depth0Past!,
+ Depth1Past: disabledCells.Depth1Past!,
+ Depth2Past: disabledCells.Depth2Past!,
+ Depth3Past: disabledCells.Depth3Past!,
+ Selected: disabledCells.Selected!,
+ Foreground: disabledCells.Foreground!),
+ Execution: new ExecutionPalette(
+ Depth0: executionCells.Depth0!,
+ Depth1: executionCells.Depth1!,
+ Depth2: executionCells.Depth2!,
+ Depth3: executionCells.Depth3!,
+ Depth0Past: executionCells.Depth0Past!,
+ Depth1Past: executionCells.Depth1Past!,
+ Depth2Past: executionCells.Depth2Past!,
+ Depth3Past: executionCells.Depth3Past!,
+ CurrentStepMarker: executionCells.CurrentStepMarker!),
+ StatusBar: new StatusBarStyle(
+ Background: dto.StatusBar?.Background ?? defaults.StatusBar.Background,
+ Foreground: dto.StatusBar?.Foreground ?? defaults.StatusBar.Foreground,
+ Padding: dto.StatusBar?.Padding ?? defaults.StatusBar.Padding,
+ ItemSpacing: dto.StatusBar?.ItemSpacing ?? defaults.StatusBar.ItemSpacing,
+ FontSize: dto.StatusBar?.FontSize ?? defaults.StatusBar.FontSize,
+ Weight: dto.StatusBar?.Weight ?? defaults.StatusBar.Weight,
+ Italic: dto.StatusBar?.Italic ?? defaults.StatusBar.Italic,
+ TimerLabelFontSize: dto.StatusBar?.TimerLabelFontSize ?? defaults.StatusBar.TimerLabelFontSize,
+ TimerLabelWeight: dto.StatusBar?.TimerLabelWeight ?? defaults.StatusBar.TimerLabelWeight,
+ TimerLabelItalic: dto.StatusBar?.TimerLabelItalic ?? defaults.StatusBar.TimerLabelItalic,
+ TimerValueFontSize: dto.StatusBar?.TimerValueFontSize ?? defaults.StatusBar.TimerValueFontSize,
+ TimerValueWeight: dto.StatusBar?.TimerValueWeight ?? defaults.StatusBar.TimerValueWeight,
+ TimerValueItalic: dto.StatusBar?.TimerValueItalic ?? defaults.StatusBar.TimerValueItalic),
+ ValidationPanel: new ValidationPanelStyle(
+ Background: dto.ValidationPanel?.Background ?? defaults.ValidationPanel.Background,
+ Foreground: dto.ValidationPanel?.Foreground ?? defaults.ValidationPanel.Foreground,
+ ErrorColor: dto.ValidationPanel?.ErrorColor ?? defaults.ValidationPanel.ErrorColor,
+ WarningColor: dto.ValidationPanel?.WarningColor ?? defaults.ValidationPanel.WarningColor,
+ MaxHeight: dto.ValidationPanel?.MaxHeight ?? defaults.ValidationPanel.MaxHeight),
+ Chrome: new ChromeColors(
+ Info: dto.Chrome?.Info ?? defaults.Chrome.Info,
+ Connected: dto.Chrome?.Connected ?? defaults.Chrome.Connected,
+ Disconnected: dto.Chrome?.Disconnected ?? defaults.Chrome.Disconnected,
+ LocalMode: dto.Chrome?.LocalMode ?? defaults.Chrome.LocalMode,
+ Connecting: dto.Chrome?.Connecting ?? defaults.Chrome.Connecting,
+ PanelBackground: dto.Chrome?.PanelBackground ?? defaults.Chrome.PanelBackground,
+ PanelHeaderBackground: dto.Chrome?.PanelHeaderBackground ?? defaults.Chrome.PanelHeaderBackground,
+ SubtleBorder: dto.Chrome?.SubtleBorder ?? defaults.Chrome.SubtleBorder,
+ Separator: dto.Chrome?.Separator ?? defaults.Chrome.Separator,
+ SecondaryForeground: dto.Chrome?.SecondaryForeground ?? defaults.Chrome.SecondaryForeground,
+ GridBorder: dto.Chrome?.GridBorder ?? defaults.Chrome.GridBorder,
+ GridBackground: dto.Chrome?.GridBackground ?? defaults.Chrome.GridBackground,
+ HeaderForeground: dto.Chrome?.HeaderForeground ?? defaults.Chrome.HeaderForeground,
+ GridLine: dto.Colors?.GridLine ?? defaults.Chrome.GridLine),
Orientation: GridOrientationValues.Parse(dto.Orientation));
}
}
diff --git a/SemiStep/SemiStep.Core/Configuration/SelectionColors.cs b/SemiStep/SemiStep.Core/Configuration/SelectionColors.cs
new file mode 100644
index 00000000..087a5de5
--- /dev/null
+++ b/SemiStep/SemiStep.Core/Configuration/SelectionColors.cs
@@ -0,0 +1,5 @@
+namespace SemiStep.Core.Configuration;
+
+public sealed record SelectionColors(
+ string Background,
+ string Foreground);
diff --git a/SemiStep/SemiStep.Core/Configuration/StatusBarStyle.cs b/SemiStep/SemiStep.Core/Configuration/StatusBarStyle.cs
new file mode 100644
index 00000000..a4fc107d
--- /dev/null
+++ b/SemiStep/SemiStep.Core/Configuration/StatusBarStyle.cs
@@ -0,0 +1,16 @@
+namespace SemiStep.Core.Configuration;
+
+public sealed record StatusBarStyle(
+ string Background,
+ string Foreground,
+ double Padding,
+ double ItemSpacing,
+ int FontSize,
+ int Weight,
+ bool Italic,
+ int TimerLabelFontSize,
+ int TimerLabelWeight,
+ bool TimerLabelItalic,
+ int TimerValueFontSize,
+ int TimerValueWeight,
+ bool TimerValueItalic);
diff --git a/SemiStep/SemiStep.Core/Configuration/ValidationPanelStyle.cs b/SemiStep/SemiStep.Core/Configuration/ValidationPanelStyle.cs
new file mode 100644
index 00000000..eb827430
--- /dev/null
+++ b/SemiStep/SemiStep.Core/Configuration/ValidationPanelStyle.cs
@@ -0,0 +1,8 @@
+namespace SemiStep.Core.Configuration;
+
+public sealed record ValidationPanelStyle(
+ string Background,
+ string Foreground,
+ string ErrorColor,
+ string WarningColor,
+ double MaxHeight);
diff --git a/SemiStep/SemiStep.Tests/Core/Configuration/ConfigFacadeGridStyleValidationTests.cs b/SemiStep/SemiStep.Tests/Core/Configuration/ConfigFacadeGridStyleValidationTests.cs
index 95cd9450..18b433be 100644
--- a/SemiStep/SemiStep.Tests/Core/Configuration/ConfigFacadeGridStyleValidationTests.cs
+++ b/SemiStep/SemiStep.Tests/Core/Configuration/ConfigFacadeGridStyleValidationTests.cs
@@ -20,8 +20,8 @@ public async Task LoadAndValidateAsync_StandardFixture_LoadsSuccessfully()
var result = await ConfigFacade.LoadAndValidateAsync(tempDir.Path);
result.IsSuccess.Should().BeTrue();
- result.Value.GridStyle.ExecutionDepth0Color.Should().StartWith("#");
- result.Value.GridStyle.ExecutionCurrentStepMarkerColor.Should().StartWith("#");
+ result.Value.GridStyle.Execution.Depth0.Should().StartWith("#");
+ result.Value.GridStyle.Execution.CurrentStepMarker.Should().StartWith("#");
}
[Fact]
diff --git a/SemiStep/SemiStep.Tests/Core/Configuration/GridStyleEditorFacadeTests.cs b/SemiStep/SemiStep.Tests/Core/Configuration/GridStyleEditorFacadeTests.cs
index 82190b4e..ed25be8b 100644
--- a/SemiStep/SemiStep.Tests/Core/Configuration/GridStyleEditorFacadeTests.cs
+++ b/SemiStep/SemiStep.Tests/Core/Configuration/GridStyleEditorFacadeTests.cs
@@ -27,7 +27,7 @@ public async Task Load_ShippedConfig_ReturnsPopulatedRecord(string equipment)
var result = await new GridStyleEditorFacade().Load(tempDir.Path);
result.IsSuccess.Should().BeTrue();
- result.Value.SelectionBackgroundColor.Should().NotBeNullOrWhiteSpace();
+ result.Value.Selection.Background.Should().NotBeNullOrWhiteSpace();
}
[Fact]
@@ -82,7 +82,8 @@ public async Task Validate_ShippedRecord_ReturnsOk()
[Fact]
public void Validate_MalformedColor_ReturnsFail()
{
- var invalid = GridStyleOptions.Default with { ExecutionDepth0Color = "not-a-color" };
+ var defaults = GridStyleOptions.Default;
+ var invalid = defaults with { Execution = defaults.Execution with { Depth0 = "not-a-color" } };
new GridStyleEditorFacade().Validate(invalid).IsFailed.Should().BeTrue();
}
@@ -97,9 +98,10 @@ public async Task Save_InvalidRecord_FailsValidationGuardBeforeWriting()
var token = TestContext.Current.CancellationToken;
var before = await File.ReadAllTextAsync(filePath, token);
- var invalid = (await facade.Load(tempDir.Path)).Value
+ var loadedForInvalid = (await facade.Load(tempDir.Path)).Value;
+ var invalid = loadedForInvalid
with
- { ExecutionDepth0Color = "not-a-color" };
+ { Execution = loadedForInvalid.Execution with { Depth0 = "not-a-color" } };
(await facade.Save(tempDir.Path, invalid)).IsFailed.Should().BeTrue();
diff --git a/SemiStep/SemiStep.Tests/Core/Configuration/GridStyleMapperTests.cs b/SemiStep/SemiStep.Tests/Core/Configuration/GridStyleMapperTests.cs
index 8cc7ebcc..31274290 100644
--- a/SemiStep/SemiStep.Tests/Core/Configuration/GridStyleMapperTests.cs
+++ b/SemiStep/SemiStep.Tests/Core/Configuration/GridStyleMapperTests.cs
@@ -20,42 +20,42 @@ public void Map_WiresEveryCellPaletteFieldFromDto()
var options = GridStyleMapper.Map(dto);
- options.SelectionBackgroundColor.Should().Be("#ABCDEF");
- options.SelectionForegroundColor.Should().Be("#FEDCBA");
- options.CellChangedColor.Should().Be("#0ABBCD");
- options.CellChangedSelectedColor.Should().Be("#0DCCEF");
-
- options.ReadOnlyCellDepth0Color.Should().Be("#11111B");
- options.ReadOnlyCellDepth1Color.Should().Be("#22222B");
- options.ReadOnlyCellDepth2Color.Should().Be("#33333B");
- options.ReadOnlyCellDepth3Color.Should().Be("#44444B");
- options.ReadOnlyCellDepth0PastColor.Should().Be("#55555B");
- options.ReadOnlyCellDepth1PastColor.Should().Be("#66666B");
- options.ReadOnlyCellDepth2PastColor.Should().Be("#77777B");
- options.ReadOnlyCellDepth3PastColor.Should().Be("#88888B");
- options.ReadOnlyCellSelectedColor.Should().Be("#99999B");
- options.ReadOnlyCellForegroundColor.Should().Be("#AAAAAC");
-
- options.DisabledCellDepth0Color.Should().Be("#11111A");
- options.DisabledCellDepth1Color.Should().Be("#22222A");
- options.DisabledCellDepth2Color.Should().Be("#33333A");
- options.DisabledCellDepth3Color.Should().Be("#44444A");
- options.DisabledCellDepth0PastColor.Should().Be("#55555A");
- options.DisabledCellDepth1PastColor.Should().Be("#66666A");
- options.DisabledCellDepth2PastColor.Should().Be("#77777A");
- options.DisabledCellDepth3PastColor.Should().Be("#88888A");
- options.DisabledCellSelectedColor.Should().Be("#99999A");
- options.DisabledCellForegroundColor.Should().Be("#AAAAAB");
-
- options.ExecutionDepth0Color.Should().Be("#111111");
- options.ExecutionDepth1Color.Should().Be("#222222");
- options.ExecutionDepth2Color.Should().Be("#333333");
- options.ExecutionDepth3Color.Should().Be("#444444");
- options.ExecutionDepth0PastColor.Should().Be("#555555");
- options.ExecutionDepth1PastColor.Should().Be("#666666");
- options.ExecutionDepth2PastColor.Should().Be("#777777");
- options.ExecutionDepth3PastColor.Should().Be("#888888");
- options.ExecutionCurrentStepMarkerColor.Should().Be("#999999");
+ options.Selection.Background.Should().Be("#ABCDEF");
+ options.Selection.Foreground.Should().Be("#FEDCBA");
+ options.ChangedCells.Changed.Should().Be("#0ABBCD");
+ options.ChangedCells.ChangedSelected.Should().Be("#0DCCEF");
+
+ options.ReadOnlyCells.Depth0.Should().Be("#11111B");
+ options.ReadOnlyCells.Depth1.Should().Be("#22222B");
+ options.ReadOnlyCells.Depth2.Should().Be("#33333B");
+ options.ReadOnlyCells.Depth3.Should().Be("#44444B");
+ options.ReadOnlyCells.Depth0Past.Should().Be("#55555B");
+ options.ReadOnlyCells.Depth1Past.Should().Be("#66666B");
+ options.ReadOnlyCells.Depth2Past.Should().Be("#77777B");
+ options.ReadOnlyCells.Depth3Past.Should().Be("#88888B");
+ options.ReadOnlyCells.Selected.Should().Be("#99999B");
+ options.ReadOnlyCells.Foreground.Should().Be("#AAAAAC");
+
+ options.DisabledCells.Depth0.Should().Be("#11111A");
+ options.DisabledCells.Depth1.Should().Be("#22222A");
+ options.DisabledCells.Depth2.Should().Be("#33333A");
+ options.DisabledCells.Depth3.Should().Be("#44444A");
+ options.DisabledCells.Depth0Past.Should().Be("#55555A");
+ options.DisabledCells.Depth1Past.Should().Be("#66666A");
+ options.DisabledCells.Depth2Past.Should().Be("#77777A");
+ options.DisabledCells.Depth3Past.Should().Be("#88888A");
+ options.DisabledCells.Selected.Should().Be("#99999A");
+ options.DisabledCells.Foreground.Should().Be("#AAAAAB");
+
+ options.Execution.Depth0.Should().Be("#111111");
+ options.Execution.Depth1.Should().Be("#222222");
+ options.Execution.Depth2.Should().Be("#333333");
+ options.Execution.Depth3.Should().Be("#444444");
+ options.Execution.Depth0Past.Should().Be("#555555");
+ options.Execution.Depth1Past.Should().Be("#666666");
+ options.Execution.Depth2Past.Should().Be("#777777");
+ options.Execution.Depth3Past.Should().Be("#888888");
+ options.Execution.CurrentStepMarker.Should().Be("#999999");
}
[Fact]
@@ -81,19 +81,19 @@ public void Map_WiresEveryChromeFieldFromDto()
var options = GridStyleMapper.Map(dto);
- options.InfoColor.Should().Be("#1976D2");
- options.ConnectedColor.Should().Be("#44BB44");
- options.DisconnectedColor.Should().Be("#FF4444");
- options.LocalModeColor.Should().Be("#6C707E");
- options.ConnectingColor.Should().Be("#FFAF0F");
- options.PanelBackgroundColor.Should().Be("#F8F8F8");
- options.PanelHeaderBackgroundColor.Should().Be("#EEEEEE");
- options.SubtleBorderColor.Should().Be("#D0D0D0");
- options.SeparatorColor.Should().Be("#C0C0C0");
- options.SecondaryForegroundColor.Should().Be("#888888");
- options.GridBorderColor.Should().Be("#808080");
- options.GridBackgroundColor.Should().Be("#FFFFFF");
- options.HeaderForegroundColor.Should().Be("#000000");
+ options.Chrome.Info.Should().Be("#1976D2");
+ options.Chrome.Connected.Should().Be("#44BB44");
+ options.Chrome.Disconnected.Should().Be("#FF4444");
+ options.Chrome.LocalMode.Should().Be("#6C707E");
+ options.Chrome.Connecting.Should().Be("#FFAF0F");
+ options.Chrome.PanelBackground.Should().Be("#F8F8F8");
+ options.Chrome.PanelHeaderBackground.Should().Be("#EEEEEE");
+ options.Chrome.SubtleBorder.Should().Be("#D0D0D0");
+ options.Chrome.Separator.Should().Be("#C0C0C0");
+ options.Chrome.SecondaryForeground.Should().Be("#888888");
+ options.Chrome.GridBorder.Should().Be("#808080");
+ options.Chrome.GridBackground.Should().Be("#FFFFFF");
+ options.Chrome.HeaderForeground.Should().Be("#000000");
}
[Fact]
@@ -103,11 +103,11 @@ public void Map_OmittedChromeSection_FallsBackToDefaults()
var options = GridStyleMapper.Map(dto);
- options.InfoColor.Should().Be(GridStyleOptions.Default.InfoColor);
- options.LocalModeColor.Should().Be(GridStyleOptions.Default.LocalModeColor);
- options.ConnectingColor.Should().Be(GridStyleOptions.Default.ConnectingColor);
- options.GridBorderColor.Should().Be(GridStyleOptions.Default.GridBorderColor);
- options.HeaderForegroundColor.Should().Be(GridStyleOptions.Default.HeaderForegroundColor);
+ options.Chrome.Info.Should().Be(GridStyleOptions.Default.Chrome.Info);
+ options.Chrome.LocalMode.Should().Be(GridStyleOptions.Default.Chrome.LocalMode);
+ options.Chrome.Connecting.Should().Be(GridStyleOptions.Default.Chrome.Connecting);
+ options.Chrome.GridBorder.Should().Be(GridStyleOptions.Default.Chrome.GridBorder);
+ options.Chrome.HeaderForeground.Should().Be(GridStyleOptions.Default.Chrome.HeaderForeground);
}
[Fact]
@@ -126,7 +126,7 @@ public void Map_OmittedChangedColor_FallsBackToDefault()
var options = GridStyleMapper.Map(dto);
- options.CellChangedColor.Should().Be(GridStyleOptions.Default.CellChangedColor);
+ options.ChangedCells.Changed.Should().Be(GridStyleOptions.Default.ChangedCells.Changed);
}
[Fact]
@@ -148,15 +148,15 @@ public void Map_WiresStatusBarFontFieldsFromDto()
var options = GridStyleMapper.Map(dto);
- options.StatusBarFontSize.Should().Be(16);
- options.StatusBarFontWeight.Should().Be(500);
- options.StatusBarItalic.Should().BeTrue();
- options.StatusBarTimerLabelFontSize.Should().Be(18);
- options.StatusBarTimerLabelFontWeight.Should().Be(600);
- options.StatusBarTimerLabelItalic.Should().BeTrue();
- options.StatusBarTimerValueFontSize.Should().Be(32);
- options.StatusBarTimerValueFontWeight.Should().Be(800);
- options.StatusBarTimerValueItalic.Should().BeTrue();
+ options.StatusBar.FontSize.Should().Be(16);
+ options.StatusBar.Weight.Should().Be(500);
+ options.StatusBar.Italic.Should().BeTrue();
+ options.StatusBar.TimerLabelFontSize.Should().Be(18);
+ options.StatusBar.TimerLabelWeight.Should().Be(600);
+ options.StatusBar.TimerLabelItalic.Should().BeTrue();
+ options.StatusBar.TimerValueFontSize.Should().Be(32);
+ options.StatusBar.TimerValueWeight.Should().Be(800);
+ options.StatusBar.TimerValueItalic.Should().BeTrue();
}
[Fact]
@@ -176,13 +176,13 @@ public void Map_WiresGridFontFieldsFromDto()
var options = GridStyleMapper.Map(dto);
- options.FontFamily.Should().Be("Cascadia Mono");
- options.HeaderFontSize.Should().Be(20);
- options.HeaderFontWeight.Should().Be(800);
- options.HeaderItalic.Should().BeTrue();
- options.CellFontSize.Should().Be(15);
- options.CellFontWeight.Should().Be(300);
- options.CellItalic.Should().BeTrue();
+ options.Fonts.FontFamily.Should().Be("Cascadia Mono");
+ options.Fonts.HeaderFontSize.Should().Be(20);
+ options.Fonts.HeaderFontWeight.Should().Be(800);
+ options.Fonts.HeaderItalic.Should().BeTrue();
+ options.Fonts.CellFontSize.Should().Be(15);
+ options.Fonts.CellFontWeight.Should().Be(300);
+ options.Fonts.CellItalic.Should().BeTrue();
}
[Fact]
@@ -192,45 +192,52 @@ public void Map_OmittedFontFields_FallBackToDefaults()
var options = GridStyleMapper.Map(dto);
- options.FontFamily.Should().Be(GridStyleOptions.Default.FontFamily);
- options.HeaderFontSize.Should().Be(14);
- options.HeaderFontWeight.Should().Be(700);
- options.HeaderItalic.Should().BeFalse();
- options.CellFontSize.Should().Be(12);
- options.CellFontWeight.Should().Be(400);
- options.CellItalic.Should().BeFalse();
- options.StatusBarFontSize.Should().Be(12);
- options.StatusBarFontWeight.Should().Be(400);
- options.StatusBarItalic.Should().BeFalse();
- options.StatusBarTimerLabelFontSize.Should().Be(14);
- options.StatusBarTimerLabelFontWeight.Should().Be(400);
- options.StatusBarTimerLabelItalic.Should().BeFalse();
- options.StatusBarTimerValueFontSize.Should().Be(24);
- options.StatusBarTimerValueFontWeight.Should().Be(400);
- options.StatusBarTimerValueItalic.Should().BeFalse();
+ options.Fonts.FontFamily.Should().Be(GridStyleOptions.Default.Fonts.FontFamily);
+ options.Fonts.HeaderFontSize.Should().Be(14);
+ options.Fonts.HeaderFontWeight.Should().Be(700);
+ options.Fonts.HeaderItalic.Should().BeFalse();
+ options.Fonts.CellFontSize.Should().Be(12);
+ options.Fonts.CellFontWeight.Should().Be(400);
+ options.Fonts.CellItalic.Should().BeFalse();
+ options.StatusBar.FontSize.Should().Be(12);
+ options.StatusBar.Weight.Should().Be(400);
+ options.StatusBar.Italic.Should().BeFalse();
+ options.StatusBar.TimerLabelFontSize.Should().Be(14);
+ options.StatusBar.TimerLabelWeight.Should().Be(400);
+ options.StatusBar.TimerLabelItalic.Should().BeFalse();
+ options.StatusBar.TimerValueFontSize.Should().Be(24);
+ options.StatusBar.TimerValueWeight.Should().Be(400);
+ options.StatusBar.TimerValueItalic.Should().BeFalse();
}
[Fact]
public void Map_OptionsToDto_WritesEveryFontField()
{
- var options = GridStyleOptions.Default with
+ var defaults = GridStyleOptions.Default;
+ var options = defaults with
{
- FontFamily = "Cascadia Mono",
- HeaderFontSize = 20,
- HeaderFontWeight = 800,
- HeaderItalic = true,
- CellFontSize = 15,
- CellFontWeight = 300,
- CellItalic = true,
- StatusBarFontSize = 18,
- StatusBarFontWeight = 500,
- StatusBarItalic = true,
- StatusBarTimerLabelFontSize = 19,
- StatusBarTimerLabelFontWeight = 600,
- StatusBarTimerLabelItalic = true,
- StatusBarTimerValueFontSize = 30,
- StatusBarTimerValueFontWeight = 800,
- StatusBarTimerValueItalic = true
+ Fonts = defaults.Fonts with
+ {
+ FontFamily = "Cascadia Mono",
+ HeaderFontSize = 20,
+ HeaderFontWeight = 800,
+ HeaderItalic = true,
+ CellFontSize = 15,
+ CellFontWeight = 300,
+ CellItalic = true
+ },
+ StatusBar = defaults.StatusBar with
+ {
+ FontSize = 18,
+ Weight = 500,
+ Italic = true,
+ TimerLabelFontSize = 19,
+ TimerLabelWeight = 600,
+ TimerLabelItalic = true,
+ TimerValueFontSize = 30,
+ TimerValueWeight = 800,
+ TimerValueItalic = true
+ }
};
var dto = GridStyleDtoMapper.Map(options);
@@ -256,7 +263,8 @@ public void Map_OptionsToDto_WritesEveryFontField()
[Fact]
public void Map_OptionsToDto_WritesLocalModeChromeColor()
{
- var options = GridStyleOptions.Default with { LocalModeColor = "#123456" };
+ var defaults = GridStyleOptions.Default;
+ var options = defaults with { Chrome = defaults.Chrome with { LocalMode = "#123456" } };
var dto = GridStyleDtoMapper.Map(options);
@@ -266,7 +274,8 @@ public void Map_OptionsToDto_WritesLocalModeChromeColor()
[Fact]
public void Map_OptionsToDto_WritesConnectingChromeColor()
{
- var options = GridStyleOptions.Default with { ConnectingColor = "#123456" };
+ var defaults = GridStyleOptions.Default;
+ var options = defaults with { Chrome = defaults.Chrome with { Connecting = "#123456" } };
var dto = GridStyleDtoMapper.Map(options);
diff --git a/SemiStep/SemiStep.Tests/Core/Configuration/GridStyleWriterTests.cs b/SemiStep/SemiStep.Tests/Core/Configuration/GridStyleWriterTests.cs
index cd0aab04..899f2451 100644
--- a/SemiStep/SemiStep.Tests/Core/Configuration/GridStyleWriterTests.cs
+++ b/SemiStep/SemiStep.Tests/Core/Configuration/GridStyleWriterTests.cs
@@ -85,7 +85,7 @@ public async Task Save_EmitsColorScalarsDoubleQuoted()
var content = await File.ReadAllTextAsync(filePath, TestContext.Current.CancellationToken);
content.Should().Contain("\"#");
- content.Should().Contain($"\"{options.SelectionBackgroundColor}\"");
+ content.Should().Contain($"\"{options.Selection.Background}\"");
var unquotedHex = Regex.Matches(content, @":\s*#[0-9A-Fa-f]+");
unquotedHex.Should().BeEmpty(
@@ -102,7 +102,8 @@ public async Task Save_WhenMoveFails_FailsAndLeavesOriginalIntactWithNoTempOrpha
var token = TestContext.Current.CancellationToken;
var before = await File.ReadAllBytesAsync(filePath, token);
- var options = await LoadOptions(tempDir.Path) with { CellFontSize = 20 };
+ var loaded = await LoadOptions(tempDir.Path);
+ var options = loaded with { Fonts = loaded.Fonts with { CellFontSize = 20 } };
Result result;
@@ -139,7 +140,8 @@ public async Task Save_UnderCommaLocale_EmitsInvariantDecimals()
{
using var tempDir = CopyShippedConfig("MBE");
var filePath = GridStyleFilePath(tempDir.Path);
- var options = await LoadOptions(tempDir.Path) with { CellPaddingLeft = 6.5 };
+ var loaded = await LoadOptions(tempDir.Path);
+ var options = loaded with { Layout = loaded.Layout with { CellPaddingLeft = 6.5 } };
var previousCulture = CultureInfo.CurrentCulture;
try
diff --git a/SemiStep/SemiStep.Tests/Helpers/GridStyleOptionsTestData.cs b/SemiStep/SemiStep.Tests/Helpers/GridStyleOptionsTestData.cs
index f0897b24..84b5f069 100644
--- a/SemiStep/SemiStep.Tests/Helpers/GridStyleOptionsTestData.cs
+++ b/SemiStep/SemiStep.Tests/Helpers/GridStyleOptionsTestData.cs
@@ -11,90 +11,101 @@ namespace SemiStep.Tests.Helpers;
/// int↔decimal and double↔decimal conversions are exact. All five italics are true — the one
/// kind whose type default (false) can equal a fixture value — so a dropped italic Seed line
/// surfaces as false ≠ true. is the non-default value, which
-/// proves BuildRecord preserves the unsurfaced field through with.
+/// guards that BuildRecord carries the unsurfaced field through positional construction from
+/// _source.Orientation.
///
public static class GridStyleOptionsTestData
{
public static GridStyleOptions Distinct()
{
return new GridStyleOptions(
- FontFamily: "Distinct Test Font",
- HeaderFontSize: 13,
- HeaderFontWeight: 300,
- HeaderItalic: true,
- CellFontSize: 11,
- CellFontWeight: 800,
- CellItalic: true,
- CellPaddingLeft: 6.5,
- CellPaddingTop: 4.5,
- CellPaddingRight: 7.5,
- CellPaddingBottom: 3.5,
- RowHeight: 28.5,
- SelectionBackgroundColor: "#010101",
- SelectionForegroundColor: "#020202",
- CellChangedColor: "#030303",
- CellChangedSelectedColor: "#040404",
- DisabledCellDepth0Color: "#050505",
- DisabledCellDepth1Color: "#060606",
- DisabledCellDepth2Color: "#070707",
- DisabledCellDepth3Color: "#080808",
- DisabledCellDepth0PastColor: "#090909",
- DisabledCellDepth1PastColor: "#0A0A0A",
- DisabledCellDepth2PastColor: "#0B0B0B",
- DisabledCellDepth3PastColor: "#0C0C0C",
- DisabledCellSelectedColor: "#0D0D0D",
- DisabledCellForegroundColor: "#0E0E0E",
- ReadOnlyCellDepth0Color: "#0F0F0F",
- ReadOnlyCellDepth1Color: "#101010",
- ReadOnlyCellDepth2Color: "#111111",
- ReadOnlyCellDepth3Color: "#121212",
- ReadOnlyCellDepth0PastColor: "#131313",
- ReadOnlyCellDepth1PastColor: "#141414",
- ReadOnlyCellDepth2PastColor: "#151515",
- ReadOnlyCellDepth3PastColor: "#161616",
- ReadOnlyCellSelectedColor: "#171717",
- ReadOnlyCellForegroundColor: "#181818",
- GridLineColor: "#191919",
- StatusBarBackgroundColor: "#1A1A1A",
- StatusBarForegroundColor: "#1B1B1B",
- StatusBarPadding: 5.5,
- StatusBarItemSpacing: 10.5,
- StatusBarFontSize: 17,
- StatusBarFontWeight: 500,
- StatusBarItalic: true,
- StatusBarTimerLabelFontSize: 19,
- StatusBarTimerLabelFontWeight: 600,
- StatusBarTimerLabelItalic: true,
- StatusBarTimerValueFontSize: 23,
- StatusBarTimerValueFontWeight: 700,
- StatusBarTimerValueItalic: true,
- ValidationPanelBackgroundColor: "#1C1C1C",
- ValidationPanelForegroundColor: "#1D1D1D",
- ValidationPanelErrorColor: "#1E1E1E",
- ValidationPanelWarningColor: "#1F1F1F",
- ValidationPanelMaxHeight: 150.5,
- ExecutionDepth0Color: "#202020",
- ExecutionDepth1Color: "#212121",
- ExecutionDepth2Color: "#222222",
- ExecutionDepth3Color: "#232323",
- ExecutionDepth0PastColor: "#242424",
- ExecutionDepth1PastColor: "#252525",
- ExecutionDepth2PastColor: "#262626",
- ExecutionDepth3PastColor: "#272727",
- ExecutionCurrentStepMarkerColor: "#282828",
- InfoColor: "#292929",
- ConnectedColor: "#2A2A2A",
- DisconnectedColor: "#2B2B2B",
- LocalModeColor: "#2C2C2C",
- ConnectingColor: "#2D2D2D",
- PanelBackgroundColor: "#2E2E2E",
- PanelHeaderBackgroundColor: "#2F2F2F",
- SubtleBorderColor: "#303030",
- SeparatorColor: "#313131",
- SecondaryForegroundColor: "#323232",
- GridBorderColor: "#333333",
- GridBackgroundColor: "#343434",
- HeaderForegroundColor: "#353535",
+ Fonts: new GridStyleFonts(
+ FontFamily: "Distinct Test Font",
+ HeaderFontSize: 13,
+ HeaderFontWeight: 300,
+ HeaderItalic: true,
+ CellFontSize: 11,
+ CellFontWeight: 800,
+ CellItalic: true),
+ Layout: new GridStyleLayout(
+ CellPaddingLeft: 6.5,
+ CellPaddingTop: 4.5,
+ CellPaddingRight: 7.5,
+ CellPaddingBottom: 3.5,
+ RowHeight: 28.5),
+ Selection: new SelectionColors(
+ Background: "#010101",
+ Foreground: "#020202"),
+ ChangedCells: new ChangedCellColors(
+ Changed: "#030303",
+ ChangedSelected: "#040404"),
+ DisabledCells: new DepthPalette(
+ Depth0: "#050505",
+ Depth1: "#060606",
+ Depth2: "#070707",
+ Depth3: "#080808",
+ Depth0Past: "#090909",
+ Depth1Past: "#0A0A0A",
+ Depth2Past: "#0B0B0B",
+ Depth3Past: "#0C0C0C",
+ Selected: "#0D0D0D",
+ Foreground: "#0E0E0E"),
+ ReadOnlyCells: new DepthPalette(
+ Depth0: "#0F0F0F",
+ Depth1: "#101010",
+ Depth2: "#111111",
+ Depth3: "#121212",
+ Depth0Past: "#131313",
+ Depth1Past: "#141414",
+ Depth2Past: "#151515",
+ Depth3Past: "#161616",
+ Selected: "#171717",
+ Foreground: "#181818"),
+ Chrome: new ChromeColors(
+ Info: "#292929",
+ Connected: "#2A2A2A",
+ Disconnected: "#2B2B2B",
+ LocalMode: "#2C2C2C",
+ Connecting: "#2D2D2D",
+ PanelBackground: "#2E2E2E",
+ PanelHeaderBackground: "#2F2F2F",
+ SubtleBorder: "#303030",
+ Separator: "#313131",
+ SecondaryForeground: "#323232",
+ GridBorder: "#333333",
+ GridBackground: "#343434",
+ HeaderForeground: "#353535",
+ GridLine: "#191919"),
+ StatusBar: new StatusBarStyle(
+ Background: "#1A1A1A",
+ Foreground: "#1B1B1B",
+ Padding: 5.5,
+ ItemSpacing: 10.5,
+ FontSize: 17,
+ Weight: 500,
+ Italic: true,
+ TimerLabelFontSize: 19,
+ TimerLabelWeight: 600,
+ TimerLabelItalic: true,
+ TimerValueFontSize: 23,
+ TimerValueWeight: 700,
+ TimerValueItalic: true),
+ ValidationPanel: new ValidationPanelStyle(
+ Background: "#1C1C1C",
+ Foreground: "#1D1D1D",
+ ErrorColor: "#1E1E1E",
+ WarningColor: "#1F1F1F",
+ MaxHeight: 150.5),
+ Execution: new ExecutionPalette(
+ Depth0: "#202020",
+ Depth1: "#212121",
+ Depth2: "#222222",
+ Depth3: "#232323",
+ Depth0Past: "#242424",
+ Depth1Past: "#252525",
+ Depth2Past: "#262626",
+ Depth3Past: "#272727",
+ CurrentStepMarker: "#282828"),
Orientation: GridOrientation.ColumnsAsSteps);
}
}
diff --git a/SemiStep/SemiStep.Tests/UI/MainWindow/AppStatusBarTimerFontTests.cs b/SemiStep/SemiStep.Tests/UI/MainWindow/AppStatusBarTimerFontTests.cs
index 64c1b68a..7217eda7 100644
--- a/SemiStep/SemiStep.Tests/UI/MainWindow/AppStatusBarTimerFontTests.cs
+++ b/SemiStep/SemiStep.Tests/UI/MainWindow/AppStatusBarTimerFontTests.cs
@@ -44,12 +44,16 @@ public ValueTask DisposeAsync()
[AvaloniaFact]
public void Timer_LabelAndValueTextBlocks_RenderAtTheirSeparateRoleSizes()
{
- var statusBar = ShowStatusBar(GridStyleOptions.Default with
+ var defaults = GridStyleOptions.Default;
+ var statusBar = ShowStatusBar(defaults with
{
- StatusBarTimerLabelFontSize = (int)TimerLabelFontSize,
- StatusBarTimerLabelFontWeight = 400,
- StatusBarTimerValueFontSize = (int)TimerValueFontSize,
- StatusBarTimerValueFontWeight = 700
+ StatusBar = defaults.StatusBar with
+ {
+ TimerLabelFontSize = (int)TimerLabelFontSize,
+ TimerLabelWeight = 400,
+ TimerValueFontSize = (int)TimerValueFontSize,
+ TimerValueWeight = 700
+ }
});
var labels = statusBar.GetVisualDescendants()
diff --git a/SemiStep/SemiStep.Tests/UI/RecipeGrid/ColumnWidthCalculatorTests.cs b/SemiStep/SemiStep.Tests/UI/RecipeGrid/ColumnWidthCalculatorTests.cs
index 657b1e91..e3c4008b 100644
--- a/SemiStep/SemiStep.Tests/UI/RecipeGrid/ColumnWidthCalculatorTests.cs
+++ b/SemiStep/SemiStep.Tests/UI/RecipeGrid/ColumnWidthCalculatorTests.cs
@@ -35,13 +35,13 @@ public sealed class ColumnWidthCalculatorTests : IAsyncLifetime
private const int LargeHeaderFontSize = 28;
private static int ContentChrome =>
- (int)Math.Ceiling(GridStyleOptions.Default.CellFontSize * ChromeFontMultiple);
+ (int)Math.Ceiling(GridStyleOptions.Default.Fonts.CellFontSize * ChromeFontMultiple);
private static int HeaderFloorChrome =>
- (int)Math.Ceiling(GridStyleOptions.Default.HeaderFontSize * ChromeFontMultiple);
+ (int)Math.Ceiling(GridStyleOptions.Default.Fonts.HeaderFontSize * ChromeFontMultiple);
private static int MinColumnWidthFloor =>
- (int)Math.Ceiling(GridStyleOptions.Default.CellFontSize * MinColumnWidthEms);
+ (int)Math.Ceiling(GridStyleOptions.Default.Fonts.CellFontSize * MinColumnWidthEms);
private readonly UIFixture _fixture = new();
private ColumnWidthCalculator _calculator = null!;
@@ -100,7 +100,7 @@ public void MultiWordHeader_FloorsAtLongestWord()
var pixelWidth = GetPixelWidth(_calculator.CalculateColumnWidth(taskColumn));
var wholeHeaderWidth = (int)Math.Ceiling(
- MeasureText(header, GridStyleOptions.Default.HeaderFontSize, FontWeight.Bold) + HeaderFloorChrome);
+ MeasureText(header, GridStyleOptions.Default.Fonts.HeaderFontSize, FontWeight.Bold) + HeaderFloorChrome);
pixelWidth.Should().BeLessThan(wholeHeaderWidth,
"the floor is the longest WORD, never the whole single-line header");
@@ -111,8 +111,8 @@ public void HeaderFloorMeasuredBoldAtHeaderFontSize()
{
var word = "Начальное";
- var boldHeaderFont = MeasureText(word, GridStyleOptions.Default.HeaderFontSize, FontWeight.Bold);
- var normalCellFont = MeasureText(word, GridStyleOptions.Default.CellFontSize, FontWeight.Normal);
+ var boldHeaderFont = MeasureText(word, GridStyleOptions.Default.Fonts.HeaderFontSize, FontWeight.Bold);
+ var normalCellFont = MeasureText(word, GridStyleOptions.Default.Fonts.CellFontSize, FontWeight.Normal);
boldHeaderFont.Should().BeGreaterThan(normalCellFont,
"the header-word floor must be measured bold at HeaderFontSize, not at the cell font");
@@ -124,7 +124,7 @@ public void LongestWordFloor_FarBelowWholeHeaderWidth()
var header = "An extremely long header label that would otherwise widen this column";
var longestWordFloor = LongestHeaderWordFloor(header);
- var wholeHeaderWidth = MeasureText(header, GridStyleOptions.Default.HeaderFontSize, FontWeight.Bold)
+ var wholeHeaderWidth = MeasureText(header, GridStyleOptions.Default.Fonts.HeaderFontSize, FontWeight.Bold)
+ HeaderFloorChrome;
longestWordFloor.Should().BeLessThan(wholeHeaderWidth / 2,
@@ -159,7 +159,7 @@ public void PropertyField_WithMax_SizesFromMaxRepresentative()
durationProperty.Max!.Value.ToString(CultureInfo.InvariantCulture),
durationProperty.FormatKind,
durationProperty.Units);
- var contentWidth = MeasureText(representative, GridStyleOptions.Default.CellFontSize, FontWeight.Normal);
+ var contentWidth = MeasureText(representative, GridStyleOptions.Default.Fonts.CellFontSize, FontWeight.Normal);
pixelWidth.Should().BeGreaterThan((int)contentWidth,
"the column must fully cover the formatted Max representative");
@@ -183,7 +183,7 @@ public void PropertyField_StringTyped_SizesFromCappedSampleNotFullMaxLength()
var pixelWidth = GetPixelWidth(_calculator.CalculateColumnWidth(stringColumn));
var cappedRepresentative = new string('0', StringSampleCap);
- var cappedContentWidth = MeasureText(cappedRepresentative, GridStyleOptions.Default.CellFontSize, FontWeight.Normal);
+ var cappedContentWidth = MeasureText(cappedRepresentative, GridStyleOptions.Default.Fonts.CellFontSize, FontWeight.Normal);
pixelWidth.Should().BeGreaterThan((int)cappedContentWidth,
"the column must fully cover the capped sample");
@@ -191,7 +191,7 @@ public void PropertyField_StringTyped_SizesFromCappedSampleNotFullMaxLength()
var fullLengthRepresentative = new string('0', stringProperty.MaxLength!.Value);
var fullLengthWidth = (int)Math.Ceiling(
- MeasureText(fullLengthRepresentative, GridStyleOptions.Default.CellFontSize, FontWeight.Normal) + ContentChrome);
+ MeasureText(fullLengthRepresentative, GridStyleOptions.Default.Fonts.CellFontSize, FontWeight.Normal) + ContentChrome);
pixelWidth.Should().BeLessThan(fullLengthWidth,
"the cap must keep the column far below the full MaxLength width");
}
@@ -309,7 +309,7 @@ public void PropertyField_NegativeMinWiderThanMax_SizesFromMinRepresentative()
[AvaloniaFact]
public void Chrome_ScalesWithFont_ChromeRemainderGrowsBeyondContentScaling()
{
- var smallCellFont = GridStyleOptions.Default.CellFontSize;
+ var smallCellFont = GridStyleOptions.Default.Fonts.CellFontSize;
var largeCellFont = LargeCellFontSize;
var durationColumn = _fixture.RecipeMetadataRegistry.GetColumn("step_duration").Value;
@@ -317,7 +317,7 @@ public void Chrome_ScalesWithFont_ChromeRemainderGrowsBeyondContentScaling()
_fixture.RecipeMetadataRegistry, GridStyleOptions.Default);
var largeFontCalculator = new ColumnWidthCalculator(
_fixture.RecipeMetadataRegistry,
- GridStyleOptions.Default with { CellFontSize = largeCellFont, HeaderFontSize = LargeHeaderFontSize });
+ GridStyleOptions.Default with { Fonts = GridStyleOptions.Default.Fonts with { CellFontSize = largeCellFont, HeaderFontSize = LargeHeaderFontSize } });
var smallFontWidth = GetPixelWidth(smallFontCalculator.CalculateColumnWidth(durationColumn));
var largeFontWidth = GetPixelWidth(largeFontCalculator.CalculateColumnWidth(durationColumn));
@@ -343,13 +343,17 @@ public void Typefaces_ThreadConfiguredFamilyWeightAndItalic_PerRole()
// The headless text shaper measures by glyph count and size only, so weight/family/italic
// produce identical widths; assert the role typefaces the measurement uses instead, which
// fails if a regression drops family/weight/italic from the measured Typeface.
- var style = GridStyleOptions.Default with
+ var defaults = GridStyleOptions.Default;
+ var style = defaults with
{
- FontFamily = "Courier New",
- CellFontWeight = 600,
- CellItalic = true,
- HeaderFontWeight = 900,
- HeaderItalic = false
+ Fonts = defaults.Fonts with
+ {
+ FontFamily = "Courier New",
+ CellFontWeight = 600,
+ CellItalic = true,
+ HeaderFontWeight = 900,
+ HeaderItalic = false
+ }
};
var calculator = new ColumnWidthCalculator(_fixture.RecipeMetadataRegistry, style);
@@ -404,7 +408,7 @@ public void ComboColumn_WiderThanContentColumn_ByChevronBudget()
[AvaloniaFact]
public void ComboChrome_StaysConstantAcrossFonts_WhileContentChromeGrows()
{
- var smallCellFont = GridStyleOptions.Default.CellFontSize;
+ var smallCellFont = GridStyleOptions.Default.Fonts.CellFontSize;
var largeCellFont = LargeCellFontSize;
var representative = new string('0', StringSampleCap);
var sampleProperty = TestPropertyTypeDefinitionBuilder.CreateString("sample", StringSampleCap);
@@ -429,7 +433,7 @@ public void ComboChrome_StaysConstantAcrossFonts_WhileContentChromeGrows()
var smallFontCalculator = new ColumnWidthCalculator(registry, GridStyleOptions.Default);
var largeFontCalculator = new ColumnWidthCalculator(
- registry, GridStyleOptions.Default with { CellFontSize = largeCellFont, HeaderFontSize = LargeHeaderFontSize });
+ registry, GridStyleOptions.Default with { Fonts = GridStyleOptions.Default.Fonts with { CellFontSize = largeCellFont, HeaderFontSize = LargeHeaderFontSize } });
var smallComboWidth = GetPixelWidth(smallFontCalculator.CalculateColumnWidth(comboColumn));
var largeComboWidth = GetPixelWidth(largeFontCalculator.CalculateColumnWidth(comboColumn));
@@ -462,7 +466,7 @@ private static double MeasureRepresentative(PropertyTypeDefinition property, dou
property.FormatKind,
property.Units);
- return MeasureText(representative, GridStyleOptions.Default.CellFontSize, FontWeight.Normal);
+ return MeasureText(representative, GridStyleOptions.Default.Fonts.CellFontSize, FontWeight.Normal);
}
private static double LongestHeaderWordFloor(string header)
@@ -477,7 +481,7 @@ private static double LongestHeaderWordFloor(string header)
var maxWordWidth = 0.0;
foreach (var word in words)
{
- var wordWidth = MeasureText(word, GridStyleOptions.Default.HeaderFontSize, FontWeight.Bold);
+ var wordWidth = MeasureText(word, GridStyleOptions.Default.Fonts.HeaderFontSize, FontWeight.Bold);
if (wordWidth > maxWordWidth)
{
maxWordWidth = wordWidth;
@@ -492,7 +496,7 @@ private double MeasureActionContentWidth()
var maxWidth = 0.0;
foreach (var action in _fixture.RecipeMetadataRegistry.GetAllActions())
{
- var width = MeasureText(action.UiName, GridStyleOptions.Default.CellFontSize, FontWeight.Normal);
+ var width = MeasureText(action.UiName, GridStyleOptions.Default.Fonts.CellFontSize, FontWeight.Normal);
if (width > maxWidth)
{
maxWidth = width;
diff --git a/SemiStep/SemiStep.Tests/UI/RecipeGrid/GridFactoryFontTests.cs b/SemiStep/SemiStep.Tests/UI/RecipeGrid/GridFactoryFontTests.cs
index a8b6745d..d3392006 100644
--- a/SemiStep/SemiStep.Tests/UI/RecipeGrid/GridFactoryFontTests.cs
+++ b/SemiStep/SemiStep.Tests/UI/RecipeGrid/GridFactoryFontTests.cs
@@ -65,7 +65,8 @@ public void NumberingColumn_CarriesConfiguredCellFont()
[AvaloniaFact]
public void NumberingColumn_EmptyFamily_UsesChromeFontDefault()
{
- var grid = BuildGrid(GridStyleOptions.Default with { FontFamily = "" });
+ var emptyFamily = GridStyleOptions.Default;
+ var grid = BuildGrid(emptyFamily with { Fonts = emptyFamily.Fonts with { FontFamily = "" } });
var numberingColumn = (DataGridTextColumn)grid.Columns[0];
@@ -112,7 +113,8 @@ public void ComboBoxCell_CarriesConfiguredCellFont()
[AvaloniaFact]
public void EmptyFamily_UsesChromeFontDefault()
{
- var factory = new TextCellFactory(GridStyleOptions.Default with { FontFamily = "" });
+ var emptyFamily = GridStyleOptions.Default;
+ var factory = new TextCellFactory(emptyFamily with { Fonts = emptyFamily.Fonts with { FontFamily = "" } });
var columnDef = _fixture.RecipeMetadataRegistry.GetColumn("task").Value;
var column = (DataGridTemplateColumn)factory.CreateEditableColumn(columnDef, new DataGridLength(100), maxLength: null);
@@ -125,15 +127,19 @@ public void EmptyFamily_UsesChromeFontDefault()
private GridStyleOptions NonDefaultStyle()
{
- return GridStyleOptions.Default with
+ var defaults = GridStyleOptions.Default;
+ return defaults with
{
- FontFamily = ConfiguredFamily,
- HeaderFontSize = ConfiguredHeaderSize,
- HeaderFontWeight = ConfiguredHeaderWeight,
- HeaderItalic = true,
- CellFontSize = ConfiguredCellSize,
- CellFontWeight = ConfiguredCellWeight,
- CellItalic = true,
+ Fonts = defaults.Fonts with
+ {
+ FontFamily = ConfiguredFamily,
+ HeaderFontSize = ConfiguredHeaderSize,
+ HeaderFontWeight = ConfiguredHeaderWeight,
+ HeaderItalic = true,
+ CellFontSize = ConfiguredCellSize,
+ CellFontWeight = ConfiguredCellWeight,
+ CellItalic = true,
+ },
};
}
diff --git a/SemiStep/SemiStep.Tests/UI/RecipeGrid/Transposed/TransposedCellBackgroundConverterTests.cs b/SemiStep/SemiStep.Tests/UI/RecipeGrid/Transposed/TransposedCellBackgroundConverterTests.cs
index 7fd15e5c..0e6af33d 100644
--- a/SemiStep/SemiStep.Tests/UI/RecipeGrid/Transposed/TransposedCellBackgroundConverterTests.cs
+++ b/SemiStep/SemiStep.Tests/UI/RecipeGrid/Transposed/TransposedCellBackgroundConverterTests.cs
@@ -155,37 +155,46 @@ private static Control BuildHostWithPalette(out IResourceDictionary resources)
// different brush instance and the reference check fails loudly.
private static GridStyleOptions BuildDistinctPaletteGridStyle()
{
- return GridStyleOptions.Default with
+ var defaults = GridStyleOptions.Default;
+ return defaults with
{
- GridBackgroundColor = "#101010",
- SelectionBackgroundColor = "#202020",
- CellChangedColor = "#303030",
- CellChangedSelectedColor = "#404040",
- ReadOnlyCellSelectedColor = "#505050",
- DisabledCellSelectedColor = "#606060",
- ExecutionDepth1Color = "#110000",
- ExecutionDepth2Color = "#120000",
- ExecutionDepth3Color = "#130000",
- ExecutionDepth0PastColor = "#140000",
- ExecutionDepth1PastColor = "#150000",
- ExecutionDepth2PastColor = "#160000",
- ExecutionDepth3PastColor = "#170000",
- ReadOnlyCellDepth0Color = "#210000",
- ReadOnlyCellDepth1Color = "#220000",
- ReadOnlyCellDepth2Color = "#230000",
- ReadOnlyCellDepth3Color = "#240000",
- ReadOnlyCellDepth0PastColor = "#250000",
- ReadOnlyCellDepth1PastColor = "#260000",
- ReadOnlyCellDepth2PastColor = "#270000",
- ReadOnlyCellDepth3PastColor = "#280000",
- DisabledCellDepth0Color = "#310000",
- DisabledCellDepth1Color = "#320000",
- DisabledCellDepth2Color = "#330000",
- DisabledCellDepth3Color = "#340000",
- DisabledCellDepth0PastColor = "#350000",
- DisabledCellDepth1PastColor = "#360000",
- DisabledCellDepth2PastColor = "#370000",
- DisabledCellDepth3PastColor = "#380000",
+ Chrome = defaults.Chrome with { GridBackground = "#101010" },
+ Selection = defaults.Selection with { Background = "#202020" },
+ ChangedCells = new ChangedCellColors(
+ Changed: "#303030",
+ ChangedSelected: "#404040"),
+ ReadOnlyCells = new DepthPalette(
+ Depth0: "#210000",
+ Depth1: "#220000",
+ Depth2: "#230000",
+ Depth3: "#240000",
+ Depth0Past: "#250000",
+ Depth1Past: "#260000",
+ Depth2Past: "#270000",
+ Depth3Past: "#280000",
+ Selected: "#505050",
+ Foreground: defaults.ReadOnlyCells.Foreground),
+ DisabledCells = new DepthPalette(
+ Depth0: "#310000",
+ Depth1: "#320000",
+ Depth2: "#330000",
+ Depth3: "#340000",
+ Depth0Past: "#350000",
+ Depth1Past: "#360000",
+ Depth2Past: "#370000",
+ Depth3Past: "#380000",
+ Selected: "#606060",
+ Foreground: defaults.DisabledCells.Foreground),
+ Execution = defaults.Execution with
+ {
+ Depth1 = "#110000",
+ Depth2 = "#120000",
+ Depth3 = "#130000",
+ Depth0Past = "#140000",
+ Depth1Past = "#150000",
+ Depth2Past = "#160000",
+ Depth3Past = "#170000",
+ },
};
}
}
diff --git a/SemiStep/SemiStep.Tests/UI/StyleEditor/GridStyleEditorViewModelTests.cs b/SemiStep/SemiStep.Tests/UI/StyleEditor/GridStyleEditorViewModelTests.cs
index 50cc34d5..2873e157 100644
--- a/SemiStep/SemiStep.Tests/UI/StyleEditor/GridStyleEditorViewModelTests.cs
+++ b/SemiStep/SemiStep.Tests/UI/StyleEditor/GridStyleEditorViewModelTests.cs
@@ -48,7 +48,7 @@ public void Seed_PopulatesEverySurfacedProperty_FromDistinctFixture()
foreach (var property in properties)
{
- var recordValue = RecordField(property).GetValue(fixture);
+ var recordValue = LeafValue(fixture, RecordPath(property));
var actual = property.GetValue(viewModel);
var because = $"property {property.Name} must be seeded from the fixture";
@@ -95,7 +95,7 @@ public void BuildRecord_PerturbingEachProperty_ChangesOnlyThatMappedField()
property.SetValue(viewModel, Perturb(property, seededValue));
var built = viewModel.BuildRecord();
- var mappedField = RecordField(property).Name;
+ var mappedField = RecordPath(property);
var changedFields = ChangedRecordFields(baseline, built);
changedFields.Should().BeEquivalentTo(
new[] { mappedField },
@@ -119,8 +119,8 @@ public void BuildRecord_AfterEditingColorAndFontSize_ChangesOnlyThoseFields()
var expected = source with
{
- CellFontSize = 18,
- SelectionBackgroundColor = "#123456"
+ Fonts = source.Fonts with { CellFontSize = 18 },
+ Selection = source.Selection with { Background = "#123456" }
};
record.Should().Be(expected);
}
@@ -132,7 +132,7 @@ public void BuildRecord_RoundsFractionalStatusBarFontSize_ToNearestInt()
viewModel.StatusBarFontSize = 14.6m;
- viewModel.BuildRecord().StatusBarFontSize.Should().Be(15);
+ viewModel.BuildRecord().StatusBar.FontSize.Should().Be(15);
}
[AvaloniaTheory]
@@ -174,14 +174,21 @@ public void CanSave_IsFalse_WhenStatusBarTimerLabelFontSizeOutOfRange(int fontSi
[AvaloniaFact]
public void Seed_PopulatesFontFamilyWeightAndItalic_FromRecord()
{
- var source = GridStyleOptions.Default with
+ var defaults = GridStyleOptions.Default;
+ var source = defaults with
{
- FontFamily = "Cascadia Code",
- HeaderFontWeight = 600,
- HeaderItalic = true,
- CellFontWeight = 500,
- StatusBarTimerLabelFontSize = 18,
- StatusBarTimerLabelItalic = true
+ Fonts = defaults.Fonts with
+ {
+ FontFamily = "Cascadia Code",
+ HeaderFontWeight = 600,
+ HeaderItalic = true,
+ CellFontWeight = 500
+ },
+ StatusBar = defaults.StatusBar with
+ {
+ TimerLabelFontSize = 18,
+ TimerLabelItalic = true
+ }
};
var viewModel = CreateViewModel(source);
@@ -206,16 +213,17 @@ public void BuildRecord_CarriesEditedFontFamilyWeightAndItalic()
var record = viewModel.BuildRecord();
- record.FontFamily.Should().Be("Consolas");
- record.HeaderFontWeight.Should().Be(900);
- record.CellItalic.Should().BeTrue();
- record.StatusBarTimerValueFontWeight.Should().Be(300);
+ record.Fonts.FontFamily.Should().Be("Consolas");
+ record.Fonts.HeaderFontWeight.Should().Be(900);
+ record.Fonts.CellItalic.Should().BeTrue();
+ record.StatusBar.TimerValueWeight.Should().Be(300);
}
[AvaloniaFact]
public void AvailableFontWeights_IncludeSeededWeights_NotInCuratedList()
{
- var source = GridStyleOptions.Default with { HeaderFontWeight = 333 };
+ var defaults = GridStyleOptions.Default;
+ var source = defaults with { Fonts = defaults.Fonts with { HeaderFontWeight = 333 } };
var viewModel = CreateViewModel(source);
viewModel.AvailableFontWeights.Should().Contain(option => option.Value == 333);
@@ -224,7 +232,8 @@ public void AvailableFontWeights_IncludeSeededWeights_NotInCuratedList()
[AvaloniaFact]
public void AvailableFontFamilies_StartWithDefaultSentinel_AndIncludeSeededFamily()
{
- var source = GridStyleOptions.Default with { FontFamily = "No Such Installed Font 12345" };
+ var defaults = GridStyleOptions.Default;
+ var source = defaults with { Fonts = defaults.Fonts with { FontFamily = "No Such Installed Font 12345" } };
var viewModel = CreateViewModel(source);
viewModel.AvailableFontFamilies[0].Value.Should().Be("");
@@ -280,7 +289,7 @@ public void ToHex_PreservesSixDigitForm_ForOpaqueColors()
viewModel.SelectionBackground = Color.Parse("#A1B2C3");
- viewModel.BuildRecord().SelectionBackgroundColor.Should().Be("#A1B2C3");
+ viewModel.BuildRecord().Selection.Background.Should().Be("#A1B2C3");
}
[AvaloniaFact]
@@ -290,7 +299,7 @@ public void ToHex_EmitsEightDigitForm_ForNonOpaqueColors()
viewModel.SelectionBackground = Color.FromArgb(0x80, 0x11, 0x22, 0x33);
- viewModel.BuildRecord().SelectionBackgroundColor.Should().Be("#80112233");
+ viewModel.BuildRecord().Selection.Background.Should().Be("#80112233");
}
[AvaloniaFact]
@@ -435,14 +444,111 @@ private static IReadOnlyList EditableProperties()
.ToList();
}
- private static PropertyInfo RecordField(PropertyInfo viewModelProperty)
+ // Hand-maintained VM-property -> nested record leaf path. NOT derived: several leaf names diverge
+ // from the VM property name (StatusBarFontWeight -> StatusBar.Weight, ValidationPanelError ->
+ // ValidationPanel.ErrorColor, every Color drops its trailing "Color"), so a mistyped entry surfaces
+ // as a red guard test rather than a silent pass. Every entry is cross-checked against the group records.
+ private static readonly IReadOnlyDictionary _viewModelToRecordPath =
+ new Dictionary
+ {
+ ["FontFamily"] = "Fonts.FontFamily",
+ ["HeaderFontSize"] = "Fonts.HeaderFontSize",
+ ["HeaderFontWeight"] = "Fonts.HeaderFontWeight",
+ ["HeaderItalic"] = "Fonts.HeaderItalic",
+ ["CellFontSize"] = "Fonts.CellFontSize",
+ ["CellFontWeight"] = "Fonts.CellFontWeight",
+ ["CellItalic"] = "Fonts.CellItalic",
+ ["CellPaddingLeft"] = "Layout.CellPaddingLeft",
+ ["CellPaddingTop"] = "Layout.CellPaddingTop",
+ ["CellPaddingRight"] = "Layout.CellPaddingRight",
+ ["CellPaddingBottom"] = "Layout.CellPaddingBottom",
+ ["RowHeight"] = "Layout.RowHeight",
+ ["SelectionBackground"] = "Selection.Background",
+ ["SelectionForeground"] = "Selection.Foreground",
+ ["CellChanged"] = "ChangedCells.Changed",
+ ["CellChangedSelected"] = "ChangedCells.ChangedSelected",
+ ["ReadOnlyCellDepth0"] = "ReadOnlyCells.Depth0",
+ ["ReadOnlyCellDepth1"] = "ReadOnlyCells.Depth1",
+ ["ReadOnlyCellDepth2"] = "ReadOnlyCells.Depth2",
+ ["ReadOnlyCellDepth3"] = "ReadOnlyCells.Depth3",
+ ["ReadOnlyCellDepth0Past"] = "ReadOnlyCells.Depth0Past",
+ ["ReadOnlyCellDepth1Past"] = "ReadOnlyCells.Depth1Past",
+ ["ReadOnlyCellDepth2Past"] = "ReadOnlyCells.Depth2Past",
+ ["ReadOnlyCellDepth3Past"] = "ReadOnlyCells.Depth3Past",
+ ["ReadOnlyCellSelected"] = "ReadOnlyCells.Selected",
+ ["ReadOnlyCellForeground"] = "ReadOnlyCells.Foreground",
+ ["DisabledCellDepth0"] = "DisabledCells.Depth0",
+ ["DisabledCellDepth1"] = "DisabledCells.Depth1",
+ ["DisabledCellDepth2"] = "DisabledCells.Depth2",
+ ["DisabledCellDepth3"] = "DisabledCells.Depth3",
+ ["DisabledCellDepth0Past"] = "DisabledCells.Depth0Past",
+ ["DisabledCellDepth1Past"] = "DisabledCells.Depth1Past",
+ ["DisabledCellDepth2Past"] = "DisabledCells.Depth2Past",
+ ["DisabledCellDepth3Past"] = "DisabledCells.Depth3Past",
+ ["DisabledCellSelected"] = "DisabledCells.Selected",
+ ["DisabledCellForeground"] = "DisabledCells.Foreground",
+ ["ExecutionDepth0"] = "Execution.Depth0",
+ ["ExecutionDepth1"] = "Execution.Depth1",
+ ["ExecutionDepth2"] = "Execution.Depth2",
+ ["ExecutionDepth3"] = "Execution.Depth3",
+ ["ExecutionDepth0Past"] = "Execution.Depth0Past",
+ ["ExecutionDepth1Past"] = "Execution.Depth1Past",
+ ["ExecutionDepth2Past"] = "Execution.Depth2Past",
+ ["ExecutionDepth3Past"] = "Execution.Depth3Past",
+ ["ExecutionCurrentStepMarker"] = "Execution.CurrentStepMarker",
+ ["StatusBarBackground"] = "StatusBar.Background",
+ ["StatusBarForeground"] = "StatusBar.Foreground",
+ ["StatusBarPadding"] = "StatusBar.Padding",
+ ["StatusBarItemSpacing"] = "StatusBar.ItemSpacing",
+ ["StatusBarFontSize"] = "StatusBar.FontSize",
+ ["StatusBarFontWeight"] = "StatusBar.Weight",
+ ["StatusBarItalic"] = "StatusBar.Italic",
+ ["StatusBarTimerLabelFontSize"] = "StatusBar.TimerLabelFontSize",
+ ["StatusBarTimerLabelFontWeight"] = "StatusBar.TimerLabelWeight",
+ ["StatusBarTimerLabelItalic"] = "StatusBar.TimerLabelItalic",
+ ["StatusBarTimerValueFontSize"] = "StatusBar.TimerValueFontSize",
+ ["StatusBarTimerValueFontWeight"] = "StatusBar.TimerValueWeight",
+ ["StatusBarTimerValueItalic"] = "StatusBar.TimerValueItalic",
+ ["ValidationPanelBackground"] = "ValidationPanel.Background",
+ ["ValidationPanelForeground"] = "ValidationPanel.Foreground",
+ ["ValidationPanelError"] = "ValidationPanel.ErrorColor",
+ ["ValidationPanelWarning"] = "ValidationPanel.WarningColor",
+ ["ValidationPanelMaxHeight"] = "ValidationPanel.MaxHeight",
+ ["GridLine"] = "Chrome.GridLine",
+ ["Info"] = "Chrome.Info",
+ ["Connected"] = "Chrome.Connected",
+ ["Disconnected"] = "Chrome.Disconnected",
+ ["LocalMode"] = "Chrome.LocalMode",
+ ["Connecting"] = "Chrome.Connecting",
+ ["PanelBackground"] = "Chrome.PanelBackground",
+ ["PanelHeaderBackground"] = "Chrome.PanelHeaderBackground",
+ ["SubtleBorder"] = "Chrome.SubtleBorder",
+ ["Separator"] = "Chrome.Separator",
+ ["SecondaryForeground"] = "Chrome.SecondaryForeground",
+ ["GridBorder"] = "Chrome.GridBorder",
+ ["GridBackground"] = "Chrome.GridBackground",
+ ["HeaderForeground"] = "Chrome.HeaderForeground",
+ };
+
+ private static string RecordPath(PropertyInfo viewModelProperty)
{
- var fieldName = viewModelProperty.PropertyType == typeof(Color)
- ? viewModelProperty.Name + "Color"
- : viewModelProperty.Name;
+ return _viewModelToRecordPath.TryGetValue(viewModelProperty.Name, out var path)
+ ? path
+ : throw new InvalidOperationException($"No GridStyleOptions path maps to {viewModelProperty.Name}");
+ }
- return typeof(GridStyleOptions).GetProperty(fieldName)
- ?? throw new InvalidOperationException($"No GridStyleOptions field maps to {viewModelProperty.Name}");
+ private static object? LeafValue(GridStyleOptions record, string dottedPath)
+ {
+ object current = record;
+ foreach (var segment in dottedPath.Split('.'))
+ {
+ var property = current.GetType().GetProperty(segment)
+ ?? throw new InvalidOperationException($"No record property '{segment}' on {current.GetType().Name}");
+ current = property.GetValue(current)
+ ?? throw new InvalidOperationException($"Record property '{segment}' was null");
+ }
+
+ return current;
}
// Color perturbation stays opaque so ToHex changes; weights are unvalidated ints so any value round-trips.
@@ -477,12 +583,42 @@ private static PropertyInfo RecordField(PropertyInfo viewModelProperty)
throw new InvalidOperationException($"No perturbation defined for {property.Name} ({property.PropertyType})");
}
+ // Typed recursive leaf walk: a string/primitive/enum property is a LEAF compared at its dotted path;
+ // a record group RECURSES. Keeps root-level Orientation (an enum) a first-class leaf, so a BuildRecord
+ // bug that also flips Orientation cannot slip the exact-one-leaf perturbation assertion.
private static IReadOnlyList ChangedRecordFields(GridStyleOptions baseline, GridStyleOptions candidate)
{
- return typeof(GridStyleOptions).GetProperties()
- .Where(property => !Equals(property.GetValue(baseline), property.GetValue(candidate)))
- .Select(property => property.Name)
- .ToList();
+ var changed = new List();
+ CollectChangedLeaves(baseline, candidate, prefix: null, changed);
+ return changed;
+ }
+
+ private static void CollectChangedLeaves(object baseline, object candidate, string? prefix, List changed)
+ {
+ // Instance-only: the static GridStyleOptions.Default property would otherwise recurse into itself.
+ foreach (var property in baseline.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
+ {
+ var baselineValue = property.GetValue(baseline);
+ var candidateValue = property.GetValue(candidate);
+ var path = prefix is null ? property.Name : $"{prefix}.{property.Name}";
+
+ if (IsLeaf(property.PropertyType))
+ {
+ if (!Equals(baselineValue, candidateValue))
+ {
+ changed.Add(path);
+ }
+ }
+ else
+ {
+ CollectChangedLeaves(baselineValue!, candidateValue!, path, changed);
+ }
+ }
+ }
+
+ private static bool IsLeaf(Type type)
+ {
+ return type.IsPrimitive || type.IsEnum || type == typeof(string);
}
private static async Task ExecuteSwallowing(ReactiveCommand command)
diff --git a/SemiStep/SemiStep.Tests/UI/StyleEditor/GridStyleEditorWindowTests.cs b/SemiStep/SemiStep.Tests/UI/StyleEditor/GridStyleEditorWindowTests.cs
index 2fe116d0..2092f232 100644
--- a/SemiStep/SemiStep.Tests/UI/StyleEditor/GridStyleEditorWindowTests.cs
+++ b/SemiStep/SemiStep.Tests/UI/StyleEditor/GridStyleEditorWindowTests.cs
@@ -37,7 +37,7 @@ public async Task Save_AfterEditingColorAndNumeric_PersistsToYaml_AndReloads()
NullLogger.Instance);
viewModel.SelectionBackground = Color.Parse("#123456");
- viewModel.CellFontSize = loaded.CellFontSize + 1;
+ viewModel.CellFontSize = loaded.Fonts.CellFontSize + 1;
viewModel.CanSave.Should().BeTrue();
var saved = await viewModel.SaveCommand.Execute();
@@ -46,8 +46,8 @@ public async Task Save_AfterEditingColorAndNumeric_PersistsToYaml_AndReloads()
viewModel.ErrorMessage.Should().BeNull();
var reloaded = (await facade.Load(configDir)).Value;
- reloaded.SelectionBackgroundColor.Should().Be("#123456");
- reloaded.CellFontSize.Should().Be(loaded.CellFontSize + 1);
+ reloaded.Selection.Background.Should().Be("#123456");
+ reloaded.Fonts.CellFontSize.Should().Be(loaded.Fonts.CellFontSize + 1);
}
[AvaloniaFact]
diff --git a/SemiStep/SemiStep.Tests/UI/Styles/CellPaletteInstallerTests.cs b/SemiStep/SemiStep.Tests/UI/Styles/CellPaletteInstallerTests.cs
index 5834092f..4c316c46 100644
--- a/SemiStep/SemiStep.Tests/UI/Styles/CellPaletteInstallerTests.cs
+++ b/SemiStep/SemiStep.Tests/UI/Styles/CellPaletteInstallerTests.cs
@@ -19,66 +19,77 @@ public sealed class CellPaletteInstallerTests
[AvaloniaFact]
public void Install_PopulatesAllCellBrushes_WithExpectedColors()
{
- var gridStyle = GridStyleOptions.Default with
+ var defaults = GridStyleOptions.Default;
+ var gridStyle = defaults with
{
- ReadOnlyCellDepth0Color = "#D8D8D8",
- ReadOnlyCellDepth1Color = "#CCD5E0",
- ReadOnlyCellDepth2Color = "#B8C3D1",
- ReadOnlyCellDepth3Color = "#94A2B3",
- ReadOnlyCellDepth0PastColor = "#C8C8C8",
- ReadOnlyCellDepth1PastColor = "#BCC4CE",
- ReadOnlyCellDepth2PastColor = "#ACB7C2",
- ReadOnlyCellDepth3PastColor = "#8590A0",
- ReadOnlyCellSelectedColor = "#6B95C0",
- ReadOnlyCellForegroundColor = "#606060",
- DisabledCellDepth0Color = "#E0E0E0",
- DisabledCellDepth1Color = "#D5DEEA",
- DisabledCellDepth2Color = "#C2CEDB",
- DisabledCellDepth3Color = "#9DABBC",
- DisabledCellDepth0PastColor = "#D0D0D0",
- DisabledCellDepth1PastColor = "#C5CDD8",
- DisabledCellDepth2PastColor = "#B5C0CC",
- DisabledCellDepth3PastColor = "#909AAA",
- DisabledCellSelectedColor = "#89B4D7",
- DisabledCellForegroundColor = "#808080",
- SelectionBackgroundColor = "#CCE4F7",
- SelectionForegroundColor = "#202020",
- CellChangedColor = "#FFCC80",
- CellChangedSelectedColor = "#EFD3A4",
- GridLineColor = "#CCCCCC",
- StatusBarBackgroundColor = "#F0F0F0",
- StatusBarForegroundColor = "#111111",
- StatusBarPadding = 5,
- StatusBarItemSpacing = 10,
- FontFamily = "Arial",
- StatusBarFontSize = 14,
- StatusBarFontWeight = 700,
- StatusBarItalic = true,
- StatusBarTimerLabelFontSize = 16,
- StatusBarTimerLabelFontWeight = 500,
- StatusBarTimerLabelItalic = true,
- StatusBarTimerValueFontSize = 28,
- StatusBarTimerValueFontWeight = 600,
- StatusBarTimerValueItalic = false,
- CellFontSize = 12,
- ValidationPanelBackgroundColor = "#FBFBFB",
- ValidationPanelForegroundColor = "#222222",
- ValidationPanelErrorColor = "#D32F2F",
- ValidationPanelWarningColor = "#F57C00",
- ValidationPanelMaxHeight = 100,
- InfoColor = "#1976D2",
- ConnectedColor = "#44BB44",
- DisconnectedColor = "#FF4444",
- LocalModeColor = "#6C707E",
- ConnectingColor = "#FFAF0F",
- PanelBackgroundColor = "#F8F8F8",
- PanelHeaderBackgroundColor = "#EEEEEE",
- SubtleBorderColor = "#D0D0D0",
- SeparatorColor = "#C0C0C0",
- SecondaryForegroundColor = "#888888",
- GridBorderColor = "#808080",
- GridBackgroundColor = "#FFFFFF",
- HeaderForegroundColor = "#000000",
+ Fonts = defaults.Fonts with
+ {
+ FontFamily = "Arial",
+ CellFontSize = 12,
+ },
+ Selection = new SelectionColors(
+ Background: "#CCE4F7",
+ Foreground: "#202020"),
+ ChangedCells = new ChangedCellColors(
+ Changed: "#FFCC80",
+ ChangedSelected: "#EFD3A4"),
+ ReadOnlyCells = new DepthPalette(
+ Depth0: "#D8D8D8",
+ Depth1: "#CCD5E0",
+ Depth2: "#B8C3D1",
+ Depth3: "#94A2B3",
+ Depth0Past: "#C8C8C8",
+ Depth1Past: "#BCC4CE",
+ Depth2Past: "#ACB7C2",
+ Depth3Past: "#8590A0",
+ Selected: "#6B95C0",
+ Foreground: "#606060"),
+ DisabledCells = new DepthPalette(
+ Depth0: "#E0E0E0",
+ Depth1: "#D5DEEA",
+ Depth2: "#C2CEDB",
+ Depth3: "#9DABBC",
+ Depth0Past: "#D0D0D0",
+ Depth1Past: "#C5CDD8",
+ Depth2Past: "#B5C0CC",
+ Depth3Past: "#909AAA",
+ Selected: "#89B4D7",
+ Foreground: "#808080"),
+ StatusBar = new StatusBarStyle(
+ Background: "#F0F0F0",
+ Foreground: "#111111",
+ Padding: 5,
+ ItemSpacing: 10,
+ FontSize: 14,
+ Weight: 700,
+ Italic: true,
+ TimerLabelFontSize: 16,
+ TimerLabelWeight: 500,
+ TimerLabelItalic: true,
+ TimerValueFontSize: 28,
+ TimerValueWeight: 600,
+ TimerValueItalic: false),
+ ValidationPanel = new ValidationPanelStyle(
+ Background: "#FBFBFB",
+ Foreground: "#222222",
+ ErrorColor: "#D32F2F",
+ WarningColor: "#F57C00",
+ MaxHeight: 100),
+ Chrome = new ChromeColors(
+ Info: "#1976D2",
+ Connected: "#44BB44",
+ Disconnected: "#FF4444",
+ LocalMode: "#6C707E",
+ Connecting: "#FFAF0F",
+ PanelBackground: "#F8F8F8",
+ PanelHeaderBackground: "#EEEEEE",
+ SubtleBorder: "#D0D0D0",
+ Separator: "#C0C0C0",
+ SecondaryForeground: "#888888",
+ GridBorder: "#808080",
+ GridBackground: "#FFFFFF",
+ HeaderForeground: "#000000",
+ GridLine: "#CCCCCC"),
};
var resources = new ResourceDictionary();
@@ -133,7 +144,7 @@ public void Install_PopulatesAllCellBrushes_WithExpectedColors()
resources[CellPaletteInstaller.StatusBarItemSpacingKey].Should().Be(10d);
resources[CellPaletteInstaller.StatusBarFontSizeKey].Should().Be(14d);
resources[CellPaletteInstaller.ValidationPanelMaxHeightKey].Should().Be(100d);
- resources[CellPaletteInstaller.RowHeightKey].Should().Be(gridStyle.RowHeight);
+ resources[CellPaletteInstaller.RowHeightKey].Should().Be(gridStyle.Layout.RowHeight);
resources[CellPaletteInstaller.AppFontFamilyKey].Should().BeOfType()
.Which.Name.Should().Be("Arial");
@@ -154,7 +165,8 @@ public void Install_PopulatesAllCellBrushes_WithExpectedColors()
[AvaloniaFact]
public void Install_EmptyFontFamily_InstallsThemeDefaultFamily()
{
- var gridStyle = GridStyleOptions.Default with { FontFamily = "" };
+ var defaults = GridStyleOptions.Default;
+ var gridStyle = defaults with { Fonts = defaults.Fonts with { FontFamily = "" } };
var resources = new ResourceDictionary();
CellPaletteInstaller.Install(resources, gridStyle);
diff --git a/SemiStep/SemiStep.Tests/UI/Styles/ExecutionPaletteInstallerTests.cs b/SemiStep/SemiStep.Tests/UI/Styles/ExecutionPaletteInstallerTests.cs
index 7247e03a..15a6727e 100644
--- a/SemiStep/SemiStep.Tests/UI/Styles/ExecutionPaletteInstallerTests.cs
+++ b/SemiStep/SemiStep.Tests/UI/Styles/ExecutionPaletteInstallerTests.cs
@@ -20,15 +20,16 @@ public void Install_PopulatesNineBrushKeys_WithExpectedColors()
{
var gridStyle = GridStyleOptions.Default with
{
- ExecutionDepth0Color = "#FFFFFF",
- ExecutionDepth1Color = "#E8F3FF",
- ExecutionDepth2Color = "#D0E7FF",
- ExecutionDepth3Color = "#A8D0FF",
- ExecutionDepth0PastColor = "#F0F0F0",
- ExecutionDepth1PastColor = "#DCE5EE",
- ExecutionDepth2PastColor = "#C4D2E0",
- ExecutionDepth3PastColor = "#9CB4CC",
- ExecutionCurrentStepMarkerColor = "#FF8800",
+ Execution = new ExecutionPalette(
+ Depth0: "#FFFFFF",
+ Depth1: "#E8F3FF",
+ Depth2: "#D0E7FF",
+ Depth3: "#A8D0FF",
+ Depth0Past: "#F0F0F0",
+ Depth1Past: "#DCE5EE",
+ Depth2Past: "#C4D2E0",
+ Depth3Past: "#9CB4CC",
+ CurrentStepMarker: "#FF8800"),
};
var resources = new ResourceDictionary();
diff --git a/SemiStep/SemiStep.UI/RecipeGrid/ColumnBuilder.cs b/SemiStep/SemiStep.UI/RecipeGrid/ColumnBuilder.cs
index 08f53efe..e9c5dd28 100644
--- a/SemiStep/SemiStep.UI/RecipeGrid/ColumnBuilder.cs
+++ b/SemiStep/SemiStep.UI/RecipeGrid/ColumnBuilder.cs
@@ -32,7 +32,7 @@ public sealed class ColumnBuilder(
public void BuildColumns(DataGrid grid)
{
- grid.RowHeight = gridStyle.RowHeight;
+ grid.RowHeight = gridStyle.Layout.RowHeight;
// The numbering column is a DataGridTextColumn with no FontFeatures property; tabular figures
// reach its generated cells through the inherited grid-level value set here.
grid.SetValue(TextElement.FontFeaturesProperty, GridFonts.TabularFigures);
@@ -53,17 +53,17 @@ private void AddNumberingColumn(DataGrid grid)
Header = "No",
HeaderTemplate = _wrappingHeaderTemplate,
Binding = new Binding("StepNumber"),
- FontSize = gridStyle.CellFontSize,
- FontWeight = (FontWeight)gridStyle.CellFontWeight,
- FontStyle = gridStyle.CellItalic ? FontStyle.Italic : FontStyle.Normal,
+ FontSize = gridStyle.Fonts.CellFontSize,
+ FontWeight = (FontWeight)gridStyle.Fonts.CellFontWeight,
+ FontStyle = gridStyle.Fonts.CellItalic ? FontStyle.Italic : FontStyle.Normal,
IsReadOnly = true,
Width = DataGridLength.Auto,
MinWidth = _widthCalculator.MinColumnWidth,
CanUserSort = false
};
- column.FontFamily = string.IsNullOrWhiteSpace(gridStyle.FontFamily)
+ column.FontFamily = string.IsNullOrWhiteSpace(gridStyle.Fonts.FontFamily)
? GridFonts.DefaultFamily
- : new FontFamily(gridStyle.FontFamily);
+ : new FontFamily(gridStyle.Fonts.FontFamily);
column.CellStyleClasses.Add(StepNumberColumnClass);
grid.Columns.Add(column);
diff --git a/SemiStep/SemiStep.UI/RecipeGrid/ColumnWidthCalculator.cs b/SemiStep/SemiStep.UI/RecipeGrid/ColumnWidthCalculator.cs
index dead1df9..68d4c252 100644
--- a/SemiStep/SemiStep.UI/RecipeGrid/ColumnWidthCalculator.cs
+++ b/SemiStep/SemiStep.UI/RecipeGrid/ColumnWidthCalculator.cs
@@ -29,12 +29,12 @@ public sealed class ColumnWidthCalculator(
private const int ComboBoxChromeWidth = 38;
// See Docs/architecture/recipe-grid-column-sizing.md
- private int ContentChrome => (int)Math.Ceiling(gridStyle.CellFontSize * ChromeFontMultiple);
+ private int ContentChrome => (int)Math.Ceiling(gridStyle.Fonts.CellFontSize * ChromeFontMultiple);
- private int HeaderFloorChrome => (int)Math.Ceiling(gridStyle.HeaderFontSize * ChromeFontMultiple);
+ private int HeaderFloorChrome => (int)Math.Ceiling(gridStyle.Fonts.HeaderFontSize * ChromeFontMultiple);
// The numbering column (ColumnBuilder) uses it as its MinWidth, so do not lower it.
- public int MinColumnWidth => (int)Math.Ceiling(gridStyle.CellFontSize * MinColumnWidthEms);
+ public int MinColumnWidth => (int)Math.Ceiling(gridStyle.Fonts.CellFontSize * MinColumnWidthEms);
public DataGridLength CalculateColumnWidth(GridColumnDefinition columnDef)
{
@@ -198,24 +198,24 @@ private IEnumerable CollectGroupDisplayStrings(string columnKey)
// Empty family means "grid default": measure with the chrome font so the width matches the
// rendered cell, which also falls back to the chrome font.
private FontFamily MeasureFontFamily =>
- string.IsNullOrWhiteSpace(gridStyle.FontFamily) ? GridFonts.DefaultFamily : new FontFamily(gridStyle.FontFamily);
+ string.IsNullOrWhiteSpace(gridStyle.Fonts.FontFamily) ? GridFonts.DefaultFamily : new FontFamily(gridStyle.Fonts.FontFamily);
// The single typeface the cell-role measurement uses, threading the configured family, weight, and
// italic so the measured width tracks the rendered cell. Exposed for the measurement-threading test.
internal Typeface CellTypeface =>
- new(MeasureFontFamily, gridStyle.CellItalic ? FontStyle.Italic : FontStyle.Normal, (FontWeight)gridStyle.CellFontWeight);
+ new(MeasureFontFamily, gridStyle.Fonts.CellItalic ? FontStyle.Italic : FontStyle.Normal, (FontWeight)gridStyle.Fonts.CellFontWeight);
internal Typeface HeaderTypeface =>
- new(MeasureFontFamily, gridStyle.HeaderItalic ? FontStyle.Italic : FontStyle.Normal, (FontWeight)gridStyle.HeaderFontWeight);
+ new(MeasureFontFamily, gridStyle.Fonts.HeaderItalic ? FontStyle.Italic : FontStyle.Normal, (FontWeight)gridStyle.Fonts.HeaderFontWeight);
private double MeasureContent(string text)
{
- return MeasureText(text, CellTypeface, gridStyle.CellFontSize);
+ return MeasureText(text, CellTypeface, gridStyle.Fonts.CellFontSize);
}
private double MeasureHeaderWord(string word)
{
- return MeasureText(word, HeaderTypeface, gridStyle.HeaderFontSize);
+ return MeasureText(word, HeaderTypeface, gridStyle.Fonts.HeaderFontSize);
}
private static double MeasureText(string text, Typeface typeface, double fontSize)
diff --git a/SemiStep/SemiStep.UI/RecipeGrid/GridFontApplier.cs b/SemiStep/SemiStep.UI/RecipeGrid/GridFontApplier.cs
index ac950815..9fe5d619 100644
--- a/SemiStep/SemiStep.UI/RecipeGrid/GridFontApplier.cs
+++ b/SemiStep/SemiStep.UI/RecipeGrid/GridFontApplier.cs
@@ -13,12 +13,12 @@ internal static class GridFontApplier
{
public static void ApplyHeaderFont(Control control, GridStyleOptions gridStyle)
{
- Apply(control, gridStyle.FontFamily, gridStyle.HeaderFontSize, gridStyle.HeaderFontWeight, gridStyle.HeaderItalic);
+ Apply(control, gridStyle.Fonts.FontFamily, gridStyle.Fonts.HeaderFontSize, gridStyle.Fonts.HeaderFontWeight, gridStyle.Fonts.HeaderItalic);
}
public static void ApplyCellFont(Control control, GridStyleOptions gridStyle)
{
- Apply(control, gridStyle.FontFamily, gridStyle.CellFontSize, gridStyle.CellFontWeight, gridStyle.CellItalic);
+ Apply(control, gridStyle.Fonts.FontFamily, gridStyle.Fonts.CellFontSize, gridStyle.Fonts.CellFontWeight, gridStyle.Fonts.CellItalic);
}
private static void Apply(Control control, string fontFamily, int fontSize, int fontWeight, bool italic)
diff --git a/SemiStep/SemiStep.UI/RecipeGrid/TextCellFactory.cs b/SemiStep/SemiStep.UI/RecipeGrid/TextCellFactory.cs
index 53286f74..664bab33 100644
--- a/SemiStep/SemiStep.UI/RecipeGrid/TextCellFactory.cs
+++ b/SemiStep/SemiStep.UI/RecipeGrid/TextCellFactory.cs
@@ -51,10 +51,10 @@ private FuncDataTemplate CreateStepStartTimeTemplate()
{
VerticalAlignment = VerticalAlignment.Center,
Padding = new Thickness(
- gridStyle.CellPaddingLeft,
- gridStyle.CellPaddingTop,
- gridStyle.CellPaddingRight,
- gridStyle.CellPaddingBottom),
+ gridStyle.Layout.CellPaddingLeft,
+ gridStyle.Layout.CellPaddingTop,
+ gridStyle.Layout.CellPaddingRight,
+ gridStyle.Layout.CellPaddingBottom),
TextAlignment = TextAlignment.Center,
};
GridFontApplier.ApplyCellFont(textBlock, gridStyle);
@@ -81,10 +81,10 @@ private FuncDataTemplate CreateMultiBindingTemplate(string c
{
VerticalAlignment = VerticalAlignment.Center,
Padding = new Thickness(
- gridStyle.CellPaddingLeft,
- gridStyle.CellPaddingTop,
- gridStyle.CellPaddingRight,
- gridStyle.CellPaddingBottom),
+ gridStyle.Layout.CellPaddingLeft,
+ gridStyle.Layout.CellPaddingTop,
+ gridStyle.Layout.CellPaddingRight,
+ gridStyle.Layout.CellPaddingBottom),
TextAlignment = TextAlignment.Center,
};
GridFontApplier.ApplyCellFont(textBlock, gridStyle);
@@ -133,10 +133,10 @@ public FuncDataTemplate CreateEditingTemplate(string columnK
MinHeight = 0,
BorderThickness = new Thickness(0),
Padding = new Thickness(
- gridStyle.CellPaddingLeft,
- gridStyle.CellPaddingTop,
- gridStyle.CellPaddingRight,
- gridStyle.CellPaddingBottom),
+ gridStyle.Layout.CellPaddingLeft,
+ gridStyle.Layout.CellPaddingTop,
+ gridStyle.Layout.CellPaddingRight,
+ gridStyle.Layout.CellPaddingBottom),
TextAlignment = TextAlignment.Center,
};
GridFontApplier.ApplyCellFont(textBox, gridStyle);
diff --git a/SemiStep/SemiStep.UI/RecipeGrid/Transposed/TransposedCellTemplateFactory.cs b/SemiStep/SemiStep.UI/RecipeGrid/Transposed/TransposedCellTemplateFactory.cs
index f1fc4c49..a5859287 100644
--- a/SemiStep/SemiStep.UI/RecipeGrid/Transposed/TransposedCellTemplateFactory.cs
+++ b/SemiStep/SemiStep.UI/RecipeGrid/Transposed/TransposedCellTemplateFactory.cs
@@ -107,10 +107,10 @@ private TextBlock CreateDisplayTextBlock(bool stretch)
{
VerticalAlignment = VerticalAlignment.Center,
Padding = new Thickness(
- gridStyle.CellPaddingLeft,
- gridStyle.CellPaddingTop,
- gridStyle.CellPaddingRight,
- gridStyle.CellPaddingBottom),
+ gridStyle.Layout.CellPaddingLeft,
+ gridStyle.Layout.CellPaddingTop,
+ gridStyle.Layout.CellPaddingRight,
+ gridStyle.Layout.CellPaddingBottom),
TextAlignment = TextAlignment.Center,
};
if (stretch)
@@ -196,10 +196,10 @@ private Control CreateTextBoxEditor()
BorderThickness = new Thickness(0),
Background = Brushes.Transparent,
Padding = new Thickness(
- gridStyle.CellPaddingLeft,
- gridStyle.CellPaddingTop,
- gridStyle.CellPaddingRight,
- gridStyle.CellPaddingBottom),
+ gridStyle.Layout.CellPaddingLeft,
+ gridStyle.Layout.CellPaddingTop,
+ gridStyle.Layout.CellPaddingRight,
+ gridStyle.Layout.CellPaddingBottom),
TextAlignment = TextAlignment.Center,
};
GridFontApplier.ApplyCellFont(textBox, gridStyle);
diff --git a/SemiStep/SemiStep.UI/RecipeGrid/Transposed/TransposedRecipeGridView.axaml.cs b/SemiStep/SemiStep.UI/RecipeGrid/Transposed/TransposedRecipeGridView.axaml.cs
index 11f87f46..40e012a1 100644
--- a/SemiStep/SemiStep.UI/RecipeGrid/Transposed/TransposedRecipeGridView.axaml.cs
+++ b/SemiStep/SemiStep.UI/RecipeGrid/Transposed/TransposedRecipeGridView.axaml.cs
@@ -328,15 +328,15 @@ private void BuildColumnCellsPool(TransposedRecipeGridSurface? surface)
: new TransposedColumnCellsPool(
surface.ParameterDescriptors,
new TransposedCellTemplateFactory(surface, _editCoordinator),
- surface.GridStyle.RowHeight);
+ surface.GridStyle.Layout.RowHeight);
}
private void ApplyGridStyle(GridStyleOptions gridStyle)
{
- Resources[CellHeightResourceKey] = gridStyle.RowHeight;
+ Resources[CellHeightResourceKey] = gridStyle.Layout.RowHeight;
Resources[MarkerSlotHeightResourceKey] = CurrentStepMarkerHeight;
Resources[StepColumnWidthResourceKey] =
- Math.Max(MinimumStepColumnWidth, gridStyle.CellFontSize * FontSizeToColumnWidthFactor);
+ Math.Max(MinimumStepColumnWidth, gridStyle.Fonts.CellFontSize * FontSizeToColumnWidthFactor);
GridFontApplier.ApplyCellFont(StepListBox, gridStyle);
GridFontApplier.ApplyHeaderFont(ParameterNameColumn, gridStyle);
diff --git a/SemiStep/SemiStep.UI/StyleEditor/GridStyleEditorViewModel.cs b/SemiStep/SemiStep.UI/StyleEditor/GridStyleEditorViewModel.cs
index 88b21040..ec9ae424 100644
--- a/SemiStep/SemiStep.UI/StyleEditor/GridStyleEditorViewModel.cs
+++ b/SemiStep/SemiStep.UI/StyleEditor/GridStyleEditorViewModel.cs
@@ -20,8 +20,9 @@ namespace SemiStep.UI.StyleEditor;
/// loaded record (a separate mutable copy — never the DI singleton), it exposes colors as
/// and sizes as ? for two-way binding.
/// gates on the Core color validator AND VM-side numeric range checks (the Core validator is
-/// colors-only). The editor surfaces effectively the whole record; rebuilds
-/// it via with over the seeded source, so any field that is not surfaced is preserved.
+/// colors-only). The editor surfaces effectively the whole record; constructs
+/// a new record positionally, carrying the one unsurfaced field
+/// straight from _source.Orientation.
///
public sealed class GridStyleEditorViewModel : ReactiveObject
{
@@ -202,86 +203,95 @@ public async Task LoadAsync()
public GridStyleOptions BuildRecord()
{
- return _source with
- {
- HeaderFontSize = ToInt(HeaderFontSize, _source.HeaderFontSize),
- CellFontSize = ToInt(CellFontSize, _source.CellFontSize),
- CellPaddingLeft = ToDouble(CellPaddingLeft, _source.CellPaddingLeft),
- CellPaddingTop = ToDouble(CellPaddingTop, _source.CellPaddingTop),
- CellPaddingRight = ToDouble(CellPaddingRight, _source.CellPaddingRight),
- CellPaddingBottom = ToDouble(CellPaddingBottom, _source.CellPaddingBottom),
- RowHeight = ToDouble(RowHeight, _source.RowHeight),
- StatusBarPadding = ToDouble(StatusBarPadding, _source.StatusBarPadding),
- StatusBarItemSpacing = ToDouble(StatusBarItemSpacing, _source.StatusBarItemSpacing),
- StatusBarFontSize = ToInt(StatusBarFontSize, _source.StatusBarFontSize),
- StatusBarTimerLabelFontSize = ToInt(StatusBarTimerLabelFontSize, _source.StatusBarTimerLabelFontSize),
- StatusBarTimerValueFontSize = ToInt(StatusBarTimerValueFontSize, _source.StatusBarTimerValueFontSize),
- ValidationPanelMaxHeight = ToDouble(ValidationPanelMaxHeight, _source.ValidationPanelMaxHeight),
- FontFamily = FontFamily ?? _source.FontFamily,
- HeaderFontWeight = HeaderFontWeight,
- HeaderItalic = HeaderItalic,
- CellFontWeight = CellFontWeight,
- CellItalic = CellItalic,
- StatusBarFontWeight = StatusBarFontWeight,
- StatusBarItalic = StatusBarItalic,
- StatusBarTimerLabelFontWeight = StatusBarTimerLabelFontWeight,
- StatusBarTimerLabelItalic = StatusBarTimerLabelItalic,
- StatusBarTimerValueFontWeight = StatusBarTimerValueFontWeight,
- StatusBarTimerValueItalic = StatusBarTimerValueItalic,
- SelectionBackgroundColor = HexColor.ToHex(SelectionBackground),
- SelectionForegroundColor = HexColor.ToHex(SelectionForeground),
- CellChangedColor = HexColor.ToHex(CellChanged),
- CellChangedSelectedColor = HexColor.ToHex(CellChangedSelected),
- DisabledCellDepth0Color = HexColor.ToHex(DisabledCellDepth0),
- DisabledCellDepth1Color = HexColor.ToHex(DisabledCellDepth1),
- DisabledCellDepth2Color = HexColor.ToHex(DisabledCellDepth2),
- DisabledCellDepth3Color = HexColor.ToHex(DisabledCellDepth3),
- DisabledCellDepth0PastColor = HexColor.ToHex(DisabledCellDepth0Past),
- DisabledCellDepth1PastColor = HexColor.ToHex(DisabledCellDepth1Past),
- DisabledCellDepth2PastColor = HexColor.ToHex(DisabledCellDepth2Past),
- DisabledCellDepth3PastColor = HexColor.ToHex(DisabledCellDepth3Past),
- DisabledCellSelectedColor = HexColor.ToHex(DisabledCellSelected),
- DisabledCellForegroundColor = HexColor.ToHex(DisabledCellForeground),
- ReadOnlyCellDepth0Color = HexColor.ToHex(ReadOnlyCellDepth0),
- ReadOnlyCellDepth1Color = HexColor.ToHex(ReadOnlyCellDepth1),
- ReadOnlyCellDepth2Color = HexColor.ToHex(ReadOnlyCellDepth2),
- ReadOnlyCellDepth3Color = HexColor.ToHex(ReadOnlyCellDepth3),
- ReadOnlyCellDepth0PastColor = HexColor.ToHex(ReadOnlyCellDepth0Past),
- ReadOnlyCellDepth1PastColor = HexColor.ToHex(ReadOnlyCellDepth1Past),
- ReadOnlyCellDepth2PastColor = HexColor.ToHex(ReadOnlyCellDepth2Past),
- ReadOnlyCellDepth3PastColor = HexColor.ToHex(ReadOnlyCellDepth3Past),
- ReadOnlyCellSelectedColor = HexColor.ToHex(ReadOnlyCellSelected),
- ReadOnlyCellForegroundColor = HexColor.ToHex(ReadOnlyCellForeground),
- GridLineColor = HexColor.ToHex(GridLine),
- StatusBarBackgroundColor = HexColor.ToHex(StatusBarBackground),
- StatusBarForegroundColor = HexColor.ToHex(StatusBarForeground),
- ValidationPanelBackgroundColor = HexColor.ToHex(ValidationPanelBackground),
- ValidationPanelForegroundColor = HexColor.ToHex(ValidationPanelForeground),
- ValidationPanelErrorColor = HexColor.ToHex(ValidationPanelError),
- ValidationPanelWarningColor = HexColor.ToHex(ValidationPanelWarning),
- ExecutionDepth0Color = HexColor.ToHex(ExecutionDepth0),
- ExecutionDepth1Color = HexColor.ToHex(ExecutionDepth1),
- ExecutionDepth2Color = HexColor.ToHex(ExecutionDepth2),
- ExecutionDepth3Color = HexColor.ToHex(ExecutionDepth3),
- ExecutionDepth0PastColor = HexColor.ToHex(ExecutionDepth0Past),
- ExecutionDepth1PastColor = HexColor.ToHex(ExecutionDepth1Past),
- ExecutionDepth2PastColor = HexColor.ToHex(ExecutionDepth2Past),
- ExecutionDepth3PastColor = HexColor.ToHex(ExecutionDepth3Past),
- ExecutionCurrentStepMarkerColor = HexColor.ToHex(ExecutionCurrentStepMarker),
- InfoColor = HexColor.ToHex(Info),
- ConnectedColor = HexColor.ToHex(Connected),
- DisconnectedColor = HexColor.ToHex(Disconnected),
- LocalModeColor = HexColor.ToHex(LocalMode),
- ConnectingColor = HexColor.ToHex(Connecting),
- PanelBackgroundColor = HexColor.ToHex(PanelBackground),
- PanelHeaderBackgroundColor = HexColor.ToHex(PanelHeaderBackground),
- SubtleBorderColor = HexColor.ToHex(SubtleBorder),
- SeparatorColor = HexColor.ToHex(Separator),
- SecondaryForegroundColor = HexColor.ToHex(SecondaryForeground),
- GridBorderColor = HexColor.ToHex(GridBorder),
- GridBackgroundColor = HexColor.ToHex(GridBackground),
- HeaderForegroundColor = HexColor.ToHex(HeaderForeground)
- };
+ return new GridStyleOptions(
+ Fonts: new GridStyleFonts(
+ FontFamily: FontFamily ?? _source.Fonts.FontFamily,
+ HeaderFontSize: ToInt(HeaderFontSize, _source.Fonts.HeaderFontSize),
+ HeaderFontWeight: HeaderFontWeight,
+ HeaderItalic: HeaderItalic,
+ CellFontSize: ToInt(CellFontSize, _source.Fonts.CellFontSize),
+ CellFontWeight: CellFontWeight,
+ CellItalic: CellItalic),
+ Layout: new GridStyleLayout(
+ CellPaddingLeft: ToDouble(CellPaddingLeft, _source.Layout.CellPaddingLeft),
+ CellPaddingTop: ToDouble(CellPaddingTop, _source.Layout.CellPaddingTop),
+ CellPaddingRight: ToDouble(CellPaddingRight, _source.Layout.CellPaddingRight),
+ CellPaddingBottom: ToDouble(CellPaddingBottom, _source.Layout.CellPaddingBottom),
+ RowHeight: ToDouble(RowHeight, _source.Layout.RowHeight)),
+ Selection: new SelectionColors(
+ Background: HexColor.ToHex(SelectionBackground),
+ Foreground: HexColor.ToHex(SelectionForeground)),
+ ChangedCells: new ChangedCellColors(
+ Changed: HexColor.ToHex(CellChanged),
+ ChangedSelected: HexColor.ToHex(CellChangedSelected)),
+ ReadOnlyCells: new DepthPalette(
+ Depth0: HexColor.ToHex(ReadOnlyCellDepth0),
+ Depth1: HexColor.ToHex(ReadOnlyCellDepth1),
+ Depth2: HexColor.ToHex(ReadOnlyCellDepth2),
+ Depth3: HexColor.ToHex(ReadOnlyCellDepth3),
+ Depth0Past: HexColor.ToHex(ReadOnlyCellDepth0Past),
+ Depth1Past: HexColor.ToHex(ReadOnlyCellDepth1Past),
+ Depth2Past: HexColor.ToHex(ReadOnlyCellDepth2Past),
+ Depth3Past: HexColor.ToHex(ReadOnlyCellDepth3Past),
+ Selected: HexColor.ToHex(ReadOnlyCellSelected),
+ Foreground: HexColor.ToHex(ReadOnlyCellForeground)),
+ DisabledCells: new DepthPalette(
+ Depth0: HexColor.ToHex(DisabledCellDepth0),
+ Depth1: HexColor.ToHex(DisabledCellDepth1),
+ Depth2: HexColor.ToHex(DisabledCellDepth2),
+ Depth3: HexColor.ToHex(DisabledCellDepth3),
+ Depth0Past: HexColor.ToHex(DisabledCellDepth0Past),
+ Depth1Past: HexColor.ToHex(DisabledCellDepth1Past),
+ Depth2Past: HexColor.ToHex(DisabledCellDepth2Past),
+ Depth3Past: HexColor.ToHex(DisabledCellDepth3Past),
+ Selected: HexColor.ToHex(DisabledCellSelected),
+ Foreground: HexColor.ToHex(DisabledCellForeground)),
+ Execution: new ExecutionPalette(
+ Depth0: HexColor.ToHex(ExecutionDepth0),
+ Depth1: HexColor.ToHex(ExecutionDepth1),
+ Depth2: HexColor.ToHex(ExecutionDepth2),
+ Depth3: HexColor.ToHex(ExecutionDepth3),
+ Depth0Past: HexColor.ToHex(ExecutionDepth0Past),
+ Depth1Past: HexColor.ToHex(ExecutionDepth1Past),
+ Depth2Past: HexColor.ToHex(ExecutionDepth2Past),
+ Depth3Past: HexColor.ToHex(ExecutionDepth3Past),
+ CurrentStepMarker: HexColor.ToHex(ExecutionCurrentStepMarker)),
+ StatusBar: new StatusBarStyle(
+ Background: HexColor.ToHex(StatusBarBackground),
+ Foreground: HexColor.ToHex(StatusBarForeground),
+ Padding: ToDouble(StatusBarPadding, _source.StatusBar.Padding),
+ ItemSpacing: ToDouble(StatusBarItemSpacing, _source.StatusBar.ItemSpacing),
+ FontSize: ToInt(StatusBarFontSize, _source.StatusBar.FontSize),
+ Weight: StatusBarFontWeight,
+ Italic: StatusBarItalic,
+ TimerLabelFontSize: ToInt(StatusBarTimerLabelFontSize, _source.StatusBar.TimerLabelFontSize),
+ TimerLabelWeight: StatusBarTimerLabelFontWeight,
+ TimerLabelItalic: StatusBarTimerLabelItalic,
+ TimerValueFontSize: ToInt(StatusBarTimerValueFontSize, _source.StatusBar.TimerValueFontSize),
+ TimerValueWeight: StatusBarTimerValueFontWeight,
+ TimerValueItalic: StatusBarTimerValueItalic),
+ ValidationPanel: new ValidationPanelStyle(
+ Background: HexColor.ToHex(ValidationPanelBackground),
+ Foreground: HexColor.ToHex(ValidationPanelForeground),
+ ErrorColor: HexColor.ToHex(ValidationPanelError),
+ WarningColor: HexColor.ToHex(ValidationPanelWarning),
+ MaxHeight: ToDouble(ValidationPanelMaxHeight, _source.ValidationPanel.MaxHeight)),
+ Chrome: new ChromeColors(
+ Info: HexColor.ToHex(Info),
+ Connected: HexColor.ToHex(Connected),
+ Disconnected: HexColor.ToHex(Disconnected),
+ LocalMode: HexColor.ToHex(LocalMode),
+ Connecting: HexColor.ToHex(Connecting),
+ PanelBackground: HexColor.ToHex(PanelBackground),
+ PanelHeaderBackground: HexColor.ToHex(PanelHeaderBackground),
+ SubtleBorder: HexColor.ToHex(SubtleBorder),
+ Separator: HexColor.ToHex(Separator),
+ SecondaryForeground: HexColor.ToHex(SecondaryForeground),
+ GridBorder: HexColor.ToHex(GridBorder),
+ GridBackground: HexColor.ToHex(GridBackground),
+ HeaderForeground: HexColor.ToHex(HeaderForeground),
+ GridLine: HexColor.ToHex(GridLine)),
+ Orientation: _source.Orientation);
}
private async Task SaveAsync()
@@ -322,88 +332,88 @@ private void Seed(GridStyleOptions options)
{
_seeding = true;
- HeaderFontSize = options.HeaderFontSize;
- CellFontSize = options.CellFontSize;
- CellPaddingLeft = (decimal)options.CellPaddingLeft;
- CellPaddingTop = (decimal)options.CellPaddingTop;
- CellPaddingRight = (decimal)options.CellPaddingRight;
- CellPaddingBottom = (decimal)options.CellPaddingBottom;
- RowHeight = (decimal)options.RowHeight;
- StatusBarPadding = (decimal)options.StatusBarPadding;
- StatusBarItemSpacing = (decimal)options.StatusBarItemSpacing;
- StatusBarFontSize = options.StatusBarFontSize;
- StatusBarTimerLabelFontSize = options.StatusBarTimerLabelFontSize;
- StatusBarTimerValueFontSize = options.StatusBarTimerValueFontSize;
- ValidationPanelMaxHeight = (decimal)options.ValidationPanelMaxHeight;
-
- FontFamily = options.FontFamily;
- HeaderFontWeight = options.HeaderFontWeight;
- HeaderItalic = options.HeaderItalic;
- CellFontWeight = options.CellFontWeight;
- CellItalic = options.CellItalic;
- StatusBarFontWeight = options.StatusBarFontWeight;
- StatusBarItalic = options.StatusBarItalic;
- StatusBarTimerLabelFontWeight = options.StatusBarTimerLabelFontWeight;
- StatusBarTimerLabelItalic = options.StatusBarTimerLabelItalic;
- StatusBarTimerValueFontWeight = options.StatusBarTimerValueFontWeight;
- StatusBarTimerValueItalic = options.StatusBarTimerValueItalic;
-
- AvailableFontFamilies = BuildFontFamilies(options.FontFamily);
+ HeaderFontSize = options.Fonts.HeaderFontSize;
+ CellFontSize = options.Fonts.CellFontSize;
+ CellPaddingLeft = (decimal)options.Layout.CellPaddingLeft;
+ CellPaddingTop = (decimal)options.Layout.CellPaddingTop;
+ CellPaddingRight = (decimal)options.Layout.CellPaddingRight;
+ CellPaddingBottom = (decimal)options.Layout.CellPaddingBottom;
+ RowHeight = (decimal)options.Layout.RowHeight;
+ StatusBarPadding = (decimal)options.StatusBar.Padding;
+ StatusBarItemSpacing = (decimal)options.StatusBar.ItemSpacing;
+ StatusBarFontSize = options.StatusBar.FontSize;
+ StatusBarTimerLabelFontSize = options.StatusBar.TimerLabelFontSize;
+ StatusBarTimerValueFontSize = options.StatusBar.TimerValueFontSize;
+ ValidationPanelMaxHeight = (decimal)options.ValidationPanel.MaxHeight;
+
+ FontFamily = options.Fonts.FontFamily;
+ HeaderFontWeight = options.Fonts.HeaderFontWeight;
+ HeaderItalic = options.Fonts.HeaderItalic;
+ CellFontWeight = options.Fonts.CellFontWeight;
+ CellItalic = options.Fonts.CellItalic;
+ StatusBarFontWeight = options.StatusBar.Weight;
+ StatusBarItalic = options.StatusBar.Italic;
+ StatusBarTimerLabelFontWeight = options.StatusBar.TimerLabelWeight;
+ StatusBarTimerLabelItalic = options.StatusBar.TimerLabelItalic;
+ StatusBarTimerValueFontWeight = options.StatusBar.TimerValueWeight;
+ StatusBarTimerValueItalic = options.StatusBar.TimerValueItalic;
+
+ AvailableFontFamilies = BuildFontFamilies(options.Fonts.FontFamily);
AvailableFontWeights = BuildFontWeights(options);
- SelectionBackground = HexColor.Parse(options.SelectionBackgroundColor);
- SelectionForeground = HexColor.Parse(options.SelectionForegroundColor);
- CellChanged = HexColor.Parse(options.CellChangedColor);
- CellChangedSelected = HexColor.Parse(options.CellChangedSelectedColor);
- DisabledCellDepth0 = HexColor.Parse(options.DisabledCellDepth0Color);
- DisabledCellDepth1 = HexColor.Parse(options.DisabledCellDepth1Color);
- DisabledCellDepth2 = HexColor.Parse(options.DisabledCellDepth2Color);
- DisabledCellDepth3 = HexColor.Parse(options.DisabledCellDepth3Color);
- DisabledCellDepth0Past = HexColor.Parse(options.DisabledCellDepth0PastColor);
- DisabledCellDepth1Past = HexColor.Parse(options.DisabledCellDepth1PastColor);
- DisabledCellDepth2Past = HexColor.Parse(options.DisabledCellDepth2PastColor);
- DisabledCellDepth3Past = HexColor.Parse(options.DisabledCellDepth3PastColor);
- DisabledCellSelected = HexColor.Parse(options.DisabledCellSelectedColor);
- DisabledCellForeground = HexColor.Parse(options.DisabledCellForegroundColor);
- ReadOnlyCellDepth0 = HexColor.Parse(options.ReadOnlyCellDepth0Color);
- ReadOnlyCellDepth1 = HexColor.Parse(options.ReadOnlyCellDepth1Color);
- ReadOnlyCellDepth2 = HexColor.Parse(options.ReadOnlyCellDepth2Color);
- ReadOnlyCellDepth3 = HexColor.Parse(options.ReadOnlyCellDepth3Color);
- ReadOnlyCellDepth0Past = HexColor.Parse(options.ReadOnlyCellDepth0PastColor);
- ReadOnlyCellDepth1Past = HexColor.Parse(options.ReadOnlyCellDepth1PastColor);
- ReadOnlyCellDepth2Past = HexColor.Parse(options.ReadOnlyCellDepth2PastColor);
- ReadOnlyCellDepth3Past = HexColor.Parse(options.ReadOnlyCellDepth3PastColor);
- ReadOnlyCellSelected = HexColor.Parse(options.ReadOnlyCellSelectedColor);
- ReadOnlyCellForeground = HexColor.Parse(options.ReadOnlyCellForegroundColor);
- GridLine = HexColor.Parse(options.GridLineColor);
- StatusBarBackground = HexColor.Parse(options.StatusBarBackgroundColor);
- StatusBarForeground = HexColor.Parse(options.StatusBarForegroundColor);
- ValidationPanelBackground = HexColor.Parse(options.ValidationPanelBackgroundColor);
- ValidationPanelForeground = HexColor.Parse(options.ValidationPanelForegroundColor);
- ValidationPanelError = HexColor.Parse(options.ValidationPanelErrorColor);
- ValidationPanelWarning = HexColor.Parse(options.ValidationPanelWarningColor);
- ExecutionDepth0 = HexColor.Parse(options.ExecutionDepth0Color);
- ExecutionDepth1 = HexColor.Parse(options.ExecutionDepth1Color);
- ExecutionDepth2 = HexColor.Parse(options.ExecutionDepth2Color);
- ExecutionDepth3 = HexColor.Parse(options.ExecutionDepth3Color);
- ExecutionDepth0Past = HexColor.Parse(options.ExecutionDepth0PastColor);
- ExecutionDepth1Past = HexColor.Parse(options.ExecutionDepth1PastColor);
- ExecutionDepth2Past = HexColor.Parse(options.ExecutionDepth2PastColor);
- ExecutionDepth3Past = HexColor.Parse(options.ExecutionDepth3PastColor);
- ExecutionCurrentStepMarker = HexColor.Parse(options.ExecutionCurrentStepMarkerColor);
- Info = HexColor.Parse(options.InfoColor);
- Connected = HexColor.Parse(options.ConnectedColor);
- Disconnected = HexColor.Parse(options.DisconnectedColor);
- LocalMode = HexColor.Parse(options.LocalModeColor);
- Connecting = HexColor.Parse(options.ConnectingColor);
- PanelBackground = HexColor.Parse(options.PanelBackgroundColor);
- PanelHeaderBackground = HexColor.Parse(options.PanelHeaderBackgroundColor);
- SubtleBorder = HexColor.Parse(options.SubtleBorderColor);
- Separator = HexColor.Parse(options.SeparatorColor);
- SecondaryForeground = HexColor.Parse(options.SecondaryForegroundColor);
- GridBorder = HexColor.Parse(options.GridBorderColor);
- GridBackground = HexColor.Parse(options.GridBackgroundColor);
- HeaderForeground = HexColor.Parse(options.HeaderForegroundColor);
+ SelectionBackground = HexColor.Parse(options.Selection.Background);
+ SelectionForeground = HexColor.Parse(options.Selection.Foreground);
+ CellChanged = HexColor.Parse(options.ChangedCells.Changed);
+ CellChangedSelected = HexColor.Parse(options.ChangedCells.ChangedSelected);
+ DisabledCellDepth0 = HexColor.Parse(options.DisabledCells.Depth0);
+ DisabledCellDepth1 = HexColor.Parse(options.DisabledCells.Depth1);
+ DisabledCellDepth2 = HexColor.Parse(options.DisabledCells.Depth2);
+ DisabledCellDepth3 = HexColor.Parse(options.DisabledCells.Depth3);
+ DisabledCellDepth0Past = HexColor.Parse(options.DisabledCells.Depth0Past);
+ DisabledCellDepth1Past = HexColor.Parse(options.DisabledCells.Depth1Past);
+ DisabledCellDepth2Past = HexColor.Parse(options.DisabledCells.Depth2Past);
+ DisabledCellDepth3Past = HexColor.Parse(options.DisabledCells.Depth3Past);
+ DisabledCellSelected = HexColor.Parse(options.DisabledCells.Selected);
+ DisabledCellForeground = HexColor.Parse(options.DisabledCells.Foreground);
+ ReadOnlyCellDepth0 = HexColor.Parse(options.ReadOnlyCells.Depth0);
+ ReadOnlyCellDepth1 = HexColor.Parse(options.ReadOnlyCells.Depth1);
+ ReadOnlyCellDepth2 = HexColor.Parse(options.ReadOnlyCells.Depth2);
+ ReadOnlyCellDepth3 = HexColor.Parse(options.ReadOnlyCells.Depth3);
+ ReadOnlyCellDepth0Past = HexColor.Parse(options.ReadOnlyCells.Depth0Past);
+ ReadOnlyCellDepth1Past = HexColor.Parse(options.ReadOnlyCells.Depth1Past);
+ ReadOnlyCellDepth2Past = HexColor.Parse(options.ReadOnlyCells.Depth2Past);
+ ReadOnlyCellDepth3Past = HexColor.Parse(options.ReadOnlyCells.Depth3Past);
+ ReadOnlyCellSelected = HexColor.Parse(options.ReadOnlyCells.Selected);
+ ReadOnlyCellForeground = HexColor.Parse(options.ReadOnlyCells.Foreground);
+ GridLine = HexColor.Parse(options.Chrome.GridLine);
+ StatusBarBackground = HexColor.Parse(options.StatusBar.Background);
+ StatusBarForeground = HexColor.Parse(options.StatusBar.Foreground);
+ ValidationPanelBackground = HexColor.Parse(options.ValidationPanel.Background);
+ ValidationPanelForeground = HexColor.Parse(options.ValidationPanel.Foreground);
+ ValidationPanelError = HexColor.Parse(options.ValidationPanel.ErrorColor);
+ ValidationPanelWarning = HexColor.Parse(options.ValidationPanel.WarningColor);
+ ExecutionDepth0 = HexColor.Parse(options.Execution.Depth0);
+ ExecutionDepth1 = HexColor.Parse(options.Execution.Depth1);
+ ExecutionDepth2 = HexColor.Parse(options.Execution.Depth2);
+ ExecutionDepth3 = HexColor.Parse(options.Execution.Depth3);
+ ExecutionDepth0Past = HexColor.Parse(options.Execution.Depth0Past);
+ ExecutionDepth1Past = HexColor.Parse(options.Execution.Depth1Past);
+ ExecutionDepth2Past = HexColor.Parse(options.Execution.Depth2Past);
+ ExecutionDepth3Past = HexColor.Parse(options.Execution.Depth3Past);
+ ExecutionCurrentStepMarker = HexColor.Parse(options.Execution.CurrentStepMarker);
+ Info = HexColor.Parse(options.Chrome.Info);
+ Connected = HexColor.Parse(options.Chrome.Connected);
+ Disconnected = HexColor.Parse(options.Chrome.Disconnected);
+ LocalMode = HexColor.Parse(options.Chrome.LocalMode);
+ Connecting = HexColor.Parse(options.Chrome.Connecting);
+ PanelBackground = HexColor.Parse(options.Chrome.PanelBackground);
+ PanelHeaderBackground = HexColor.Parse(options.Chrome.PanelHeaderBackground);
+ SubtleBorder = HexColor.Parse(options.Chrome.SubtleBorder);
+ Separator = HexColor.Parse(options.Chrome.Separator);
+ SecondaryForeground = HexColor.Parse(options.Chrome.SecondaryForeground);
+ GridBorder = HexColor.Parse(options.Chrome.GridBorder);
+ GridBackground = HexColor.Parse(options.Chrome.GridBackground);
+ HeaderForeground = HexColor.Parse(options.Chrome.HeaderForeground);
_seeding = false;
RecomputeCanSave();
@@ -461,11 +471,11 @@ private static IReadOnlyList BuildFontWeights(GridStyleOptions
var seededWeights = new[]
{
- options.HeaderFontWeight,
- options.CellFontWeight,
- options.StatusBarFontWeight,
- options.StatusBarTimerLabelFontWeight,
- options.StatusBarTimerValueFontWeight
+ options.Fonts.HeaderFontWeight,
+ options.Fonts.CellFontWeight,
+ options.StatusBar.Weight,
+ options.StatusBar.TimerLabelWeight,
+ options.StatusBar.TimerValueWeight
};
foreach (var weight in seededWeights.Distinct())
diff --git a/SemiStep/SemiStep.UI/Styles/CellPaletteInstaller.cs b/SemiStep/SemiStep.UI/Styles/CellPaletteInstaller.cs
index fdc80bc4..fac74408 100644
--- a/SemiStep/SemiStep.UI/Styles/CellPaletteInstaller.cs
+++ b/SemiStep/SemiStep.UI/Styles/CellPaletteInstaller.cs
@@ -72,67 +72,67 @@ public static void Install(IResourceDictionary resources, GridStyleOptions gridS
ArgumentNullException.ThrowIfNull(resources);
ArgumentNullException.ThrowIfNull(gridStyle);
- resources[CellReadOnlyDepth0BrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCellDepth0Color);
- resources[CellReadOnlyDepth1BrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCellDepth1Color);
- resources[CellReadOnlyDepth2BrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCellDepth2Color);
- resources[CellReadOnlyDepth3BrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCellDepth3Color);
- resources[CellReadOnlyDepth0PastBrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCellDepth0PastColor);
- resources[CellReadOnlyDepth1PastBrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCellDepth1PastColor);
- resources[CellReadOnlyDepth2PastBrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCellDepth2PastColor);
- resources[CellReadOnlyDepth3PastBrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCellDepth3PastColor);
- resources[CellReadOnlySelectedBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCellSelectedColor);
- resources[CellReadOnlyForegroundBrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCellForegroundColor);
- resources[CellDisabledDepth0BrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCellDepth0Color);
- resources[CellDisabledDepth1BrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCellDepth1Color);
- resources[CellDisabledDepth2BrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCellDepth2Color);
- resources[CellDisabledDepth3BrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCellDepth3Color);
- resources[CellDisabledDepth0PastBrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCellDepth0PastColor);
- resources[CellDisabledDepth1PastBrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCellDepth1PastColor);
- resources[CellDisabledDepth2PastBrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCellDepth2PastColor);
- resources[CellDisabledDepth3PastBrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCellDepth3PastColor);
- resources[CellDisabledSelectedBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCellSelectedColor);
- resources[CellDisabledForegroundBrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCellForegroundColor);
- resources[SelectionBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.SelectionBackgroundColor);
- resources[SelectionForegroundBrushKey] = PaletteBrushFactory.From(gridStyle.SelectionForegroundColor);
- resources[CellChangedBrushKey] = PaletteBrushFactory.From(gridStyle.CellChangedColor);
+ resources[CellReadOnlyDepth0BrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCells.Depth0);
+ resources[CellReadOnlyDepth1BrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCells.Depth1);
+ resources[CellReadOnlyDepth2BrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCells.Depth2);
+ resources[CellReadOnlyDepth3BrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCells.Depth3);
+ resources[CellReadOnlyDepth0PastBrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCells.Depth0Past);
+ resources[CellReadOnlyDepth1PastBrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCells.Depth1Past);
+ resources[CellReadOnlyDepth2PastBrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCells.Depth2Past);
+ resources[CellReadOnlyDepth3PastBrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCells.Depth3Past);
+ resources[CellReadOnlySelectedBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCells.Selected);
+ resources[CellReadOnlyForegroundBrushKey] = PaletteBrushFactory.From(gridStyle.ReadOnlyCells.Foreground);
+ resources[CellDisabledDepth0BrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCells.Depth0);
+ resources[CellDisabledDepth1BrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCells.Depth1);
+ resources[CellDisabledDepth2BrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCells.Depth2);
+ resources[CellDisabledDepth3BrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCells.Depth3);
+ resources[CellDisabledDepth0PastBrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCells.Depth0Past);
+ resources[CellDisabledDepth1PastBrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCells.Depth1Past);
+ resources[CellDisabledDepth2PastBrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCells.Depth2Past);
+ resources[CellDisabledDepth3PastBrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCells.Depth3Past);
+ resources[CellDisabledSelectedBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCells.Selected);
+ resources[CellDisabledForegroundBrushKey] = PaletteBrushFactory.From(gridStyle.DisabledCells.Foreground);
+ resources[SelectionBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.Selection.Background);
+ resources[SelectionForegroundBrushKey] = PaletteBrushFactory.From(gridStyle.Selection.Foreground);
+ resources[CellChangedBrushKey] = PaletteBrushFactory.From(gridStyle.ChangedCells.Changed);
resources[CellChangedSelectedBackgroundBrushKey] =
- PaletteBrushFactory.From(gridStyle.CellChangedSelectedColor);
- resources[GridLineBrushKey] = PaletteBrushFactory.From(gridStyle.GridLineColor);
- resources[StatusBarBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.StatusBarBackgroundColor);
- resources[StatusBarForegroundBrushKey] = PaletteBrushFactory.From(gridStyle.StatusBarForegroundColor);
- resources[StatusBarPaddingKey] = new Thickness(gridStyle.StatusBarPadding);
- resources[StatusBarItemSpacingKey] = gridStyle.StatusBarItemSpacing;
- resources[AppFontFamilyKey] = ToFontFamily(gridStyle.FontFamily);
- resources[StatusBarFontSizeKey] = (double)gridStyle.StatusBarFontSize;
- resources[StatusBarFontWeightKey] = (FontWeight)gridStyle.StatusBarFontWeight;
- resources[StatusBarFontStyleKey] = ToFontStyle(gridStyle.StatusBarItalic);
- resources[StatusBarTimerLabelFontSizeKey] = (double)gridStyle.StatusBarTimerLabelFontSize;
- resources[StatusBarTimerLabelFontWeightKey] = (FontWeight)gridStyle.StatusBarTimerLabelFontWeight;
- resources[StatusBarTimerLabelFontStyleKey] = ToFontStyle(gridStyle.StatusBarTimerLabelItalic);
- resources[StatusBarTimerValueFontSizeKey] = (double)gridStyle.StatusBarTimerValueFontSize;
- resources[StatusBarTimerValueFontWeightKey] = (FontWeight)gridStyle.StatusBarTimerValueFontWeight;
- resources[StatusBarTimerValueFontStyleKey] = ToFontStyle(gridStyle.StatusBarTimerValueItalic);
- resources[ErrorBrushKey] = PaletteBrushFactory.From(gridStyle.ValidationPanelErrorColor);
- resources[WarningBrushKey] = PaletteBrushFactory.From(gridStyle.ValidationPanelWarningColor);
+ PaletteBrushFactory.From(gridStyle.ChangedCells.ChangedSelected);
+ resources[GridLineBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.GridLine);
+ resources[StatusBarBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.StatusBar.Background);
+ resources[StatusBarForegroundBrushKey] = PaletteBrushFactory.From(gridStyle.StatusBar.Foreground);
+ resources[StatusBarPaddingKey] = new Thickness(gridStyle.StatusBar.Padding);
+ resources[StatusBarItemSpacingKey] = gridStyle.StatusBar.ItemSpacing;
+ resources[AppFontFamilyKey] = ToFontFamily(gridStyle.Fonts.FontFamily);
+ resources[StatusBarFontSizeKey] = (double)gridStyle.StatusBar.FontSize;
+ resources[StatusBarFontWeightKey] = (FontWeight)gridStyle.StatusBar.Weight;
+ resources[StatusBarFontStyleKey] = ToFontStyle(gridStyle.StatusBar.Italic);
+ resources[StatusBarTimerLabelFontSizeKey] = (double)gridStyle.StatusBar.TimerLabelFontSize;
+ resources[StatusBarTimerLabelFontWeightKey] = (FontWeight)gridStyle.StatusBar.TimerLabelWeight;
+ resources[StatusBarTimerLabelFontStyleKey] = ToFontStyle(gridStyle.StatusBar.TimerLabelItalic);
+ resources[StatusBarTimerValueFontSizeKey] = (double)gridStyle.StatusBar.TimerValueFontSize;
+ resources[StatusBarTimerValueFontWeightKey] = (FontWeight)gridStyle.StatusBar.TimerValueWeight;
+ resources[StatusBarTimerValueFontStyleKey] = ToFontStyle(gridStyle.StatusBar.TimerValueItalic);
+ resources[ErrorBrushKey] = PaletteBrushFactory.From(gridStyle.ValidationPanel.ErrorColor);
+ resources[WarningBrushKey] = PaletteBrushFactory.From(gridStyle.ValidationPanel.WarningColor);
resources[ValidationPanelBackgroundBrushKey] =
- PaletteBrushFactory.From(gridStyle.ValidationPanelBackgroundColor);
+ PaletteBrushFactory.From(gridStyle.ValidationPanel.Background);
resources[ValidationPanelForegroundBrushKey] =
- PaletteBrushFactory.From(gridStyle.ValidationPanelForegroundColor);
- resources[ValidationPanelMaxHeightKey] = gridStyle.ValidationPanelMaxHeight;
- resources[InfoBrushKey] = PaletteBrushFactory.From(gridStyle.InfoColor);
- resources[ConnectedBrushKey] = PaletteBrushFactory.From(gridStyle.ConnectedColor);
- resources[DisconnectedBrushKey] = PaletteBrushFactory.From(gridStyle.DisconnectedColor);
- resources[LocalModeBrushKey] = PaletteBrushFactory.From(gridStyle.LocalModeColor);
- resources[ConnectingBrushKey] = PaletteBrushFactory.From(gridStyle.ConnectingColor);
- resources[PanelBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.PanelBackgroundColor);
- resources[PanelHeaderBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.PanelHeaderBackgroundColor);
- resources[SubtleBorderBrushKey] = PaletteBrushFactory.From(gridStyle.SubtleBorderColor);
- resources[SeparatorBrushKey] = PaletteBrushFactory.From(gridStyle.SeparatorColor);
- resources[SecondaryForegroundBrushKey] = PaletteBrushFactory.From(gridStyle.SecondaryForegroundColor);
- resources[GridBorderBrushKey] = PaletteBrushFactory.From(gridStyle.GridBorderColor);
- resources[GridBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.GridBackgroundColor);
- resources[HeaderForegroundBrushKey] = PaletteBrushFactory.From(gridStyle.HeaderForegroundColor);
- resources[RowHeightKey] = gridStyle.RowHeight;
+ PaletteBrushFactory.From(gridStyle.ValidationPanel.Foreground);
+ resources[ValidationPanelMaxHeightKey] = gridStyle.ValidationPanel.MaxHeight;
+ resources[InfoBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.Info);
+ resources[ConnectedBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.Connected);
+ resources[DisconnectedBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.Disconnected);
+ resources[LocalModeBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.LocalMode);
+ resources[ConnectingBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.Connecting);
+ resources[PanelBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.PanelBackground);
+ resources[PanelHeaderBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.PanelHeaderBackground);
+ resources[SubtleBorderBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.SubtleBorder);
+ resources[SeparatorBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.Separator);
+ resources[SecondaryForegroundBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.SecondaryForeground);
+ resources[GridBorderBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.GridBorder);
+ resources[GridBackgroundBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.GridBackground);
+ resources[HeaderForegroundBrushKey] = PaletteBrushFactory.From(gridStyle.Chrome.HeaderForeground);
+ resources[RowHeightKey] = gridStyle.Layout.RowHeight;
}
// An empty family means "theme default": FontFamily.Default keeps the application's default
diff --git a/SemiStep/SemiStep.UI/Styles/ExecutionPaletteInstaller.cs b/SemiStep/SemiStep.UI/Styles/ExecutionPaletteInstaller.cs
index e8f4f220..b3c124c3 100644
--- a/SemiStep/SemiStep.UI/Styles/ExecutionPaletteInstaller.cs
+++ b/SemiStep/SemiStep.UI/Styles/ExecutionPaletteInstaller.cs
@@ -22,14 +22,14 @@ public static void Install(IResourceDictionary resources, GridStyleOptions gridS
ArgumentNullException.ThrowIfNull(gridStyle);
// Depth0 brushes (the "outside any loop" tint) are installed but not yet consumed by any selector.
- resources[ExecRowDepth0BrushKey] = PaletteBrushFactory.From(gridStyle.ExecutionDepth0Color);
- resources[ExecRowDepth1BrushKey] = PaletteBrushFactory.From(gridStyle.ExecutionDepth1Color);
- resources[ExecRowDepth2BrushKey] = PaletteBrushFactory.From(gridStyle.ExecutionDepth2Color);
- resources[ExecRowDepth3BrushKey] = PaletteBrushFactory.From(gridStyle.ExecutionDepth3Color);
- resources[ExecRowDepth0PastBrushKey] = PaletteBrushFactory.From(gridStyle.ExecutionDepth0PastColor);
- resources[ExecRowDepth1PastBrushKey] = PaletteBrushFactory.From(gridStyle.ExecutionDepth1PastColor);
- resources[ExecRowDepth2PastBrushKey] = PaletteBrushFactory.From(gridStyle.ExecutionDepth2PastColor);
- resources[ExecRowDepth3PastBrushKey] = PaletteBrushFactory.From(gridStyle.ExecutionDepth3PastColor);
- resources[CurrentStepMarkerBrushKey] = PaletteBrushFactory.From(gridStyle.ExecutionCurrentStepMarkerColor);
+ resources[ExecRowDepth0BrushKey] = PaletteBrushFactory.From(gridStyle.Execution.Depth0);
+ resources[ExecRowDepth1BrushKey] = PaletteBrushFactory.From(gridStyle.Execution.Depth1);
+ resources[ExecRowDepth2BrushKey] = PaletteBrushFactory.From(gridStyle.Execution.Depth2);
+ resources[ExecRowDepth3BrushKey] = PaletteBrushFactory.From(gridStyle.Execution.Depth3);
+ resources[ExecRowDepth0PastBrushKey] = PaletteBrushFactory.From(gridStyle.Execution.Depth0Past);
+ resources[ExecRowDepth1PastBrushKey] = PaletteBrushFactory.From(gridStyle.Execution.Depth1Past);
+ resources[ExecRowDepth2PastBrushKey] = PaletteBrushFactory.From(gridStyle.Execution.Depth2Past);
+ resources[ExecRowDepth3PastBrushKey] = PaletteBrushFactory.From(gridStyle.Execution.Depth3Past);
+ resources[CurrentStepMarkerBrushKey] = PaletteBrushFactory.From(gridStyle.Execution.CurrentStepMarker);
}
}
diff --git a/docs/plans/completed/20260805-grid-style-nest-record.md b/docs/plans/completed/20260805-grid-style-nest-record.md
new file mode 100644
index 00000000..39291ae6
--- /dev/null
+++ b/docs/plans/completed/20260805-grid-style-nest-record.md
@@ -0,0 +1,157 @@
+# Slice 3 — Nest GridStyleOptions to mirror the consumption groups
+
+## Overview
+
+Slice 3 of the #118 debloat, and the structural core: replace the flat, 78-arity `GridStyleOptions` record with a
+one-level nested root composing ~10 per-group records. This is the big line-count win and the **only slice that ripples
+the runtime consumers** — but every touch is a mechanical property-path change guarded by the compiler, and slice 1's
+save→load + Seed/perturbation guards catch any dropped or cross-wired field.
+
+**The YAML format, the DTOs, and `GridStyleValidator` do not change.** The file on disk is byte-identical before and
+after; the DTO layer keeps its nested snake_case shape and its per-key error reporting. Only the *record* the DTO maps
+to/from changes shape, plus everything that reads that record.
+
+**Shape choices:**
+- **One-level nesting** — ~10 group records directly on the root, matching how consumers read (short paths like
+ `gridStyle.ReadOnlyCells.Depth1`); the load mapper owns the small DTO-tree walk (`colors.cells.readonly` → the group).
+- **Standalone `ExecutionPalette`** — one reusable `DepthPalette` for the two identical 10-field blocks (ReadOnly +
+ Disabled); Execution is its own record (8 depths + marker, no selected/foreground).
+
+**Colors stay `string` in this slice.** Typing them as `StyleColor` is slice 4 — doing it here would mean re-touching
+all 53 in place. So the group records carry `string …Color` fields exactly as the flat record does today; only the
+*grouping* changes.
+
+## The target shape
+
+Root record (one level, ~10 groups + orientation):
+
+```csharp
+public sealed record GridStyleOptions(
+ GridStyleFonts Fonts,
+ GridStyleLayout Layout,
+ SelectionColors Selection,
+ ChangedCellColors ChangedCells, // colors.cells.changed / changed_selected
+ DepthPalette ReadOnlyCells, // colors.cells.readonly (10 fields)
+ DepthPalette DisabledCells, // colors.cells.disabled (10 fields, identical shape)
+ ExecutionPalette Execution, // colors.cells.execution (8 depths + marker)
+ StatusBarStyle StatusBar,
+ ValidationPanelStyle ValidationPanel,
+ ChromeColors Chrome, // 13 chrome + grid_line folded in (see note)
+ GridOrientation Orientation); // unsurfaced, carried through
+```
+
+Group records (all `public sealed record`, positional so a missed field is CS7036; Core, `SemiStep.Core.Configuration`;
+new files, one per record; colors as `string`, sizes `int`/`double` as today):
+
+| Record | Fields | DTO source |
+|---|---|---|
+| `GridStyleFonts` | FontFamily, HeaderFontSize, HeaderFontWeight, HeaderItalic, CellFontSize, CellFontWeight, CellItalic (7) | `fonts` |
+| `GridStyleLayout` | CellPaddingLeft/Top/Right/Bottom, RowHeight (5) | `layout` |
+| `SelectionColors` | Background, Foreground (2) | `colors.selection` |
+| `ChangedCellColors` | Changed, ChangedSelected (2) | `colors.cells.changed` / `changed_selected` |
+| `DepthPalette` | Depth0/1/2/3, Depth0Past/1Past/2Past/3Past, Selected, Foreground (10) | `colors.cells.readonly` **and** `…disabled` (one type, used twice) |
+| `ExecutionPalette` | Depth0/1/2/3, Depth0Past/1Past/2Past/3Past, CurrentStepMarker (9) | `colors.cells.execution` |
+| `StatusBarStyle` | Background, Foreground, Padding, ItemSpacing, FontSize, Weight, Italic, TimerLabelFontSize/Weight/Italic, TimerValueFontSize/Weight/Italic (13) | `status_bar` |
+| `ValidationPanelStyle` | Background, Foreground, ErrorColor, WarningColor, MaxHeight (5) | `validation_panel` |
+| `ChromeColors` | Info, Connected, Disconnected, LocalMode, Connecting, PanelBackground, PanelHeaderBackground, SubtleBorder, Separator, SecondaryForeground, GridBorder, GridBackground, HeaderForeground, **GridLine** (14) | `chrome` (13) + `colors.grid_line` |
+
+7+5+2+2+10+10+9+13+5+14 = 77 surfaced + `Orientation` = 78 fields, all accounted for.
+
+**Two loose fields** sit outside a sub-DTO in the source (`colors.grid_line`, `colors.cells.changed`/`changed_selected`).
+(a) `grid_line` folds into `ChromeColors.GridLine` — it is grid chrome (beside `grid_border`/`grid_background`), and
+one-level nesting means no lone `string` on the root; the mapper reads it from `colors.grid_line` into `Chrome`.
+(b) `changed`/`changed_selected` become a small `ChangedCellColors` group. Both are the small cross-section reads the
+one-level shape absorbs into the mapper.
+
+## Acceptance Evidence
+
+- `GridStyleOptions` is the nested root above; the ~10 group records exist; `DepthPalette` is one type used for both
+ ReadOnly and Disabled. `Default` is composed from per-group values, byte-equal to today's flat `Default`.
+- Both mappers rebuilt as per-group construction; **no YAML/DTO/validator change** — `SaveThenLoad_DistinctFixture…`
+ (record→dto→yaml→dto→record) stays green, proving the nested record round-trips losslessly through the unchanged file
+ format.
+- Every consumer reads the nested path (`gridStyle.Fonts.CellFontSize`, `gridStyle.ReadOnlyCells.Depth1`, …); the app
+ compiles with zero flat-field references left.
+- Slice 1's guards, adapted to the nested record, still bite (proven by scratch checks); the `SurfacedEditablePropertyCount`
+ (77) is unchanged — the VM's public property surface does NOT change in this slice (that's slice 5).
+- `dotnet build SemiStep.slnx` 0 warnings; full `dotnet test` green; `dotnet format` clean; screenshot tests
+ (`SemiStep.Screenshots/GridStyleEditorScreenshotTests`, if present) unchanged.
+
+## Task 1: The full nested rewrite — record, mappers, consumers, VM, fixture, ALL test fixes (one atomic commit)
+
+The record-shape change ripples until nothing references a flat field — this is **one atomic edit** across production AND
+tests. A partial state does not compile. This task includes the slice-1 guard-test *adaptation* too (the name map + the
+leaf walk), because those tests must reach green in the same commit — deferring them would leave committed red tests.
+Do it all, reach full build+test green, commit once. (Task 2 only *proves* the adapted guards still bite and updates the
+doc — no source change there.)
+
+**Files:**
+- Create: `Configuration/GridStyleFonts.cs`, `GridStyleLayout.cs`, `SelectionColors.cs`, `ChangedCellColors.cs`, `DepthPalette.cs`, `ExecutionPalette.cs`, `StatusBarStyle.cs`, `ValidationPanelStyle.cs`, `ChromeColors.cs` (all `SemiStep.Core.Configuration`, BOM, positional `public sealed record`).
+- Rewrite: `Configuration/GridStyleOptions.cs`, `Mapping/GridStyleMapper.cs`, `GridStyleDtoMapper.cs`.
+- Modify: `SemiStep.UI/StyleEditor/GridStyleEditorViewModel.cs` (`Seed`/`BuildRecord` nested paths — the ~77 public properties do NOT change; `RecomputeCanSave`/`Validate` unchanged).
+- Modify consumers (path rename only): `RecipeGrid/GridFontApplier.cs`, `ColumnBuilder.cs`, `ColumnWidthCalculator.cs`, `TextCellFactory.cs`, `Transposed/TransposedCellTemplateFactory.cs`, `Transposed/TransposedRecipeGridView.axaml.cs`, `ActiveRecipeGridSurface.cs`, `Styles/CellPaletteInstaller.cs`, `Styles/ExecutionPaletteInstaller.cs`. (`App.axaml.cs`/`ConfigFacade`/DI: carrier only, verify untouched.)
+- Modify tests (flat-path → nested; compiler drives): `Helpers/GridStyleOptionsTestData.cs`, and every test that reads/`with`-mutates a flat record field — at least `GridStyleEditorFacadeTests`, `GridStyleEditorViewModelTests` (the non-guard tests + the guard adaptation), `GridStyleEditorWindowTests`, `ConfigFacadeGridStyleValidationTests`, `GridStyleMapperTests`, `CellPaletteInstallerTests`, `ExecutionPaletteInstallerTests`, `GridFactoryFontTests`, `ColumnWidthCalculatorTests`, `AppStatusBarTimerFontTests`, `TransposedCellBackgroundConverterTests`, `ActiveRecipeGridSurfaceTests`, `GridStyleWriterTests`, `GridStyleOrientationTests`, `SemiStep.Screenshots/RecipeGridScreenshotTests`. **This list is a starting point — grep the whole solution for the flat field names and fix every hit; the build gate is the ground truth.**
+
+- [x] Create the ~10 group records (positional, `string` colors, per the table). `DepthPalette` is ONE type used for both `ReadOnlyCells` and `DisabledCells`.
+- [x] Rewrite `GridStyleOptions` as the nested root + `Default`, every value byte-equal to today's flat `Default`.
+- [x] **Rewrite both mappers, preserving the CURRENT fallback regimes exactly** (read `GridStyleMapper.cs` first — do NOT blanket-add `?? defaults`):
+ - Whole-DTO-null → early `return GridStyleOptions.Default` (unchanged).
+ - `?? defaults..` per field ONLY for fonts / layout / selection / changed-cells / grid_line / status_bar / validation_panel / chrome — the defaults now read off the nested `GridStyleOptions.Default`'s group instances.
+ - **The three cell palettes (readonly / disabled / execution) keep their NO-fallback `!` chain** (`dto.Colors!.Cells!.ReadOnly!.Depth0!`, …) — validator-guaranteed presence; adding `?? defaults` there would silently swap a validator failure for a `#000000` placeholder. Keep the validator-precondition intent.
+ - `Orientation` via `GridOrientationValues.Parse`, not `??`.
+ - `Chrome.GridLine` ↔ `colors.grid_line`; `ChangedCells` ↔ `colors.cells.changed`/`changed_selected` (the two cross-section reads the one-level nesting accepts). `GridStyleDtoMapper` reads `options..` into each DTO section field-for-field.
+- [x] Rewire the VM `Seed`/`BuildRecord` to nested paths (`BuildRecord` builds each group positionally; `Orientation` carried from `_source.Orientation`); rewire all ~9 consumers; rebuild `Distinct()` nested (same 77 distinct values).
+- [x] **Adapt the slice-1 guards to the nested record** (`GridStyleEditorViewModelTests`):
+ - `RecordField` → a VM-property→**nested-path** map (`SelectionBackground`→`Selection.Background`, `HeaderFontSize`→`Fonts.HeaderFontSize`, `RowHeight`→`Layout.RowHeight`, `ReadOnlyCellDepth1`→`ReadOnlyCells.Depth1`, `ExecutionCurrentStepMarker`→`Execution.CurrentStepMarker`, `Info`→`Chrome.Info`, `GridLine`→`Chrome.GridLine`, …). Exhaustive (all 77); count assert stays 77. This map is **hand-maintained, not derived** — several leaf names differ from the VM property name (`StatusBarFontWeight`→`StatusBar.Weight`, `StatusBarTimerLabelFontWeight`→`StatusBar.TimerLabelWeight`, `FontFamily`→`Fonts.FontFamily`), so a mistyped entry surfaces as a red guard test, not a silent pass. Cross-check each entry against the group records.
+ - `ChangedRecordFields` → a **typed recursive leaf walk**: a property whose type is `string`/primitive/`enum` is a LEAF reported at its dotted path; a property whose type is a `record` group RECURSES. This keeps root-level `Orientation` (an enum) a first-class leaf — otherwise a `BuildRecord` bug that also flips Orientation would slip the exact-one-leaf assertion. Compare two `GridStyleOptions`, return the set of differing leaf paths; the perturbation asserts that set == `{ mappedPath }`.
+ - Seed-populates compares each VM property to the fixture's mapped nested-leaf value (`Convert.ToDecimal` for boxed numerics, `HexColor.Parse` for colors — only the record access nests).
+- [x] `dotnet build SemiStep.slnx` 0 warnings (zero flat-field references left) AND full `dotnet test` green (all non-guard tests + the adapted guards). One commit.
+
+## Task 2: Prove the adapted guards still bite + doc
+
+No source change — verification + doc only.
+
+- [x] **Non-vacuity scratch checks (do NOT commit any break):**
+ - **Perturbation** — a dropped `BuildRecord` field is a compile error now (positional construction → CS7036), so the runtime check is a **same-type cross-wire**: temporarily pass a sibling's value into the wrong positional slot (e.g. `SelectionForeground` into `SelectionColors`'s Background slot, or swap the `ReadOnlyCells`/`DisabledCells` `DepthPalette` instances in `BuildRecord`) → the perturbation test goes red for the mis-wired leaf(s). Revert.
+ - **Seed-populates** — delete a `Seed` line (a nullable numeric or an italic) → red. Revert.
+ - **Save→load** — delete a `GridStyleDtoMapper` line (nullable object-initializer → compiles, drifts through the fallback) → `SaveThenLoad_DistinctFixture` red. Revert.
+ - Record all three results in the progress log. If the perturbation cross-wire does NOT go red, the leaf walk is wrong — that's a Task-1 defect to fix (re-open Task 1), not something to paper over.
+- [x] Interim doc note in `Docs/architecture/grid-style-configuration.md`: the record is now nested per group (full rewrite is slice 5); YAML/DTO/validator unchanged. **Note for slice 4:** the error-path key `colors.grid_line` and the record path `Chrome.GridLine` deliberately diverge — slice 4's mapper-resident validation must keep emitting the `colors.grid_line` key, not rename it.
+- [x] `dotnet build SemiStep.slnx` 0 warnings; full `dotnet test` green (incl. screenshot tests); `dotnet format`. (screenshot project pre-existing-broken at HEAD, excluded from slnx)
+
+## Post-Completion
+
+**Next:** slice 4 — introduce `StyleColor` (readonly record struct, Core, Avalonia-free) and flip the 53 color fields
+(now living in the group records) from `string` to `StyleColor`; fold `GridStyleValidator`'s per-key checks into the
+load mapper (aggregate, same error identity); delete `HexColor`; retarget `PaletteBrushFactory.From(StyleColor)`. Then
+slice 5 — split the VM into per-group drafts + grouped AXAML, which closes #118. Two forks await slice 4/5 planning:
+`StyleColor` struct-vs-class + the `#RGB`/`#ARGB` parse discrepancy (slice 4), and the AXAML rename scope + facade
+`Validate` fate (slice 5).
+
+**Executed by exec:**
+- branch: grid-style-nest-record
+
+## Verify it yourself
+
+This slice is a pure structural refactor: the on-disk YAML is byte-identical, the DTOs/validator are untouched,
+and the operator-visible behaviour of the style editor and grid is unchanged. There is no manual repro that
+distinguishes before from after — the whole point is that nothing observable moves. The evidence is in the tests
+and the diff.
+
+1. **Nothing on disk changed.** `git diff master...HEAD -- '*Dto*.cs' '*Validator*.cs' 'SemiStep/**/grid_style.yaml'`
+ returns empty — only the record shape and its readers moved.
+2. **The record round-trips losslessly through the unchanged file format.** Run the acceptance test that drives
+ record→dto→yaml→dto→record with a distinct-value fixture:
+ `dotnet test SemiStep/SemiStep.Tests/SemiStep.Tests.csproj --filter "FullyQualifiedName~SaveThenLoad_DistinctFixture"`
+ — green means every one of the 78 fields survived the nesting and lands on the correct target field.
+3. **The silent-drop guards still bite the nested record.** The slice-1 guards were adapted (nested-path map +
+ typed leaf walk) and their non-vacuity was re-proven by scratch checks — see the progress log entries for the
+ cross-wire → `BuildRecord_PerturbingEachProperty_ChangesOnlyThatMappedField` RED, delete-Seed-line →
+ `Seed_PopulatesEverySurfacedProperty_FromDistinctFixture` RED, delete-DtoMapper-line → `SaveThenLoad_DistinctFixture` RED.
+ Run all three green: `dotnet test SemiStep/SemiStep.Tests/SemiStep.Tests.csproj --filter "FullyQualifiedName~GridStyleEditorViewModelTests"`.
+4. **Zero flat-field references remain and the app builds clean.** `dotnet build SemiStep.slnx` → 0 warnings, 0 errors;
+ full `dotnet test` → 1687 passed. A dropped or cross-wired field would surface as a red guard or a build error, not
+ a silent revert.
+5. **Manual smoke (optional):** open the style editor, change a handful of values across groups (a font size, a
+ selection color, a status-bar timer weight, orientation), save, reopen — every value persists. Pre-fix commit that
+ would fail a dropped-field scenario is demonstrated by the Task-2 scratch checks, not reproducible by hand.