Editor/Walkway/ArchWalkwayInteractions.cs

Editor UI/tool code for walkway editing. Defines ArchWalkwayHandler which handles user interactions for editing walkway geometry (reshaping, moving, height adjustments) and ArchWalkwayPreview which draws a ghost/preview of a walkway (volume, plate, brackets, footprints, labels).

File Access
namespace Sunless.Architecture;

public sealed class ArchWalkwayHandler : IArchHandler
{
	public ArchKind Kind => ArchKind.Walkway;

	public bool Draw( ArchTool tool, ArchSelection picked )
	{
		if ( picked.Item is not ArchRoom room )
		{
			return false;
		}

		var outline = ArchFloorGen.Footprint( room );

		if ( outline.Count < 3 )
		{
			return false;
		}

		var gesture = ArchGesture.On( ArchKind.Walkway, room.Id );
		var deck = ArchWalkway.Deck( tool.Plan, room );
		var top = deck?.BaseHeight ?? MathF.Max( room.BaseHeight + room.WallHeight, room.WalkwayTop );
		var reshaped = ArchShapeHandles.Corners( tool, gesture, outline, top );

		reshaped |= ArchShapeHandles.Edges( tool, gesture, outline, top );

		if ( reshaped )
		{
			ArchWalkway.Reshape( tool.Plan, room, outline, tool.Kit );
		}

		var middle = Middle( ArchFloorGen.Footprint( room ) );
		var changed = reshaped;

		if ( ArchShapeHandles.Move( tool, gesture.At( "body" ), middle, out var moved ) && (moved - middle).Length >= 0.01f )
		{
			var shifted = ArchFloorGen.Footprint( room );

			ArchRemap.Translation( moved - middle ).Loop( shifted );
			ArchWalkway.Reshape( tool.Plan, room, shifted, tool.Kit );
			changed = true;
		}

		if ( ArchShapeHandles.Height( tool, gesture.At( "deck" ), middle, top, out var head, Gizmo.Colors.Roll ) )
		{
			ArchWalkway.Reheight( room, deck, tool.Kit, MathF.Max( 60f, head - room.BaseHeight ) );
			changed = true;
		}

		if ( ArchShapeHandles.Height( tool, gesture.At( "floor" ), middle, room.BaseHeight, out var floor, Gizmo.Colors.Pitch ) )
		{
			ArchWalkway.Raise( tool.Plan, room, tool.Kit, floor );
			changed = true;
		}

		return changed;
	}

	static Vector2 Middle( IReadOnlyList<Vector2> outline )
	{
		if ( outline is not { Count: > 0 } )
		{
			return Vector2.Zero;
		}

		ArchFootprint.Bounds( outline, out var min, out var max );

		return (min + max) * 0.5f;
	}
}

public sealed class ArchWalkwayPreview : IArchPreview
{
	public ArchKind Kind => ArchKind.Walkway;

	public void Draw( ArchPreviewContext context )
	{
		if ( context.Payload is not ArchRoom link || context.Tool is null )
		{
			return;
		}

		var loop = ArchFloorGen.Footprint( link );

		if ( loop.Count < 3 )
		{
			return;
		}

		ArchFootprint.Bounds( loop, out var min, out var max );

		var deck = ArchWalkway.Deck( context.Tool.Plan, link );
		var floor = link.BaseHeight + context.Lift;
		var head = (deck?.BaseHeight ?? link.BaseHeight + link.WallHeight) + context.Lift;

		ArchGhost.Volume( min, max, floor, head );
		ArchGhost.Plate( min, max, floor, 8 );
		ArchGhost.Brackets( min, max, head );

		var alongX = max.x - min.x >= max.y - min.y;
		var run = alongX ? max.x - min.x : max.y - min.y;
		var across = alongX ? max.y - min.y : max.x - min.x;

		Mouth( min, max, alongX, true, floor, head );
		Mouth( min, max, alongX, false, floor, head );
		ArchGhost.Note( new Vector3( max.x, max.y, head ), $"walkway — {run:0} x {across:0}, {head - floor:0} clear" );
	}

	static void Mouth( Vector2 min, Vector2 max, bool alongX, bool low, float bottom, float top )
	{
		var start = alongX
			? new Vector2( low ? min.x : max.x, min.y )
			: new Vector2( min.x, low ? min.y : max.y );
		var end = alongX
			? new Vector2( low ? min.x : max.x, max.y )
			: new Vector2( max.x, low ? min.y : max.y );

		ArchGhost.Footprint( new List<Vector2> { start, end }, bottom, ArchGhost.Accent );
		ArchGhost.Footprint( new List<Vector2> { start, end }, top, ArchGhost.Accent );

		Gizmo.Draw.Color = ArchGhost.Accent;
		Gizmo.Draw.Line( new Vector3( start.x, start.y, bottom ), new Vector3( start.x, start.y, top ) );
		Gizmo.Draw.Line( new Vector3( end.x, end.y, bottom ), new Vector3( end.x, end.y, top ) );
	}
}