| | | 1 | | namespace Allyaria.Theming.ThemeTypes; |
| | | 2 | | |
| | | 3 | | /// <summary> |
| | | 4 | | /// Represents the lowest layer in the Allyaria theming hierarchy, responsible for holding and rendering individual styl |
| | | 5 | | /// properties (such as colors, borders, and font-related values). |
| | | 6 | | /// </summary> |
| | | 7 | | /// <remarks> |
| | | 8 | | /// <para> |
| | | 9 | | /// A <see cref="ThemeStyle" /> corresponds to a single <see cref="ComponentState" /> (e.g., Default, Hovered) and |
| | | 10 | | /// manages a collection of <see cref="StyleType" /> → <see cref="IStyleValue" /> mappings. |
| | | 11 | | /// </para> |
| | | 12 | | /// <para> |
| | | 13 | | /// This class ensures contrast compliance for color-related properties according to WCAG 2.2 AA standards and produ |
| | | 14 | | /// normalized CSS through <see cref="BuildCss" />. |
| | | 15 | | /// </para> |
| | | 16 | | /// </remarks> |
| | | 17 | | internal sealed class ThemeStyle |
| | | 18 | | { |
| | | 19 | | /// <summary>Stores all style values for this theme style, keyed by <see cref="StyleType" />.</summary> |
| | 2719 | 20 | | private readonly Dictionary<StyleType, IStyleValue> _children = new(); |
| | | 21 | | |
| | | 22 | | /// <summary> |
| | | 23 | | /// Builds the CSS output for this style definition and its contained <see cref="IStyleValue" /> instances. |
| | | 24 | | /// </summary> |
| | | 25 | | /// <param name="builder">The <see cref="CssBuilder" /> used to accumulate CSS declarations.</param> |
| | | 26 | | /// <param name="navigator"> |
| | | 27 | | /// The <see cref="ThemeNavigator" /> defining which <see cref="StyleType" /> values should be included. |
| | | 28 | | /// </param> |
| | | 29 | | /// <param name="varPrefix">An optional CSS variable prefix used when generating scoped variable names.</param> |
| | | 30 | | /// <returns>A <see cref="CssBuilder" /> containing the merged style declarations.</returns> |
| | | 31 | | internal CssBuilder BuildCss(CssBuilder builder, ThemeNavigator navigator, string? varPrefix = "") |
| | | 32 | | { |
| | 40 | 33 | | if (navigator.StyleTypes.Count is 0) |
| | | 34 | | { |
| | 106 | 35 | | foreach (var child in _children) |
| | | 36 | | { |
| | 31 | 37 | | builder = BuildCssValue(builder: builder, key: child.Key, value: child.Value, varPrefix: varPrefix); |
| | | 38 | | } |
| | | 39 | | } |
| | | 40 | | else |
| | | 41 | | { |
| | 74 | 42 | | foreach (var key in navigator.StyleTypes) |
| | | 43 | | { |
| | 19 | 44 | | builder = BuildCssValue(builder: builder, key: key, value: Get(key: key), varPrefix: varPrefix); |
| | | 45 | | } |
| | | 46 | | } |
| | | 47 | | |
| | 40 | 48 | | return builder; |
| | | 49 | | } |
| | | 50 | | |
| | | 51 | | /// <summary>Appends an individual CSS property or grouped value to the provided builder.</summary> |
| | | 52 | | /// <param name="builder">The <see cref="CssBuilder" /> used for output.</param> |
| | | 53 | | /// <param name="key">The <see cref="StyleType" /> identifying the CSS property.</param> |
| | | 54 | | /// <param name="value">The value associated with the style property.</param> |
| | | 55 | | /// <param name="varPrefix">An optional CSS variable prefix for variable-scoped styles.</param> |
| | | 56 | | /// <returns>The updated <see cref="CssBuilder" />.</returns> |
| | | 57 | | private static CssBuilder BuildCssValue(CssBuilder builder, StyleType key, IStyleValue? value, string? varPrefix) |
| | 50 | 58 | | => value is null |
| | 50 | 59 | | ? builder |
| | 50 | 60 | | : value is StyleGroup group |
| | 50 | 61 | | ? group.BuildCss(builder: builder, varPrefix: varPrefix) |
| | 50 | 62 | | : builder.Add(name: key.GetDescription(), value: value.Value, varPrefix: varPrefix); |
| | | 63 | | |
| | | 64 | | /// <summary> |
| | | 65 | | /// Ensures sufficient contrast between foreground and background color properties, following WCAG 2.2 AA standards |
| | | 66 | | /// visual accessibility. |
| | | 67 | | /// </summary> |
| | | 68 | | /// <remarks> |
| | | 69 | | /// This method adjusts key color-related properties—such as text, border, and accent colors— to maintain a minimum |
| | | 70 | | /// contrast ratio of 4.5:1 against the background. |
| | | 71 | | /// </remarks> |
| | | 72 | | private void EnsureContrast() |
| | | 73 | | { |
| | 4708 | 74 | | var accentColor = ((StyleColor?)Get(key: StyleType.AccentColor))?.Color; |
| | 4708 | 75 | | var backgroundColor = ((StyleColor?)Get(key: StyleType.BackgroundColor))?.Color; |
| | 4708 | 76 | | var borderColor = ((StyleColor?)Get(key: StyleType.BorderColor))?.Color; |
| | 4708 | 77 | | var caretColor = ((StyleColor?)Get(key: StyleType.CaretColor))?.Color; |
| | 4708 | 78 | | var color = ((StyleColor?)Get(key: StyleType.Color))?.Color; |
| | 4708 | 79 | | var outlineColor = ((StyleColor?)Get(key: StyleType.OutlineColor))?.Color; |
| | 4708 | 80 | | var textDecorationColor = ((StyleColor?)Get(key: StyleType.TextDecorationColor))?.Color; |
| | | 81 | | |
| | 4708 | 82 | | if (backgroundColor?.IsTransparent() ?? true) |
| | | 83 | | { |
| | 1570 | 84 | | return; |
| | | 85 | | } |
| | | 86 | | |
| | 3138 | 87 | | accentColor = accentColor?.EnsureContrast(background: backgroundColor.Value, minimumRatio: 4.5); |
| | 3138 | 88 | | borderColor = borderColor?.EnsureContrast(background: backgroundColor.Value, minimumRatio: 4.5); |
| | 3138 | 89 | | caretColor = caretColor?.EnsureContrast(background: backgroundColor.Value, minimumRatio: 4.5); |
| | 3138 | 90 | | color = color?.EnsureContrast(background: backgroundColor.Value, minimumRatio: 4.5); |
| | 3138 | 91 | | outlineColor = outlineColor?.EnsureContrast(background: backgroundColor.Value, minimumRatio: 4.5); |
| | 3138 | 92 | | textDecorationColor = textDecorationColor?.EnsureContrast(background: backgroundColor.Value, minimumRatio: 4.5); |
| | | 93 | | |
| | 3138 | 94 | | SetColor(key: StyleType.AccentColor, color: accentColor) |
| | 3138 | 95 | | .SetColor(key: StyleType.BackgroundColor, color: backgroundColor) |
| | 3138 | 96 | | .SetColor(key: StyleType.BorderColor, color: borderColor) |
| | 3138 | 97 | | .SetColor(key: StyleType.CaretColor, color: caretColor) |
| | 3138 | 98 | | .SetColor(key: StyleType.Color, color: color) |
| | 3138 | 99 | | .SetColor(key: StyleType.OutlineColor, color: outlineColor) |
| | 3138 | 100 | | .SetColor(key: StyleType.TextDecorationColor, color: textDecorationColor); |
| | 3138 | 101 | | } |
| | | 102 | | |
| | | 103 | | /// <summary>Retrieves the <see cref="IStyleValue" /> associated with the given <see cref="StyleType" />.</summary> |
| | | 104 | | /// <param name="key">The <see cref="StyleType" /> to retrieve.</param> |
| | | 105 | | /// <returns>The associated <see cref="IStyleValue" />, or <see langword="null" /> if not found.</returns> |
| | 32975 | 106 | | private IStyleValue? Get(StyleType key) => _children.GetValueOrDefault(key: key); |
| | | 107 | | |
| | | 108 | | /// <summary>Applies a <see cref="ThemeUpdater" /> to set or update style values for this theme style.</summary> |
| | | 109 | | /// <param name="updater">The <see cref="ThemeUpdater" /> containing the update definition.</param> |
| | | 110 | | /// <returns>The same <see cref="ThemeStyle" /> instance for fluent configuration.</returns> |
| | | 111 | | internal ThemeStyle Set(ThemeUpdater updater) |
| | | 112 | | { |
| | 17512 | 113 | | var isColor = false; |
| | | 114 | | |
| | 70048 | 115 | | foreach (var key in updater.Navigator.StyleTypes) |
| | | 116 | | { |
| | 17512 | 117 | | SetValue(key: key, value: updater.Value); |
| | | 118 | | |
| | 17512 | 119 | | if (!isColor && updater.Value is StyleColor) |
| | | 120 | | { |
| | 4708 | 121 | | isColor = true; |
| | | 122 | | } |
| | | 123 | | } |
| | | 124 | | |
| | 17512 | 125 | | if (isColor) |
| | | 126 | | { |
| | 4708 | 127 | | EnsureContrast(); |
| | | 128 | | } |
| | | 129 | | |
| | 17512 | 130 | | return this; |
| | | 131 | | } |
| | | 132 | | |
| | | 133 | | /// <summary>Assigns a specific color value to the given <see cref="StyleType" />.</summary> |
| | | 134 | | /// <param name="key">The <see cref="StyleType" /> identifying the color property.</param> |
| | | 135 | | /// <param name="color">The <see cref="HexColor" /> to assign.</param> |
| | | 136 | | /// <returns>The current <see cref="ThemeStyle" /> instance for chaining.</returns> |
| | | 137 | | private ThemeStyle SetColor(StyleType key, HexColor? color) |
| | 21966 | 138 | | => color is null |
| | 21966 | 139 | | ? this |
| | 21966 | 140 | | : SetValue(key: key, value: new StyleColor(value: color)); |
| | | 141 | | |
| | | 142 | | /// <summary>Sets or removes a style value for the specified <see cref="StyleType" />.</summary> |
| | | 143 | | /// <param name="key">The <see cref="StyleType" /> identifying the CSS property.</param> |
| | | 144 | | /// <param name="value">The <see cref="IStyleValue" /> representing the style value.</param> |
| | | 145 | | /// <returns>The same <see cref="ThemeStyle" /> instance for fluent chaining.</returns> |
| | | 146 | | private ThemeStyle SetValue(StyleType key, IStyleValue? value) |
| | | 147 | | { |
| | 30059 | 148 | | if (string.IsNullOrWhiteSpace(value: value?.Value)) |
| | | 149 | | { |
| | 1 | 150 | | _children.Remove(key: key); |
| | | 151 | | } |
| | | 152 | | else |
| | | 153 | | { |
| | 30058 | 154 | | _children[key: key] = value; |
| | | 155 | | } |
| | | 156 | | |
| | 30059 | 157 | | return this; |
| | | 158 | | } |
| | | 159 | | } |