Editor/Wall/ArchWallModUi.cs

Editor UI helper for wall modifiers. It builds sidebar controls to choose modifier kind, which face/side it sits on, size parameters, and material role icons for wall parts.

Reflection
using System;
using Editor;
using Sandbox;

namespace Sunless.Architecture;

// Read by the Wall detail subtool and by Select, off the same part - a draft and a placed modifier
// carry identical controls because they are the same record.
public static class ArchWallModUi
{
	// What a block of masonry standing on or cut out of an elevation is worth being made of.
	static readonly ArchSurface[] Fields =
	{
		ArchSurface.Pillar,
		ArchSurface.WallBase,
		ArchSurface.WallExterior,
		ArchSurface.Foundation,
		ArchSurface.Panel,
		ArchSurface.Trim,
		ArchSurface.Sign
	};

	public static void Kinds( ToolSidebarWidget panel, ArchWallMod chosen, Action<ArchWallMod> adopt )
	{
		using var grid = ArchIconGrid.In( panel.AddGroup( "Detail" ) );

		foreach ( var value in Enum.GetValues<ArchWallMod>() )
		{
			var kind = value;

			grid.Pick( kind.Label(), $"wallmod_{kind}".ToLowerInvariant(), kind.Glyph(), chosen == kind, () => adopt( kind ) );
		}
	}

	// On surface is a placement mode - it asks the drag which face it landed on - so it is only ever
	// offered on a draft. A part already standing on a wall has an answer.
	public static void Sides( ToolSidebarWidget panel, ArchWallSide chosen, bool drafting, Action<ArchWallSide> adopt )
	{
		using var grid = ArchIconGrid.In( panel.AddGroup( "Face" ) );

		foreach ( var value in Enum.GetValues<ArchWallSide>() )
		{
			var side = value;

			if ( side == ArchWallSide.OnSurface && !drafting )
			{
				continue;
			}

			grid.Pick( side.Label(), $"wallside_{side}".ToLowerInvariant(), side.Glyph(), chosen == side, () => adopt( side ) );
		}
	}

	public static void Build( ToolSidebarWidget panel, ArchWallModPart modifier, Action refresh, Action changed )
	{
		Kinds( panel, modifier.Kind, kind =>
		{
			Rebanded( modifier, kind );

			modifier.Kind = kind;
			modifier.Field = kind.Field();
			changed?.Invoke();
			refresh?.Invoke();
		} );

		Sides( panel, modifier.Side, false, side =>
		{
			modifier.Side = side;
			changed?.Invoke();
			refresh?.Invoke();
		} );

		Sizes( panel, modifier, changed );
		Fill( panel, modifier, changed );
	}

	// A plate carries a band of its own where a pier climbs to the wall head, so a kind switch switches that
	// too: a plate's own two feet left on a pier is a stub where a whole elevation was asked for.
	static void Rebanded( ArchWallModPart modifier, ArchWallMod kind )
	{
		if ( kind.Plated() )
		{
			modifier.Height = kind.Rise( modifier.Height );

			return;
		}

		if ( modifier.Kind.Plated() )
		{
			modifier.Base = 0f;
			modifier.Height = 0f;
		}
	}

	// Station first: it is the contract that keeps the modifier on the wall when the wall moves. A
	// draft has none to show - the drag is what names it.
	public static void Sizes( ToolSidebarWidget panel, ArchWallModPart modifier, Action changed, bool station = true )
	{
		var group = panel.AddGroup( "Block" );

		if ( station )
		{
			group.Add( ArchPartUi.Number( "Along the wall", modifier.Along, 0f, value => modifier.Along = value, changed ) );
		}

		group.Add( ArchPartUi.Number( station ? "Width" : "Width when clicked", modifier.Width, 16f, value => modifier.Width = value, changed ) );
		group.Add( ArchPartUi.Number( "Starts above the foot", modifier.Base, 0f, value => modifier.Base = value, changed ) );

		// A draft's height belongs to its own Climb group, which says it in storeys.
		if ( station )
		{
			group.Add( ArchPartUi.Number( "Height", modifier.Height, 0f, value => modifier.Height = value, changed ) );
		}

		group.Add( ArchPartUi.Number(
			modifier.Carves ? "Bites in" : "Stands proud", modifier.Depth, 3f, value => modifier.Depth = value, changed ) );
	}

	public static void Fill( ToolSidebarWidget panel, ArchWallModPart modifier, Action changed )
	{
		using var grid = ArchIconGrid.In( panel.AddGroup( "Material role" ) );

		foreach ( var value in Fields )
		{
			var field = value;

			grid.Pick( $"Skinned as {field}", $"surface_{field}".ToLowerInvariant(), Glyph( field ),
				modifier.Field == field, () => { modifier.Field = field; changed?.Invoke(); } );
		}
	}

	static string Glyph( ArchSurface field ) => field switch
	{
		ArchSurface.Foundation => "foundation",
		ArchSurface.WallBase => "grid_4x4",
		ArchSurface.Panel => "crop_square",
		ArchSurface.Pillar => "view_column",
		ArchSurface.Trim => "horizontal_rule",
		ArchSurface.Sign => "label",
		_ => "villa"
	};
}