AI: Add 2D translation gizmo for axis-constrained node moves
This commit is contained in:
@@ -4,6 +4,15 @@ using ZeroSpace.MapEditor.Services;
|
||||
|
||||
namespace ZeroSpace.MapEditor.Views;
|
||||
|
||||
/// <summary>Which part of the translation gizmo was hit or is active.</summary>
|
||||
public enum GizmoPart
|
||||
{
|
||||
None,
|
||||
Center,
|
||||
XAxis,
|
||||
YAxis,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the star map: connection lines between connected systems and
|
||||
/// one circle per system. Names are intentionally not drawn on the
|
||||
@@ -13,6 +22,9 @@ public sealed class MapDrawable : IDrawable
|
||||
{
|
||||
public const double NodeRadius = 14;
|
||||
|
||||
/// <summary>Screen-space length of each gizmo axis arm, in pixels.</summary>
|
||||
public const double GizmoArmLength = 64;
|
||||
|
||||
public IList<MapNode> Nodes { get; set; } = Array.Empty<MapNode>();
|
||||
|
||||
public Func<string, MapNode?> FindNode { get; set; } = _ => null;
|
||||
@@ -32,6 +44,12 @@ public sealed class MapDrawable : IDrawable
|
||||
/// <summary>Whether the galaxy map background is shown.</summary>
|
||||
public bool ShowBackground { get; set; } = true;
|
||||
|
||||
/// <summary>Node the translation gizmo is drawn for (the active selection).</summary>
|
||||
public MapNode? GizmoNode { get; set; }
|
||||
|
||||
/// <summary>Gizmo part currently hovered or being dragged, for highlight.</summary>
|
||||
public GizmoPart ActiveGizmoPart { get; set; } = GizmoPart.None;
|
||||
|
||||
public void Draw(ICanvas canvas, RectF dirtyRect)
|
||||
{
|
||||
canvas.FillColor = Color.FromArgb("#1e2430");
|
||||
@@ -92,6 +110,82 @@ public sealed class MapDrawable : IDrawable
|
||||
canvas.StrokeSize = 1.5f;
|
||||
canvas.DrawRectangle(box);
|
||||
}
|
||||
|
||||
if (GizmoNode is not null)
|
||||
{
|
||||
DrawGizmo(canvas, GizmoNode);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Draws the 2D translation gizmo at the node's screen position:
|
||||
/// a red +X arm, a green +Y arm, and a center square for free XY moves.
|
||||
/// Screen-space, so it stays the same size at any zoom level.
|
||||
/// </summary>
|
||||
private void DrawGizmo(ICanvas canvas, MapNode node)
|
||||
{
|
||||
var cx = (float)(node.X * Scale + OffsetX);
|
||||
var cy = (float)(node.Y * Scale + OffsetY);
|
||||
const float arm = (float)GizmoArmLength;
|
||||
|
||||
DrawGizmoArm(canvas, cx, cy, cx + arm, cy, isX: true,
|
||||
active: ActiveGizmoPart == GizmoPart.XAxis);
|
||||
DrawGizmoArm(canvas, cx, cy, cx, cy + arm, isX: false,
|
||||
active: ActiveGizmoPart == GizmoPart.YAxis);
|
||||
|
||||
// Center handle: free XY move.
|
||||
const float size = 12f;
|
||||
var centerColor = ActiveGizmoPart == GizmoPart.Center
|
||||
? Color.FromArgb("#fde047")
|
||||
: Color.FromArgb("#eab308");
|
||||
canvas.FillColor = Color.FromArgb("#111827");
|
||||
canvas.FillRectangle(cx - size / 2 - 2, cy - size / 2 - 2, size + 4, size + 4);
|
||||
canvas.FillColor = centerColor;
|
||||
canvas.FillRectangle(cx - size / 2, cy - size / 2, size, size);
|
||||
}
|
||||
|
||||
private static void DrawGizmoArm(ICanvas canvas, float x1, float y1, float x2, float y2,
|
||||
bool isX, bool active)
|
||||
{
|
||||
var color = isX
|
||||
? (active ? Color.FromArgb("#f87171") : Color.FromArgb("#ef4444"))
|
||||
: (active ? Color.FromArgb("#4ade80") : Color.FromArgb("#22c55e"));
|
||||
|
||||
// Dark underlay so the arm reads against the bright galaxy map.
|
||||
canvas.StrokeColor = Color.FromArgb("#111827");
|
||||
canvas.StrokeSize = 5;
|
||||
canvas.DrawLine(x1, y1, x2, y2);
|
||||
canvas.StrokeColor = color;
|
||||
canvas.StrokeSize = 3;
|
||||
canvas.DrawLine(x1, y1, x2, y2);
|
||||
|
||||
// Arrowhead pointing along the axis.
|
||||
const float headLen = 12f;
|
||||
const float headHalf = 6f;
|
||||
PointF tip, a, b;
|
||||
if (isX)
|
||||
{
|
||||
tip = new PointF(x2, y2);
|
||||
a = new PointF(x2 - headLen, y2 - headHalf);
|
||||
b = new PointF(x2 - headLen, y2 + headHalf);
|
||||
}
|
||||
else
|
||||
{
|
||||
tip = new PointF(x2, y2);
|
||||
a = new PointF(x2 - headHalf, y2 - headLen);
|
||||
b = new PointF(x2 + headHalf, y2 - headLen);
|
||||
}
|
||||
|
||||
var head = new PathF();
|
||||
head.MoveTo(tip);
|
||||
head.LineTo(a);
|
||||
head.LineTo(b);
|
||||
head.Close();
|
||||
canvas.FillColor = color;
|
||||
canvas.FillPath(head);
|
||||
canvas.StrokeColor = Color.FromArgb("#111827");
|
||||
canvas.StrokeSize = 1.5f;
|
||||
canvas.DrawPath(head);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user