Code/ThemeProvider.razor.cs
using System;
using System.Collections.Generic;
using BetterUI.Extensions;

namespace BetterUI;

/// <summary>
/// A panel that manages the global theme.
/// </summary>
/// <remarks>
/// Use this panel to set the theme of your UI.
/// </remarks>
public sealed partial class ThemeProvider : Panel
{
	/// <summary>
	/// The current theme.
	/// </summary>
	public Theme Theme { get; private set; }

	/// <summary>
	/// The mode of theme injection.
	/// </summary>
	/// <remarks>
	/// Theme injection is the process of automatically passing the theme to all children
	/// of this panel. This enum specifies the mode of theme injection.
	/// </remarks>
	public ThemeInjectionMode InjectionMode { get; set; } = ThemeInjectionMode.Manual;

	/// <inheritdoc />
	protected override void OnAfterTreeRender( bool firstTime )
	{
		EnsureThemeUpdated();
	}

	private void EnsureThemeUpdated()
	{
		switch ( InjectionMode )
		{
			case ThemeInjectionMode.Direct:
				IterateChildren( Children );
				break;
			case ThemeInjectionMode.Descendents:
				IterateChildren( Descendants );
				break;
		}
	}

	private void IterateChildren( IEnumerable<Panel> panels )
	{
		foreach ( var child in panels )
			ApplyThemeToPanel( child );
	}

	private void ApplyThemeToPanel( Panel panel )
	{
		var type = TypeLibrary.GetType( panel.GetType() );
		
		var attr = type.GetAttribute<InjectThemeAttribute>();
		if ( attr is null ) return;
		
		panel.BindClass( "light", () => Theme is Theme.Light );
		panel.BindClass( "dark", () => Theme is Theme.Dark );
	}

	/// <summary>
	/// Sets the theme.
	/// </summary>
	/// <param name="theme">The new theme.</param>
	public void SetTheme( Theme theme )
	{
		Theme = theme;
		Scene.RunSceneEvent<IThemeEvent>( x => x.OnThemeChanged( theme ) );
	}
	
	/// <summary>
	/// Switches the theme to the opposite one.
	/// </summary>
	public void SwitchTheme() => SetTheme( Theme is Theme.Light ? Theme.Dark : Theme.Light );

	/// <inheritdoc />
	protected override int BuildHash() => HashCode.Combine( Theme, InjectionMode );
}