Editor/Road/ArchRoadHandlers.cs
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;
	}
}