diff --git a/Terminal.Gui/Configuration/ConfigPropertyHostTypes.cs b/Terminal.Gui/Configuration/ConfigPropertyHostTypes.cs index 6fd66cd061..698cf8c37b 100644 --- a/Terminal.Gui/Configuration/ConfigPropertyHostTypes.cs +++ b/Terminal.Gui/Configuration/ConfigPropertyHostTypes.cs @@ -40,31 +40,15 @@ internal static class ConfigPropertyHostTypes typeof (SchemeManager), typeof (ThemeManager), typeof (Color), - typeof (Glyphs), typeof (Driver), typeof (Key), - typeof (NerdFonts), typeof (Trace), typeof (View), typeof (BorderView), - typeof (Button), - typeof (CharMap), - typeof (CheckBox), - typeof (Dialog), typeof (FileDialog), typeof (FileDialogStyle), - typeof (FrameView), - typeof (HexView), - typeof (LinearRangeDefaults), - typeof (Menu), typeof (MenuBar), - typeof (MessageBox), typeof (PopoverMenu), - typeof (SelectorBase), - typeof (StatusBar), - typeof (TextField), - typeof (TextView), - typeof (Window) ]; [DynamicDependency (PRESERVED_MEMBERS, typeof (Application))] @@ -72,30 +56,14 @@ internal static class ConfigPropertyHostTypes [DynamicDependency (PRESERVED_MEMBERS, typeof (SchemeManager))] [DynamicDependency (PRESERVED_MEMBERS, typeof (ThemeManager))] [DynamicDependency (PRESERVED_MEMBERS, typeof (Color))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (Glyphs))] [DynamicDependency (PRESERVED_MEMBERS, typeof (Driver))] [DynamicDependency (PRESERVED_MEMBERS, typeof (Key))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (NerdFonts))] [DynamicDependency (PRESERVED_MEMBERS, typeof (Trace))] [DynamicDependency (PRESERVED_MEMBERS, typeof (View))] [DynamicDependency (PRESERVED_MEMBERS, typeof (BorderView))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (Button))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (CharMap))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (CheckBox))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (Dialog))] [DynamicDependency (PRESERVED_MEMBERS, typeof (FileDialog))] [DynamicDependency (PRESERVED_MEMBERS, typeof (FileDialogStyle))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (FrameView))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (HexView))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (LinearRangeDefaults))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (Menu))] [DynamicDependency (PRESERVED_MEMBERS, typeof (MenuBar))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (MessageBox))] [DynamicDependency (PRESERVED_MEMBERS, typeof (PopoverMenu))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (SelectorBase))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (StatusBar))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (TextField))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (TextView))] - [DynamicDependency (PRESERVED_MEMBERS, typeof (Window))] internal static Type [] GetTypes () => _types; } diff --git a/Terminal.Gui/Configuration/Settings/ButtonSettings.cs b/Terminal.Gui/Configuration/Settings/ButtonSettings.cs index 2ec80020f8..6e67c7eb1a 100644 --- a/Terminal.Gui/Configuration/Settings/ButtonSettings.cs +++ b/Terminal.Gui/Configuration/Settings/ButtonSettings.cs @@ -1,19 +1,33 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for visual defaults (ThemeScope). +/// Immutable settings record for visual defaults (ThemeScope). /// -public class ButtonSettings +/// +/// +/// is the compile-time-known fallback (constructor defaults). +/// holds the currently effective values and is updated atomically by +/// via Volatile.Write at startup and on theme switch. Mid-render +/// consumers always observe either the previous or the next reference — never a partially populated one. +/// +/// +public sealed record ButtonSettings { - /// Gets or sets the default shadow style for buttons. - public ShadowStyles DefaultShadow { get; set; } = ShadowStyles.Opaque; + /// Gets the default shadow style for buttons. + public ShadowStyles DefaultShadow { get; init; } = ShadowStyles.Opaque; - /// Gets or sets the default mouse highlight states for buttons. - public MouseState DefaultMouseHighlightStates { get; set; } = MouseState.In | MouseState.Pressed | MouseState.PressedOutside; + /// Gets the default mouse highlight states for buttons. + public MouseState DefaultMouseHighlightStates { get; init; } = MouseState.In | MouseState.Pressed | MouseState.PressedOutside; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static ButtonSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static ButtonSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static ButtonSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static ButtonSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/CharMapSettings.cs b/Terminal.Gui/Configuration/Settings/CharMapSettings.cs index e476205c9e..437b626eae 100644 --- a/Terminal.Gui/Configuration/Settings/CharMapSettings.cs +++ b/Terminal.Gui/Configuration/Settings/CharMapSettings.cs @@ -1,16 +1,22 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for defaults (ThemeScope). +/// Immutable settings record for defaults (ThemeScope). /// -public class CharMapSettings +public sealed record CharMapSettings { - /// Gets or sets the default cursor style for character map views. - public CursorStyle DefaultCursorStyle { get; set; } = CursorStyle.BlinkingBlock; + /// Gets the default cursor style for character map views. + public CursorStyle DefaultCursorStyle { get; init; } = CursorStyle.BlinkingBlock; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static CharMapSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static CharMapSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static CharMapSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static CharMapSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/CheckBoxSettings.cs b/Terminal.Gui/Configuration/Settings/CheckBoxSettings.cs index 408d20ed43..e75b562b94 100644 --- a/Terminal.Gui/Configuration/Settings/CheckBoxSettings.cs +++ b/Terminal.Gui/Configuration/Settings/CheckBoxSettings.cs @@ -1,16 +1,22 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for visual defaults (ThemeScope). +/// Immutable settings record for visual defaults (ThemeScope). /// -public class CheckBoxSettings +public sealed record CheckBoxSettings { - /// Gets or sets the default mouse highlight states for checkboxes. - public MouseState DefaultMouseHighlightStates { get; set; } = MouseState.PressedOutside | MouseState.Pressed | MouseState.In; + /// Gets the default mouse highlight states for checkboxes. + public MouseState DefaultMouseHighlightStates { get; init; } = MouseState.PressedOutside | MouseState.Pressed | MouseState.In; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static CheckBoxSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static CheckBoxSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static CheckBoxSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static CheckBoxSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/DialogSettings.cs b/Terminal.Gui/Configuration/Settings/DialogSettings.cs index 60c01d6aef..82f7d34ac8 100644 --- a/Terminal.Gui/Configuration/Settings/DialogSettings.cs +++ b/Terminal.Gui/Configuration/Settings/DialogSettings.cs @@ -1,25 +1,31 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for visual defaults (ThemeScope). +/// Immutable settings record for visual defaults (ThemeScope). /// -public class DialogSettings +public sealed record DialogSettings { - /// Gets or sets the default shadow style for dialogs. - public ShadowStyles DefaultShadow { get; set; } = ShadowStyles.Transparent; + /// Gets the default shadow style for dialogs. + public ShadowStyles DefaultShadow { get; init; } = ShadowStyles.Transparent; - /// Gets or sets the default border style for dialogs. - public LineStyle DefaultBorderStyle { get; set; } = LineStyle.Heavy; + /// Gets the default border style for dialogs. + public LineStyle DefaultBorderStyle { get; init; } = LineStyle.Heavy; - /// Gets or sets the default button alignment for dialogs. - public Alignment DefaultButtonAlignment { get; set; } = Alignment.End; + /// Gets the default button alignment for dialogs. + public Alignment DefaultButtonAlignment { get; init; } = Alignment.End; - /// Gets or sets the default button alignment modes for dialogs. - public AlignmentModes DefaultButtonAlignmentModes { get; set; } = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems; + /// Gets the default button alignment modes for dialogs. + public AlignmentModes DefaultButtonAlignmentModes { get; init; } = AlignmentModes.StartToEnd | AlignmentModes.AddSpaceBetweenItems; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static DialogSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static DialogSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static DialogSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static DialogSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/FrameViewSettings.cs b/Terminal.Gui/Configuration/Settings/FrameViewSettings.cs index 89748a8fe2..f65bfac8f2 100644 --- a/Terminal.Gui/Configuration/Settings/FrameViewSettings.cs +++ b/Terminal.Gui/Configuration/Settings/FrameViewSettings.cs @@ -1,16 +1,22 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for defaults (ThemeScope). +/// Immutable settings record for defaults (ThemeScope). /// -public class FrameViewSettings +public sealed record FrameViewSettings { - /// Gets or sets the default border style for frame views. - public LineStyle DefaultBorderStyle { get; set; } = LineStyle.Rounded; + /// Gets the default border style for frame views. + public LineStyle DefaultBorderStyle { get; init; } = LineStyle.Rounded; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static FrameViewSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static FrameViewSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static FrameViewSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static FrameViewSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/GlyphSettings.cs b/Terminal.Gui/Configuration/Settings/GlyphSettings.cs index b604e95ea2..6f8b658c6c 100644 --- a/Terminal.Gui/Configuration/Settings/GlyphSettings.cs +++ b/Terminal.Gui/Configuration/Settings/GlyphSettings.cs @@ -3,443 +3,449 @@ namespace Terminal.Gui.Configuration; /// /// Settings POCO for defaults (ThemeScope). /// -public class GlyphSettings +public sealed record GlyphSettings { /// Unicode replacement character; used when a wide glyph can't be output because it would be clipped. - public Rune WideGlyphReplacement { get; set; } = (Rune)' '; + public Rune WideGlyphReplacement { get; init; } = (Rune)' '; /// File icon. - public Rune File { get; set; } = (Rune)'☰'; + public Rune File { get; init; } = (Rune)'☰'; /// Folder icon. - public Rune Folder { get; set; } = (Rune)'꤉'; + public Rune Folder { get; init; } = (Rune)'꤉'; /// Horizontal Ellipsis. - public Rune HorizontalEllipsis { get; set; } = (Rune)'…'; + public Rune HorizontalEllipsis { get; init; } = (Rune)'…'; /// Vertical Four Dots. - public Rune VerticalFourDots { get; set; } = (Rune)'⁞'; + public Rune VerticalFourDots { get; init; } = (Rune)'⁞'; /// Null symbol. - public Rune Null { get; set; } = (Rune)'␀'; + public Rune Null { get; init; } = (Rune)'␀'; /// Checked indicator. - public Rune CheckStateChecked { get; set; } = (Rune)'☑'; + public Rune CheckStateChecked { get; init; } = (Rune)'☒'; /// Not Checked indicator. - public Rune CheckStateUnChecked { get; set; } = (Rune)'☐'; + public Rune CheckStateUnChecked { get; init; } = (Rune)'☐'; /// Null Checked indicator. - public Rune CheckStateNone { get; set; } = (Rune)'⬛'; + public Rune CheckStateNone { get; init; } = (Rune)'□'; /// Selected indicator. - public Rune Selected { get; set; } = (Rune)'◉'; + public Rune Selected { get; init; } = (Rune)'◉'; /// Not Selected indicator. - public Rune UnSelected { get; set; } = (Rune)'○'; + public Rune UnSelected { get; init; } = (Rune)'○'; /// Right arrow. - public Rune RightArrow { get; set; } = (Rune)'►'; + public Rune RightArrow { get; init; } = (Rune)'►'; /// Left arrow. - public Rune LeftArrow { get; set; } = (Rune)'◄'; + public Rune LeftArrow { get; init; } = (Rune)'◄'; /// Down arrow. - public Rune DownArrow { get; set; } = (Rune)'▼'; + public Rune DownArrow { get; init; } = (Rune)'▼'; /// Up arrow. - public Rune UpArrow { get; set; } = (Rune)'▲'; + public Rune UpArrow { get; init; } = (Rune)'▲'; /// Left default indicator. - public Rune LeftDefaultIndicator { get; set; } = (Rune)'►'; + public Rune LeftDefaultIndicator { get; init; } = (Rune)'►'; /// Right default indicator. - public Rune RightDefaultIndicator { get; set; } = (Rune)'◄'; + public Rune RightDefaultIndicator { get; init; } = (Rune)'◄'; /// Left Bracket. - public Rune LeftBracket { get; set; } = (Rune)'⟦'; + public Rune LeftBracket { get; init; } = (Rune)'⟦'; /// Right Bracket. - public Rune RightBracket { get; set; } = (Rune)'⟧'; + public Rune RightBracket { get; init; } = (Rune)'⟧'; /// Half block meter segment. - public Rune BlocksMeterSegment { get; set; } = (Rune)'▌'; + public Rune BlocksMeterSegment { get; init; } = (Rune)'▌'; /// Continuous block meter segment. - public Rune ContinuousMeterSegment { get; set; } = (Rune)'█'; + public Rune ContinuousMeterSegment { get; init; } = (Rune)'█'; /// Stipple pattern. - public Rune Stipple { get; set; } = (Rune)'░'; + public Rune Stipple { get; init; } = (Rune)'░'; /// Diamond. - public Rune Diamond { get; set; } = (Rune)'◊'; + public Rune Diamond { get; init; } = (Rune)'◊'; /// Close. - public Rune Close { get; set; } = (Rune)'✘'; + public Rune Close { get; init; } = (Rune)'✘'; /// Minimize. - public Rune Minimize { get; set; } = (Rune)'❏'; + public Rune Minimize { get; init; } = (Rune)'❏'; /// Maximize. - public Rune Maximize { get; set; } = (Rune)'✽'; + public Rune Maximize { get; init; } = (Rune)'✽'; /// Dot. - public Rune Dot { get; set; } = (Rune)'∙'; + public Rune Dot { get; init; } = (Rune)'∙'; /// Dotted Square. - public Rune DottedSquare { get; set; } = (Rune)'⬚'; + public Rune DottedSquare { get; init; } = (Rune)'⬚'; /// Black Circle. - public Rune BlackCircle { get; set; } = (Rune)'●'; + public Rune BlackCircle { get; init; } = (Rune)'●'; /// Expand. - public Rune Expand { get; set; } = (Rune)'+'; + public Rune Expand { get; init; } = (Rune)'+'; /// Collapse. - public Rune Collapse { get; set; } = (Rune)'-'; + public Rune Collapse { get; init; } = (Rune)'-'; /// Identical To. - public Rune IdenticalTo { get; set; } = (Rune)'≡'; + public Rune IdenticalTo { get; init; } = (Rune)'≡'; /// Move indicator. - public Rune Move { get; set; } = (Rune)'◊'; + public Rune Move { get; init; } = (Rune)'◊'; /// Size Horizontally indicator. - public Rune SizeHorizontal { get; set; } = (Rune)'↔'; + public Rune SizeHorizontal { get; init; } = (Rune)'↔'; /// Size Vertical indicator. - public Rune SizeVertical { get; set; } = (Rune)'↕'; + public Rune SizeVertical { get; init; } = (Rune)'↕'; /// Size Top Left indicator. - public Rune SizeTopLeft { get; set; } = (Rune)'↖'; + public Rune SizeTopLeft { get; init; } = (Rune)'↖'; /// Size Top Right indicator. - public Rune SizeTopRight { get; set; } = (Rune)'↗'; + public Rune SizeTopRight { get; init; } = (Rune)'↗'; /// Size Bottom Right indicator. - public Rune SizeBottomRight { get; set; } = (Rune)'↘'; + public Rune SizeBottomRight { get; init; } = (Rune)'↘'; /// Size Bottom Left indicator. - public Rune SizeBottomLeft { get; set; } = (Rune)'↙'; + public Rune SizeBottomLeft { get; init; } = (Rune)'↙'; /// Apple (non-BMP). - public Rune Apple { get; set; } = "🍎".ToRunes () [0]; + public Rune Apple { get; init; } = "🍎".ToRunes () [0]; /// Apple (BMP). - public Rune AppleBMP { get; set; } = (Rune)'❦'; + public Rune AppleBMP { get; init; } = (Rune)'❦'; /// Copy indicator. - public Rune Copy { get; set; } = (Rune)'⧉'; + public Rune Copy { get; init; } = (Rune)'⧉'; /// Box Drawings Horizontal Line - Light. - public Rune HLine { get; set; } = (Rune)'─'; + public Rune HLine { get; init; } = (Rune)'─'; /// Box Drawings Vertical Line - Light. - public Rune VLine { get; set; } = (Rune)'│'; + public Rune VLine { get; init; } = (Rune)'│'; /// Box Drawings Double Horizontal. - public Rune HLineDbl { get; set; } = (Rune)'═'; + public Rune HLineDbl { get; init; } = (Rune)'═'; /// Box Drawings Double Vertical. - public Rune VLineDbl { get; set; } = (Rune)'║'; + public Rune VLineDbl { get; init; } = (Rune)'║'; /// Box Drawings Heavy Double Dash Horizontal. - public Rune HLineHvDa2 { get; set; } = (Rune)'╍'; + public Rune HLineHvDa2 { get; init; } = (Rune)'╍'; /// Box Drawings Heavy Triple Dash Vertical. - public Rune VLineHvDa3 { get; set; } = (Rune)'┇'; + public Rune VLineHvDa3 { get; init; } = (Rune)'┇'; /// Box Drawings Heavy Triple Dash Horizontal. - public Rune HLineHvDa3 { get; set; } = (Rune)'┅'; + public Rune HLineHvDa3 { get; init; } = (Rune)'┅'; /// Box Drawings Heavy Quadruple Dash Horizontal. - public Rune HLineHvDa4 { get; set; } = (Rune)'┉'; + public Rune HLineHvDa4 { get; init; } = (Rune)'┉'; /// Box Drawings Heavy Double Dash Vertical. - public Rune VLineHvDa2 { get; set; } = (Rune)'╏'; + public Rune VLineHvDa2 { get; init; } = (Rune)'╏'; /// Box Drawings Heavy Quadruple Dash Vertical. - public Rune VLineHvDa4 { get; set; } = (Rune)'┋'; + public Rune VLineHvDa4 { get; init; } = (Rune)'┋'; /// Box Drawings Light Double Dash Horizontal. - public Rune HLineDa2 { get; set; } = (Rune)'╌'; + public Rune HLineDa2 { get; init; } = (Rune)'╌'; /// Box Drawings Light Triple Dash Vertical. - public Rune VLineDa3 { get; set; } = (Rune)'┆'; + public Rune VLineDa3 { get; init; } = (Rune)'┆'; /// Box Drawings Light Triple Dash Horizontal. - public Rune HLineDa3 { get; set; } = (Rune)'┄'; + public Rune HLineDa3 { get; init; } = (Rune)'┄'; /// Box Drawings Light Quadruple Dash Horizontal. - public Rune HLineDa4 { get; set; } = (Rune)'┈'; + public Rune HLineDa4 { get; init; } = (Rune)'┈'; /// Box Drawings Light Double Dash Vertical. - public Rune VLineDa2 { get; set; } = (Rune)'╎'; + public Rune VLineDa2 { get; init; } = (Rune)'╎'; /// Box Drawings Light Quadruple Dash Vertical. - public Rune VLineDa4 { get; set; } = (Rune)'┊'; + public Rune VLineDa4 { get; init; } = (Rune)'┊'; /// Box Drawings Heavy Horizontal. - public Rune HLineHv { get; set; } = (Rune)'━'; + public Rune HLineHv { get; init; } = (Rune)'━'; /// Box Drawings Heavy Vertical. - public Rune VLineHv { get; set; } = (Rune)'┃'; + public Rune VLineHv { get; init; } = (Rune)'┃'; /// Box Drawings Light Left. - public Rune HalfLeftLine { get; set; } = (Rune)'╴'; + public Rune HalfLeftLine { get; init; } = (Rune)'╴'; /// Box Drawings Light Up. - public Rune HalfTopLine { get; set; } = (Rune)'╵'; + public Rune HalfTopLine { get; init; } = (Rune)'╵'; /// Box Drawings Light Right. - public Rune HalfRightLine { get; set; } = (Rune)'╶'; + public Rune HalfRightLine { get; init; } = (Rune)'╶'; /// Box Drawings Light Down. - public Rune HalfBottomLine { get; set; } = (Rune)'╷'; + public Rune HalfBottomLine { get; init; } = (Rune)'╷'; /// Box Drawings Heavy Left. - public Rune HalfLeftLineHv { get; set; } = (Rune)'╸'; + public Rune HalfLeftLineHv { get; init; } = (Rune)'╸'; /// Box Drawings Heavy Up. - public Rune HalfTopLineHv { get; set; } = (Rune)'╹'; + public Rune HalfTopLineHv { get; init; } = (Rune)'╹'; /// Box Drawings Heavy Right. - public Rune HalfRightLineHv { get; set; } = (Rune)'╺'; + public Rune HalfRightLineHv { get; init; } = (Rune)'╺'; /// Box Drawings Light Down Heavy. - public Rune HalfBottomLineLt { get; set; } = (Rune)'╻'; + public Rune HalfBottomLineLt { get; init; } = (Rune)'╻'; /// Box Drawings Light Horizontal and Heavy Horizontal. - public Rune RightSideLineLtHv { get; set; } = (Rune)'╼'; + public Rune RightSideLineLtHv { get; init; } = (Rune)'╼'; /// Box Drawings Light Vertical and Heavy Horizontal. - public Rune BottomSideLineLtHv { get; set; } = (Rune)'╽'; + public Rune BottomSideLineLtHv { get; init; } = (Rune)'╽'; /// Box Drawings Heavy Left and Light Horizontal. - public Rune LeftSideLineHvLt { get; set; } = (Rune)'╾'; + public Rune LeftSideLineHvLt { get; init; } = (Rune)'╾'; /// Box Drawings Heavy Vertical and Light Horizontal. - public Rune TopSideLineHvLt { get; set; } = (Rune)'╿'; + public Rune TopSideLineHvLt { get; init; } = (Rune)'╿'; /// Box Drawings Upper Left Corner - Light. - public Rune ULCorner { get; set; } = (Rune)'┌'; + public Rune ULCorner { get; init; } = (Rune)'┌'; /// Box Drawings Upper Left Corner - Double. - public Rune ULCornerDbl { get; set; } = (Rune)'╔'; + public Rune ULCornerDbl { get; init; } = (Rune)'╔'; /// Box Drawings Upper Left Corner - Rounded. - public Rune ULCornerR { get; set; } = (Rune)'╭'; + public Rune ULCornerR { get; init; } = (Rune)'╭'; /// Box Drawings Upper Left Corner - Heavy. - public Rune ULCornerHv { get; set; } = (Rune)'┏'; + public Rune ULCornerHv { get; init; } = (Rune)'┏'; /// Box Drawings Upper Left Corner - Heavy Vertical Light Horizontal. - public Rune ULCornerHvLt { get; set; } = (Rune)'┎'; + public Rune ULCornerHvLt { get; init; } = (Rune)'┎'; /// Box Drawings Upper Left Corner - Light Vertical Heavy Horizontal. - public Rune ULCornerLtHv { get; set; } = (Rune)'┍'; + public Rune ULCornerLtHv { get; init; } = (Rune)'┍'; /// Box Drawings Upper Left Corner - Double Down Single Horizontal. - public Rune ULCornerDblSingle { get; set; } = (Rune)'╓'; + public Rune ULCornerDblSingle { get; init; } = (Rune)'╓'; /// Box Drawings Upper Left Corner - Single Down Double Horizontal. - public Rune ULCornerSingleDbl { get; set; } = (Rune)'╒'; + public Rune ULCornerSingleDbl { get; init; } = (Rune)'╒'; /// Box Drawings Lower Left Corner - Light. - public Rune LLCorner { get; set; } = (Rune)'└'; + public Rune LLCorner { get; init; } = (Rune)'└'; /// Box Drawings Lower Left Corner - Heavy. - public Rune LLCornerHv { get; set; } = (Rune)'┗'; + public Rune LLCornerHv { get; init; } = (Rune)'┗'; /// Box Drawings Lower Left Corner - Heavy Vertical Light Horizontal. - public Rune LLCornerHvLt { get; set; } = (Rune)'┖'; + public Rune LLCornerHvLt { get; init; } = (Rune)'┖'; /// Box Drawings Lower Left Corner - Light Vertical Heavy Horizontal. - public Rune LLCornerLtHv { get; set; } = (Rune)'┕'; + public Rune LLCornerLtHv { get; init; } = (Rune)'┕'; /// Box Drawings Lower Left Corner - Double. - public Rune LLCornerDbl { get; set; } = (Rune)'╚'; + public Rune LLCornerDbl { get; init; } = (Rune)'╚'; /// Box Drawings Lower Left Corner - Single Vertical Double Horizontal. - public Rune LLCornerSingleDbl { get; set; } = (Rune)'╘'; + public Rune LLCornerSingleDbl { get; init; } = (Rune)'╘'; /// Box Drawings Lower Left Corner - Double Vertical Single Horizontal. - public Rune LLCornerDblSingle { get; set; } = (Rune)'╙'; + public Rune LLCornerDblSingle { get; init; } = (Rune)'╙'; /// Box Drawings Lower Left Corner - Rounded. - public Rune LLCornerR { get; set; } = (Rune)'╰'; + public Rune LLCornerR { get; init; } = (Rune)'╰'; /// Box Drawings Upper Right Corner - Light. - public Rune URCorner { get; set; } = (Rune)'┐'; + public Rune URCorner { get; init; } = (Rune)'┐'; /// Box Drawings Upper Right Corner - Double. - public Rune URCornerDbl { get; set; } = (Rune)'╗'; + public Rune URCornerDbl { get; init; } = (Rune)'╗'; /// Box Drawings Upper Right Corner - Rounded. - public Rune URCornerR { get; set; } = (Rune)'╮'; + public Rune URCornerR { get; init; } = (Rune)'╮'; /// Box Drawings Upper Right Corner - Heavy. - public Rune URCornerHv { get; set; } = (Rune)'┓'; + public Rune URCornerHv { get; init; } = (Rune)'┓'; /// Box Drawings Upper Right Corner - Heavy Vertical Light Horizontal. - public Rune URCornerHvLt { get; set; } = (Rune)'┑'; + public Rune URCornerHvLt { get; init; } = (Rune)'┑'; /// Box Drawings Upper Right Corner - Light Vertical Heavy Horizontal. - public Rune URCornerLtHv { get; set; } = (Rune)'┒'; + public Rune URCornerLtHv { get; init; } = (Rune)'┒'; /// Box Drawings Upper Right Corner - Double Vertical Single Horizontal. - public Rune URCornerDblSingle { get; set; } = (Rune)'╖'; + public Rune URCornerDblSingle { get; init; } = (Rune)'╖'; /// Box Drawings Upper Right Corner - Single Vertical Double Horizontal. - public Rune URCornerSingleDbl { get; set; } = (Rune)'╕'; + public Rune URCornerSingleDbl { get; init; } = (Rune)'╕'; /// Box Drawings Lower Right Corner - Light. - public Rune LRCorner { get; set; } = (Rune)'┘'; + public Rune LRCorner { get; init; } = (Rune)'┘'; /// Box Drawings Lower Right Corner - Double. - public Rune LRCornerDbl { get; set; } = (Rune)'╝'; + public Rune LRCornerDbl { get; init; } = (Rune)'╝'; /// Box Drawings Lower Right Corner - Rounded. - public Rune LRCornerR { get; set; } = (Rune)'╯'; + public Rune LRCornerR { get; init; } = (Rune)'╯'; /// Box Drawings Lower Right Corner - Heavy. - public Rune LRCornerHv { get; set; } = (Rune)'┛'; + public Rune LRCornerHv { get; init; } = (Rune)'┛'; /// Box Drawings Lower Right Corner - Double Vertical Single Horizontal. - public Rune LRCornerDblSingle { get; set; } = (Rune)'╜'; + public Rune LRCornerDblSingle { get; init; } = (Rune)'╜'; /// Box Drawings Lower Right Corner - Single Vertical Double Horizontal. - public Rune LRCornerSingleDbl { get; set; } = (Rune)'╛'; + public Rune LRCornerSingleDbl { get; init; } = (Rune)'╛'; /// Box Drawings Lower Right Corner - Light Vertical Heavy Horizontal. - public Rune LRCornerLtHv { get; set; } = (Rune)'┙'; + public Rune LRCornerLtHv { get; init; } = (Rune)'┙'; /// Box Drawings Lower Right Corner - Heavy Vertical Light Horizontal. - public Rune LRCornerHvLt { get; set; } = (Rune)'┚'; + public Rune LRCornerHvLt { get; init; } = (Rune)'┚'; /// Box Drawings Left Tee - Light. - public Rune LeftTee { get; set; } = (Rune)'├'; + public Rune LeftTee { get; init; } = (Rune)'├'; /// Box Drawings Left Tee - Single Vertical Double Horizontal. - public Rune LeftTeeDblH { get; set; } = (Rune)'╞'; + public Rune LeftTeeDblH { get; init; } = (Rune)'╞'; /// Box Drawings Left Tee - Double Vertical Single Horizontal. - public Rune LeftTeeDblV { get; set; } = (Rune)'╟'; + public Rune LeftTeeDblV { get; init; } = (Rune)'╟'; /// Box Drawings Left Tee - Double. - public Rune LeftTeeDbl { get; set; } = (Rune)'╠'; + public Rune LeftTeeDbl { get; init; } = (Rune)'╠'; /// Box Drawings Left Tee - Heavy Horizontal Light Vertical. - public Rune LeftTeeHvH { get; set; } = (Rune)'┝'; + public Rune LeftTeeHvH { get; init; } = (Rune)'┝'; /// Box Drawings Left Tee - Light Horizontal Heavy Vertical. - public Rune LeftTeeHvV { get; set; } = (Rune)'┠'; + public Rune LeftTeeHvV { get; init; } = (Rune)'┠'; /// Box Drawings Left Tee - Heavy. - public Rune LeftTeeHvDblH { get; set; } = (Rune)'┣'; + public Rune LeftTeeHvDblH { get; init; } = (Rune)'┣'; /// Box Drawings Right Tee - Light. - public Rune RightTee { get; set; } = (Rune)'┤'; + public Rune RightTee { get; init; } = (Rune)'┤'; /// Box Drawings Right Tee - Single Vertical Double Horizontal. - public Rune RightTeeDblH { get; set; } = (Rune)'╡'; + public Rune RightTeeDblH { get; init; } = (Rune)'╡'; /// Box Drawings Right Tee - Double Vertical Single Horizontal. - public Rune RightTeeDblV { get; set; } = (Rune)'╢'; + public Rune RightTeeDblV { get; init; } = (Rune)'╢'; /// Box Drawings Right Tee - Double. - public Rune RightTeeDbl { get; set; } = (Rune)'╣'; + public Rune RightTeeDbl { get; init; } = (Rune)'╣'; /// Box Drawings Right Tee - Heavy Horizontal Light Vertical. - public Rune RightTeeHvH { get; set; } = (Rune)'┥'; + public Rune RightTeeHvH { get; init; } = (Rune)'┥'; /// Box Drawings Right Tee - Light Horizontal Heavy Vertical. - public Rune RightTeeHvV { get; set; } = (Rune)'┨'; + public Rune RightTeeHvV { get; init; } = (Rune)'┨'; /// Box Drawings Right Tee - Heavy. - public Rune RightTeeHvDblH { get; set; } = (Rune)'┫'; + public Rune RightTeeHvDblH { get; init; } = (Rune)'┫'; /// Box Drawings Top Tee - Light. - public Rune TopTee { get; set; } = (Rune)'┬'; + public Rune TopTee { get; init; } = (Rune)'┬'; /// Box Drawings Top Tee - Single Vertical Double Horizontal. - public Rune TopTeeDblH { get; set; } = (Rune)'╤'; + public Rune TopTeeDblH { get; init; } = (Rune)'╤'; /// Box Drawings Top Tee - Double Vertical Single Horizontal. - public Rune TopTeeDblV { get; set; } = (Rune)'╥'; + public Rune TopTeeDblV { get; init; } = (Rune)'╥'; /// Box Drawings Top Tee - Double. - public Rune TopTeeDbl { get; set; } = (Rune)'╦'; + public Rune TopTeeDbl { get; init; } = (Rune)'╦'; /// Box Drawings Top Tee - Heavy Horizontal Light Vertical. - public Rune TopTeeHvH { get; set; } = (Rune)'┯'; + public Rune TopTeeHvH { get; init; } = (Rune)'┯'; /// Box Drawings Top Tee - Light Horizontal Heavy Vertical. - public Rune TopTeeHvV { get; set; } = (Rune)'┰'; + public Rune TopTeeHvV { get; init; } = (Rune)'┰'; /// Box Drawings Top Tee - Heavy. - public Rune TopTeeHvDblH { get; set; } = (Rune)'┳'; + public Rune TopTeeHvDblH { get; init; } = (Rune)'┳'; /// Box Drawings Bottom Tee - Light. - public Rune BottomTee { get; set; } = (Rune)'┴'; + public Rune BottomTee { get; init; } = (Rune)'┴'; /// Box Drawings Bottom Tee - Single Vertical Double Horizontal. - public Rune BottomTeeDblH { get; set; } = (Rune)'╧'; + public Rune BottomTeeDblH { get; init; } = (Rune)'╧'; /// Box Drawings Bottom Tee - Double Vertical Single Horizontal. - public Rune BottomTeeDblV { get; set; } = (Rune)'╨'; + public Rune BottomTeeDblV { get; init; } = (Rune)'╨'; /// Box Drawings Bottom Tee - Double. - public Rune BottomTeeDbl { get; set; } = (Rune)'╩'; + public Rune BottomTeeDbl { get; init; } = (Rune)'╩'; /// Box Drawings Bottom Tee - Heavy Horizontal Light Vertical. - public Rune BottomTeeHvH { get; set; } = (Rune)'┷'; + public Rune BottomTeeHvH { get; init; } = (Rune)'┷'; /// Box Drawings Bottom Tee - Light Horizontal Heavy Vertical. - public Rune BottomTeeHvV { get; set; } = (Rune)'┸'; + public Rune BottomTeeHvV { get; init; } = (Rune)'┸'; /// Box Drawings Bottom Tee - Heavy. - public Rune BottomTeeHvDblH { get; set; } = (Rune)'┻'; + public Rune BottomTeeHvDblH { get; init; } = (Rune)'┻'; /// Box Drawings Cross - Light. - public Rune Cross { get; set; } = (Rune)'┼'; + public Rune Cross { get; init; } = (Rune)'┼'; /// Box Drawings Cross - Single Vertical Double Horizontal. - public Rune CrossDblH { get; set; } = (Rune)'╪'; + public Rune CrossDblH { get; init; } = (Rune)'╪'; /// Box Drawings Cross - Double Vertical Single Horizontal. - public Rune CrossDblV { get; set; } = (Rune)'╫'; + public Rune CrossDblV { get; init; } = (Rune)'╫'; /// Box Drawings Cross - Double. - public Rune CrossDbl { get; set; } = (Rune)'╬'; + public Rune CrossDbl { get; init; } = (Rune)'╬'; /// Box Drawings Cross - Heavy Horizontal Light Vertical. - public Rune CrossHvH { get; set; } = (Rune)'┿'; + public Rune CrossHvH { get; init; } = (Rune)'┿'; /// Box Drawings Cross - Light Horizontal Heavy Vertical. - public Rune CrossHvV { get; set; } = (Rune)'╂'; + public Rune CrossHvV { get; init; } = (Rune)'╂'; /// Box Drawings Cross - Heavy. - public Rune CrossHv { get; set; } = (Rune)'╋'; + public Rune CrossHv { get; init; } = (Rune)'╋'; /// Shadow - Vertical Start. - public Rune ShadowVerticalStart { get; set; } = (Rune)'▖'; + public Rune ShadowVerticalStart { get; init; } = (Rune)'▖'; /// Shadow - Vertical. - public Rune ShadowVertical { get; set; } = (Rune)'▌'; + public Rune ShadowVertical { get; init; } = (Rune)'▌'; /// Shadow - Horizontal Start. - public Rune ShadowHorizontalStart { get; set; } = (Rune)'▝'; + public Rune ShadowHorizontalStart { get; init; } = (Rune)'▝'; /// Shadow - Horizontal. - public Rune ShadowHorizontal { get; set; } = (Rune)'▀'; + public Rune ShadowHorizontal { get; init; } = (Rune)'▀'; /// Shadow - Horizontal End. - public Rune ShadowHorizontalEnd { get; set; } = (Rune)'▘'; + public Rune ShadowHorizontalEnd { get; init; } = (Rune)'▘'; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static GlyphSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static GlyphSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static GlyphSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static GlyphSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/HexViewSettings.cs b/Terminal.Gui/Configuration/Settings/HexViewSettings.cs index 10c7721a3e..de11f4a0bf 100644 --- a/Terminal.Gui/Configuration/Settings/HexViewSettings.cs +++ b/Terminal.Gui/Configuration/Settings/HexViewSettings.cs @@ -1,16 +1,22 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for defaults (ThemeScope). +/// Immutable settings record for defaults (ThemeScope). /// -public class HexViewSettings +public sealed record HexViewSettings { - /// Gets or sets the default cursor style for hex views. - public CursorStyle DefaultCursorStyle { get; set; } = CursorStyle.BlinkingBlock; + /// Gets the default cursor style for hex views. + public CursorStyle DefaultCursorStyle { get; init; } = CursorStyle.BlinkingBlock; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static HexViewSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static HexViewSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static HexViewSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static HexViewSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/LinearRangeSettings.cs b/Terminal.Gui/Configuration/Settings/LinearRangeSettings.cs index 5c155dc3f9..a0233914d7 100644 --- a/Terminal.Gui/Configuration/Settings/LinearRangeSettings.cs +++ b/Terminal.Gui/Configuration/Settings/LinearRangeSettings.cs @@ -1,16 +1,22 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for defaults (ThemeScope). +/// Immutable settings record for defaults (ThemeScope). /// -public class LinearRangeSettings +public sealed record LinearRangeSettings { - /// Gets or sets the default cursor style for linear range views. - public CursorStyle DefaultCursorStyle { get; set; } = CursorStyle.BlinkingBlock; + /// Gets the default cursor style for linear range views. + public CursorStyle DefaultCursorStyle { get; init; } = CursorStyle.BlinkingBlock; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static LinearRangeSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static LinearRangeSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static LinearRangeSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static LinearRangeSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/MenuBarSettings.cs b/Terminal.Gui/Configuration/Settings/MenuBarSettings.cs index 342497936a..143778d81c 100644 --- a/Terminal.Gui/Configuration/Settings/MenuBarSettings.cs +++ b/Terminal.Gui/Configuration/Settings/MenuBarSettings.cs @@ -1,19 +1,25 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for defaults. +/// Immutable settings record for defaults. /// -public class MenuBarSettings +public sealed record MenuBarSettings { - /// Gets or sets the default border style for menu bars. - public LineStyle DefaultBorderStyle { get; set; } = LineStyle.None; + /// Gets the default border style for menu bars. + public LineStyle DefaultBorderStyle { get; init; } = LineStyle.None; - /// Gets or sets the default activation key for menu bars. - public Key DefaultKey { get; set; } = Key.F10; + /// Gets the default activation key for menu bars. + public Key DefaultKey { get; init; } = Key.F10; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static MenuBarSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static MenuBarSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static MenuBarSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static MenuBarSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/MenuSettings.cs b/Terminal.Gui/Configuration/Settings/MenuSettings.cs index 65fdc8f7ec..334961e3c5 100644 --- a/Terminal.Gui/Configuration/Settings/MenuSettings.cs +++ b/Terminal.Gui/Configuration/Settings/MenuSettings.cs @@ -1,16 +1,22 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for defaults (ThemeScope). +/// Immutable settings record for defaults (ThemeScope). /// -public class MenuSettings +public sealed record MenuSettings { - /// Gets or sets the default border style for menus. - public LineStyle DefaultBorderStyle { get; set; } = LineStyle.None; + /// Gets the default border style for menus. + public LineStyle DefaultBorderStyle { get; init; } = LineStyle.None; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static MenuSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static MenuSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static MenuSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static MenuSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/MessageBoxSettings.cs b/Terminal.Gui/Configuration/Settings/MessageBoxSettings.cs index 4e5ff7af13..2f7f0f67c4 100644 --- a/Terminal.Gui/Configuration/Settings/MessageBoxSettings.cs +++ b/Terminal.Gui/Configuration/Settings/MessageBoxSettings.cs @@ -1,19 +1,25 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for visual defaults (ThemeScope). +/// Immutable settings record for visual defaults (ThemeScope). /// -public class MessageBoxSettings +public sealed record MessageBoxSettings { - /// Gets or sets the default border style for message boxes. - public LineStyle DefaultBorderStyle { get; set; } = LineStyle.Heavy; + /// Gets the default border style for message boxes. + public LineStyle DefaultBorderStyle { get; init; } = LineStyle.Heavy; - /// Gets or sets the default button alignment for message boxes. - public Alignment DefaultButtonAlignment { get; set; } = Alignment.Center; + /// Gets the default button alignment for message boxes. + public Alignment DefaultButtonAlignment { get; init; } = Alignment.Center; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static MessageBoxSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static MessageBoxSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static MessageBoxSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static MessageBoxSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/NerdFontsSettings.cs b/Terminal.Gui/Configuration/Settings/NerdFontsSettings.cs index b5a4a287c3..776f1759e7 100644 --- a/Terminal.Gui/Configuration/Settings/NerdFontsSettings.cs +++ b/Terminal.Gui/Configuration/Settings/NerdFontsSettings.cs @@ -1,16 +1,22 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for defaults (ThemeScope). +/// Immutable settings record for defaults (ThemeScope). /// -public class NerdFontsSettings +public sealed record NerdFontsSettings { - /// Gets or sets whether Nerd Fonts glyphs are enabled. - public bool Enable { get; set; } = false; + /// Gets whether Nerd Fonts glyphs are enabled. + public bool Enable { get; init; } = false; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static NerdFontsSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static NerdFontsSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static NerdFontsSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static NerdFontsSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/PopoverMenuSettings.cs b/Terminal.Gui/Configuration/Settings/PopoverMenuSettings.cs index 6cb41b741d..69ff95fba0 100644 --- a/Terminal.Gui/Configuration/Settings/PopoverMenuSettings.cs +++ b/Terminal.Gui/Configuration/Settings/PopoverMenuSettings.cs @@ -1,16 +1,22 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for defaults (SettingsScope). +/// Immutable settings record for defaults (SettingsScope). /// -public class PopoverMenuSettings +public sealed record PopoverMenuSettings { - /// Gets or sets the default activation key for popover menus. - public Key DefaultKey { get; set; } = Key.F10.WithShift; + /// Gets the default activation key for popover menus. + public Key DefaultKey { get; init; } = Key.F10.WithShift; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static PopoverMenuSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static PopoverMenuSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static PopoverMenuSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static PopoverMenuSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/SelectorBaseSettings.cs b/Terminal.Gui/Configuration/Settings/SelectorBaseSettings.cs index 9c00011337..18fade844e 100644 --- a/Terminal.Gui/Configuration/Settings/SelectorBaseSettings.cs +++ b/Terminal.Gui/Configuration/Settings/SelectorBaseSettings.cs @@ -1,16 +1,22 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for defaults (ThemeScope). +/// Immutable settings record for defaults (ThemeScope). /// -public class SelectorBaseSettings +public sealed record SelectorBaseSettings { - /// Gets or sets the default mouse highlight states for selectors. - public MouseState DefaultMouseHighlightStates { get; set; } = MouseState.In; + /// Gets the default mouse highlight states for selectors. + public MouseState DefaultMouseHighlightStates { get; init; } = MouseState.In; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static SelectorBaseSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static SelectorBaseSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static SelectorBaseSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static SelectorBaseSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/StatusBarSettings.cs b/Terminal.Gui/Configuration/Settings/StatusBarSettings.cs index 51932645ce..609f55b3bd 100644 --- a/Terminal.Gui/Configuration/Settings/StatusBarSettings.cs +++ b/Terminal.Gui/Configuration/Settings/StatusBarSettings.cs @@ -1,16 +1,22 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for defaults (ThemeScope). +/// Immutable settings record for defaults (ThemeScope). /// -public class StatusBarSettings +public sealed record StatusBarSettings { - /// Gets or sets the default separator line style for status bars. - public LineStyle DefaultSeparatorLineStyle { get; set; } = LineStyle.Single; + /// Gets the default separator line style for status bars. + public LineStyle DefaultSeparatorLineStyle { get; init; } = LineStyle.Single; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static StatusBarSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static StatusBarSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static StatusBarSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static StatusBarSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/TextFieldSettings.cs b/Terminal.Gui/Configuration/Settings/TextFieldSettings.cs index c0caf63bf7..b16955d7a7 100644 --- a/Terminal.Gui/Configuration/Settings/TextFieldSettings.cs +++ b/Terminal.Gui/Configuration/Settings/TextFieldSettings.cs @@ -1,16 +1,22 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for defaults (ThemeScope). +/// Immutable settings record for defaults (ThemeScope). /// -public class TextFieldSettings +public sealed record TextFieldSettings { - /// Gets or sets the default cursor style for text fields. - public CursorStyle DefaultCursorStyle { get; set; } = CursorStyle.BlinkingBar; + /// Gets the default cursor style for text fields. + public CursorStyle DefaultCursorStyle { get; init; } = CursorStyle.BlinkingBar; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static TextFieldSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static TextFieldSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static TextFieldSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static TextFieldSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/TextViewSettings.cs b/Terminal.Gui/Configuration/Settings/TextViewSettings.cs index 5505dedb2b..5b19d26845 100644 --- a/Terminal.Gui/Configuration/Settings/TextViewSettings.cs +++ b/Terminal.Gui/Configuration/Settings/TextViewSettings.cs @@ -1,16 +1,22 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for defaults (ThemeScope). +/// Immutable settings record for defaults (ThemeScope). /// -public class TextViewSettings +public sealed record TextViewSettings { - /// Gets or sets the default cursor style for text views. - public CursorStyle DefaultCursorStyle { get; set; } = CursorStyle.BlinkingBar; + /// Gets the default cursor style for text views. + public CursorStyle DefaultCursorStyle { get; init; } = CursorStyle.BlinkingBar; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static TextViewSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static TextViewSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static TextViewSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static TextViewSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/Settings/ThemeDefinition.cs b/Terminal.Gui/Configuration/Settings/ThemeDefinition.cs new file mode 100644 index 0000000000..8530b11b90 --- /dev/null +++ b/Terminal.Gui/Configuration/Settings/ThemeDefinition.cs @@ -0,0 +1,100 @@ +using Terminal.Gui.Drawing; + +namespace Terminal.Gui.Configuration; + +/// +/// POCO that represents a single named theme in the MEC-bound configuration tree. +/// +/// +/// +/// A contains an optional dictionary of s plus an optional +/// per-component override for each of the 18 ThemeScope-flavored settings POCOs bound by +/// . +/// +/// +/// Null = no theme-level override. Each override property is nullable. When a property is , +/// the theme does not contribute a value for that component and the root-level *Settings section continues to +/// supply the effective default. When a property is non-, the theme contributes a +/// fully-populated replacement POCO; how the consumer combines it with the root defaults +/// (wholesale-replace vs. property-level merge) is a manager-rewire concern and is not encoded here. +/// +/// +/// Why nullable subsections (not "missing dictionary entry" or "explicit empty object"): using nullability on +/// strongly-typed properties keeps the binder honest — MEC populates a property iff the JSON section is present, and +/// a consumer can ask theme.Button is null without reflecting over a generic bag. A "missing entry in +/// dictionary" alternative would force a stringly-typed lookup; an "explicit empty object" alternative would make +/// "I appear in JSON but override nothing" indistinguishable from "I appear in JSON to override defaults to their +/// own values" — both ambiguities are avoided by nullability. +/// +/// +/// This type is the bind target for the Themes section of config.json after the Phase D rewrite. No +/// production code consumes it yet; the consumer (a rewired MecThemeManager reading via +/// IOptionsMonitor<ThemeSettings>) lands in a subsequent commit. This type ships with binding tests +/// only; reviewers can object to specific subsections without reading manager code that does not yet exist. +/// +/// +public class ThemeDefinition +{ + /// + /// Gets or sets the dictionary of named s contributed by this theme. + /// means the theme contributes no schemes. + /// + public Dictionary? Schemes { get; set; } + + /// Per-theme override for . = no override. + public ButtonSettings? Button { get; set; } + + /// Per-theme override for . = no override. + public CheckBoxSettings? CheckBox { get; set; } + + /// Per-theme override for . = no override. + public CharMapSettings? CharMap { get; set; } + + /// Per-theme override for . = no override. + public DialogSettings? Dialog { get; set; } + + /// Per-theme override for . = no override. + public FrameViewSettings? FrameView { get; set; } + + /// Per-theme override for . = no override. + public HexViewSettings? HexView { get; set; } + + /// Per-theme override for . = no override. + public LinearRangeSettings? LinearRange { get; set; } + + /// Per-theme override for . = no override. + public MenuBarSettings? MenuBar { get; set; } + + /// Per-theme override for . = no override. + public MenuSettings? Menu { get; set; } + + /// Per-theme override for . = no override. + public MessageBoxSettings? MessageBox { get; set; } + + /// Per-theme override for . = no override. + public NerdFontsSettings? NerdFonts { get; set; } + + /// Per-theme override for . = no override. + public PopoverMenuSettings? PopoverMenu { get; set; } + + /// Per-theme override for . = no override. + public SelectorBaseSettings? SelectorBase { get; set; } + + /// Per-theme override for . = no override. + public StatusBarSettings? StatusBar { get; set; } + + /// Per-theme override for . = no override. + public TextFieldSettings? TextField { get; set; } + + /// Per-theme override for . = no override. + public TextViewSettings? TextView { get; set; } + + /// Per-theme override for . = no override. + public WindowSettings? Window { get; set; } + + /// + /// Per-theme override for . = no override. + /// Section name in JSON is Glyphs (matching ). + /// + public GlyphSettings? Glyphs { get; set; } +} diff --git a/Terminal.Gui/Configuration/Settings/TuiConfigurationBuilder.cs b/Terminal.Gui/Configuration/Settings/TuiConfigurationBuilder.cs index 831b5e3d9d..6e1c1a7297 100644 --- a/Terminal.Gui/Configuration/Settings/TuiConfigurationBuilder.cs +++ b/Terminal.Gui/Configuration/Settings/TuiConfigurationBuilder.cs @@ -123,25 +123,27 @@ public void ApplyToStaticFacades () BindSection (config, "Key", s => KeySettings.Defaults = s); BindSection (config, "Trace", s => TraceSettings.Defaults = s); - // ThemeScope POCOs - BindSection (config, "Button", s => ButtonSettings.Defaults = s); - BindSection (config, "CheckBox", s => CheckBoxSettings.Defaults = s); - BindSection (config, "CharMap", s => CharMapSettings.Defaults = s); - BindSection (config, "Dialog", s => DialogSettings.Defaults = s); - BindSection (config, "FrameView", s => FrameViewSettings.Defaults = s); - BindSection (config, "HexView", s => HexViewSettings.Defaults = s); - BindSection (config, "LinearRange", s => LinearRangeSettings.Defaults = s); - BindSection (config, "MenuBar", s => MenuBarSettings.Defaults = s); - BindSection (config, "Menu", s => MenuSettings.Defaults = s); - BindSection (config, "MessageBox", s => MessageBoxSettings.Defaults = s); - BindSection (config, "NerdFonts", s => NerdFontsSettings.Defaults = s); - BindSection (config, "PopoverMenu", s => PopoverMenuSettings.Defaults = s); - BindSection (config, "SelectorBase", s => SelectorBaseSettings.Defaults = s); - BindSection (config, "StatusBar", s => StatusBarSettings.Defaults = s); - BindSection (config, "TextField", s => TextFieldSettings.Defaults = s); - BindSection (config, "TextView", s => TextViewSettings.Defaults = s); - BindSection (config, "Window", s => WindowSettings.Defaults = s); - BindSection (config, "Glyphs", s => GlyphSettings.Defaults = s); + // ThemeScope POCOs: two-pass overlay (root section + Themes::
) writes Current. + // TODO(A2): when ThemeSettings converts to record + Current, this becomes an immutable snapshot. + string activeTheme = ThemeSettings.Defaults.Theme; + BindThemeScope (config, "Button", activeTheme, s => ButtonSettings.Current = s); + BindThemeScope (config, "CheckBox", activeTheme, s => CheckBoxSettings.Current = s); + BindThemeScope (config, "CharMap", activeTheme, s => CharMapSettings.Current = s); + BindThemeScope (config, "Dialog", activeTheme, s => DialogSettings.Current = s); + BindThemeScope (config, "FrameView", activeTheme, s => FrameViewSettings.Current = s); + BindThemeScope (config, "HexView", activeTheme, s => HexViewSettings.Current = s); + BindThemeScope (config, "LinearRange", activeTheme, s => LinearRangeSettings.Current = s); + BindThemeScope (config, "MenuBar", activeTheme, s => MenuBarSettings.Current = s); + BindThemeScope (config, "Menu", activeTheme, s => MenuSettings.Current = s); + BindThemeScope (config, "MessageBox", activeTheme, s => MessageBoxSettings.Current = s); + BindThemeScope (config, "NerdFonts", activeTheme, s => NerdFontsSettings.Current = s); + BindThemeScope (config, "PopoverMenu", activeTheme, s => PopoverMenuSettings.Current = s); + BindThemeScope (config, "SelectorBase", activeTheme, s => SelectorBaseSettings.Current = s); + BindThemeScope (config, "StatusBar", activeTheme, s => StatusBarSettings.Current = s); + BindThemeScope (config, "TextField", activeTheme, s => TextFieldSettings.Current = s); + BindThemeScope (config, "TextView", activeTheme, s => TextViewSettings.Current = s); + BindThemeScope (config, "Window", activeTheme, s => WindowSettings.Current = s); + BindThemeScope (config, "Glyphs", activeTheme, s => GlyphSettings.Current = s); } /// @@ -171,4 +173,19 @@ public void ApplyToStaticFacades () config.GetSection (sectionName).Bind (settings); apply (settings); } + + /// + /// Two-pass overlay bind for ThemeScope POCOs. Binds the root section, then overlays + /// Themes::. Properties not present in the + /// overlay JSON retain the root value (property-level merge — matches legacy CM Scope.Apply semantics). + /// + [UnconditionalSuppressMessage ("Trimming", "IL2026", Justification = "Settings POCOs are simple types preserved by DynamicDependency in ConfigPropertyHostTypes.")] + [UnconditionalSuppressMessage ("AOT", "IL3050", Justification = "Settings POCOs are simple types; no generic instantiation needed at runtime.")] + private static void BindThemeScope (IConfiguration config, string sectionName, string activeTheme, Action apply) where T : new () + { + T settings = new (); + config.GetSection (sectionName).Bind (settings); + config.GetSection ($"Themes:{activeTheme}:{sectionName}").Bind (settings); + apply (settings); + } } diff --git a/Terminal.Gui/Configuration/Settings/WindowSettings.cs b/Terminal.Gui/Configuration/Settings/WindowSettings.cs index 13ace188ff..4a424290f7 100644 --- a/Terminal.Gui/Configuration/Settings/WindowSettings.cs +++ b/Terminal.Gui/Configuration/Settings/WindowSettings.cs @@ -1,19 +1,25 @@ namespace Terminal.Gui.Configuration; /// -/// Settings POCO for visual defaults (ThemeScope). +/// Immutable settings record for visual defaults (ThemeScope). /// -public class WindowSettings +public sealed record WindowSettings { - /// Gets or sets the default shadow style for windows. - public ShadowStyles DefaultShadow { get; set; } = ShadowStyles.None; + /// Gets the default shadow style for windows. + public ShadowStyles DefaultShadow { get; init; } = ShadowStyles.None; - /// Gets or sets the default border style for windows. - public LineStyle DefaultBorderStyle { get; set; } = LineStyle.Single; + /// Gets the default border style for windows. + public LineStyle DefaultBorderStyle { get; init; } = LineStyle.Single; - /// - /// The static facade instance. Always contains the current effective values. - /// Updated by the MEC binding at initialization. - /// - public static WindowSettings Defaults { get; set; } = new (); + /// The compile-time-known defaults. + public static WindowSettings Default { get; } = new (); + + /// The currently effective values, updated atomically by . + public static WindowSettings Current + { + get => Volatile.Read (ref _current); + internal set => Volatile.Write (ref _current, value); + } + + private static WindowSettings _current = Default; } diff --git a/Terminal.Gui/Configuration/SourceGenerationContext.cs b/Terminal.Gui/Configuration/SourceGenerationContext.cs index 800dc4dad7..d7e734b420 100644 --- a/Terminal.Gui/Configuration/SourceGenerationContext.cs +++ b/Terminal.Gui/Configuration/SourceGenerationContext.cs @@ -23,7 +23,6 @@ namespace Terminal.Gui.Configuration; [JsonSerializable (typeof (Color))] [JsonSerializable (typeof (Key))] [JsonSerializable (typeof (Key []))] -[JsonSerializable (typeof (Glyphs))] [JsonSerializable (typeof (Alignment))] [JsonSerializable (typeof (AlignmentModes))] [JsonSerializable (typeof (LineStyle))] @@ -48,6 +47,8 @@ namespace Terminal.Gui.Configuration; [JsonSerializable (typeof (ConcurrentDictionary))] [JsonSerializable (typeof (Scheme))] [JsonSerializable (typeof (Dictionary))] +[JsonSerializable (typeof (ThemeDefinition))] +[JsonSerializable (typeof (Dictionary))] [JsonSerializable (typeof (TraceCategory))] [JsonSerializable (typeof (SizeDetectionMode))] diff --git a/Terminal.Gui/Drawing/Glyphs.cs b/Terminal.Gui/Drawing/Glyphs.cs index d39e7fde09..4f39bf7178 100644 --- a/Terminal.Gui/Drawing/Glyphs.cs +++ b/Terminal.Gui/Drawing/Glyphs.cs @@ -21,723 +21,259 @@ namespace Terminal.Gui.Drawing; /// public class Glyphs { - // IMPORTANT: If you change these, make sure to update the ./Resources/config.json file as - // IMPORTANT: it is the source of truth for the default glyphs at runtime. - // IMPORTANT: Configuration Manager test SaveDefaults uses this class to generate the default config file - // IMPORTANT: in ./UnitTests/bin/Debug/netX.0/config.json + // The default glyph values live on the GlyphSettings record's `init` defaults. + // Resources/config.json is the source of truth for the runtime glyphs; the embedded + // config is loaded and applied via TuiConfigurationBuilder.ApplyToStaticFacades, with + // theme overlays composed under "Themes::Glyphs". /// Unicode replacement character; used by Drivers when rendering in cases where a wide glyph can't /// be output because it would be clipped. Defaults to ' ' (Space). - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune WideGlyphReplacement - { - get => GlyphSettings.Defaults.WideGlyphReplacement; - set => GlyphSettings.Defaults.WideGlyphReplacement = value; - } + public static Rune WideGlyphReplacement => GlyphSettings.Current.WideGlyphReplacement; /// File icon. Defaults to ☰ (Trigram For Heaven) - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune File - { - get => GlyphSettings.Defaults.File; - set => GlyphSettings.Defaults.File = value; - } + public static Rune File => GlyphSettings.Current.File; /// Folder icon. Defaults to ꤉ (Kayah Li Digit Nine) - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Folder - { - get => GlyphSettings.Defaults.Folder; - set => GlyphSettings.Defaults.Folder = value; - } + public static Rune Folder => GlyphSettings.Current.Folder; /// Horizontal Ellipsis - … U+2026 - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HorizontalEllipsis - { - get => GlyphSettings.Defaults.HorizontalEllipsis; - set => GlyphSettings.Defaults.HorizontalEllipsis = value; - } + public static Rune HorizontalEllipsis => GlyphSettings.Current.HorizontalEllipsis; /// Vertical Four Dots - ⁞ U+205e - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune VerticalFourDots - { - get => GlyphSettings.Defaults.VerticalFourDots; - set => GlyphSettings.Defaults.VerticalFourDots = value; - } + public static Rune VerticalFourDots => GlyphSettings.Current.VerticalFourDots; #region ----------------- Single Glyphs ----------------- /// Null symbol ('␀') - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Null - { - get => GlyphSettings.Defaults.Null; - set => GlyphSettings.Defaults.Null = value; - } + public static Rune Null => GlyphSettings.Current.Null; /// Checked indicator (e.g. for and ). - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune CheckStateChecked - { - get => GlyphSettings.Defaults.CheckStateChecked; - set => GlyphSettings.Defaults.CheckStateChecked = value; - } + public static Rune CheckStateChecked => GlyphSettings.Current.CheckStateChecked; /// Not Checked indicator (e.g. for and ). - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune CheckStateUnChecked - { - get => GlyphSettings.Defaults.CheckStateUnChecked; - set => GlyphSettings.Defaults.CheckStateUnChecked = value; - } + public static Rune CheckStateUnChecked => GlyphSettings.Current.CheckStateUnChecked; /// Null Checked indicator (e.g. for and ). - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune CheckStateNone - { - get => GlyphSettings.Defaults.CheckStateNone; - set => GlyphSettings.Defaults.CheckStateNone = value; - } + public static Rune CheckStateNone => GlyphSettings.Current.CheckStateNone; /// Selected indicator (e.g. for and ). - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Selected - { - get => GlyphSettings.Defaults.Selected; - set => GlyphSettings.Defaults.Selected = value; - } + public static Rune Selected => GlyphSettings.Current.Selected; /// Not Selected indicator (e.g. for and ). - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune UnSelected - { - get => GlyphSettings.Defaults.UnSelected; - set => GlyphSettings.Defaults.UnSelected = value; - } + public static Rune UnSelected => GlyphSettings.Current.UnSelected; /// Horizontal arrow. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune RightArrow - { - get => GlyphSettings.Defaults.RightArrow; - set => GlyphSettings.Defaults.RightArrow = value; - } + public static Rune RightArrow => GlyphSettings.Current.RightArrow; /// Left arrow. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LeftArrow - { - get => GlyphSettings.Defaults.LeftArrow; - set => GlyphSettings.Defaults.LeftArrow = value; - } + public static Rune LeftArrow => GlyphSettings.Current.LeftArrow; /// Down arrow. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune DownArrow - { - get => GlyphSettings.Defaults.DownArrow; - set => GlyphSettings.Defaults.DownArrow = value; - } + public static Rune DownArrow => GlyphSettings.Current.DownArrow; /// Vertical arrow. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune UpArrow - { - get => GlyphSettings.Defaults.UpArrow; - set => GlyphSettings.Defaults.UpArrow = value; - } + public static Rune UpArrow => GlyphSettings.Current.UpArrow; /// Left default indicator (e.g. for . - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LeftDefaultIndicator - { - get => GlyphSettings.Defaults.LeftDefaultIndicator; - set => GlyphSettings.Defaults.LeftDefaultIndicator = value; - } + public static Rune LeftDefaultIndicator => GlyphSettings.Current.LeftDefaultIndicator; /// Horizontal default indicator (e.g. for . - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune RightDefaultIndicator - { - get => GlyphSettings.Defaults.RightDefaultIndicator; - set => GlyphSettings.Defaults.RightDefaultIndicator = value; - } + public static Rune RightDefaultIndicator => GlyphSettings.Current.RightDefaultIndicator; /// Left Bracket (e.g. for . Default is (U+005B) - [. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LeftBracket - { - get => GlyphSettings.Defaults.LeftBracket; - set => GlyphSettings.Defaults.LeftBracket = value; - } + public static Rune LeftBracket => GlyphSettings.Current.LeftBracket; /// Horizontal Bracket (e.g. for . Default is (U+005D) - ]. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune RightBracket - { - get => GlyphSettings.Defaults.RightBracket; - set => GlyphSettings.Defaults.RightBracket = value; - } + public static Rune RightBracket => GlyphSettings.Current.RightBracket; /// Half block meter segment (e.g. for ). - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune BlocksMeterSegment - { - get => GlyphSettings.Defaults.BlocksMeterSegment; - set => GlyphSettings.Defaults.BlocksMeterSegment = value; - } + public static Rune BlocksMeterSegment => GlyphSettings.Current.BlocksMeterSegment; /// Continuous block meter segment (e.g. for ). - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ContinuousMeterSegment - { - get => GlyphSettings.Defaults.ContinuousMeterSegment; - set => GlyphSettings.Defaults.ContinuousMeterSegment = value; - } + public static Rune ContinuousMeterSegment => GlyphSettings.Current.ContinuousMeterSegment; /// Stipple pattern (e.g. for ). Default is Light Shade (U+2591) - â–‘. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Stipple - { - get => GlyphSettings.Defaults.Stipple; - set => GlyphSettings.Defaults.Stipple = value; - } + public static Rune Stipple => GlyphSettings.Current.Stipple; /// Diamond. Default is Lozenge (U+25CA) - â—Š. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Diamond - { - get => GlyphSettings.Defaults.Diamond; - set => GlyphSettings.Defaults.Diamond = value; - } + public static Rune Diamond => GlyphSettings.Current.Diamond; /// Close. Default is Heavy Ballot X (U+2718) - ✘. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Close - { - get => GlyphSettings.Defaults.Close; - set => GlyphSettings.Defaults.Close = value; - } + public static Rune Close => GlyphSettings.Current.Close; /// Minimize. Default is Lower Horizontal Shadowed White Circle (U+274F) - ❏. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Minimize - { - get => GlyphSettings.Defaults.Minimize; - set => GlyphSettings.Defaults.Minimize = value; - } + public static Rune Minimize => GlyphSettings.Current.Minimize; /// Maximize. Default is Upper Horizontal Shadowed White Circle (U+273D) - ✽. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Maximize - { - get => GlyphSettings.Defaults.Maximize; - set => GlyphSettings.Defaults.Maximize = value; - } + public static Rune Maximize => GlyphSettings.Current.Maximize; /// Dot. Default is (U+2219) - ∙. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Dot - { - get => GlyphSettings.Defaults.Dot; - set => GlyphSettings.Defaults.Dot = value; - } + public static Rune Dot => GlyphSettings.Current.Dot; /// Dotted Square - ⬚ U+02b1a┝ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune DottedSquare - { - get => GlyphSettings.Defaults.DottedSquare; - set => GlyphSettings.Defaults.DottedSquare = value; - } + public static Rune DottedSquare => GlyphSettings.Current.DottedSquare; /// Black Circle . Default is (U+025cf) - ●. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune BlackCircle // Black Circle - ● U+025cf - { - get => GlyphSettings.Defaults.BlackCircle; - set => GlyphSettings.Defaults.BlackCircle = value; - } + public static Rune BlackCircle => GlyphSettings.Current.BlackCircle; /// Expand (e.g. for . - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Expand - { - get => GlyphSettings.Defaults.Expand; - set => GlyphSettings.Defaults.Expand = value; - } + public static Rune Expand => GlyphSettings.Current.Expand; /// Expand (e.g. for . - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Collapse - { - get => GlyphSettings.Defaults.Collapse; - set => GlyphSettings.Defaults.Collapse = value; - } + public static Rune Collapse => GlyphSettings.Current.Collapse; /// Identical To (U+226) - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune IdenticalTo - { - get => GlyphSettings.Defaults.IdenticalTo; - set => GlyphSettings.Defaults.IdenticalTo = value; - } + public static Rune IdenticalTo => GlyphSettings.Current.IdenticalTo; /// Move indicator. Default is Lozenge (U+25CA) - â—Š. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Move - { - get => GlyphSettings.Defaults.Move; - set => GlyphSettings.Defaults.Move = value; - } + public static Rune Move => GlyphSettings.Current.Move; /// Size Horizontally indicator. Default is ┥Left Right Arrow - ↔ U+02194 - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune SizeHorizontal - { - get => GlyphSettings.Defaults.SizeHorizontal; - set => GlyphSettings.Defaults.SizeHorizontal = value; - } + public static Rune SizeHorizontal => GlyphSettings.Current.SizeHorizontal; /// Size Vertical indicator. Default Up Down Arrow - ↕ U+02195 - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune SizeVertical - { - get => GlyphSettings.Defaults.SizeVertical; - set => GlyphSettings.Defaults.SizeVertical = value; - } + public static Rune SizeVertical => GlyphSettings.Current.SizeVertical; /// Size Top Left indicator. North West Arrow - ↖ U+02196 - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune SizeTopLeft - { - get => GlyphSettings.Defaults.SizeTopLeft; - set => GlyphSettings.Defaults.SizeTopLeft = value; - } + public static Rune SizeTopLeft => GlyphSettings.Current.SizeTopLeft; /// Size Top Right indicator. North East Arrow - ↗ U+02197 - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune SizeTopRight - { - get => GlyphSettings.Defaults.SizeTopRight; - set => GlyphSettings.Defaults.SizeTopRight = value; - } + public static Rune SizeTopRight => GlyphSettings.Current.SizeTopRight; /// Size Bottom Right indicator. South East Arrow - ↘ U+02198 - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune SizeBottomRight - { - get => GlyphSettings.Defaults.SizeBottomRight; - set => GlyphSettings.Defaults.SizeBottomRight = value; - } + public static Rune SizeBottomRight => GlyphSettings.Current.SizeBottomRight; /// Size Bottom Left indicator. South West Arrow - ↙ U+02199 - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune SizeBottomLeft - { - get => GlyphSettings.Defaults.SizeBottomLeft; - set => GlyphSettings.Defaults.SizeBottomLeft = value; - } + public static Rune SizeBottomLeft => GlyphSettings.Current.SizeBottomLeft; /// Apple (non-BMP). Because snek. And because it's an example of a non-BMP surrogate pair. See Issue #2610. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Apple // nonBMP - { - get => GlyphSettings.Defaults.Apple; - set => GlyphSettings.Defaults.Apple = value; - } + public static Rune Apple => GlyphSettings.Current.Apple; /// Apple (BMP). Because snek. See Issue #2610. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune AppleBMP - { - get => GlyphSettings.Defaults.AppleBMP; - set => GlyphSettings.Defaults.AppleBMP = value; - } + public static Rune AppleBMP => GlyphSettings.Current.AppleBMP; /// Copy indicator. Two Joined Squares - ⧉ U+29C9. Used for code block copy buttons. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Copy - { - get => GlyphSettings.Defaults.Copy; - set => GlyphSettings.Defaults.Copy = value; - } + public static Rune Copy => GlyphSettings.Current.Copy; #endregion #region ----------------- Lines ----------------- /// Box Drawings Horizontal Line - Light (U+2500) - ─ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HLine - { - get => GlyphSettings.Defaults.HLine; - set => GlyphSettings.Defaults.HLine = value; - } + public static Rune HLine => GlyphSettings.Current.HLine; /// Box Drawings Vertical Line - Light (U+2502) - │ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune VLine - { - get => GlyphSettings.Defaults.VLine; - set => GlyphSettings.Defaults.VLine = value; - } + public static Rune VLine => GlyphSettings.Current.VLine; /// Box Drawings Double Horizontal (U+2550) - ═ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HLineDbl - { - get => GlyphSettings.Defaults.HLineDbl; - set => GlyphSettings.Defaults.HLineDbl = value; - } + public static Rune HLineDbl => GlyphSettings.Current.HLineDbl; /// Box Drawings Double Vertical (U+2551) - â•‘ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune VLineDbl - - { - - get => GlyphSettings.Defaults.VLineDbl; - - set => GlyphSettings.Defaults.VLineDbl = value; - - } + public static Rune VLineDbl => GlyphSettings.Current.VLineDbl; /// Box Drawings Heavy Double Dash Horizontal (U+254D) - ╍ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HLineHvDa2 - - { - - get => GlyphSettings.Defaults.HLineHvDa2; - - set => GlyphSettings.Defaults.HLineHvDa2 = value; - - } + public static Rune HLineHvDa2 => GlyphSettings.Current.HLineHvDa2; /// Box Drawings Heavy Triple Dash Vertical (U+2507) - ┇ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune VLineHvDa3 - { - get => GlyphSettings.Defaults.VLineHvDa3; - set => GlyphSettings.Defaults.VLineHvDa3 = value; - } + public static Rune VLineHvDa3 => GlyphSettings.Current.VLineHvDa3; /// Box Drawings Heavy Triple Dash Horizontal (U+2505) - â”… - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HLineHvDa3 - - { - - get => GlyphSettings.Defaults.HLineHvDa3; - - set => GlyphSettings.Defaults.HLineHvDa3 = value; - - } + public static Rune HLineHvDa3 => GlyphSettings.Current.HLineHvDa3; /// Box Drawings Heavy Quadruple Dash Horizontal (U+2509) - ┉ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HLineHvDa4 - - { - - get => GlyphSettings.Defaults.HLineHvDa4; - - set => GlyphSettings.Defaults.HLineHvDa4 = value; - - } + public static Rune HLineHvDa4 => GlyphSettings.Current.HLineHvDa4; /// Box Drawings Heavy Double Dash Vertical (U+254F) - ╏ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune VLineHvDa2 - - { - - get => GlyphSettings.Defaults.VLineHvDa2; - - set => GlyphSettings.Defaults.VLineHvDa2 = value; - - } + public static Rune VLineHvDa2 => GlyphSettings.Current.VLineHvDa2; /// Box Drawings Heavy Quadruple Dash Vertical (U+250B) - ┋ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune VLineHvDa4 - - { - - get => GlyphSettings.Defaults.VLineHvDa4; - - set => GlyphSettings.Defaults.VLineHvDa4 = value; - - } + public static Rune VLineHvDa4 => GlyphSettings.Current.VLineHvDa4; /// Box Drawings Light Double Dash Horizontal (U+254C) - ╌ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HLineDa2 - - { - - get => GlyphSettings.Defaults.HLineDa2; - - set => GlyphSettings.Defaults.HLineDa2 = value; - - } + public static Rune HLineDa2 => GlyphSettings.Current.HLineDa2; /// Box Drawings Light Triple Dash Vertical (U+2506) - ┆ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune VLineDa3 - - { - - get => GlyphSettings.Defaults.VLineDa3; - - set => GlyphSettings.Defaults.VLineDa3 = value; - - } + public static Rune VLineDa3 => GlyphSettings.Current.VLineDa3; /// Box Drawings Light Triple Dash Horizontal (U+2504) - ┄ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HLineDa3 - - { - - get => GlyphSettings.Defaults.HLineDa3; - - set => GlyphSettings.Defaults.HLineDa3 = value; - - } + public static Rune HLineDa3 => GlyphSettings.Current.HLineDa3; /// Box Drawings Light Quadruple Dash Horizontal (U+2508) - ┈ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HLineDa4 - - { - - get => GlyphSettings.Defaults.HLineDa4; - - set => GlyphSettings.Defaults.HLineDa4 = value; - - } + public static Rune HLineDa4 => GlyphSettings.Current.HLineDa4; /// Box Drawings Light Double Dash Vertical (U+254E) - ╎ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune VLineDa2 - - { - - get => GlyphSettings.Defaults.VLineDa2; - - set => GlyphSettings.Defaults.VLineDa2 = value; - - } + public static Rune VLineDa2 => GlyphSettings.Current.VLineDa2; /// Box Drawings Light Quadruple Dash Vertical (U+250A) - ┊ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune VLineDa4 - - { - - get => GlyphSettings.Defaults.VLineDa4; - - set => GlyphSettings.Defaults.VLineDa4 = value; - - } + public static Rune VLineDa4 => GlyphSettings.Current.VLineDa4; /// Box Drawings Heavy Horizontal (U+2501) - ━ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HLineHv - - { - - get => GlyphSettings.Defaults.HLineHv; - - set => GlyphSettings.Defaults.HLineHv = value; - - } + public static Rune HLineHv => GlyphSettings.Current.HLineHv; /// Box Drawings Heavy Vertical (U+2503) - ┃ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune VLineHv - - { - - get => GlyphSettings.Defaults.VLineHv; - - set => GlyphSettings.Defaults.VLineHv = value; - - } + public static Rune VLineHv => GlyphSettings.Current.VLineHv; /// Box Drawings Light Left (U+2574) - â•´ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HalfLeftLine - - { - - get => GlyphSettings.Defaults.HalfLeftLine; - - set => GlyphSettings.Defaults.HalfLeftLine = value; - - } + public static Rune HalfLeftLine => GlyphSettings.Current.HalfLeftLine; /// Box Drawings Light Vertical (U+2575) - ╵ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HalfTopLine - - { - - get => GlyphSettings.Defaults.HalfTopLine; - - set => GlyphSettings.Defaults.HalfTopLine = value; - - } + public static Rune HalfTopLine => GlyphSettings.Current.HalfTopLine; /// Box Drawings Light Horizontal (U+2576) - â•¶ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HalfRightLine - - { - - get => GlyphSettings.Defaults.HalfRightLine; - - set => GlyphSettings.Defaults.HalfRightLine = value; - - } + public static Rune HalfRightLine => GlyphSettings.Current.HalfRightLine; /// Box Drawings Light Down (U+2577) - â•· - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HalfBottomLine - - { - - get => GlyphSettings.Defaults.HalfBottomLine; - - set => GlyphSettings.Defaults.HalfBottomLine = value; - - } + public static Rune HalfBottomLine => GlyphSettings.Current.HalfBottomLine; /// Box Drawings Heavy Left (U+2578) - ╸ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HalfLeftLineHv - - { - - get => GlyphSettings.Defaults.HalfLeftLineHv; - - set => GlyphSettings.Defaults.HalfLeftLineHv = value; - - } + public static Rune HalfLeftLineHv => GlyphSettings.Current.HalfLeftLineHv; /// Box Drawings Heavy Vertical (U+2579) - ╹ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HalfTopLineHv - - { - - get => GlyphSettings.Defaults.HalfTopLineHv; - - set => GlyphSettings.Defaults.HalfTopLineHv = value; - - } + public static Rune HalfTopLineHv => GlyphSettings.Current.HalfTopLineHv; /// Box Drawings Heavy Horizontal (U+257A) - ╺ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HalfRightLineHv - - { - - get => GlyphSettings.Defaults.HalfRightLineHv; - - set => GlyphSettings.Defaults.HalfRightLineHv = value; - - } + public static Rune HalfRightLineHv => GlyphSettings.Current.HalfRightLineHv; /// Box Drawings Light Vertical and Horizontal (U+257B) - â•» - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune HalfBottomLineLt - - { - - get => GlyphSettings.Defaults.HalfBottomLineLt; - - set => GlyphSettings.Defaults.HalfBottomLineLt = value; - - } + public static Rune HalfBottomLineLt => GlyphSettings.Current.HalfBottomLineLt; /// Box Drawings Light Horizontal and Heavy Horizontal (U+257C) - ╼ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune RightSideLineLtHv - - { - - get => GlyphSettings.Defaults.RightSideLineLtHv; - - set => GlyphSettings.Defaults.RightSideLineLtHv = value; - - } + public static Rune RightSideLineLtHv => GlyphSettings.Current.RightSideLineLtHv; /// Box Drawings Light Vertical and Heavy Horizontal (U+257D) - ╽ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune BottomSideLineLtHv - - { - - get => GlyphSettings.Defaults.BottomSideLineLtHv; - - set => GlyphSettings.Defaults.BottomSideLineLtHv = value; - - } + public static Rune BottomSideLineLtHv => GlyphSettings.Current.BottomSideLineLtHv; /// Box Drawings Heavy Left and Light Horizontal (U+257E) - ╾ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LeftSideLineHvLt - - { - - get => GlyphSettings.Defaults.LeftSideLineHvLt; - - set => GlyphSettings.Defaults.LeftSideLineHvLt = value; - - } + public static Rune LeftSideLineHvLt => GlyphSettings.Current.LeftSideLineHvLt; /// Box Drawings Heavy Vertical and Light Horizontal (U+257F) - â•¿ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune TopSideLineHvLt - - { - - get => GlyphSettings.Defaults.TopSideLineHvLt; - - set => GlyphSettings.Defaults.TopSideLineHvLt = value; - - } + public static Rune TopSideLineHvLt => GlyphSettings.Current.TopSideLineHvLt; #endregion @@ -745,107 +281,35 @@ public static Rune TopSideLineHvLt /// Box Drawings Upper Left Corner - Light Vertical and Light Horizontal (U+250C) - ┌ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ULCorner - - { - - get => GlyphSettings.Defaults.ULCorner; - - set => GlyphSettings.Defaults.ULCorner = value; - - } + public static Rune ULCorner => GlyphSettings.Current.ULCorner; /// Box Drawings Upper Left Corner - Double (U+2554) - â•” - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ULCornerDbl - - { - - get => GlyphSettings.Defaults.ULCornerDbl; - - set => GlyphSettings.Defaults.ULCornerDbl = value; - - } + public static Rune ULCornerDbl => GlyphSettings.Current.ULCornerDbl; /// Box Drawings Upper Left Corner - Light Arc Down and Horizontal (U+256D) - â•­ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ULCornerR - - { - - get => GlyphSettings.Defaults.ULCornerR; - - set => GlyphSettings.Defaults.ULCornerR = value; - - } + public static Rune ULCornerR => GlyphSettings.Current.ULCornerR; /// Box Drawings Heavy Down and Horizontal (U+250F) - ┏ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ULCornerHv - - { - - get => GlyphSettings.Defaults.ULCornerHv; - - set => GlyphSettings.Defaults.ULCornerHv = value; - - } + public static Rune ULCornerHv => GlyphSettings.Current.ULCornerHv; /// Box Drawings Down Heavy and Horizontal Light (U+251E) - ┎ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ULCornerHvLt - - { - - get => GlyphSettings.Defaults.ULCornerHvLt; - - set => GlyphSettings.Defaults.ULCornerHvLt = value; - - } + public static Rune ULCornerHvLt => GlyphSettings.Current.ULCornerHvLt; /// Box Drawings Down Light and Horizontal Heavy (U+250D) - ┎ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ULCornerLtHv - - { - - get => GlyphSettings.Defaults.ULCornerLtHv; - - set => GlyphSettings.Defaults.ULCornerLtHv = value; - - } + public static Rune ULCornerLtHv => GlyphSettings.Current.ULCornerLtHv; /// Box Drawings Double Down and Single Horizontal (U+2553) - â•“ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ULCornerDblSingle - - { - - get => GlyphSettings.Defaults.ULCornerDblSingle; - - set => GlyphSettings.Defaults.ULCornerDblSingle = value; - - } + public static Rune ULCornerDblSingle => GlyphSettings.Current.ULCornerDblSingle; /// Box Drawings Single Down and Double Horizontal (U+2552) - â•’ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ULCornerSingleDbl - - { - - get => GlyphSettings.Defaults.ULCornerSingleDbl; - - set => GlyphSettings.Defaults.ULCornerSingleDbl = value; - - } + public static Rune ULCornerSingleDbl => GlyphSettings.Current.ULCornerSingleDbl; #endregion @@ -853,107 +317,35 @@ public static Rune ULCornerSingleDbl /// Box Drawings Lower Left Corner - Light Vertical and Light Horizontal (U+2514) - â”” - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LLCorner - - { - - get => GlyphSettings.Defaults.LLCorner; - - set => GlyphSettings.Defaults.LLCorner = value; - - } + public static Rune LLCorner => GlyphSettings.Current.LLCorner; /// Box Drawings Heavy Vertical and Horizontal (U+2517) - â”— - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LLCornerHv - - { - - get => GlyphSettings.Defaults.LLCornerHv; - - set => GlyphSettings.Defaults.LLCornerHv = value; - - } + public static Rune LLCornerHv => GlyphSettings.Current.LLCornerHv; /// Box Drawings Heavy Vertical and Horizontal Light (U+2516) - â”– - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LLCornerHvLt - - { - - get => GlyphSettings.Defaults.LLCornerHvLt; - - set => GlyphSettings.Defaults.LLCornerHvLt = value; - - } + public static Rune LLCornerHvLt => GlyphSettings.Current.LLCornerHvLt; /// Box Drawings Vertical Light and Horizontal Heavy (U+2511) - ┕ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LLCornerLtHv - - { - - get => GlyphSettings.Defaults.LLCornerLtHv; - - set => GlyphSettings.Defaults.LLCornerLtHv = value; - - } + public static Rune LLCornerLtHv => GlyphSettings.Current.LLCornerLtHv; /// Box Drawings Double Vertical and Double Left (U+255A) - ╚ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LLCornerDbl - - { - - get => GlyphSettings.Defaults.LLCornerDbl; - - set => GlyphSettings.Defaults.LLCornerDbl = value; - - } + public static Rune LLCornerDbl => GlyphSettings.Current.LLCornerDbl; /// Box Drawings Single Vertical and Double Left (U+2558) - ╘ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LLCornerSingleDbl - - { - - get => GlyphSettings.Defaults.LLCornerSingleDbl; - - set => GlyphSettings.Defaults.LLCornerSingleDbl = value; - - } + public static Rune LLCornerSingleDbl => GlyphSettings.Current.LLCornerSingleDbl; /// Box Drawings Double Down and Single Left (U+2559) - â•™ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LLCornerDblSingle - - { - - get => GlyphSettings.Defaults.LLCornerDblSingle; - - set => GlyphSettings.Defaults.LLCornerDblSingle = value; - - } + public static Rune LLCornerDblSingle => GlyphSettings.Current.LLCornerDblSingle; /// Box Drawings Upper Left Corner - Light Arc Down and Left (U+2570) - â•° - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LLCornerR - - { - - get => GlyphSettings.Defaults.LLCornerR; - - set => GlyphSettings.Defaults.LLCornerR = value; - - } + public static Rune LLCornerR => GlyphSettings.Current.LLCornerR; #endregion @@ -961,107 +353,35 @@ public static Rune LLCornerR /// Box Drawings Upper Horizontal Corner - Light Vertical and Light Horizontal (U+2510) - ┐ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune URCorner - - { - - get => GlyphSettings.Defaults.URCorner; - - set => GlyphSettings.Defaults.URCorner = value; - - } + public static Rune URCorner => GlyphSettings.Current.URCorner; /// Box Drawings Upper Horizontal Corner - Double Vertical and Double Horizontal (U+2557) - â•— - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune URCornerDbl - - { - - get => GlyphSettings.Defaults.URCornerDbl; - - set => GlyphSettings.Defaults.URCornerDbl = value; - - } + public static Rune URCornerDbl => GlyphSettings.Current.URCornerDbl; /// Box Drawings Upper Horizontal Corner - Light Arc Vertical and Horizontal (U+256E) - â•® - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune URCornerR - - { - - get => GlyphSettings.Defaults.URCornerR; - - set => GlyphSettings.Defaults.URCornerR = value; - - } + public static Rune URCornerR => GlyphSettings.Current.URCornerR; /// Box Drawings Heavy Down and Left (U+2513) - ┓ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune URCornerHv - - { - - get => GlyphSettings.Defaults.URCornerHv; - - set => GlyphSettings.Defaults.URCornerHv = value; - - } + public static Rune URCornerHv => GlyphSettings.Current.URCornerHv; /// Box Drawings Heavy Vertical and Left Down Light (U+2511) - ┑ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune URCornerHvLt - - { - - get => GlyphSettings.Defaults.URCornerHvLt; - - set => GlyphSettings.Defaults.URCornerHvLt = value; - - } + public static Rune URCornerHvLt => GlyphSettings.Current.URCornerHvLt; /// Box Drawings Down Light and Horizontal Heavy (U+2514) - â”’ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune URCornerLtHv - - { - - get => GlyphSettings.Defaults.URCornerLtHv; - - set => GlyphSettings.Defaults.URCornerLtHv = value; - - } + public static Rune URCornerLtHv => GlyphSettings.Current.URCornerLtHv; /// Box Drawings Double Vertical and Single Left (U+2556) - â•– - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune URCornerDblSingle - - { - - get => GlyphSettings.Defaults.URCornerDblSingle; - - set => GlyphSettings.Defaults.URCornerDblSingle = value; - - } + public static Rune URCornerDblSingle => GlyphSettings.Current.URCornerDblSingle; /// Box Drawings Single Vertical and Double Left (U+2555) - â•• - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune URCornerSingleDbl - - { - - get => GlyphSettings.Defaults.URCornerSingleDbl; - - set => GlyphSettings.Defaults.URCornerSingleDbl = value; - - } + public static Rune URCornerSingleDbl => GlyphSettings.Current.URCornerSingleDbl; #endregion @@ -1069,107 +389,35 @@ public static Rune URCornerSingleDbl /// Box Drawings Lower Right Corner - Light (U+2518) - ┘ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LRCorner - - { - - get => GlyphSettings.Defaults.LRCorner; - - set => GlyphSettings.Defaults.LRCorner = value; - - } + public static Rune LRCorner => GlyphSettings.Current.LRCorner; /// Box Drawings Lower Right Corner - Double (U+255D) - ╝ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LRCornerDbl - - { - - get => GlyphSettings.Defaults.LRCornerDbl; - - set => GlyphSettings.Defaults.LRCornerDbl = value; - - } + public static Rune LRCornerDbl => GlyphSettings.Current.LRCornerDbl; /// Box Drawings Lower Right Corner - Rounded (U+256F) - ╯ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LRCornerR - - { - - get => GlyphSettings.Defaults.LRCornerR; - - set => GlyphSettings.Defaults.LRCornerR = value; - - } + public static Rune LRCornerR => GlyphSettings.Current.LRCornerR; /// Box Drawings Lower Right Corner - Heavy (U+251B) - â”› - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LRCornerHv - - { - - get => GlyphSettings.Defaults.LRCornerHv; - - set => GlyphSettings.Defaults.LRCornerHv = value; - - } + public static Rune LRCornerHv => GlyphSettings.Current.LRCornerHv; /// Box Drawings Lower Right Corner - Double Vertical and Single Horizontal (U+255C) - ╜ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LRCornerDblSingle - - { - - get => GlyphSettings.Defaults.LRCornerDblSingle; - - set => GlyphSettings.Defaults.LRCornerDblSingle = value; - - } + public static Rune LRCornerDblSingle => GlyphSettings.Current.LRCornerDblSingle; /// Box Drawings Lower Right Corner - Single Vertical and Double Horizontal (U+255B) - â•› - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LRCornerSingleDbl - - { - - get => GlyphSettings.Defaults.LRCornerSingleDbl; - - set => GlyphSettings.Defaults.LRCornerSingleDbl = value; - - } + public static Rune LRCornerSingleDbl => GlyphSettings.Current.LRCornerSingleDbl; /// Box Drawings Lower Right Corner - Light Vertical and Heavy Horizontal (U+2519) - â”™ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LRCornerLtHv - - { - - get => GlyphSettings.Defaults.LRCornerLtHv; - - set => GlyphSettings.Defaults.LRCornerLtHv = value; - - } + public static Rune LRCornerLtHv => GlyphSettings.Current.LRCornerLtHv; /// Box Drawings Lower Right Corner - Heavy Vertical and Light Horizontal (U+251A) - ┚ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LRCornerHvLt - - { - - get => GlyphSettings.Defaults.LRCornerHvLt; - - set => GlyphSettings.Defaults.LRCornerHvLt = value; - - } + public static Rune LRCornerHvLt => GlyphSettings.Current.LRCornerHvLt; #endregion @@ -1177,367 +425,115 @@ public static Rune LRCornerHvLt /// Box Drawings Left Tee - Single Vertical and Single Horizontal (U+251C) - ├ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LeftTee - - { - - get => GlyphSettings.Defaults.LeftTee; - - set => GlyphSettings.Defaults.LeftTee = value; - - } + public static Rune LeftTee => GlyphSettings.Current.LeftTee; /// Box Drawings Left Tee - Single Vertical and Double Horizontal (U+255E) - ╞ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LeftTeeDblH - - { - - get => GlyphSettings.Defaults.LeftTeeDblH; - - set => GlyphSettings.Defaults.LeftTeeDblH = value; - - } + public static Rune LeftTeeDblH => GlyphSettings.Current.LeftTeeDblH; /// Box Drawings Left Tee - Double Vertical and Single Horizontal (U+255F) - ╟ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LeftTeeDblV - - { - - get => GlyphSettings.Defaults.LeftTeeDblV; - - set => GlyphSettings.Defaults.LeftTeeDblV = value; - - } + public static Rune LeftTeeDblV => GlyphSettings.Current.LeftTeeDblV; /// Box Drawings Left Tee - Double Vertical and Double Horizontal (U+2560) - â•  - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LeftTeeDbl - - { - - get => GlyphSettings.Defaults.LeftTeeDbl; - - set => GlyphSettings.Defaults.LeftTeeDbl = value; - - } + public static Rune LeftTeeDbl => GlyphSettings.Current.LeftTeeDbl; /// Box Drawings Left Tee - Heavy Horizontal and Light Vertical (U+2523) - ┝ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LeftTeeHvH - - { - - get => GlyphSettings.Defaults.LeftTeeHvH; - - set => GlyphSettings.Defaults.LeftTeeHvH = value; - - } + public static Rune LeftTeeHvH => GlyphSettings.Current.LeftTeeHvH; /// Box Drawings Left Tee - Light Horizontal and Heavy Vertical (U+252B) - â”  - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LeftTeeHvV - - { - - get => GlyphSettings.Defaults.LeftTeeHvV; - - set => GlyphSettings.Defaults.LeftTeeHvV = value; - - } + public static Rune LeftTeeHvV => GlyphSettings.Current.LeftTeeHvV; /// Box Drawings Left Tee - Heavy Vertical and Heavy Horizontal (U+2527) - ┣ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune LeftTeeHvDblH - - { - - get => GlyphSettings.Defaults.LeftTeeHvDblH; - - set => GlyphSettings.Defaults.LeftTeeHvDblH = value; - - } + public static Rune LeftTeeHvDblH => GlyphSettings.Current.LeftTeeHvDblH; /// Box Drawings Right Tee - Single Vertical and Single Horizontal (U+2524) - ┤ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune RightTee - - { - - get => GlyphSettings.Defaults.RightTee; - - set => GlyphSettings.Defaults.RightTee = value; - - } + public static Rune RightTee => GlyphSettings.Current.RightTee; /// Box Drawings Right Tee - Single Vertical and Double Horizontal (U+2561) - â•¡ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune RightTeeDblH - - { - - get => GlyphSettings.Defaults.RightTeeDblH; - - set => GlyphSettings.Defaults.RightTeeDblH = value; - - } + public static Rune RightTeeDblH => GlyphSettings.Current.RightTeeDblH; /// Box Drawings Right Tee - Double Vertical and Single Horizontal (U+2562) - â•¢ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune RightTeeDblV - - { - - get => GlyphSettings.Defaults.RightTeeDblV; - - set => GlyphSettings.Defaults.RightTeeDblV = value; - - } + public static Rune RightTeeDblV => GlyphSettings.Current.RightTeeDblV; /// Box Drawings Right Tee - Double Vertical and Double Horizontal (U+2563) - â•£ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune RightTeeDbl - - { - - get => GlyphSettings.Defaults.RightTeeDbl; - - set => GlyphSettings.Defaults.RightTeeDbl = value; - - } + public static Rune RightTeeDbl => GlyphSettings.Current.RightTeeDbl; /// Box Drawings Right Tee - Heavy Horizontal and Light Vertical (U+2528) - ┥ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune RightTeeHvH - - { - - get => GlyphSettings.Defaults.RightTeeHvH; - - set => GlyphSettings.Defaults.RightTeeHvH = value; - - } + public static Rune RightTeeHvH => GlyphSettings.Current.RightTeeHvH; /// Box Drawings Right Tee - Light Horizontal and Heavy Vertical (U+2530) - ┨ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune RightTeeHvV - - { - - get => GlyphSettings.Defaults.RightTeeHvV; - - set => GlyphSettings.Defaults.RightTeeHvV = value; - - } + public static Rune RightTeeHvV => GlyphSettings.Current.RightTeeHvV; /// Box Drawings Right Tee - Heavy Vertical and Heavy Horizontal (U+252C) - ┫ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune RightTeeHvDblH - - { - - get => GlyphSettings.Defaults.RightTeeHvDblH; - - set => GlyphSettings.Defaults.RightTeeHvDblH = value; - - } + public static Rune RightTeeHvDblH => GlyphSettings.Current.RightTeeHvDblH; /// Box Drawings Top Tee - Single Vertical and Single Horizontal (U+252C) - ┬ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune TopTee - - { - - get => GlyphSettings.Defaults.TopTee; - - set => GlyphSettings.Defaults.TopTee = value; - - } + public static Rune TopTee => GlyphSettings.Current.TopTee; /// Box Drawings Top Tee - Single Vertical and Double Horizontal (U+2564) - ╤ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune TopTeeDblH - - { - - get => GlyphSettings.Defaults.TopTeeDblH; - - set => GlyphSettings.Defaults.TopTeeDblH = value; - - } + public static Rune TopTeeDblH => GlyphSettings.Current.TopTeeDblH; /// Box Drawings Top Tee - Double Vertical and Single Horizontal (U+2565) - â•¥ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune TopTeeDblV - - { - - get => GlyphSettings.Defaults.TopTeeDblV; - - set => GlyphSettings.Defaults.TopTeeDblV = value; - - } + public static Rune TopTeeDblV => GlyphSettings.Current.TopTeeDblV; /// Box Drawings Top Tee - Double Vertical and Double Horizontal (U+2566) - ╦ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune TopTeeDbl - - { - - get => GlyphSettings.Defaults.TopTeeDbl; - - set => GlyphSettings.Defaults.TopTeeDbl = value; - - } + public static Rune TopTeeDbl => GlyphSettings.Current.TopTeeDbl; /// Box Drawings Top Tee - Heavy Horizontal and Light Vertical (U+252F) - ┯ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune TopTeeHvH - - { - - get => GlyphSettings.Defaults.TopTeeHvH; - - set => GlyphSettings.Defaults.TopTeeHvH = value; - - } + public static Rune TopTeeHvH => GlyphSettings.Current.TopTeeHvH; /// Box Drawings Top Tee - Light Horizontal and Heavy Vertical (U+2537) - â”° - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune TopTeeHvV - - { - - get => GlyphSettings.Defaults.TopTeeHvV; - - set => GlyphSettings.Defaults.TopTeeHvV = value; - - } + public static Rune TopTeeHvV => GlyphSettings.Current.TopTeeHvV; /// Box Drawings Top Tee - Heavy Vertical and Heavy Horizontal (U+2533) - ┳ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune TopTeeHvDblH - - { - - get => GlyphSettings.Defaults.TopTeeHvDblH; - - set => GlyphSettings.Defaults.TopTeeHvDblH = value; - - } + public static Rune TopTeeHvDblH => GlyphSettings.Current.TopTeeHvDblH; /// Box Drawings Bottom Tee - Single Vertical and Single Horizontal (U+2534) - â”´ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune BottomTee - - { - - get => GlyphSettings.Defaults.BottomTee; - - set => GlyphSettings.Defaults.BottomTee = value; - - } + public static Rune BottomTee => GlyphSettings.Current.BottomTee; /// Box Drawings Bottom Tee - Single Vertical and Double Horizontal (U+2567) - â•§ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune BottomTeeDblH - - { - - get => GlyphSettings.Defaults.BottomTeeDblH; - - set => GlyphSettings.Defaults.BottomTeeDblH = value; - - } + public static Rune BottomTeeDblH => GlyphSettings.Current.BottomTeeDblH; /// Box Drawings Bottom Tee - Double Vertical and Single Horizontal (U+2568) - ╨ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune BottomTeeDblV - - { - - get => GlyphSettings.Defaults.BottomTeeDblV; - - set => GlyphSettings.Defaults.BottomTeeDblV = value; - - } + public static Rune BottomTeeDblV => GlyphSettings.Current.BottomTeeDblV; /// Box Drawings Bottom Tee - Double Vertical and Double Horizontal (U+2569) - â•© - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune BottomTeeDbl - - { - - get => GlyphSettings.Defaults.BottomTeeDbl; - - set => GlyphSettings.Defaults.BottomTeeDbl = value; - - } + public static Rune BottomTeeDbl => GlyphSettings.Current.BottomTeeDbl; /// Box Drawings Bottom Tee - Heavy Horizontal and Light Vertical (U+2535) - â”· - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune BottomTeeHvH - - { - - get => GlyphSettings.Defaults.BottomTeeHvH; - - set => GlyphSettings.Defaults.BottomTeeHvH = value; - - } + public static Rune BottomTeeHvH => GlyphSettings.Current.BottomTeeHvH; /// Box Drawings Bottom Tee - Light Horizontal and Heavy Vertical (U+253D) - ┸ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune BottomTeeHvV - - { - - get => GlyphSettings.Defaults.BottomTeeHvV; - - set => GlyphSettings.Defaults.BottomTeeHvV = value; - - } + public static Rune BottomTeeHvV => GlyphSettings.Current.BottomTeeHvV; /// Box Drawings Bottom Tee - Heavy Vertical and Heavy Horizontal (U+2539) - â”» - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune BottomTeeHvDblH - - { - - get => GlyphSettings.Defaults.BottomTeeHvDblH; - - set => GlyphSettings.Defaults.BottomTeeHvDblH = value; - - } + public static Rune BottomTeeHvDblH => GlyphSettings.Current.BottomTeeHvDblH; #endregion @@ -1545,94 +541,31 @@ public static Rune BottomTeeHvDblH /// Box Drawings Cross - Single Vertical and Single Horizontal (U+253C) - ┼ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune Cross - - { - - get => GlyphSettings.Defaults.Cross; - - set => GlyphSettings.Defaults.Cross = value; - - } + public static Rune Cross => GlyphSettings.Current.Cross; /// Box Drawings Cross - Single Vertical and Double Horizontal (U+256A) - ╪ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune CrossDblH - - { - - get => GlyphSettings.Defaults.CrossDblH; - - set => GlyphSettings.Defaults.CrossDblH = value; - - } + public static Rune CrossDblH => GlyphSettings.Current.CrossDblH; /// Box Drawings Cross - Double Vertical and Single Horizontal (U+256B) - â•« - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune CrossDblV - - { - - get => GlyphSettings.Defaults.CrossDblV; - - set => GlyphSettings.Defaults.CrossDblV = value; - - } + public static Rune CrossDblV => GlyphSettings.Current.CrossDblV; /// Box Drawings Cross - Double Vertical and Double Horizontal (U+256C) - ╬ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune CrossDbl - - { - - get => GlyphSettings.Defaults.CrossDbl; - - set => GlyphSettings.Defaults.CrossDbl = value; - - } + public static Rune CrossDbl => GlyphSettings.Current.CrossDbl; /// Box Drawings Cross - Heavy Horizontal and Light Vertical (U+253F) - ┿ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune CrossHvH - - { - - get => GlyphSettings.Defaults.CrossHvH; - - set => GlyphSettings.Defaults.CrossHvH = value; - - } + public static Rune CrossHvH => GlyphSettings.Current.CrossHvH; /// Box Drawings Cross - Light Horizontal and Heavy Vertical (U+2541) - â•‚ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune CrossHvV - - { - - get => GlyphSettings.Defaults.CrossHvV; - - set => GlyphSettings.Defaults.CrossHvV = value; - - } + public static Rune CrossHvV => GlyphSettings.Current.CrossHvV; /// Box Drawings Cross - Heavy Vertical and Heavy Horizontal (U+254B) - â•‹ - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune CrossHv - - { - - get => GlyphSettings.Defaults.CrossHv; - - set => GlyphSettings.Defaults.CrossHv = value; - - } + public static Rune CrossHv => GlyphSettings.Current.CrossHv; #endregion @@ -1640,68 +573,23 @@ public static Rune CrossHv /// Shadow - Vertical Start - Left Half Block - â–Œ U+0258c - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ShadowVerticalStart // Half: '\u2596' â––; - - { - - get => GlyphSettings.Defaults.ShadowVerticalStart; - - set => GlyphSettings.Defaults.ShadowVerticalStart = value; - - } + public static Rune ShadowVerticalStart => GlyphSettings.Current.ShadowVerticalStart; /// Shadow - Vertical - Left Half Block - â–Œ U+0258c - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ShadowVertical - - { - - get => GlyphSettings.Defaults.ShadowVertical; - - set => GlyphSettings.Defaults.ShadowVertical = value; - - } + public static Rune ShadowVertical => GlyphSettings.Current.ShadowVertical; /// Shadow - Horizontal Start - Upper Half Block - â–€ U+02580 - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ShadowHorizontalStart // Half: ▝ U+0259d; - - { - - get => GlyphSettings.Defaults.ShadowHorizontalStart; - - set => GlyphSettings.Defaults.ShadowHorizontalStart = value; - - } + public static Rune ShadowHorizontalStart => GlyphSettings.Current.ShadowHorizontalStart; /// Shadow - Horizontal - Upper Half Block - â–€ U+02580 - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ShadowHorizontal - - { - - get => GlyphSettings.Defaults.ShadowHorizontal; - - set => GlyphSettings.Defaults.ShadowHorizontal = value; - - } + public static Rune ShadowHorizontal => GlyphSettings.Current.ShadowHorizontal; /// Shadow - Horizontal End - Quadrant Upper Left - â–˜ U+02598 - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Rune ShadowHorizontalEnd - - { - - get => GlyphSettings.Defaults.ShadowHorizontalEnd; - - set => GlyphSettings.Defaults.ShadowHorizontalEnd = value; - - } + public static Rune ShadowHorizontalEnd => GlyphSettings.Current.ShadowHorizontalEnd; #endregion } \ No newline at end of file diff --git a/Terminal.Gui/Text/NerdFonts.cs b/Terminal.Gui/Text/NerdFonts.cs index 2253a3ecef..dd1d05a1c3 100644 --- a/Terminal.Gui/Text/NerdFonts.cs +++ b/Terminal.Gui/Text/NerdFonts.cs @@ -15,12 +15,7 @@ internal class NerdFonts /// If , enables the use of Nerd unicode symbols. This requires specific font(s) to be /// installed on the users machine to work correctly. Defaults to . /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static bool Enable - { - get => NerdFontsSettings.Defaults.Enable; - set => NerdFontsSettings.Defaults.Enable = value; - } + public static bool Enable => NerdFontsSettings.Current.Enable; /// Mapping of file extension to name. public Dictionary ExtensionToIcon { get; set; } = new () diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 3e05362cc2..6a9a4718fa 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -53,22 +53,12 @@ public class Button : View, IDesignable, IAcceptTarget /// /// Gets or sets whether s are shown with a shadow effect by default. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static ShadowStyles DefaultShadow - { - get => ButtonSettings.Defaults.DefaultShadow; - set => ButtonSettings.Defaults.DefaultShadow = value; - } + public static ShadowStyles DefaultShadow => ButtonSettings.Current.DefaultShadow; /// /// Gets or sets the default Highlight Style. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static MouseState DefaultMouseHighlightStates - { - get => ButtonSettings.Defaults.DefaultMouseHighlightStates; - set => ButtonSettings.Defaults.DefaultMouseHighlightStates = value; - } + public static MouseState DefaultMouseHighlightStates => ButtonSettings.Current.DefaultMouseHighlightStates; /// Initializes a new instance of . public Button () @@ -108,7 +98,7 @@ public Button () /// /// Called before the Button's initial is applied during construction. - /// Override to change or suppress the default shadow set + /// Override to change or suppress the default shadow � set /// to the desired style, or set to /// to skip applying any shadow. /// @@ -131,10 +121,10 @@ private void RaiseInitializingShadowStyle () { ValueChangingEventArgs args = new (null, DefaultShadow); - // 1. Virtual method subclasses override to change/suppress the default shadow. + // 1. Virtual method � subclasses override to change/suppress the default shadow. OnInitializingShadowStyle (args); - // 2. Event external subscribers get a chance to customize. + // 2. Event � external subscribers get a chance to customize. InitializingShadowStyle?.Invoke (this, args); // 3. Apply the (potentially modified) shadow style unless already handled. diff --git a/Terminal.Gui/Views/CharMap/CharMap.cs b/Terminal.Gui/Views/CharMap/CharMap.cs index 69133b56bc..69447ac091 100644 --- a/Terminal.Gui/Views/CharMap/CharMap.cs +++ b/Terminal.Gui/Views/CharMap/CharMap.cs @@ -54,12 +54,7 @@ public class CharMap : View, IDesignable, IValue /// /// Gets or sets the default cursor style. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static CursorStyle DefaultCursorStyle - { - get => CharMapSettings.Defaults.DefaultCursorStyle; - set => CharMapSettings.Defaults.DefaultCursorStyle = value; - } + public static CursorStyle DefaultCursorStyle => CharMapSettings.Current.DefaultCursorStyle; private const int COLUMN_WIDTH = 3; // Width of each column of glyphs private const int HEADER_HEIGHT = 1; // Height of the header diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs index df991b4cc5..070071065e 100644 --- a/Terminal.Gui/Views/CheckBox.cs +++ b/Terminal.Gui/Views/CheckBox.cs @@ -24,12 +24,7 @@ public class CheckBox : View, IValue /// /// Gets or sets the default Highlight Style. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static MouseState DefaultMouseHighlightStates - { - get => CheckBoxSettings.Defaults.DefaultMouseHighlightStates; - set => CheckBoxSettings.Defaults.DefaultMouseHighlightStates = value; - } + public static MouseState DefaultMouseHighlightStates => CheckBoxSettings.Current.DefaultMouseHighlightStates; /// /// Initializes a new instance of . diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs index 5da9e0e0c9..5ef860b26a 100644 --- a/Terminal.Gui/Views/Dialog.cs +++ b/Terminal.Gui/Views/Dialog.cs @@ -64,42 +64,22 @@ public class Dialog : Dialog /// The default border style for new instances. Can be configured via /// and theme files. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static LineStyle DefaultBorderStyle - { - get => DialogSettings.Defaults.DefaultBorderStyle; - set => DialogSettings.Defaults.DefaultBorderStyle = value; - } + public static LineStyle DefaultBorderStyle => DialogSettings.Current.DefaultBorderStyle; /// /// The default button alignment for new instances. Can be configured via theme files. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Alignment DefaultButtonAlignment - { - get => DialogSettings.Defaults.DefaultButtonAlignment; - set => DialogSettings.Defaults.DefaultButtonAlignment = value; - } + public static Alignment DefaultButtonAlignment => DialogSettings.Current.DefaultButtonAlignment; /// /// The default button alignment modes for new instances. Can be configured via theme files. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static AlignmentModes DefaultButtonAlignmentModes - { - get => DialogSettings.Defaults.DefaultButtonAlignmentModes; - set => DialogSettings.Defaults.DefaultButtonAlignmentModes = value; - } + public static AlignmentModes DefaultButtonAlignmentModes => DialogSettings.Current.DefaultButtonAlignmentModes; /// /// The default shadow style for new instances. Can be configured via theme files. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static ShadowStyles DefaultShadow - { - get => DialogSettings.Defaults.DefaultShadow; - set => DialogSettings.Defaults.DefaultShadow = value; - } + public static ShadowStyles DefaultShadow => DialogSettings.Current.DefaultShadow; /// /// Helper property that gets whether the dialog was canceled (Result is or 1). diff --git a/Terminal.Gui/Views/FrameView.cs b/Terminal.Gui/Views/FrameView.cs index 68ad2940fd..8202312d84 100644 --- a/Terminal.Gui/Views/FrameView.cs +++ b/Terminal.Gui/Views/FrameView.cs @@ -40,10 +40,5 @@ public FrameView () /// This property can be set in a Theme to change the default for all /// s. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static LineStyle DefaultBorderStyle - { - get => FrameViewSettings.Defaults.DefaultBorderStyle; - set => FrameViewSettings.Defaults.DefaultBorderStyle = value; - } + public static LineStyle DefaultBorderStyle => FrameViewSettings.Current.DefaultBorderStyle; } diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs index e772bf72f0..47a43211cb 100644 --- a/Terminal.Gui/Views/HexView.cs +++ b/Terminal.Gui/Views/HexView.cs @@ -80,12 +80,7 @@ public class HexView : View, IDesignable /// /// Gets or sets the default cursor style. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static CursorStyle DefaultCursorStyle - { - get => HexViewSettings.Defaults.DefaultCursorStyle; - set => HexViewSettings.Defaults.DefaultCursorStyle = value; - } + public static CursorStyle DefaultCursorStyle => HexViewSettings.Current.DefaultCursorStyle; /// /// Gets or sets the view-specific default key bindings for . Contains only bindings diff --git a/Terminal.Gui/Views/LinearRange/LinearRangeDefaults.cs b/Terminal.Gui/Views/LinearRange/LinearRangeDefaults.cs index 04f5b7e145..11bf70e814 100644 --- a/Terminal.Gui/Views/LinearRange/LinearRangeDefaults.cs +++ b/Terminal.Gui/Views/LinearRange/LinearRangeDefaults.cs @@ -8,10 +8,5 @@ namespace Terminal.Gui.Views; public static class LinearRangeDefaults { /// Gets or sets the default cursor style applied to a new linear range view. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static CursorStyle DefaultCursorStyle - { - get => LinearRangeSettings.Defaults.DefaultCursorStyle; - set => LinearRangeSettings.Defaults.DefaultCursorStyle = value; - } + public static CursorStyle DefaultCursorStyle => LinearRangeSettings.Current.DefaultCursorStyle; } diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index 8c10d3e5f9..f8e1ea4c80 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -52,12 +52,7 @@ public class Menu : Bar, IValue /// /// Gets or sets the default Border Style for Menus. The default is . /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static LineStyle DefaultBorderStyle - { - get => MenuSettings.Defaults.DefaultBorderStyle; - set => MenuSettings.Defaults.DefaultBorderStyle = value; - } + public static LineStyle DefaultBorderStyle => MenuSettings.Current.DefaultBorderStyle; /// public Menu () : this ([]) { } diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index 905f0d5262..73e9eaa61f 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -440,19 +440,14 @@ internal set /// /// Gets or sets the default Border Style for the MenuBar. The default is . /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public new static LineStyle DefaultBorderStyle - { - get => MenuBarSettings.Defaults.DefaultBorderStyle; - set => MenuBarSettings.Defaults.DefaultBorderStyle = value; - } + public new static LineStyle DefaultBorderStyle => MenuBarSettings.Current.DefaultBorderStyle; /// The default key for activating menu bars. [ConfigurationProperty (Scope = typeof (SettingsScope))] public static Key DefaultKey { - get => MenuBarSettings.Defaults.DefaultKey; - set => MenuBarSettings.Defaults.DefaultKey = value; + get => MenuBarSettings.Current.DefaultKey; + set => MenuBarSettings.Current = MenuBarSettings.Current with { DefaultKey = value }; } /// diff --git a/Terminal.Gui/Views/Menu/PopoverMenu.cs b/Terminal.Gui/Views/Menu/PopoverMenu.cs index c8b5b0da45..e5f01c64f7 100644 --- a/Terminal.Gui/Views/Menu/PopoverMenu.cs +++ b/Terminal.Gui/Views/Menu/PopoverMenu.cs @@ -295,8 +295,8 @@ public Key Key [ConfigurationProperty (Scope = typeof (SettingsScope))] public static Key DefaultKey { - get => PopoverMenuSettings.Defaults.DefaultKey; - set => PopoverMenuSettings.Defaults.DefaultKey = value; + get => PopoverMenuSettings.Current.DefaultKey; + set => PopoverMenuSettings.Current = PopoverMenuSettings.Current with { DefaultKey = value }; } /// diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index 56fb3f873c..b7c8aeb8bb 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -57,21 +57,11 @@ public static class MessageBox /// Defines the default border styling for . Can be configured via /// . /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static LineStyle DefaultBorderStyle - { - get => MessageBoxSettings.Defaults.DefaultBorderStyle; - set => MessageBoxSettings.Defaults.DefaultBorderStyle = value; - } + public static LineStyle DefaultBorderStyle => MessageBoxSettings.Current.DefaultBorderStyle; /// The default for . /// This property can be set in a Theme. - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static Alignment DefaultButtonAlignment - { - get => MessageBoxSettings.Defaults.DefaultButtonAlignment; - set => MessageBoxSettings.Defaults.DefaultButtonAlignment = value; - } + public static Alignment DefaultButtonAlignment => MessageBoxSettings.Current.DefaultButtonAlignment; /// /// Displays an auto-sized error . diff --git a/Terminal.Gui/Views/Selectors/SelectorBase.cs b/Terminal.Gui/Views/Selectors/SelectorBase.cs index e962e9a38e..3a8a0ebeeb 100644 --- a/Terminal.Gui/Views/Selectors/SelectorBase.cs +++ b/Terminal.Gui/Views/Selectors/SelectorBase.cs @@ -25,12 +25,7 @@ public abstract class SelectorBase : View, IOrientation, IValue /// /// Gets or sets the default Highlight Style. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static MouseState DefaultMouseHighlightStates - { - get => SelectorBaseSettings.Defaults.DefaultMouseHighlightStates; - set => SelectorBaseSettings.Defaults.DefaultMouseHighlightStates = value; - } + public static MouseState DefaultMouseHighlightStates => SelectorBaseSettings.Current.DefaultMouseHighlightStates; /// /// Initializes a new instance of the class. diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index 110a0b56c0..44f973c588 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -59,12 +59,7 @@ private void OnThemeChanged (object? sender, App.EventArgs e) /// /// Gets or sets the default Line Style for the separators between the shortcuts of the StatusBar. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static LineStyle DefaultSeparatorLineStyle - { - get => StatusBarSettings.Defaults.DefaultSeparatorLineStyle; - set => StatusBarSettings.Defaults.DefaultSeparatorLineStyle = value; - } + public static LineStyle DefaultSeparatorLineStyle => StatusBarSettings.Current.DefaultSeparatorLineStyle; /// protected override void OnSubViewLayout (LayoutEventArgs args) diff --git a/Terminal.Gui/Views/TextInput/TextField/TextField.cs b/Terminal.Gui/Views/TextInput/TextField/TextField.cs index a4212617e7..c53c32023a 100644 --- a/Terminal.Gui/Views/TextInput/TextField/TextField.cs +++ b/Terminal.Gui/Views/TextInput/TextField/TextField.cs @@ -79,12 +79,7 @@ public partial class TextField : View, IDesignable, IValue /// /// Gets or sets the default cursor style. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static CursorStyle DefaultCursorStyle - { - get => TextFieldSettings.Defaults.DefaultCursorStyle; - set => TextFieldSettings.Defaults.DefaultCursorStyle = value; - } + public static CursorStyle DefaultCursorStyle => TextFieldSettings.Current.DefaultCursorStyle; /// /// Initializes a new instance of the class. diff --git a/Terminal.Gui/Views/TextInput/TextView/TextView.cs b/Terminal.Gui/Views/TextInput/TextView/TextView.cs index e3b94cab8d..f01856589b 100644 --- a/Terminal.Gui/Views/TextInput/TextView/TextView.cs +++ b/Terminal.Gui/Views/TextInput/TextView/TextView.cs @@ -104,12 +104,7 @@ public partial class TextView : View, IDesignable /// /// Gets or sets the default cursor style. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static CursorStyle DefaultCursorStyle - { - get => TextViewSettings.Defaults.DefaultCursorStyle; - set => TextViewSettings.Defaults.DefaultCursorStyle = value; - } + public static CursorStyle DefaultCursorStyle => TextViewSettings.Current.DefaultCursorStyle; private CultureInfo? _currentCulture; diff --git a/Terminal.Gui/Views/Window.cs b/Terminal.Gui/Views/Window.cs index a93b1a3301..e6a90b6213 100644 --- a/Terminal.Gui/Views/Window.cs +++ b/Terminal.Gui/Views/Window.cs @@ -33,12 +33,7 @@ public Window () /// /// Gets or sets whether all s are shown with a shadow effect by default. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static ShadowStyles DefaultShadow - { - get => WindowSettings.Defaults.DefaultShadow; - set => WindowSettings.Defaults.DefaultShadow = value; - } + public static ShadowStyles DefaultShadow => WindowSettings.Current.DefaultShadow; // TODO: enable this ///// @@ -58,10 +53,5 @@ public static ShadowStyles DefaultShadow /// This property can be set in a Theme to change the default for all /// s. /// - [ConfigurationProperty (Scope = typeof (ThemeScope))] - public static LineStyle DefaultBorderStyle - { - get => WindowSettings.Defaults.DefaultBorderStyle; - set => WindowSettings.Defaults.DefaultBorderStyle = value; - } + public static LineStyle DefaultBorderStyle => WindowSettings.Current.DefaultBorderStyle; } diff --git a/Tests/UnitTestsParallelizable/Configuration/MecBinderAccessibilitySpike.cs b/Tests/UnitTestsParallelizable/Configuration/MecBinderAccessibilitySpike.cs new file mode 100644 index 0000000000..4942cf9d37 --- /dev/null +++ b/Tests/UnitTestsParallelizable/Configuration/MecBinderAccessibilitySpike.cs @@ -0,0 +1,121 @@ +// Copilot - Claude Opus 4.7 - SPIKE (delete before mass rollout if pattern fails) + +using Microsoft.Extensions.Configuration; + +namespace ConfigurationTests; + +/// +/// Spike: validate that the MEC binder writes to { get; internal set; } properties on records used as +/// nested bind targets via the two-pass Bind(existingInstance) overlay pattern. +/// +/// +/// The sender's A2 design contract for #5416 requires immutable *Settings records with internal set, +/// atomic swap via Volatile.Write, and a two-pass MEC overlay. If the MEC binder honors only public setters +/// by default (which my read of Microsoft.Extensions.Configuration.Binder suggests), the pattern collapses +/// and the design needs revisiting. +/// +public class MecBinderAccessibilitySpike +{ + public sealed record InternalSetPoco + { + public string Name { get; internal set; } = "default-name"; + public int Count { get; internal set; } = 0; + } + + public sealed record InitOnlyPoco + { + public string Name { get; init; } = "default-name"; + public int Count { get; init; } = 0; + } + + private static IConfiguration ConfigFromJson (string json) + { + MemoryStream stream = new (); + StreamWriter writer = new (stream); + writer.Write (json); + writer.Flush (); + stream.Position = 0; + + return new ConfigurationBuilder ().AddJsonStream (stream).Build (); + } + + /// + /// Documents that { get; internal set; } properties are silently ignored by MEC's binder via + /// the default Bind(existingInstance) path. Both passes complete with no exception, but neither pass + /// mutates the POCO — both properties stay at their constructor defaults. + /// + /// + /// This contradicts the A2 design contract proposed by the source session for #5416, which prescribed + /// { get; internal set; }. The opposite holds: internal set fails; init works. + /// + [Fact] + public void TwoPassBind_InternalSet_SilentlyIgnoredByBinder () + { + IConfiguration cfg = ConfigFromJson (""" + { + "Root": { "Name": "from-root", "Count": 10 }, + "Themes": { "Dark": { "Poco": { "Count": 99 } } } + } + """); + + InternalSetPoco next = new (); + cfg.GetSection ("Root").Bind (next); + cfg.GetSection ("Themes:Dark:Poco").Bind (next); + + // Observed: ctor defaults preserved on both properties; binder never wrote. + // (Default BindingFlags = Public | Instance excludes internal accessors.) + Assert.Equal ("default-name", next.Name); + Assert.Equal (0, next.Count); + } + + /// + /// Documents that { get; init; } properties are written by MEC's Bind(existingInstance) + /// via the two-pass overlay. The root pass populates one property, a subsequent overlay pass writes a second + /// property without disturbing the first. + /// + /// + /// This is the working alternative to the failed internal set pattern. It is what the A2 manager + /// rewire should use for 's 18 nullable subsection POCOs. + /// + [Fact] + public void TwoPassBind_InitOnly_OverlaysCorrectly () + { + IConfiguration cfg = ConfigFromJson (""" + { + "Root": { "Name": "from-root", "Count": 10 }, + "Themes": { "Dark": { "Poco": { "Count": 99 } } } + } + """); + + InitOnlyPoco next = new (); + cfg.GetSection ("Root").Bind (next); + cfg.GetSection ("Themes:Dark:Poco").Bind (next); + + Assert.Equal ("from-root", next.Name); + Assert.Equal (99, next.Count); + } + + /// + /// Documents that opting into BinderOptions.BindNonPublicProperties = true rescues the + /// internal set pattern — the binder writes both properties. This is the escape hatch if the design + /// truly requires assembly-private mutability; the trade-off is an extra trim hint and a non-default code path + /// at every bind site. + /// + [Fact] + public void TwoPassBind_InternalSet_WorksWithBindNonPublicProperties () + { + IConfiguration cfg = ConfigFromJson (""" + { + "Root": { "Name": "from-root", "Count": 10 }, + "Themes": { "Dark": { "Poco": { "Count": 99 } } } + } + """); + + InternalSetPoco next = new (); + cfg.GetSection ("Root").Bind (next, o => o.BindNonPublicProperties = true); + cfg.GetSection ("Themes:Dark:Poco").Bind (next, o => o.BindNonPublicProperties = true); + + Assert.Equal ("from-root", next.Name); + Assert.Equal (99, next.Count); + } +} diff --git a/Tests/UnitTestsParallelizable/Configuration/MecSettingsTests.cs b/Tests/UnitTestsParallelizable/Configuration/MecSettingsTests.cs index 6dae2cc2e8..1434d21e85 100644 --- a/Tests/UnitTestsParallelizable/Configuration/MecSettingsTests.cs +++ b/Tests/UnitTestsParallelizable/Configuration/MecSettingsTests.cs @@ -77,18 +77,18 @@ public void CheckBoxSettings_Defaults_HasCorrectValues () public void StaticFacade_CanBeOverridden () { // Save original - ButtonSettings original = ButtonSettings.Defaults; + ButtonSettings original = ButtonSettings.Current; try { ButtonSettings custom = new () { DefaultShadow = ShadowStyles.None }; - ButtonSettings.Defaults = custom; + ButtonSettings.Current = custom; - Assert.Equal (ShadowStyles.None, ButtonSettings.Defaults.DefaultShadow); + Assert.Equal (ShadowStyles.None, ButtonSettings.Current.DefaultShadow); } finally { - ButtonSettings.Defaults = original; + ButtonSettings.Current = original; } } diff --git a/Tests/UnitTestsParallelizable/Configuration/ScopeJsonConverterTests.cs b/Tests/UnitTestsParallelizable/Configuration/ScopeJsonConverterTests.cs index b6c907962a..978bced36a 100644 --- a/Tests/UnitTestsParallelizable/Configuration/ScopeJsonConverterTests.cs +++ b/Tests/UnitTestsParallelizable/Configuration/ScopeJsonConverterTests.cs @@ -10,7 +10,9 @@ public class ScopeJsonConverterTests [InlineData ("\"Key.Separator\":\"@\"")] [InlineData ("\"Themes\":[]")] [InlineData ("\"Themes\":[{\"themeName\":{}}]")] - [InlineData ("\"Themes\":[{\"themeName\":{\"Dialog.DefaultButtonAlignment\":\"End\"}}]")] + // A2.4: dropped "Dialog.DefaultButtonAlignment" InlineData row — Dialog.DefaultButtonAlignment lost + // [ConfigurationProperty] in A2.4 when view-facade theme setters were removed; ScopeJsonConverter now + // rejects it. CM and this test are removed in step D. public void RoundTripConversion_Property_Positive (string configPropertyJson) { // Arrange diff --git a/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs b/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs index bad5ec8d3b..afbfe60067 100644 --- a/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs +++ b/Tests/UnitTestsParallelizable/Configuration/SourcesManagerTests.cs @@ -240,7 +240,7 @@ public void Load_WithNullResourceName_ReturnsFalse () Assert.False (result); } - [Fact] + [Fact (Skip = "A2.2: Glyphs lost [ConfigurationProperty]; Resources/config.json Glyphs.X flat keys are now CM-unknown. Test removed with CM in step D.")] public void Load_WithValidResource_UpdatesSettingsScope () { // Arrange @@ -260,7 +260,7 @@ public void Load_WithValidResource_UpdatesSettingsScope () // Verify settingsScope is updated as expected } - [Fact] + [Fact (Skip = "A2.2: Glyphs lost [ConfigurationProperty]; Resources/config.json Glyphs.X flat keys are now CM-unknown. Test removed with CM in step D.")] public void Load_Runtime_Overrides () { // Arrange @@ -398,7 +398,7 @@ public void Load_WithDifferentLocations_AddsAllSourcesToCollection () } } - [Fact] + [Fact (Skip = "A2.2: Glyphs lost [ConfigurationProperty]; Resources/config.json Glyphs.X flat keys are now CM-unknown. Test removed with CM in step D.")] public void Load_AddsResourceSourceToCollection () { // Arrange diff --git a/Tests/UnitTestsParallelizable/Configuration/ThemeDefinitionBindingTests.cs b/Tests/UnitTestsParallelizable/Configuration/ThemeDefinitionBindingTests.cs new file mode 100644 index 0000000000..4a8fe8a5bb --- /dev/null +++ b/Tests/UnitTestsParallelizable/Configuration/ThemeDefinitionBindingTests.cs @@ -0,0 +1,174 @@ +// Copilot - Claude Opus 4.7 + +using Microsoft.Extensions.Configuration; +using Terminal.Gui.Configuration; +using Terminal.Gui.Drawing; + +namespace ConfigurationTests; + +/// +/// Binding tests for against in-memory MEC providers. +/// +/// +/// +/// These tests validate the bind-target shape only. No production code consumes +/// yet — the consumer (rewired MecThemeManager) lands in a subsequent +/// commit. Tests use AddJsonStream against in-memory JSON, not Resources/config.json, so the +/// tests remain valid while the embedded library config keeps its legacy flat shape. +/// +/// +public class ThemeDefinitionBindingTests +{ + /// + /// A nested JSON sample with two themes — one fully-populated, one partial-override — binds to + /// Dictionary<string, ThemeDefinition>. The partial theme has s in every + /// subsection it did not mention. + /// + [Fact] + public void Bind_FullAndPartialThemes_PartialHasNullsInOmittedSubsections () + { + string json = """ + { + "Themes": { + "Full": { + "Button": { "DefaultShadow": "None" }, + "CheckBox": { }, + "Dialog": { "DefaultBorderStyle": "Single" }, + "FrameView": { }, + "HexView": { }, + "LinearRange":{ }, + "MenuBar": { }, + "Menu": { }, + "MessageBox": { "DefaultBorderStyle": "Double" }, + "NerdFonts": { }, + "PopoverMenu":{ }, + "SelectorBase":{ }, + "StatusBar": { }, + "TextField": { }, + "TextView": { }, + "Window": { "DefaultBorderStyle": "Heavy" }, + "Glyphs": { }, + "CharMap": { } + }, + "Partial": { + "Button": { "DefaultShadow": "Transparent" } + } + } + } + """; + + IConfigurationBuilder builder = new ConfigurationBuilder () + .AddJsonStream (JsonStream (json)); + IConfiguration config = builder.Build (); + + Dictionary themes = new (); + config.GetSection ("Themes").Bind (themes); + + Assert.Equal (2, themes.Count); + Assert.True (themes.ContainsKey ("Full")); + Assert.True (themes.ContainsKey ("Partial")); + + ThemeDefinition full = themes ["Full"]; + Assert.NotNull (full.Button); + Assert.Equal (ShadowStyles.None, full.Button!.DefaultShadow); + Assert.NotNull (full.Dialog); + Assert.Equal (LineStyle.Single, full.Dialog!.DefaultBorderStyle); + Assert.NotNull (full.MessageBox); + Assert.Equal (LineStyle.Double, full.MessageBox!.DefaultBorderStyle); + Assert.NotNull (full.Window); + Assert.Equal (LineStyle.Heavy, full.Window!.DefaultBorderStyle); + + ThemeDefinition partial = themes ["Partial"]; + Assert.NotNull (partial.Button); + Assert.Equal (ShadowStyles.Transparent, partial.Button!.DefaultShadow); + + // Every subsection the partial theme did not mention must be null. + Assert.Null (partial.CheckBox); + Assert.Null (partial.CharMap); + Assert.Null (partial.Dialog); + Assert.Null (partial.FrameView); + Assert.Null (partial.HexView); + Assert.Null (partial.LinearRange); + Assert.Null (partial.MenuBar); + Assert.Null (partial.Menu); + Assert.Null (partial.MessageBox); + Assert.Null (partial.NerdFonts); + Assert.Null (partial.PopoverMenu); + Assert.Null (partial.SelectorBase); + Assert.Null (partial.StatusBar); + Assert.Null (partial.TextField); + Assert.Null (partial.TextView); + Assert.Null (partial.Window); + Assert.Null (partial.Glyphs); + Assert.Null (partial.Schemes); + } + + /// + /// A nested JSON sample with Schemes as a dictionary inside a binds. This + /// test surfaces whether MEC's reflection-based binder can populate the immutable via its + /// parameterless constructor and init-only property. If this test fails it + /// signals that the manager-rewire commit will need a SchemeDefinition DTO wrapper to mediate binding. + /// + [Fact] + public void Bind_SchemesDictionaryInsideTheme_PopulatesSchemes () + { + string json = """ + { + "Themes": { + "Test": { + "Schemes": { + "Base": { }, + "Toplevel":{ } + } + } + } + } + """; + + IConfigurationBuilder builder = new ConfigurationBuilder () + .AddJsonStream (JsonStream (json)); + IConfiguration config = builder.Build (); + + Dictionary themes = new (); + config.GetSection ("Themes").Bind (themes); + + Assert.True (themes.ContainsKey ("Test")); + ThemeDefinition test = themes ["Test"]; + + Assert.NotNull (test.Schemes); + Assert.Equal (2, test.Schemes!.Count); + Assert.True (test.Schemes.ContainsKey ("Base")); + Assert.True (test.Schemes.ContainsKey ("Toplevel")); + Assert.NotNull (test.Schemes ["Base"]); + Assert.NotNull (test.Schemes ["Toplevel"]); + } + + /// + /// An empty Themes section binds to an empty dictionary without throwing. + /// + [Fact] + public void Bind_EmptyThemesSection_ProducesEmptyDictionary () + { + string json = """{ "Themes": { } }"""; + + IConfigurationBuilder builder = new ConfigurationBuilder () + .AddJsonStream (JsonStream (json)); + IConfiguration config = builder.Build (); + + Dictionary themes = new (); + config.GetSection ("Themes").Bind (themes); + + Assert.Empty (themes); + } + + private static Stream JsonStream (string json) + { + MemoryStream stream = new (); + StreamWriter writer = new (stream); + writer.Write (json); + writer.Flush (); + stream.Position = 0; + + return stream; + } +} diff --git a/Tests/UnitTestsParallelizable/Configuration/ThemeOverlayMergeTests.cs b/Tests/UnitTestsParallelizable/Configuration/ThemeOverlayMergeTests.cs new file mode 100644 index 0000000000..38be800a3c --- /dev/null +++ b/Tests/UnitTestsParallelizable/Configuration/ThemeOverlayMergeTests.cs @@ -0,0 +1,132 @@ +// Copilot - Claude Opus 4.7 + +using Terminal.Gui.Configuration; + +namespace ConfigurationTests; + +/// +/// End-to-end tests for the A2.1 two-pass MEC theme overlay applied through +/// . +/// +/// +/// +/// The contract under test: BindThemeScope<T> binds the root section first, then overlays +/// Themes:{active}:{section}. Properties present only in the root must survive; properties present +/// in the overlay must win. This mirrors legacy CM Scope.Apply property-level merge semantics. +/// +/// +public class ThemeOverlayMergeTests +{ + /// + /// When the theme overlay only mentions one property of a ThemeScope POCO, the other properties keep their + /// root-section values (not the compile-time defaults, not ). + /// + [Fact] + public void ApplyToStaticFacades_ThemeOverlay_PreservesRootDefaultsForUnmentionedProperties () + { + DialogSettings originalDialog = DialogSettings.Current; + ThemeSettings originalTheme = ThemeSettings.Defaults; + + try + { + TuiConfigurationBuilder tuiBuilder = new (); + + tuiBuilder.RuntimeConfig = """ + { + "Theme": { "Theme": "Custom" }, + "Dialog": { + "DefaultShadow": "Opaque", + "DefaultBorderStyle": "Double", + "DefaultButtonAlignment": "Start" + }, + "Themes": { + "Custom": { + "Dialog": { + "DefaultBorderStyle": "Single" + } + } + } + } + """; + + tuiBuilder.ApplyToStaticFacades (); + + Assert.Equal (LineStyle.Single, DialogSettings.Current.DefaultBorderStyle); + Assert.Equal (ShadowStyles.Opaque, DialogSettings.Current.DefaultShadow); + Assert.Equal (Alignment.Start, DialogSettings.Current.DefaultButtonAlignment); + } + finally + { + DialogSettings.Current = originalDialog; + ThemeSettings.Defaults = originalTheme; + } + } + + /// + /// When no theme overlay exists for a POCO, the root section's values are applied verbatim. + /// + [Fact] + public void ApplyToStaticFacades_NoOverlay_UsesRootValuesAsIs () + { + ButtonSettings originalButton = ButtonSettings.Current; + ThemeSettings originalTheme = ThemeSettings.Defaults; + + try + { + TuiConfigurationBuilder tuiBuilder = new (); + + tuiBuilder.RuntimeConfig = """ + { + "Theme": { "Theme": "Custom" }, + "Button": { + "DefaultShadow": "None" + }, + "Themes": { "Custom": { } } + } + """; + + tuiBuilder.ApplyToStaticFacades (); + + Assert.Equal (ShadowStyles.None, ButtonSettings.Current.DefaultShadow); + } + finally + { + ButtonSettings.Current = originalButton; + ThemeSettings.Defaults = originalTheme; + } + } + + /// + /// The atomic-swap pattern produces a new reference on each apply, never + /// mutates the existing instance in place. A reader that captured the prior reference still sees the prior + /// values. + /// + [Fact] + public void ApplyToStaticFacades_AtomicSwap_DoesNotMutatePriorReference () + { + ButtonSettings originalButton = ButtonSettings.Current; + ThemeSettings originalTheme = ThemeSettings.Defaults; + + try + { + TuiConfigurationBuilder tuiBuilder = new (); + tuiBuilder.RuntimeConfig = """{ "Button": { "DefaultShadow": "Transparent" } }"""; + tuiBuilder.ApplyToStaticFacades (); + + ButtonSettings captured = ButtonSettings.Current; + Assert.Equal (ShadowStyles.Transparent, captured.DefaultShadow); + + tuiBuilder.RuntimeConfig = """{ "Button": { "DefaultShadow": "None" } }"""; + tuiBuilder.ApplyToStaticFacades (); + + Assert.Equal (ShadowStyles.Transparent, captured.DefaultShadow); + Assert.Equal (ShadowStyles.None, ButtonSettings.Current.DefaultShadow); + Assert.NotSame (captured, ButtonSettings.Current); + } + finally + { + ButtonSettings.Current = originalButton; + ThemeSettings.Defaults = originalTheme; + } + } +} diff --git a/Tools/MigrateConfig/MigrateConfig.csproj b/Tools/MigrateConfig/MigrateConfig.csproj new file mode 100644 index 0000000000..7e312839d0 --- /dev/null +++ b/Tools/MigrateConfig/MigrateConfig.csproj @@ -0,0 +1,18 @@ + + + + Exe + net10.0 + enable + enable + Terminal.Gui.Tools.MigrateConfig + migrate-tui-config + + false + + + diff --git a/Tools/MigrateConfig/Program.cs b/Tools/MigrateConfig/Program.cs new file mode 100644 index 0000000000..17210851d2 --- /dev/null +++ b/Tools/MigrateConfig/Program.cs @@ -0,0 +1,208 @@ +// Migrates a pre-MEC Terminal.Gui config.json (flat-key, array-themes shape) +// to the nested MEC-native shape consumed by TuiConfigurationBuilder. +// +// Transforms applied (recursively): +// 1. Any object property name containing '.' is split into nested objects. +// "Button.DefaultShadow": "Opaque" -> "Button": { "DefaultShadow": "Opaque" } +// 2. "Themes" as an array of single-key objects becomes a dictionary. +// "Themes": [ { "Dark": { ... } } ] -> "Themes": { "Dark": { ... } } +// 3. "Schemes" inside a theme follows the same array -> dictionary collapse. +// +// Usage: +// migrate-tui-config [output.json] +// +// If output.json is omitted, the migrated JSON is written to stdout. +// Exit codes: 0 success, 1 usage error, 2 I/O or JSON parse error. + +using System.Text.Encodings.Web; +using System.Text.Json; +using System.Text.Json.Nodes; + +namespace Terminal.Gui.Tools.MigrateConfig; + +internal static class Program +{ + private static int Main (string [] args) + { + if (args.Length is < 1 or > 2) + { + Console.Error.WriteLine ("Usage: migrate-tui-config [output.json]"); + Console.Error.WriteLine ("If output.json is omitted, the result is written to stdout."); + + return 1; + } + + string inputPath = args [0]; + string? outputPath = args.Length == 2 ? args [1] : null; + + string text; + + try + { + text = File.ReadAllText (inputPath); + } + catch (Exception ex) + { + Console.Error.WriteLine ($"Could not read \"{inputPath}\": {ex.Message}"); + + return 2; + } + + JsonNodeOptions nodeOpts = new () { PropertyNameCaseInsensitive = false }; + + JsonDocumentOptions docOpts = new () + { + CommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true + }; + + JsonNode? root; + + try + { + root = JsonNode.Parse (text, nodeOpts, docOpts); + } + catch (JsonException ex) + { + Console.Error.WriteLine ($"Could not parse \"{inputPath}\" as JSON: {ex.Message}"); + + return 2; + } + + if (root is not JsonObject rootObj) + { + Console.Error.WriteLine ("Top-level JSON value is not an object; cannot migrate."); + + return 2; + } + + JsonObject migrated = MigrateObject (rootObj); + + JsonSerializerOptions writeOpts = new () + { + WriteIndented = true, + Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping + }; + + string output = migrated.ToJsonString (writeOpts); + + if (outputPath is null) + { + Console.WriteLine (output); + } + else + { + try + { + File.WriteAllText (outputPath, output); + Console.Error.WriteLine ($"Migrated \"{inputPath}\" -> \"{outputPath}\"."); + } + catch (Exception ex) + { + Console.Error.WriteLine ($"Could not write \"{outputPath}\": {ex.Message}"); + + return 2; + } + } + + return 0; + } + + private static JsonObject MigrateObject (JsonObject src) + { + JsonObject result = []; + + foreach (KeyValuePair pair in src) + { + JsonNode? value = pair.Value is null ? null : Clone (pair.Value); + JsonNode? migratedValue = MigrateValue (pair.Key, value); + + MergeDottedKey (result, pair.Key, migratedValue); + } + + return result; + } + + private static JsonNode? MigrateValue (string keyName, JsonNode? value) + { + if (value is JsonObject obj) + { + return MigrateObject (obj); + } + + if (value is JsonArray arr && IsArrayOfSingleKeyObjects (arr) && IsArrayDictKey (keyName)) + { + JsonObject dict = []; + + foreach (JsonNode? item in arr) + { + if (item is not JsonObject itemObj) + { + continue; + } + + foreach (KeyValuePair entry in itemObj) + { + JsonNode? entryValue = entry.Value is null ? null : MigrateValue (entry.Key, Clone (entry.Value)); + dict [entry.Key] = entryValue; + } + } + + return dict; + } + + return value; + } + + private static bool IsArrayDictKey (string keyName) => + keyName is "Themes" or "Schemes"; + + private static bool IsArrayOfSingleKeyObjects (JsonArray arr) + { + if (arr.Count == 0) + { + return false; + } + + foreach (JsonNode? item in arr) + { + if (item is not JsonObject obj || obj.Count != 1) + { + return false; + } + } + + return true; + } + + private static void MergeDottedKey (JsonObject target, string key, JsonNode? value) + { + if (!key.Contains ('.')) + { + target [key] = value; + + return; + } + + string [] parts = key.Split ('.'); + JsonObject cursor = target; + + for (var i = 0; i < parts.Length - 1; i++) + { + string part = parts [i]; + + if (cursor [part] is not JsonObject next) + { + next = []; + cursor [part] = next; + } + + cursor = next; + } + + cursor [parts [^1]] = value; + } + + private static JsonNode Clone (JsonNode node) => + JsonNode.Parse (node.ToJsonString ())!; +} diff --git a/Tools/MigrateConfig/README.md b/Tools/MigrateConfig/README.md new file mode 100644 index 0000000000..1c7cf6d05e --- /dev/null +++ b/Tools/MigrateConfig/README.md @@ -0,0 +1,79 @@ +# migrate-tui-config + +Migrates a pre-MEC Terminal.Gui `config.json` (flat-key, array-themes +shape) to the nested shape consumed by `TuiConfigurationBuilder`. + +## When you need this + +Terminal.Gui v2.x originally used a flat-key `config.json` format: + +```json +{ + "Button.DefaultShadow": "Opaque", + "Themes": [ + { "Dark": { "Glyphs.CheckStateChecked": "☑" } } + ] +} +``` + +Starting in the release that ships PR #5416, the library only reads +the nested MEC-native shape: + +```json +{ + "Button": { "DefaultShadow": "Opaque" }, + "Themes": { + "Dark": { "Glyphs": { "CheckStateChecked": "☑" } } + } +} +``` + +Files in the old shape are detected at load time and ignored with a +`WARN` log; their settings fall through to defaults. This tool produces +a migrated copy you can drop in place. + +## Usage + +```bash +# Write to a new file +dotnet run --project Tools/MigrateConfig -- ./.tui/config.json ./.tui/config.migrated.json + +# Or pipe to stdout +dotnet run --project Tools/MigrateConfig -- ./.tui/config.json +``` + +Exit codes: + +- `0` — success +- `1` — usage error (wrong number of arguments) +- `2` — I/O or JSON parse error + +## What it does + +Three transforms applied recursively: + +1. Property names containing `.` are split into nested objects. + `"Button.DefaultShadow": "Opaque"` becomes + `"Button": { "DefaultShadow": "Opaque" }`. +2. `"Themes"` arrays of single-key objects become dictionaries. + `"Themes": [{"Dark": {...}}]` becomes `"Themes": {"Dark": {...}}`. +3. `"Schemes"` arrays inside a theme get the same collapse. + +JSON comments and trailing commas in the input are tolerated but +**not** preserved in the output — re-add hand-edited comments after +migration. + +## What it doesn't do + +- It does not validate that the resulting JSON binds cleanly to the + v2.x Settings POCOs. Run your app afterwards and watch for any + `OptionsValidationException`. +- It does not preserve property order beyond what `System.Text.Json` + guarantees for a `JsonObject`. + +## Lifecycle + +This tool is not part of the shipping `Terminal.Gui` library and is +not included in `Terminal.slnx`. It exists to ease the one-time +migration in the release that drops the legacy shape. It will be +removed in a future release once the upgrade window is past. diff --git a/Tools/README.md b/Tools/README.md new file mode 100644 index 0000000000..66494a5333 --- /dev/null +++ b/Tools/README.md @@ -0,0 +1,28 @@ +# Tools/ + +Standalone utilities that are **not** part of the shipping `Terminal.Gui` +library and **not** part of the `Examples/` set users learn the library +from. + +## What lives here + +| Tool | Purpose | Shipped? | +|------|---------|----------| +| [`MigrateConfig/`](./MigrateConfig/) | Converts a pre-MEC flat-key `config.json` to the nested MEC schema | No — standalone console app, run on demand | + +## Conventions + +- Each tool gets its own folder + `.csproj` + `README.md`. +- Tool csprojs are **not** added to `Terminal.slnx`. They are built + on demand (`dotnet run --project Tools//`). +- Tools are not user-facing examples. If you want to demonstrate + Terminal.Gui idioms, add to `Examples/` instead. +- Tools may be deleted in any release without a deprecation cycle. + +## Why a separate folder? + +Without this folder, one-off utilities tend to land either inside +`Terminal.Gui.dll` (bloating AOT output) or inside `Examples/` (where +they mislead newcomers into thinking they're recommended app patterns). +The `Tools/` convention is also used by `dotnet/roslyn`, +`dotnet/runtime`, and `dotnet/aspnetcore`. diff --git a/specs/remove-legacy-cm-prep.md b/specs/remove-legacy-cm-prep.md new file mode 100644 index 0000000000..ff2921a97b --- /dev/null +++ b/specs/remove-legacy-cm-prep.md @@ -0,0 +1,223 @@ +# CM Removal — Pre-Work Inventories & Baselines + +Companion artifact for [`remove-legacy-cm.md`](./remove-legacy-cm.md). +Captures the empirical "before" state used by phases **G** (examples sweep), +**E** (test deletion), and **I** (AOT measurement) so the eventual delta +claim is reproducible. + +Generated against base SHA `83ded73aa` (`origin/copilot/replace-cm-with-mec`, +post Phase A1/B/C of #5411). My branch is rebased onto this commit; spec +commit is `f122ebe6a`. + +--- + +## 1. AOT Size Baseline (Phase I) + +Command: `dotnet publish Examples/NativeAot/NativeAot.csproj -c Release -r win-x64` +Platform: Windows x64, .NET 10 SDK, clean `bin/obj` before publish. + +| Artifact | Bytes | MB | +|--------------------------------|--------------|--------| +| `NativeAot.exe` (AOT, single) | 23,873,536 | 22.77 | +| `Terminal.Gui.dll` (Release) | 1,854,464 | 1.77 | +| Publish dir total | 96,319,509 | 91.86 | + +### Reflection-rooting metric + +`Terminal.Gui/Configuration/ConfigPropertyHostTypes.cs` currently roots +**31** types via `[DynamicDependency]`. Deleting this file in Phase E +should drop those 31 root anchors and let the AOT trimmer collect their +unused members. This is the headline number for the "AOT size reduction" +claim in #5416's PR description. + +### Reproducing this number after Phase E + +```powershell +Remove-Item Examples/NativeAot/bin, Examples/NativeAot/obj -Recurse -Force -EA 0 +Remove-Item Terminal.Gui/bin, Terminal.Gui/obj -Recurse -Force -EA 0 +dotnet publish Examples/NativeAot/NativeAot.csproj -c Release -r win-x64 +Get-Item Examples/NativeAot/bin/Release/net10.0/win-x64/publish/NativeAot.exe | + Select-Object Name, Length +``` + +Record the new `NativeAot.exe` byte count and the delta vs. 23,873,536. +A meaningful win should be ≥ 1 MB given the 31 rooted types include +view types with large transitive surfaces (`MenuBar`, `StatusBar`, +`TableView`, `TreeView`, `Dialog`, etc.). + +--- + +## 2. Examples Inventory (Phase G) + +Total `Examples/` files touching CM: **~115 source/doc files**, but +**99% are the same one-line incantation** — the actual rewrite surface +is small. + +### 2.1 Dominant pattern (~105 occurrences, mostly UICatalog scenarios) + +Single call in scenario constructor / `Main`: + +```csharp +ConfigurationManager.Enable (ConfigLocations.All); +``` + +**Replacement:** Replace with MEC equivalent (likely +`Application.Create ().UseTuiConfiguration ().Init ()` or whatever the +final builder shape settles on — see spec §5.6). + +Files using only this pattern (mechanical sed, no review needed): +all `Examples/UICatalog/Scenarios/*.cs` except the four below. + +### 2.2 Non-trivial scenarios needing review + +| File | Lines | What it does | Replacement notes | +|------|-------|--------------|-------------------| +| `Examples/UICatalog/UICatalogRunnable.cs` | 30, 131, 203, 715 | Subscribes to `ConfigurationManager.Applied`; reads `IsEnabled`; uses `[ConfigurationProperty (Scope = typeof (AppSettingsScope), OmitClassName = true)]` on a settings field | Subscribe to `ThemeChanges.ThemeChanged` (already exists post-#5411); move `[ConfigurationProperty]` to a POCO bound via `IOptionsMonitor` | +| `Examples/UICatalog/Runner.cs` | 14, 39, 373, 378, 379 | Sets `RuntimeConfig` JSON string for driver overrides; explicit `Load + Apply`; bridges `ThemeManager.ThemeChanged → CM.Apply` | Build an in-memory `IConfigurationSource` for the driver overrides; drop the bridge (MEC reload handles it) | +| `Examples/UICatalog/Scenarios/ConfigurationEditor.cs` | 23, 51, 75, 222, 227, 232, 247 | The CM editor scenario itself — enumerates `SourcesManager.Sources`, edits `RuntimeConfig`, calls `GetHardCodedConfig`/`GetEmptyConfig` | Rewrite to walk `IConfigurationRoot.Providers` and edit a designated `IConfigurationSource`. **This scenario is the heaviest item in Phase G.** | +| `Examples/UICatalog/Scenarios/Themes.cs` | 89 | `ConfigurationManager.Apply ()` after a theme switch | Drop the call (MEC reload-on-change) | +| `Examples/UICatalog/Scenarios/ThemeFallback.cs` | 73 | Same pattern | Drop the call | +| `Examples/UICatalog/Scenarios/CodeViewDemo.cs` | 153 | Same pattern | Drop the call | +| `Examples/UICatalog/Scenarios/TextInputControls.cs` | 384 | `ConfigurationManager.Applied += ...` | Subscribe to `ThemeChanges.ThemeChanged` | +| `Examples/UICatalog/Scenarios/Shortcuts.cs` | 345 | Reads `ConfigurationManager.IsEnabled` | Drop the check (MEC is always on or behind a builder opt-in) | +| `Examples/UICatalog/UICatalog.cs` | 296 | `Enable` call at app startup | Replace with builder call | +| `Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs` | 57 | `Enable` call | Replace | + +### 2.3 Standalone examples (each has one `Enable` call) + +`Example.cs`, `SelfContained/Program.cs`, `CommunityToolkitExample/Program.cs`, +`NativeAot/Program.cs`, `ShortcutTest.cs`, `ReactiveExample/Program.cs`, +`PromptExample/Program.cs`, `ScenarioRunner/Program.cs` (2 calls). + +`Examples/Example/Example.cs:13` additionally sets `RuntimeConfig` +to a JSON literal — needs the same in-memory-source treatment as +`Runner.cs`. + +### 2.4 Docs / READMEs to update (Phase H) + +`Examples/Config/README.md`, `Examples/CommunityToolkitExample/README.md`, +`Examples/SelfContained/README.md`, `Examples/ReactiveExample/README.md`, +`Examples/NativeAot/README.md` — all reference the legacy `Enable` +incantation. + +`Examples/Config/example_config.json` — uses the legacy flat +`"ConfigurationManager.ThrowOnJsonErrors"` key. Migrate when D-02 +lands. + +### 2.5 AOT-blocker note + +`Examples/NativeAot/README.md` explicitly documents `ConfigurationManager.Initialize`, +`DeepCloner`, `SourceGenerationContext` as the reasons AOT works the way +it does today. After CM removal, this README needs a full rewrite — the +trim warnings should largely disappear. + +--- + +## 3. Test Inventory (Phase E) + +Total Configuration tests across all test projects: **25 files**. + +### 3.1 DELETE — pure CM/scope/Initialize tests (8 files) + +| File | Project | CM refs | +|------|---------|---------| +| `ConfigurationMangerTests.cs` | NonParallelizable | 49 | +| `ConfigPropertyAssemblyScanTests.cs` | NonParallelizable | 9 | +| `SourcesManagerLoadNullJsonTests.cs` | NonParallelizable | 5 | +| `GlyphTests.cs` | NonParallelizable | uses `static CM` import, `RuntimeConfig`, `ThemeManager.GetCurrentTheme()` | +| `SourcesManagerTests.cs` | Parallelizable | 69 | +| `ScopeTests.cs` | Parallelizable | 18 | +| `ScopeJsonConverterTests.cs` | Parallelizable | 5 | +| `SettingsScopeTests.cs` / `ThemeScopeTests.cs` | Parallelizable | 2 / 4 | +| `ConfigPropertyTests.cs` / `ConfigPropertyHostTypesTests.cs` / `ConfigurationPropertyAttributeTests.cs` | Parallelizable | 10 / 10 / 2 | +| `DeepClonerTests.cs` | Parallelizable | 7 | + +(That's 13 files counted, not 8 — the 8/13 split depends on whether +glyph behavior gets re-tested against MEC; see §3.3 *port* row.) + +### 3.2 KEEP — JSON converter tests still valid against `TuiSerializerContext` (8 files) + +These test converter behavior, not CM machinery. The converters +themselves are kept (per spec §4.2) and now wire through +`TuiSerializerContext`. + +- `AttributeJsonConverterTests.cs` +- `ColorJsonConverterTests.cs` +- `ConcurrentDictionaryJsonConverterTests.cs` +- `KeyJsonConverterTests.cs` +- `KeyCodeJsonConverterTests.cs` +- `RuneJsonConverterTests.cs` +- `SchemeJsonConverterTests.cs` *(verify it doesn't depend on `ThemeScope` resolution)* +- `MemorySizeEstimator.cs` *(helper, not a test)* + +### 3.3 KEEP — already MEC-based (3 files) + +- `MecThemeTests.cs` +- `MecSettingsTests.cs` +- `MecAppSettingsTests.cs` +- `SchemeManagerTests.cs` *(verify which manager — likely MEC since legacy `SchemeManager` is `[Obsolete]`)* + +### 3.4 PORT — behaviors that should survive as MEC tests + +The `GlyphTests` "Apply over defaults" behavior is a real +user-observable contract: when you override a glyph in `config.json`, +`Glyphs.LeftBracket` reflects it. After deletion, add an equivalent +test against the MEC pipeline (load JSON → assert `Glyphs.*`). + +Same applies to any `ConfigurationMangerTests` case that asserts +end-to-end behavior (file-load → apply → property-reflects) that isn't +already covered by `MecSettingsTests` / `MecThemeTests`. A line-by-line +audit is required in Phase E — not in this prep doc. + +### 3.5 Benchmarks (Phase E) + +| File | Action | +|------|--------| +| `Tests/Benchmarks/Configuration/ConfigurationManagerLoadBenchmark.cs` | **Port** to measure `Application.Create().UseTuiConfiguration().Init()` cold-start, or delete if MEC startup is well-characterized elsewhere | +| `Tests/Benchmarks/Configuration/ThemeSwitchBenchmark.cs` | **Port** to measure `IThemeManager.Theme = "X"` (which triggers `ThemeChanges.ThemeChanged`) — this is still the same user-facing operation | + +These benchmarks are the only quantitative basis for any "startup is +faster after CM removal" claim, so porting (not deleting) is preferred. + +### 3.6 Cross-test-project sanity check + +One outlier hit in +`Tests/UnitTestsParallelizable/Input/Keyboard/CommandInsertCaretKeyBindingTests.cs` +matched the broad regex but contains no actual CM references — false +positive (likely `Settings` substring). No action. + +--- + +## 4. What This Prep Did NOT Touch + +- D-02 (`config.json` shape decision) — still gated on source-session input. +- `TuiConfigMigrator` design — blocked on D-02. +- Migration guide draft — deferred until D-02 resolves so the user-facing + examples are accurate. +- Phase A2 (Mec managers own theme/scheme data) — owned by the source + session per the last cross-session sync. + +--- + +## 5. Next Executable Step (now unblocked) + +D-02 resolved to **α-lite** (detect + warn; no library-side migration). +See `remove-legacy-cm.md` §5.4 and Phase D for the full scope. Concretely: + +1. Rewrite `Terminal.Gui/Resources/config.json` once (flat → nested, + array-themes → dict-themes). +2. Add ~20 LOC peek-and-warn to `TuiConfigurationBuilder.AddTuiJsonFile` + (detect top-level keys containing `.` or `Themes` as a JSON array; + emit one `WARN` log per offending file). +3. Delete `Terminal.Gui/Configuration/ScopeJsonConverter.cs` — nothing + left in the library reads the legacy shape. +4. Wire `JsonConfigurationSource.OnLoadException → TuiJsonErrors` + aggregator in `TuiConfigurationExtensions` (spec §5.7). +5. Rewrite every `Examples/.../config.json` to nested. +6. Add `Tools/MigrateConfig/` console app (~50 LOC, separate csproj, + not in any shipping solution). +7. Two parallelizable tests: one asserts the warning fires on a + flat-key sample, one on an array-themes sample. **No translation + logic to test.** + +Once Phase D lands, Phase A2 (Mec managers consume `IOptionsMonitor.CurrentValue` directly) unblocks. diff --git a/specs/remove-legacy-cm.md b/specs/remove-legacy-cm.md new file mode 100644 index 0000000000..e06b376e99 --- /dev/null +++ b/specs/remove-legacy-cm.md @@ -0,0 +1,487 @@ +# Spec: Remove Legacy ConfigurationManager (Phase 4–6 of CM → MEC) + +> **Status:** Draft — follow-up to PR #5411 (`copilot/replace-cm-with-mec`). +> **Tracking Issue:** [#4943](https://github.com/gui-cs/Terminal.Gui/issues/4943) +> **PR:** [#5416 — `tig/remove-cm-followup`](https://github.com/gui-cs/Terminal.Gui/pull/5416) (stacked on `copilot/replace-cm-with-mec`) +> **Predecessor Spec:** [`specs/replace-cm-with-mec.md`](./replace-cm-with-mec.md) + +--- + +## 1. Purpose + +PR #5411 completed the **functional** CM → MEC migration plus the cross-cutting cleanup that did not require deleting CM itself: + +- Per‑component Settings POCOs with static `Defaults` facades. +- `TuiConfigurationBuilder` + `TuiConfigurationExtensions` providing the MEC-based source chain. +- `IThemeManager` / `ISchemeManager` services. `IThemeManager.ThemeChanged` exists and is wired (A1 done). +- `Terminal.Gui.Configuration.ThemeChanges` static facade — bridges both `ConfigurationManager.Applied` (because `CM.Apply()` writes `ConfigProperty` values directly, bypassing C# setters) and `IThemeManager.ThemeChanged` into a single observer-friendly event. All four internal view subscribers (`Menu`, `MenuBar`, `StatusBar`, `LineCanvas`) have migrated off `ConfigurationManager.Applied`. +- `Terminal.Gui.Configuration.TuiSerializerContext` — internal, non-obsolete holder of the configured `SourceGenerationContext` instance with all custom converters and JSON options. All 7 internal JSON converter consumers reference `TuiSerializerContext.Instance` directly; their `#pragma warning disable CS0618` blocks are gone. `ConfigurationManager.SerializerContext` is now a one-line delegator. +- All `[ConfigurationProperty(...)]` attributes removed from production view/option properties (only the attribute *class* still exists). +- `ConfigurationManager`, `SourcesManager`, `ConfigProperty`, `Scope`, `DeepCloner`, `ScopeJsonConverter` marked `[Obsolete]` and only referenced internally during the transition. + +This PR finishes the migration by **deleting** every legacy CM type, **migrating** the embedded `Resources/config.json` to a MEC-compatible shape, **promoting** `MecThemeManager`/`MecSchemeManager` from event-bridging wrappers to data owners (A2), and **removing** all CM-specific tests. The result is the AOT size reduction and parallel-test unlock that motivated #4943. + +This corresponds to **Phases 4, 5, and 6** of the predecessor spec, executed as a single stacked PR. See the `Phase 3A.x — Internal subscriber rewiring (landed in #5411)` subsection of [`specs/replace-cm-with-mec.md`](./replace-cm-with-mec.md) for the authoritative record of what shipped in the parent PR. + +--- + +## 2. Goals + +1. **Complete Phase A2** — Migrate runtime theme/scheme data ownership from `ConfigurationManager.Settings["Themes"]` (parsed by `ScopeJsonConverter`) into `IOptionsMonitor`. This is the gating prerequisite for deleting `ScopeJsonConverter` and is tightly coupled to the D-02 resource-shape decision (see §5.4). +2. **Delete every legacy CM type and attribute** (no `[Obsolete]` shim period — they were already shimmed in #5411). +3. **Eliminate CM-related anti-trim patterns**: `ConfigPropertyHostTypes.GetTypes()`, `[DynamicDependency]` on 29 host types, `Assembly.GetTypes()` reflection scan, `DeepCloner` reflection, `MakeGenericType` usage. +4. **Migrate the embedded `Terminal.Gui/Resources/config.json`** from the legacy flat-key `Themes: [ { Name: { "Class.Prop": ... } } ]` layout to a MEC-readable shape. Either nested-only with a one-release migration helper, **or** a custom MEC source that parses the legacy shape directly — see D-02 in §5.4. +5. **Delete the four-line `ConfigurationManager.Applied` bridge** inside `ThemeChanges` once `ConfigurationManager` is gone. Keep `ThemeChanges` itself as the supported public observer facade. +6. **Delete the `ConfigurationManager.SerializerContext` delegator** (one field). All real consumers already reference `TuiSerializerContext.Instance`. +7. **Delete all CM-only tests** (`ConfigurationMangerTests`, `ConfigPropertyAssemblyScanTests`, `ConfigPropertyTests`, `ConfigPropertyHostTypesTests`, `DeepClonerTests`, `ScopeJsonConverterTests`, `ScopeTests`, `SettingsScopeTests`, `ThemeScopeTests`, `ConfigurationPropertyAttributeTests`, legacy `SourcesManagerTests`). Keep MEC equivalents. +8. **Measure and record the NativeAOT binary-size delta** in PR #5416 so the stated motivation in #4943 is verifiable. +9. **Update `docfx/docs/config.md`** so the documented contract matches the shipped contract (Constitution: *Documentation Is the Spec*). + +### Non‑goals + +- Generic Host / `IHostBuilder` adoption (deferred, Q‑02). +- View constructor injection of `IOptionsMonitor` (deferred — static `Defaults` facade remains per D‑01). +- Multi-instance `IApplication` scoping of themes (D‑03). +- Lazy driver registration / assembly splitting / TextMate conditional compilation (out of scope per #10 of predecessor spec). + +--- + +## 3. Constitution Alignment + +| Tenet | How this PR aligns | +|-------|--------------------| +| ✅ **Testability First** | Removing CM static state moves every theme/scheme test into `UnitTestsParallelizable`. | +| ✅ **Performance Is a Feature** | Removes the `Assembly.GetTypes()` scan + DeepCloner reflection at module init. Hot path (`Rune` read on a glyph, `ShadowStyles` read on Button) becomes a direct field read on a POCO. | +| ✅ **Users Have Final Control** | All configurable surfaces stay configurable. Only the *internal mechanism* changes; the documented public configuration model is MEC. | +| ✅ **Separation of Concerns** | Views no longer reach into a process-wide static (`ConfigurationManager.Settings["Foo"]`). They observe `IOptionsMonitor` (via the static `Defaults` facade for now). | +| ✅ **Respect What Came Before** | The 9‑level precedence semantics are preserved through the MEC provider chain assembled in `TuiConfigurationExtensions`. The user-facing JSON keeps the same *shape* aside from the flat→nested key migration. | +| 🔴 **Documentation Is the Spec** | `docfx/docs/config.md` is rewritten in this PR; no out-of-date references to `ConfigurationManager.Enable`, `RuntimeConfig`, `Applied` event, etc. | + +--- + +## 4. Current Residual Surface (post‑#5411) + +The exhaustive inventory of what still has to go. Discovered by `grep`ping the worktree against `copilot/replace-cm-with-mec` HEAD. + +### 4.1 Types to **delete** entirely (`Terminal.Gui/Configuration/`) + +| File | Notes | +|------|-------| +| `ConfigurationManager.cs` (~33 KB) | Static facade; obsolete | +| `ConfigurationManagerEventArgs.cs` | `Applied`/`Updated` event args | +| `ConfigurationManagerNotEnabledException.cs` | No "enabled" concept in MEC | +| `ConfigurationPropertyAttribute.cs` | Attribute already unused on production properties | +| `ConfigProperty.cs` (~28 KB) | Reflection-discovered config property descriptor | +| `ConfigPropertyHostTypes.cs` | `[DynamicDependency]` rooting of 29 host types | +| `ConfigLocations.cs` | Replaced by MEC provider ordering in `TuiConfigurationExtensions` | +| `DeepCloner.cs` (~19 KB) | Reflection-based deep clone; not needed once POCOs replace `Scope` | +| `Scope.cs`, `SettingsScope.cs`, `AppSettingsScope.cs`, `ThemeScope.cs` | Scope abstractions superseded by typed `IOptions` | +| `ScopeJsonConverter.cs` | Flat-key scope deserializer | +| `DictionaryJsonConverter.cs`, `ConcurrentDictionaryJsonConverter.cs` | Only needed by the flat-key scope format | +| `KeyArrayJsonConverter.cs`, `KeyCodeJsonConverter.cs`, `MouseFlagsArrayJsonConverter.cs` | Audit — only delete if unused after migration; otherwise keep and register on the MEC-side `JsonSerializerOptions` | + +### 4.2 Types to **keep** (already MEC-shaped) and harden + +| File | Action | +|------|--------| +| `TuiSerializerContext.cs` | **Already exists post-#5411.** No change. This is the canonical JSON context for the MEC era. | +| `SourceGenerationContext.cs` | Keep. Re-audit `[JsonSerializable]` entries — remove any that reference `SettingsScope` / `ThemeScope` / `AppSettingsScope`. Add `ThemeSettings` and the per-component settings POCOs. | +| `ThemeChanges.cs` | **Already exists post-#5411.** Keep as the supported observer facade. In this PR: remove the `ConfigurationManager.Applied` bridge inside it (one branch of an `OR`) once `ConfigurationManager` is deleted. | +| `AttributeJsonConverter.cs`, `SchemeJsonConverter.cs`, `RuneJsonConverter.cs`, `KeyJsonConverter.cs`, `ColorJsonConverter.cs`, `TraceCategoryJsonConverter.cs`, `DictionaryJsonConverter.cs`, `ConcurrentDictionaryJsonConverter.cs` | Keep. **Already point at `TuiSerializerContext.Instance` post-#5411.** No further work. | +| `Settings/*Settings.cs` (all 30+) | Keep. These are the POCOs that became the source of truth in #5411. **Pattern divergence (A2.1+):** ThemeScope POCOs (the 17/18 bind targets of `BindThemeScope`) are immutable `sealed record` + `Default`/`Current` with `Volatile`-swapped atomic publish; SettingsScope POCOs remain mutable `Defaults`. Rationale: only ThemeScope participates in theme overlay merge; SettingsScope is bound once at app start and never per-theme. | +| `Settings/TuiConfigurationBuilder.cs` | Keep. Becomes the **only** configuration entry point. | +| `Settings/TuiConfigurationExtensions.cs` | Keep. | +| `Settings/IThemeManager.cs`, `Settings/ISchemeManager.cs`, `Settings/MecThemeManager.cs`, `Settings/MecSchemeManager.cs` | Keep. **A1 (event plumbing) is done post-#5411.** A2 (data ownership) is this PR's work — see §5.2. | + +### 4.3 Types to **rename** (drop the `Mec` prefix once the legacy peers are gone) + +| Old (post‑#5411) | New | +|------------------|-----| +| `MecSchemeManager` | `SchemeManager` (replaces the deleted legacy static class) | +| `MecThemeManager` | `ThemeManager` (replaces the deleted legacy static class) | +| `Tests/UnitTestsParallelizable/Configuration/MecSettingsTests.cs` | `SettingsTests.cs` | +| `Tests/UnitTestsParallelizable/Configuration/MecThemeTests.cs` | `ThemeTests.cs` | +| `Tests/UnitTestsParallelizable/Configuration/MecAppSettingsTests.cs` | `AppSettingsTests.cs` | + +The legacy `SchemeManager` / `ThemeManager` are deleted in §4.1; the rename is a single namespace move and does not collide. + +### 4.4 Callers to **rewire** (production source outside `Configuration/`) + +| File | Current dependency | Replacement | +|------|--------------------|-------------| +| `ModuleInitializers.cs` | Calls `ConfigurationManager.Initialize ()` then `mecBuilder.ApplyToStaticFacades ()` | Single call to `TuiConfigurationBuilder.ApplyToStaticFacades ()`. Delete the dual-init suppression. Gated by A2 completion. | +| `App/ApplicationImpl.Lifecycle.cs` | `ConfigurationManager.PrintJsonErrors ()` | Replace with an equivalent aggregated-error printer fed by `JsonConfigurationSource.OnLoadException`. See §5.4 and §6 Phase D. | +| `App/Application.cs` | Several `ConfigurationManager.*` references in xmldoc/method bodies | Replace with `TuiConfigurationBuilder` references or delete. | +| `App/Tracing/Trace.cs` | One CM reference | Replace with `TraceSettings.Defaults.EnabledCategories`. | +| `Drawing/Scheme.cs` | xmldoc references CM in a comment | Update wording. | +| `Drawing/Glyphs.cs` | (None — already POCO-backed via `GlyphSettings.Defaults`.) | No change. Confirm via grep after removal. | +| `Input/CommandBindingsBase.cs` | Comment refers to `ConfigurationManager.Apply` / `DeepMemberWiseCopy` | Update comment; the dictionary‑copy workaround stays because the underlying race is now in `IOptionsMonitor` callbacks. | +| Stale `using Terminal.Gui.Configuration;` / xmldoc references in: `Views/Menu/PopoverMenu.cs`, `Views/Selectors/SelectorBase.cs`, `Views/Window.cs`, `Views/Dialog.cs`, `Views/FrameView.cs`, `Views/MessageBox.cs`, `Views/HexView.cs`, `Views/CharMap/CharMap.cs`, `Views/CheckBox.cs`, `Views/Button.cs`, `Views/FileDialogs/FileDialog.cs`, `Views/FileDialogs/FileDialogStyle.cs`, `Views/LinearRange/LinearRangeDefaults.cs`, `Views/LinearRange/LinearRangeViewBase.cs`, `Views/TextInput/TextField/TextField.cs`, `Views/TextInput/TextView/TextView.cs`, `Views/TreeView/TreeViewT.cs`, `ViewBase/View.Keyboard.cs`, `ViewBase/Mouse/View.Mouse.cs`, `ViewBase/Adornment/BorderView.Arrangement.cs`, `Text/NerdFonts.cs`, `FileServices/FileSystemIconProvider.cs`, `Drawing/Color/Color.cs`, `Input/Keyboard/Key.cs`, `Drivers/Driver.cs`, `App/Legacy/Application.Mouse.cs` | No live code dependency (verified — `[ConfigurationProperty(` grep returns only the attribute file itself). | Mechanical comment sweep. | + +**Already rewired in #5411 (no action in this PR):** `Views/Menu/Menu.cs`, `Views/Menu/MenuBar.cs`, `Views/StatusBar.cs`, `Drawing/LineCanvas/LineCanvas.cs` all subscribe to `ThemeChanges.ThemeChanged` instead of `ConfigurationManager.Applied`. + +### 4.5 Resource files + +| File | Action | +|------|--------| +| `Terminal.Gui/Resources/config.json` (52 KB, ~1,500 lines) | **Rewrite** in nested MEC format. Keys like `"Button.DefaultShadow"` become `Button: { DefaultShadow: ... }`. The `"Themes": [ { "Default": { ... } } ]` array-of-single-key-objects becomes `Themes: { Default: { ... }, Dark: { ... } }`. Schemes stay as a nested map. The `$schema` URL stays; the hosted schema must be updated in tandem (Q‑03 — file a tracking issue). | +| `Examples/Config/example_config.json` | Migrate alongside library `config.json`. | +| `Tests/.../*config.json` (if any) | Audit and migrate. | + +### 4.6 Tests to **delete** (`Tests/`) + +| File | Reason | +|------|--------| +| `UnitTests.NonParallelizable/Configuration/ConfigurationMangerTests.cs` | Tests deleted facade. | +| `UnitTests.NonParallelizable/Configuration/ConfigPropertyAssemblyScanTests.cs` | Tests deleted reflection scan. | +| `UnitTests.NonParallelizable/Configuration/SourcesManagerLoadNullJsonTests.cs` | Replaced by `SourcesManagerTests` in `UnitTestsParallelizable` (which already test the MEC builder). | +| `UnitTests.NonParallelizable/Configuration/GlyphTests.cs` | Move to parallelizable if it doesn't depend on static state; otherwise replace with MEC equivalent. | +| `UnitTestsParallelizable/Configuration/ConfigPropertyTests.cs` | Tests deleted type. | +| `UnitTestsParallelizable/Configuration/ConfigPropertyHostTypesTests.cs` | Tests deleted type. | +| `UnitTestsParallelizable/Configuration/ConfigurationPropertyAttributeTests.cs` | Tests deleted attribute. | +| `UnitTestsParallelizable/Configuration/DeepClonerTests.cs` | Tests deleted utility. | +| `UnitTestsParallelizable/Configuration/ScopeJsonConverterTests.cs` | Tests deleted converter. | +| `UnitTestsParallelizable/Configuration/ScopeTests.cs` | Tests deleted base class. | +| `UnitTestsParallelizable/Configuration/SettingsScopeTests.cs` | Tests deleted scope. | +| `UnitTestsParallelizable/Configuration/ThemeScopeTests.cs` | Tests deleted scope. | +| `Benchmarks/Configuration/ConfigurationManagerLoadBenchmark.cs` | Replace with `TuiConfigurationBuilderBuildBenchmark`. | +| `Benchmarks/Configuration/ThemeSwitchBenchmark.cs` | Rewrite against `IThemeManager.SwitchTheme`. | + +### 4.7 Tests to **keep** (and audit) + +- `UnitTestsParallelizable/Configuration/MecAppSettingsTests.cs` +- `UnitTestsParallelizable/Configuration/MecSettingsTests.cs` +- `UnitTestsParallelizable/Configuration/MecThemeTests.cs` +- `UnitTestsParallelizable/Configuration/SourcesManagerTests.cs` (MEC version — confirm not the legacy one) +- `UnitTestsParallelizable/Configuration/KeyJsonConverterTests.cs` +- `UnitTestsParallelizable/Configuration/RuneJsonConverterTests.cs` +- `UnitTestsParallelizable/Configuration/SchemeJsonConverterTests.cs` +- All `*DefaultKeyBindingsTests.cs` (these read `View.DefaultKeyBindings`; after CM removal they read it via the bound POCO) + +### 4.8 Examples to **rewrite** + +| File | Action | +|------|--------| +| `Examples/UICatalog/Runner.cs` | Replace `ConfigurationManager.RuntimeConfig = "..."` with `TuiConfigurationBuilder.Runtime(...)` | +| `Examples/UICatalog/UICatalog.cs` and `UICatalogRunnable.cs` | Replace `ConfigurationManager.Enable/Apply/Applied` with the new builder + `IOptionsMonitor.OnChange` | +| `Examples/UICatalog/Scenarios/ConfigurationEditor.cs` | Heavy CM user. Rewrite to enumerate the `Settings/*Settings.cs` POCOs and serialize via `SourceGenerationContext`. | +| `Examples/UICatalog/Scenarios/Themes.cs`, `ThemeFallback.cs` | Use `IThemeManager.ThemeChanged` and `IThemeManager.SwitchTheme`. | +| `Examples/NativeAot/Program.cs`, `Examples/SelfContained/Program.cs`, `Examples/ReactiveExample/Program.cs`, `Examples/CommunityToolkitExample/Program.cs`, `Examples/ScenarioRunner/Program.cs`, `Examples/ShortcutTest/ShortcutTest.cs`, `Examples/PromptExample/Program.cs`, `Examples/Example/Example.cs` | Drop `ConfigurationManager.Enable (...)`; replace with `new TuiConfigurationBuilder ().ApplyToStaticFacades ()` (or remove entirely if defaults are sufficient). | +| `Examples/Config/README.md`, `Examples/NativeAot/README.md`, `Examples/SelfContained/README.md`, `Examples/ReactiveExample/README.md`, `Examples/CommunityToolkitExample/README.md` | Update documentation to reference MEC. | +| All `Examples/UICatalog/Scenarios/*.cs` files listed in §4 of the discovery output | Most contain only stale `using Terminal.Gui.Configuration;` plus an occasional `ConfigurationManager.Applied` subscription. Sweep with a single mechanical pass. | + +--- + +## 5. Detailed Design + +### 5.1 New `IOptionsMonitor` flow + +```text +TuiConfigurationBuilder.Build() + └─ IConfigurationBuilder + ├─ AddTuiLibraryDefaults() (embedded Terminal.Gui.Resources.config.json) + ├─ AddTuiAppDefaults(appName) (entry assembly config.json) + ├─ AddTuiUserFiles(appName) (~/.tui/*.json, ./.tui/*.json) + ├─ AddTuiEnvironmentVariable() (TUI_CONFIG) + └─ AddTuiRuntimeConfig(json) (in-memory string) + └─ IConfiguration root + └─ services.Configure(root.GetSection("Themes")) + └─ services.Configure(root.GetSection("Application")) + └─ services.Configure(root.GetSection("Button")) + └─ ... one Configure per POCO + +TuiConfigurationBuilder.ApplyToStaticFacades() + └─ ButtonSettings.Defaults = options.Get() + └─ DialogSettings.Defaults = options.Get() + └─ ... per POCO + └─ Hook IOptionsMonitor.OnChange(newValue => XxxSettings.Defaults = newValue) +``` + +`ButtonSettings.Defaults` (and peers) remain the **static facade** used by all views per D‑01. The only change versus #5411 is that the facade is now updated *only* by the MEC monitor — never by `ConfigurationManager.Apply`. + +### 5.2 ThemeManager / SchemeManager: from event-bridging wrapper to data owner (A2) + +Post-#5411 status (**A1 — done**): `IThemeManager.ThemeChanged` exists; `MecThemeManager` subscribes to the legacy static `ThemeManager.ThemeChanged` in its constructor and forwards. `ThemeChanges.ThemeChanged` is the public observer facade used by internal views; it bridges both `ConfigurationManager.Applied` *and* `IThemeManager.ThemeChanged` because `CM.Apply()` writes `ConfigProperty` values directly and bypasses the C# setter, so `ThemeChanged` alone would miss most theme switches today. + +This PR's work (**A2**): make `MecThemeManager` / `MecSchemeManager` the **owners** of theme and scheme runtime data, so the `ConfigurationManager.Applied` half of the `ThemeChanges` bridge can be deleted along with `ConfigurationManager` itself. + +Today the runtime theme/scheme dictionary lives in `ConfigurationManager.Settings["Themes"]`, parsed by `ScopeJsonConverter`. The target shape: + +```csharp +public sealed class ThemeManager : IThemeManager +{ + private readonly IOptionsMonitor _monitor; + private IDisposable? _subscription; + + public ThemeManager (IOptionsMonitor monitor) + { + _monitor = monitor; + _subscription = _monitor.OnChange (HandleChanged); + } + + public string ActiveTheme + { + get => _monitor.CurrentValue.ActiveTheme; + set => SwitchTheme (value); + } + + public IReadOnlyList ThemeNames => _monitor.CurrentValue.Themes.Keys.ToImmutableList (); + + public event EventHandler? ThemeChanged; + + public void SwitchTheme (string name) { /* mutate in-memory provider + reload */ } + + private void HandleChanged (ThemeSettings settings, string? name) + => ThemeChanged?.Invoke (this, settings.ActiveTheme); +} +``` + +A2 is the **gating prerequisite** for deleting `ScopeJsonConverter` and is tied to the D-02 resource-shape decision: the MEC binder cannot bind `ThemeSettings` from the legacy flat-key `Themes: [ { Name: { "Class.Prop": ... } } ]` shape without a custom source. Either: + +- **Option α (recommended):** Rewrite `Terminal.Gui/Resources/config.json` to the MEC-native nested shape, ship `TuiConfigMigrator` for user files. `ScopeJsonConverter` deletes cleanly. +- **Option β:** Keep the legacy shape and write a `LegacyTuiConfigurationSource : IConfigurationSource` that re-emits flat keys as nested MEC keys at load time. Smaller user blast radius; larger ongoing maintenance. + +§5.4 carries the analysis. + +`SchemeManager` becomes an instance class fronting `ThemeSettings.Schemes`. The static `SchemeManager.GetScheme(string)` API used pervasively in views is kept as a **static convenience facade** that forwards to the registered service (via a static `Application.Services` accessor) or to a process-wide fallback when no app has been created — same shape as the `XxxSettings.Defaults` pattern adopted in #5411. + +### 5.3 `ThemeChanges` bridge cleanup + +`ThemeChanges` currently raises its event in response to **either**: + +1. `ConfigurationManager.Applied` (legacy CM apply path), or +2. `IThemeManager.ThemeChanged` (MEC path, post‑A1). + +After A2 lands, (1) is dead — every theme switch goes through `MecThemeManager.SwitchTheme` ⇒ `IOptionsMonitor.OnChange` ⇒ `IThemeManager.ThemeChanged` ⇒ `ThemeChanges.ThemeChanged`. The `ConfigurationManager.Applied` subscription inside `ThemeChanges` deletes alongside `ConfigurationManager`. + +No view-side changes are required — `ThemeChanges` is the public surface and stays. + +### 5.4 Library `config.json`: resolving D-02 + +**Resolution: α-lite (detect + warn, no library-side migration).** + +The MEC read path is pure-nested. `TuiConfigurationBuilder.AddTuiJsonFile` (and the equivalent extension overloads) peek the JSON before binding and, if they detect pre-MEC shapes — top-level keys containing `.`, or `Themes` as a JSON array — emit a single `WARN`-level log identifying the file path and pointing at the migration documentation URL. The legacy shape is **not** parsed; affected settings fall through to defaults. + +**Rationale.** v2 is GA, so silent breakage (γ) is inappropriate. But hand-edited `config.json` usage is rare and concentrated in app authors who control their own upgrade timing, so a perpetual translation layer (β `LegacyTuiConfigurationSource`) or a full bidirectional migrator (α `TuiConfigMigrator`) buys little and costs permanent library surface. α-lite is the minimal GA-appropriate response: ~20 LOC of detection + one log line — no parsing of the legacy shape, no auto-migration, no round-trip tests, no deprecation cycle to engineer. + +**Library resource rewrite (one-time, in this PR).** `Terminal.Gui/Resources/config.json` is regenerated in the nested MEC-native shape: + +Before (flat): +```json +{ + "Themes": [ + { + "Default": { + "Button.DefaultShadow": "Opaque", + "Glyphs.CheckStateChecked": "☑", + "Schemes": [ { "Base": { "Normal": { "Foreground": "White", "Background": "Black" } } } ] + } + } + ] +} +``` + +After (nested, MEC-native): +```json +{ + "Themes": { + "Default": { + "Button": { "DefaultShadow": "Opaque" }, + "Glyphs": { "CheckStateChecked": "☑" }, + "Schemes": { "Base": { "Normal": { "Foreground": "White", "Background": "Black" } } } + } + } +} +``` + +`Examples/Config/example_config.json` and every UICatalog/example config file gets the same treatment. + +**Detection heuristic (implementation note).** Single `JsonDocument` walk, no schema knowledge required: + +- *Flat key* = any top-level property name in the root object whose name contains `.`. +- *Array themes* = the `Themes` property exists and its `ValueKind == JsonValueKind.Array`. + +Both checks together: < 20 LOC, no allocation beyond the `JsonDocument`. The warn message names the file and links to `docfx/docs/migrate-cm-to-mec.md` (and the migration tool below). + +**Migration aid.** A standalone `Tools/MigrateConfig/` console app (~50 LOC) lives in the repo but is **not** shipped in `Terminal.Gui.dll` and **not** added to any solution that ships. The detection warning references it. The tool is deletable any time without a deprecation cycle. + +**Cleanup horizon.** The detection-and-warn code itself can be deleted in a future minor when issue volume indicates no remaining users hit it. It is small enough to keep indefinitely if preferred — the decision is non-coupled to anything in this PR. + +**Explicitly rejected alternatives** (recorded so reviewers don't re-litigate): + +- ❌ *"Keep both shapes forever, just document both."* Combines β's permanent maintenance cost with α's user confusion cost. No upside. +- ❌ *"Silently translate legacy shapes."* Opaque failure mode if translation is wrong; GA-inappropriate. +- ❌ *"Throw on legacy shape."* Too aggressive for a non-malformed JSON file. Warn-and-ignore is the right contract — the file is still valid JSON, it just no longer matches our schema. + +### 5.5 Source generation context cleanup + +`TuiSerializerContext` (created in #5411) already configures all custom converters and `JsonSerializerOptions`. Remaining work in this PR: + +- Audit `SourceGenerationContext`'s `[JsonSerializable]` entries. Remove anything that references `SettingsScope` / `ThemeScope` / `AppSettingsScope` (those types are being deleted). +- Ensure every Settings POCO actively bound by `TuiConfigurationBuilder` is registered: + +```csharp +[JsonSerializable (typeof (ThemeSettings))] +[JsonSerializable (typeof (ApplicationSettings))] +[JsonSerializable (typeof (ButtonSettings))] +[JsonSerializable (typeof (DialogSettings))] +[JsonSerializable (typeof (GlyphSettings))] +// ... one entry per Settings/*Settings.cs POCO +[JsonSerializable (typeof (Scheme))] +[JsonSerializable (typeof (Attribute))] +[JsonSerializable (typeof (Color))] +[JsonSerializable (typeof (Dictionary))] +[JsonSerializable (typeof (Dictionary))] +internal partial class SourceGenerationContext : JsonSerializerContext { } +``` + +The converter registration is owned by `TuiSerializerContext` and does not move. + +### 5.6 ModuleInitializer simplification + +After A2 + the legacy CM deletion: + +```csharp +[ModuleInitializer] +internal static void InitializeTuiConfiguration () +{ + TuiConfigurationBuilder builder = new (); + builder.ApplyToStaticFacades (); +} +``` + +Single call. No `ConfigurationManager.Initialize ()`, no `#pragma warning disable CS0618`. + +### 5.7 `PrintJsonErrors` replacement (behavior-preserving) + +The v2 contract is "don't fail-fast on bad config.json; collect errors and print at shutdown." MEC supports this via `JsonConfigurationSource.OnLoadException`: + +```csharp +builder.AddJsonStream (stream, source => +{ + source.OnLoadException = ctx => + { + TuiJsonErrors.Add ($"{ctx.Source}: {ctx.Exception.Message}"); + ctx.Ignored = true; + }; +}); +``` + +Wire the same hook on every JSON source registered by `TuiConfigurationExtensions`. `ApplicationImpl.Lifecycle.Shutdown` calls `TuiJsonErrors.Print ()` (or routes through `Logging`) in place of the current `ConfigurationManager.PrintJsonErrors ()` call. + +Caveat: `OnLoadException` covers file/parse errors only. Bind/POCO validation errors (`OptionsValidationException`) don't have an equivalent hook in MEC and will throw on first `IOptions.Value` access. This is acceptable — bind errors are programmer-level (POCO shape mismatch), not user-level (typo in JSON). + +--- + +## 6. Implementation Phases (within this PR) + +Each phase is one or more commits. Tests must build and pass at every commit. **Phases A1, B, and the JSON-converter half of C landed in #5411 and are not repeated here.** + +### Phase D — Library `config.json` rewrite + α-lite detection *(blocks Phase A2)* +Scope (per §5.4 resolution): +1. Rewrite `Terminal.Gui/Resources/config.json` in the nested MEC-native shape. +2. Add the ~20 LOC peek-and-warn to `TuiConfigurationBuilder.AddTuiJsonFile` (and any sibling overloads). Detection heuristic: flat top-level keys containing `.`, or `Themes` as a JSON array. +3. Delete `Terminal.Gui/Configuration/ScopeJsonConverter.cs` (with the legacy parser gone, nothing reads the legacy shape inside `Terminal.Gui.dll`). +4. Rewrite `Examples/Config/example_config.json` and every UICatalog / example config to nested. +5. Add `Tools/MigrateConfig/` — standalone console app (~50 LOC), separate csproj, not in any shipping solution. +6. Add CHANGELOG entry pointing at the tool and the migration guide URL. +7. Wire `JsonConfigurationSource.OnLoadException` per §5.7 so v2 deferred-error behavior is preserved. +8. File a follow-up issue to update the hosted JSON schema (Q-03). + +Tests for Phase D: **two** parallelizable tests — one asserts the warning fires on a flat-key sample, one asserts it fires on an array-themes sample. No translation logic to test, so no round-trip tests are required. + +### Phase A2 — Mec managers own runtime theme/scheme data +- Have `MecThemeManager` / `MecSchemeManager` read theme and scheme dictionaries from `IOptionsMonitor.CurrentValue` instead of delegating to the legacy static `ThemeManager` / `SchemeManager`. +- `SwitchTheme` mutates the underlying in-memory MEC source and triggers `IOptionsMonitor.OnChange`. +- Confirm `ThemeChanges.ThemeChanged` is now raised exclusively through the `IThemeManager.ThemeChanged` path; verify no `ConfigurationManager.Applied` round-trip happens at runtime. +- Add tests in `MecThemeTests` for switch / add / remove scheme paths that previously delegated to the legacy code. + +### Phase C-finish — `SourceGenerationContext` POCO audit +- Remove `SettingsScope` / `ThemeScope` / `AppSettingsScope` from `[JsonSerializable]` list. +- Add per-component POCOs and `Dictionary` / `Dictionary` per §5.5. +- Delete the `ConfigurationManager.SerializerContext` one-line delegator field (kept post-#5411 purely to avoid an obsolete-attr breaking change in the parent PR). + +### Phase E — Delete legacy CM types + tests +- Delete every file listed in §4.1 and every test listed in §4.6. +- Delete `ModuleInitializers.cs`'s `ConfigurationManager.Initialize ()` call (§5.6). +- Replace `ApplicationImpl.Lifecycle`'s `ConfigurationManager.PrintJsonErrors ()` call with `TuiJsonErrors.Print ()` per §5.7. +- Delete the `ConfigurationManager.Applied` branch inside `ThemeChanges` per §5.3. +- Sweep stale doc-comment references (§4.4 last row). + +### Phase F — Rename `MecThemeManager`/`MecSchemeManager` → `ThemeManager`/`SchemeManager` +- Now that the legacy types are gone, the `Mec` prefix is redundant. +- Pure rename — no behavior change. + +### Phase G — Update examples (Runner, UICatalog, NativeAot, etc.) +- §4.8 sweep. + +### Phase H — Update `docfx/docs/config.md` + migration guide +- Rewrite `config.md` to reflect the MEC contract. +- Remove every reference to `ConfigurationManager.Enable`, `ConfigLocations`, `ConfigurationProperty`, `SettingsScope`, `ThemeScope`, `AppSettingsScope`. +- Document `TuiConfigurationBuilder`, `IThemeManager`, `ISchemeManager`, the nested JSON schema, the migration helper, `ThemeChanges`, `TuiSerializerContext`. +- Add `docfx/docs/migrate-cm-to-mec.md` cheatsheet. + +### Phase I — Verification +- Run full test matrix (`UnitTestsParallelizable`, `UnitTests.NonParallelizable`, `IntegrationTests`). +- Confirm zero new warnings (the `CS0618` suppressions are gone — if anything tries to use a deleted obsolete API the build fails by design). +- Build `Examples/NativeAot` with `PublishAot=true` and record `before` / `after` binary size in the PR description. +- Run `Benchmarks/Configuration/*` and record delta. + +--- + +## 7. Breaking Changes (Public API) + +The following public surface is **removed**. Source-incompatible for any consumer that touched these (the predecessor #5411 marked them all `[Obsolete]`). + +- `Terminal.Gui.Configuration.ConfigurationManager` (class) +- `Terminal.Gui.Configuration.ConfigurationManagerNotEnabledException` +- `Terminal.Gui.Configuration.ConfigurationPropertyAttribute` +- `Terminal.Gui.Configuration.ConfigLocations` +- `Terminal.Gui.Configuration.SettingsScope`, `ThemeScope`, `AppSettingsScope`, `Scope` +- `Terminal.Gui.Configuration.ConfigProperty` +- `Terminal.Gui.Configuration.ConfigPropertyHostTypes` +- `Terminal.Gui.Configuration.SourcesManager` (replaced by `TuiConfigurationBuilder`) +- `Terminal.Gui.Configuration.DeepCloner` +- `Terminal.Gui.Configuration.ScopeJsonConverter` +- Static `Terminal.Gui.Configuration.ThemeManager` — replaced by instance `IThemeManager` +- Static `Terminal.Gui.Configuration.SchemeManager` static class — replaced by instance `ISchemeManager` (with a static convenience facade for `GetScheme(string)` only) +- The `Terminal.Gui.Configuration.ConfigurationManager.Applied` / `Updated` events + +### JSON file breaking change + +User config files in the legacy flat-key format (`"Button.DefaultShadow": "..."`) or array-themes format are **not parsed** by the new pipeline; affected settings fall through to defaults. A `WARN`-level log identifies offending files and points at the migration documentation + standalone migration tool (`Tools/MigrateConfig/`). No library-side auto-migration ships (per §5.4 α-lite resolution). + +### Migration guide + +A separate `docfx/docs/migrate-cm-to-mec.md` is added in Phase H with a side-by-side cheatsheet. The PR description links to it. + +--- + +## 8. Risks & Mitigations + +| Risk | Mitigation | +|------|------------| +| Hidden third-party consumers still call obsolete CM API | They received an `[Obsolete]` warning in #5411 with the message pointing at `TuiConfigurationBuilder`. One release window has elapsed by the time this PR ships. | +| Nested JSON breaks existing user config files | α-lite (§5.4): detect-and-warn at load time, fall through to defaults rather than fail. Standalone `Tools/MigrateConfig/` console app + `migrate-cm-to-mec.md` guide give users a one-shot fix path. If issue volume after release indicates more help is needed, β (`LegacyTuiConfigurationSource`) remains a viable hotfix. | +| Renaming `MecThemeManager` → `ThemeManager` clashes with the deleted legacy `ThemeManager` if a partial revert lands | Phase F runs only after Phase E commits; if reverted, the rename is reverted with it. | +| AOT size delta is smaller than predicted | Acceptable. The parallel-test and decoupling wins still justify the change. Record actual delta in the PR. | +| `IOptionsMonitor.OnChange` is not invoked in NativeAOT due to missing trim roots | Verified in #5411. Re-verify with a smoke test in `Examples/NativeAot` as part of Phase I. | +| Bind-time (`OptionsValidationException`) errors are not aggregated like file-parse errors | Accepted. Bind errors are programmer-level (POCO shape mismatch). The user-level "typo in JSON" path is fully preserved via `JsonConfigurationSource.OnLoadException` (§5.7). | + +--- + +## 9. Out of Scope + +Carried forward from the predecessor spec (§10), unchanged: + +1. Generic Host / `IHostBuilder` adoption. +2. Constructor injection mandate for all Views. +3. View-level DI. +4. Multiple concurrent `IApplication` instances. +5. TextMate / Markdig conditional compilation. +6. Lazy driver registration. +7. Assembly splitting. + +--- + +*Authors: @copilot. Companion to [`specs/replace-cm-with-mec.md`](./replace-cm-with-mec.md). Tracking PR: #5416.*