CascadingValue.razor.cs
using System;
using BetterUI.Extensions;
using Sandbox.Diagnostics;

namespace BetterUI;

/// <summary>
/// A panel that cascades a value to its children.
/// </summary>
/// <remarks>
/// Children that have a property with the same name as the Name property,
/// and the same type as the Value property, will have that property set to the Value.
/// </remarks>
public partial class CascadingValue : Panel
{
	/// <summary>
	/// The name of the property to cascade.
	/// </summary>
	/// <remarks>
	/// If empty, the name of the property will be the same as the name of the property
	/// on the child panel.
	/// </remarks>
	public string Name { get; set; } = null!;

	/// <summary>
	/// The value to cascade.
	/// </summary>
	public object Value { get; set; } = null!;

	protected override void OnAfterTreeRender( bool firstTime )
	{
		EnsurePropertiesUpdated();
	}

	    /// <summary>
    /// Ensures that all properties on descendant panels are updated with the cascading value.
    /// </summary>
    private void EnsurePropertiesUpdated()
    {
        Assert.NotNull(Value, "CascadingValue must have a value");
        IterateChildren();
    }

    /// <summary>
    /// Iterates over all descendant panels and attempts to set the cascading value to their properties.
    /// </summary>
    private void IterateChildren()
    {
        foreach (var panel in Descendants)
            TrySetValueToPanelProperties(panel);
    }

    /// <summary>
    /// Tries to set the cascading value to the properties of a given panel if they match the specified name and type.
    /// </summary>
    /// <param name="panel">The panel whose properties are to be set.</param>
    private void TrySetValueToPanelProperties(Panel panel)
    {
        var type = TypeLibrary.GetType(panel.GetType());
        var properties = type.Properties;

        foreach (var property in properties)
        {
            var valueType = Value.GetType();
            var name = string.IsNullOrEmpty(Name) ? property.Name : Name;

            if (!property.IsCascadingProperty(name, valueType))
                continue;

            property.SetValue(panel, Value);
            panel.StateHasChanged();
        }
    }

	protected override int BuildHash() => HashCode.Combine( Name, Value );
}