< Summary

Information
Class: Allyaria.Theming.Helpers.ThemeBuilder
Assembly: Allyaria.Theming
File(s): /home/runner/work/allyaria/allyaria/src/Allyaria.Theming/Helpers/ThemeBuilder.cs
Line coverage
100%
Covered lines: 300
Uncovered lines: 0
Coverable lines: 300
Total lines: 439
Line coverage: 100%
Branch coverage
100%
Covered branches: 24
Total branches: 24
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor()100%11100%
ApplyTheme(...)100%22100%
Build()100%22100%
Create(...)100%22100%
CreateGlobalBody(...)100%11100%
CreateGlobalFocus(...)100%11100%
CreateGlobalHtml(...)100%11100%
CreateHeading1(...)100%11100%
CreateHeading2(...)100%11100%
CreateHeading3(...)100%11100%
CreateHeading4(...)100%11100%
CreateHeading5(...)100%11100%
CreateHeading6(...)100%11100%
CreateLink(...)100%11100%
CreateSurface(...)100%11100%
CreateText(...)100%11100%
Set(...)100%1818100%

File(s)

/home/runner/work/allyaria/allyaria/src/Allyaria.Theming/Helpers/ThemeBuilder.cs

#LineLine coverage
 1namespace Allyaria.Theming.Helpers;
 2
 3/// <summary>
 4/// Provides the core logic for constructing Allyaria themes by combining colors, typography, and layout rules into a
 5/// complete <see cref="Theme" />.
 6/// </summary>
 7/// <remarks>
 8///     <para>
 9///     The <see cref="ThemeBuilder" /> orchestrates creation of default themes for light, dark, and high-contrast modes
 10///     applying a series of <see cref="ThemeApplierBase" />-derived builders.
 11///     </para>
 12///     <para>
 13///     It supports programmatic customization through <see cref="Set(ThemeUpdater)" /> and can optionally accept a
 14///     <see cref="Brand" /> definition to influence default colors and fonts.
 15///     </para>
 16/// </remarks>
 17internal sealed class ThemeBuilder
 18{
 19    /// <summary>Indicates whether the builder has completed an initialization pass.</summary>
 20    private bool _isReady;
 21
 22    /// <summary>Provides color, font, and palette mappings for the current theme context.</summary>
 1623    private ThemeMapper _mapper = new();
 24
 25    /// <summary>The underlying <see cref="Theme" /> being composed.</summary>
 1626    private Theme _theme = new();
 27
 28    /// <summary>Applies all <see cref="ThemeUpdater" /> entries from the specified applier to the current theme.</summa
 29    /// <param name="applier">A <see cref="ThemeApplierBase" /> instance providing theme updates.</param>
 30    private void ApplyTheme(ThemeApplierBase applier)
 31    {
 1203232        foreach (var updater in applier)
 33        {
 561634            _theme.Set(updater: updater);
 35        }
 40036    }
 37
 38    /// <summary>Finalizes and returns the built <see cref="Theme" />, resetting the builder for reuse.</summary>
 39    /// <returns>A completed <see cref="Theme" /> instance representing the composed theme.</returns>
 40    public Theme Build()
 41    {
 842        if (!_isReady)
 43        {
 244            Create();
 45        }
 46
 847        var theme = _theme;
 48
 849        _mapper = new ThemeMapper();
 850        _theme = new Theme();
 851        _isReady = false;
 52
 853        return theme;
 54    }
 55
 56    /// <summary>Initializes a new base theme structure using the provided <see cref="Brand" /> configuration.</summary>
 57    /// <param name="brand">
 58    /// An optional <see cref="Brand" /> instance providing color and font defaults. If omitted, system defaults are use
 59    /// </param>
 60    /// <returns>The current <see cref="ThemeBuilder" /> instance for fluent configuration.</returns>
 61    public ThemeBuilder Create(Brand? brand = null)
 62    {
 863        _mapper = new ThemeMapper(brand: brand);
 864        _theme = new Theme();
 65
 4866        for (var contrast = 0; contrast < 2; contrast++)
 67        {
 1668            var isHighContrast = contrast is 1;
 69
 1670            CreateGlobalBody(isHighContrast: isHighContrast);
 1671            CreateGlobalFocus(isHighContrast: isHighContrast);
 1672            CreateGlobalHtml(isHighContrast: isHighContrast);
 73
 1674            CreateHeading1(isHighContrast: isHighContrast);
 1675            CreateHeading2(isHighContrast: isHighContrast);
 1676            CreateHeading3(isHighContrast: isHighContrast);
 1677            CreateHeading4(isHighContrast: isHighContrast);
 1678            CreateHeading5(isHighContrast: isHighContrast);
 1679            CreateHeading6(isHighContrast: isHighContrast);
 80
 1681            CreateLink(isHighContrast: isHighContrast);
 1682            CreateSurface(isHighContrast: isHighContrast);
 1683            CreateText(isHighContrast: isHighContrast);
 84        }
 85
 886        _isReady = true;
 87
 888        return this;
 89    }
 90
 91    /// <summary>Configures base body styles such as colors, fonts, layout, and overflow behavior.</summary>
 92    /// <param name="isHighContrast">Indicates whether high-contrast adjustments are applied.</param>
 93    private void CreateGlobalBody(bool isHighContrast)
 94    {
 1695        ApplyTheme(
 1696            applier: new ThemeColorApplier(
 1697                themeMapper: _mapper,
 1698                isHighContrast: isHighContrast,
 1699                componentType: ComponentType.GlobalBody,
 16100                paletteType: PaletteType.Surface,
 16101                isVariant: false,
 16102                hasBackground: true,
 16103                isOutline: false
 16104            )
 16105        );
 106
 16107        ApplyTheme(
 16108            applier: new ThemeFontApplier(
 16109                themeMapper: _mapper,
 16110                isHighContrast: isHighContrast,
 16111                componentType: ComponentType.GlobalBody,
 16112                fontFace: FontFaceType.SansSerif,
 16113                fontSize: Sizing.Relative,
 16114                lineHeight: "1.5"
 16115            )
 16116        );
 117
 16118        ApplyTheme(
 16119            applier: new ThemeApplier(
 16120                themeMapper: _mapper,
 16121                isHighContrast: isHighContrast,
 16122                componentType: ComponentType.GlobalBody,
 16123                styleType: StyleType.Margin,
 16124                value: new StyleLength(value: Sizing.Size0)
 16125            )
 16126        );
 127
 16128        ApplyTheme(
 16129            applier: new ThemeApplier(
 16130                themeMapper: _mapper,
 16131                isHighContrast: isHighContrast,
 16132                componentType: ComponentType.GlobalBody,
 16133                styleType: StyleType.Padding,
 16134                value: new StyleLength(value: Sizing.Size0)
 16135            )
 16136        );
 137
 16138        ApplyTheme(
 16139            applier: new ThemeApplier(
 16140                themeMapper: _mapper,
 16141                isHighContrast: isHighContrast,
 16142                componentType: ComponentType.GlobalBody,
 16143                styleType: StyleType.MinHeight,
 16144                value: new StyleLength(value: Sizing.Full)
 16145            )
 16146        );
 147
 16148        ApplyTheme(
 16149            applier: new ThemeApplier(
 16150                themeMapper: _mapper,
 16151                isHighContrast: isHighContrast,
 16152                componentType: ComponentType.GlobalBody,
 16153                styleType: StyleType.OverflowBlock,
 16154                value: new StyleOverflow(kind: StyleOverflow.Kind.Clip)
 16155            )
 16156        );
 16157    }
 158
 159    /// <summary>Configures global focus outline behavior.</summary>
 160    /// <param name="isHighContrast">Indicates whether high-contrast adjustments are applied.</param>
 161    private void CreateGlobalFocus(bool isHighContrast)
 16162        => ApplyTheme(
 16163            applier: new ThemeOutlineApplier(
 16164                themeMapper: _mapper,
 16165                isHighContrast: isHighContrast,
 16166                componentType: ComponentType.GlobalFocus,
 16167                paletteType: PaletteType.Surface
 16168            )
 16169        );
 170
 171    /// <summary>Configures root HTML-level styles including sizing, box model, and scrolling behavior.</summary>
 172    /// <param name="isHighContrast">Indicates whether high-contrast adjustments are applied.</param>
 173    private void CreateGlobalHtml(bool isHighContrast)
 174    {
 16175        ApplyTheme(
 16176            applier: new ThemeFontApplier(
 16177                themeMapper: _mapper,
 16178                isHighContrast: isHighContrast,
 16179                componentType: ComponentType.GlobalHtml,
 16180                fontSize: Sizing.Size3,
 16181                lineHeight: "1.5"
 16182            )
 16183        );
 184
 16185        ApplyTheme(
 16186            applier: new ThemeApplier(
 16187                themeMapper: _mapper,
 16188                isHighContrast: isHighContrast,
 16189                componentType: ComponentType.GlobalHtml,
 16190                styleType: StyleType.Margin,
 16191                value: new StyleLength(value: Sizing.Size0)
 16192            )
 16193        );
 194
 16195        ApplyTheme(
 16196            applier: new ThemeApplier(
 16197                themeMapper: _mapper,
 16198                isHighContrast: isHighContrast,
 16199                componentType: ComponentType.GlobalHtml,
 16200                styleType: StyleType.Padding,
 16201                value: new StyleLength(value: Sizing.Size0)
 16202            )
 16203        );
 204
 16205        ApplyTheme(
 16206            applier: new ThemeApplier(
 16207                themeMapper: _mapper,
 16208                isHighContrast: isHighContrast,
 16209                componentType: ComponentType.GlobalHtml,
 16210                styleType: StyleType.BoxSizing,
 16211                value: new StyleBoxSizing(kind: StyleBoxSizing.Kind.BorderBox)
 16212            )
 16213        );
 214
 16215        ApplyTheme(
 16216            applier: new ThemeApplier(
 16217                themeMapper: _mapper,
 16218                isHighContrast: isHighContrast,
 16219                componentType: ComponentType.GlobalHtml,
 16220                styleType: StyleType.MinHeight,
 16221                value: new StyleLength(value: Sizing.Full)
 16222            )
 16223        );
 224
 16225        ApplyTheme(
 16226            applier: new ThemeApplier(
 16227                themeMapper: _mapper,
 16228                isHighContrast: isHighContrast,
 16229                componentType: ComponentType.GlobalHtml,
 16230                styleType: StyleType.ScrollBehavior,
 16231                value: new StyleScrollBehavior(kind: StyleScrollBehavior.Kind.Smooth)
 16232            )
 16233        );
 234
 16235        ApplyTheme(
 16236            applier: new ThemeApplier(
 16237                themeMapper: _mapper,
 16238                isHighContrast: isHighContrast,
 16239                componentType: ComponentType.GlobalHtml,
 16240                styleType: StyleType.TextSizeAdjust,
 16241                value: new StyleLength(value: Sizing.Full)
 16242            )
 16243        );
 16244    }
 245
 246    /// <summary>Creates typographic rules for &lt;h1&gt; headings.</summary>
 247    private void CreateHeading1(bool isHighContrast)
 16248        => ApplyTheme(
 16249            applier: new ThemeFontApplier(
 16250                themeMapper: _mapper,
 16251                isHighContrast: isHighContrast,
 16252                componentType: ComponentType.Heading1,
 16253                fontSize: Sizing.RelativeLarge4,
 16254                fontWeight: StyleFontWeight.Kind.Weight700,
 16255                lineHeight: "1.2",
 16256                marginBottom: Sizing.RelativeLarge1
 16257            )
 16258        );
 259
 260    /// <summary>Creates typographic rules for &lt;h2&gt; headings.</summary>
 261    private void CreateHeading2(bool isHighContrast)
 16262        => ApplyTheme(
 16263            applier: new ThemeFontApplier(
 16264                themeMapper: _mapper,
 16265                isHighContrast: isHighContrast,
 16266                componentType: ComponentType.Heading2,
 16267                fontSize: Sizing.RelativeLarge3,
 16268                fontWeight: StyleFontWeight.Kind.Weight700,
 16269                lineHeight: "1.25",
 16270                marginBottom: Sizing.Relative
 16271            )
 16272        );
 273
 274    /// <summary>Creates typographic rules for &lt;h3&gt; headings.</summary>
 275    private void CreateHeading3(bool isHighContrast)
 16276        => ApplyTheme(
 16277            applier: new ThemeFontApplier(
 16278                themeMapper: _mapper,
 16279                isHighContrast: isHighContrast,
 16280                componentType: ComponentType.Heading3,
 16281                fontSize: Sizing.RelativeLarge2,
 16282                fontWeight: StyleFontWeight.Kind.Weight600,
 16283                lineHeight: "1.3",
 16284                marginBottom: Sizing.RelativeSmall2
 16285            )
 16286        );
 287
 288    /// <summary>Creates typographic rules for &lt;h4&gt; headings.</summary>
 289    private void CreateHeading4(bool isHighContrast)
 16290        => ApplyTheme(
 16291            applier: new ThemeFontApplier(
 16292                themeMapper: _mapper,
 16293                isHighContrast: isHighContrast,
 16294                componentType: ComponentType.Heading4,
 16295                fontSize: Sizing.RelativeLarge1,
 16296                fontWeight: StyleFontWeight.Kind.Weight600,
 16297                lineHeight: "1.4",
 16298                marginBottom: Sizing.RelativeSmall3
 16299            )
 16300        );
 301
 302    /// <summary>Creates typographic rules for &lt;h5&gt; headings.</summary>
 303    private void CreateHeading5(bool isHighContrast)
 16304        => ApplyTheme(
 16305            applier: new ThemeFontApplier(
 16306                themeMapper: _mapper,
 16307                isHighContrast: isHighContrast,
 16308                componentType: ComponentType.Heading5,
 16309                fontSize: Sizing.Relative,
 16310                fontWeight: StyleFontWeight.Kind.Weight600,
 16311                lineHeight: "1.5",
 16312                marginBottom: Sizing.RelativeSmall4
 16313            )
 16314        );
 315
 316    /// <summary>Creates typographic rules for &lt;h6&gt; headings.</summary>
 317    private void CreateHeading6(bool isHighContrast)
 16318        => ApplyTheme(
 16319            applier: new ThemeFontApplier(
 16320                themeMapper: _mapper,
 16321                isHighContrast: isHighContrast,
 16322                componentType: ComponentType.Heading6,
 16323                fontSize: Sizing.RelativeSmall1,
 16324                fontWeight: StyleFontWeight.Kind.Weight500,
 16325                lineHeight: "1.5",
 16326                marginBottom: Sizing.RelativeSmall4
 16327            )
 16328        );
 329
 330    /// <summary>Creates link styles, including color, text decoration, and thickness.</summary>
 331    private void CreateLink(bool isHighContrast)
 16332        => ApplyTheme(
 16333            applier: new ThemeFontApplier(
 16334                themeMapper: _mapper,
 16335                isHighContrast: isHighContrast,
 16336                componentType: ComponentType.Link,
 16337                paletteType: PaletteType.Primary,
 16338                textDecorationLine: StyleTextDecorationLine.Kind.Underline,
 16339                textDecorationStyle: StyleTextDecorationStyle.Kind.Solid,
 16340                textDecorationThickness: Sizing.Thin
 16341            )
 16342        );
 343
 344    /// <summary>Configures background and spacing for surface containers.</summary>
 345    private void CreateSurface(bool isHighContrast)
 346    {
 16347        ApplyTheme(
 16348            applier: new ThemeColorApplier(
 16349                themeMapper: _mapper,
 16350                isHighContrast: isHighContrast,
 16351                componentType: ComponentType.Surface,
 16352                paletteType: PaletteType.Elevation1,
 16353                isVariant: false,
 16354                hasBackground: true,
 16355                isOutline: false
 16356            )
 16357        );
 358
 16359        ApplyTheme(
 16360            applier: new ThemeApplier(
 16361                themeMapper: _mapper,
 16362                isHighContrast: isHighContrast,
 16363                componentType: ComponentType.Surface,
 16364                styleType: StyleType.Margin,
 16365                value: new StyleLength(value: Sizing.Size2)
 16366            )
 16367        );
 368
 16369        ApplyTheme(
 16370            applier: new ThemeApplier(
 16371                themeMapper: _mapper,
 16372                isHighContrast: isHighContrast,
 16373                componentType: ComponentType.Surface,
 16374                styleType: StyleType.Padding,
 16375                value: new StyleLength(value: Sizing.Size3)
 16376            )
 16377        );
 16378    }
 379
 380    /// <summary>Creates typography settings for general text content.</summary>
 381    private void CreateText(bool isHighContrast)
 16382        => ApplyTheme(
 16383            applier: new ThemeFontApplier(
 16384                themeMapper: _mapper,
 16385                isHighContrast: isHighContrast,
 16386                componentType: ComponentType.Text,
 16387                fontSize: Sizing.Relative,
 16388                lineHeight: "1.5",
 16389                marginBottom: Sizing.Relative
 16390            )
 16391        );
 392
 393    /// <summary>Applies a <see cref="ThemeUpdater" /> to modify an existing theme definition.</summary>
 394    /// <param name="updater">The <see cref="ThemeUpdater" /> containing the desired updates.</param>
 395    /// <returns>The same <see cref="ThemeBuilder" /> instance for fluent chaining.</returns>
 396    /// <exception cref="AryArgumentException">
 397    /// Thrown when an invalid update is attempted, such as modifying system or high-contrast themes, or restricted comp
 398    /// states.
 399    /// </exception>
 400    public ThemeBuilder Set(ThemeUpdater updater)
 401    {
 10402        if (updater.Navigator.ThemeTypes.Contains(value: ThemeType.System))
 403        {
 1404            throw new AryArgumentException(
 1405                message: "System theme cannot be set directly.", argName: nameof(updater.Value)
 1406            );
 407        }
 408
 9409        if (updater.Navigator.ComponentStates.Contains(value: ComponentState.Hidden) ||
 9410            updater.Navigator.ComponentStates.Contains(value: ComponentState.ReadOnly))
 411        {
 2412            throw new AryArgumentException(
 2413                message: "Hidden and read-only states cannot be set directly.", argName: nameof(updater.Value)
 2414            );
 415        }
 416
 7417        if (updater.Navigator.ThemeTypes.Contains(value: ThemeType.HighContrastDark) ||
 7418            updater.Navigator.ThemeTypes.Contains(value: ThemeType.HighContrastLight))
 419        {
 2420            throw new AryArgumentException(
 2421                message: "Cannot alter High Contrast themes.", argName: nameof(updater.Value)
 2422            );
 423        }
 424
 5425        if (updater.Navigator.ComponentStates.Contains(value: ComponentState.Focused) &&
 5426            (updater.Navigator.StyleTypes.Contains(value: StyleType.OutlineOffset) ||
 5427                updater.Navigator.StyleTypes.Contains(value: StyleType.OutlineStyle) ||
 5428                updater.Navigator.StyleTypes.Contains(value: StyleType.OutlineWidth)))
 429        {
 3430            throw new AryArgumentException(
 3431                message: "Cannot change focused outline offset, style or width.", argName: nameof(updater.Value)
 3432            );
 433        }
 434
 2435        _theme = _theme.Set(updater: updater);
 436
 2437        return this;
 438    }
 439}