Editor/Road/ArchRoadHandlers.cs

Editor handlers for bridge and tunnel arch parts. Each handler reports its kind and draws interactive span controls by delegating to ArchRoadSpanHandler.Draw, which handles sliding and endpoint station gizmos and clamps endpoints to valid ranges.

Native Interop
namespace Sunless.Architecture;

public sealed class ArchBridgeHandler : IArchHandler
{
	public ArchKind Kind => ArchKind.Bridge;

	public bool Draw( ArchTool tool, ArchSelection picked )
	{
		return picked.Item is ArchBridgePart bridge
			&& ArchRoadSpanHandler.Draw( tool, ArchGesture.On( ArchKind.Bridge, bridge.Id ), tool.Plan.RoadCarrying( bridge ),
				bridge.From, bridge.To, ArchBridge.ShortestSpan, Color.Orange,
				from => bridge.From = from, to => bridge.To = to );
	}
}

public sealed class ArchTunnelHandler : IArchHandler
{
	public ArchKind Kind => ArchKind.Tunnel;

	public bool Draw( ArchTool tool, ArchSelection picked )
	{
		return picked.Item is ArchTunnelPart tunnel
			&& ArchRoadSpanHandler.Draw( tool, ArchGesture.On( ArchKind.Tunnel, tunnel.Id ), tool.Plan.RoadHolding( tunnel ),
				tunnel.From, tunnel.To, ArchTunnel.ShortestBore, Color.Cyan,
				from => tunnel.From = from, to => tunnel.To = to );
	}
}

static class ArchRoadSpanHandler
{
	public static bool Draw( ArchTool tool, ArchGesture gesture, ArchRoadPart road, float from, float to, float shortest, Color tint,
		Action<float> setFrom, Action<float> setTo )
	{
		if ( road is null || !tool.Resolved( road.Id, road.Curve ).IsUsable )
		{
			return false;
		}

		var curve = tool.Resolved( road.Id, road.Curve );
		var changed = false;
		var middle = (from + to) * 0.5f;

		// The span travels whole: one delta moves both ends, so a bridge keeps its length wherever it is slid to.
		if ( ArchShapeHandles.Slid( tool, gesture.At( "slide" ), curve, middle, out var delta, Gizmo.Colors.Blue ) )
		{
			setFrom( Math.Clamp( from + delta, 0f, curve.Length ) );
			setTo( Math.Clamp( to + delta, 0f, curve.Length ) );
			changed = true;
		}

		if ( ArchShapeHandles.Station( tool, gesture.At( "from" ), curve, from, out var landedFrom, tint ) )
		{
			setFrom( Math.Clamp( landedFrom, 0f, to - shortest ) );
			changed = true;
		}

		if ( ArchShapeHandles.Station( tool, gesture.At( "to" ), curve, to, out var landedTo, tint ) )
		{
			setTo( Math.Clamp( landedTo, from + shortest, curve.Length ) );
			changed = true;
		}

		return changed;
	}
}