Files
ZeroSpace/ZeroSpace/ZeroSpace.MapEditor/Views/MapDrawable.cs
T

146 lines
4.7 KiB
C#

using Microsoft.Maui.Graphics;
using ZeroSpace.MapEditor.Models;
using ZeroSpace.MapEditor.Services;
namespace ZeroSpace.MapEditor.Views;
/// <summary>
/// Draws the star map: connection lines between connected systems and
/// one circle per system. Names are intentionally not drawn on the
/// canvas; details live in the inspector.
/// </summary>
public sealed class MapDrawable : IDrawable
{
public const double NodeRadius = 14;
public IList<MapNode> Nodes { get; set; } = Array.Empty<MapNode>();
public Func<string, MapNode?> FindNode { get; set; } = _ => null;
/// <summary>Screen pixels per world unit.</summary>
public double Scale { get; set; } = 1;
public double OffsetX { get; set; }
public double OffsetY { get; set; }
public RectF? SelectionBox { get; set; }
/// <summary>In-game galaxy map screenshot, drawn under the nodes.</summary>
public IImage? BackgroundImage { get; set; }
/// <summary>Whether the galaxy map background is shown.</summary>
public bool ShowBackground { get; set; } = true;
public void Draw(ICanvas canvas, RectF dirtyRect)
{
canvas.FillColor = Color.FromArgb("#1e2430");
canvas.FillRectangle(dirtyRect);
DrawBackground(canvas);
PointF ToScreen(double worldX, double worldY) =>
new((float)(worldX * Scale + OffsetX), (float)(worldY * Scale + OffsetY));
// Connections first, so nodes draw on top of the lines.
canvas.StrokeColor = Color.FromArgb("#5b6b82");
canvas.StrokeSize = Math.Max(1, (float)(1.5 * Scale));
foreach (var node in Nodes)
{
var from = ToScreen(node.X, node.Y);
foreach (var name in node.Doc.ConnectedSystems)
{
var target = FindNode(name);
if (target is null || ReferenceEquals(target, node))
{
continue;
}
var to = ToScreen(target.X, target.Y);
canvas.DrawLine(from, to);
}
}
foreach (var node in Nodes)
{
var center = ToScreen(node.X, node.Y);
var radius = (float)(NodeRadius * Scale);
canvas.FillColor = ColorForOwner(node.Doc.Owner);
canvas.FillCircle(center, radius);
if (node.IsHighlighted)
{
canvas.StrokeColor = Color.FromArgb("#FFD700");
canvas.StrokeSize = 3;
canvas.DrawCircle(center, radius + 4);
}
if (node.IsSelected)
{
canvas.StrokeColor = Colors.White;
canvas.StrokeSize = 3;
canvas.DrawCircle(center, node.IsHighlighted ? radius + 8 : radius + 6);
}
}
if (SelectionBox is { Width: > 0, Height: > 0 } box)
{
canvas.FillColor = Color.FromArgb("#334a90d9");
canvas.FillRectangle(box);
canvas.StrokeColor = Color.FromArgb("#4a90d9");
canvas.StrokeSize = 1.5f;
canvas.DrawRectangle(box);
}
}
/// <summary>
/// Draws the galaxy map screenshot fitted inside the world square,
/// preserving aspect ratio so node world coordinates line up exactly
/// with the map underneath.
/// </summary>
private void DrawBackground(ICanvas canvas)
{
if (!ShowBackground || BackgroundImage is null)
{
return;
}
var imageWidth = BackgroundImage.Width;
var imageHeight = BackgroundImage.Height;
if (imageWidth <= 0 || imageHeight <= 0)
{
return;
}
const double world = StarSystemDocStore.WorldSize;
var fit = Math.Min(world / imageWidth, world / imageHeight);
var drawWidth = imageWidth * fit;
var drawHeight = imageHeight * fit;
var originX = (world - drawWidth) / 2;
var originY = (world - drawHeight) / 2;
canvas.DrawImage(BackgroundImage,
(float)(originX * Scale + OffsetX),
(float)(originY * Scale + OffsetY),
(float)(drawWidth * Scale),
(float)(drawHeight * Scale));
}
public static Color ColorForOwner(string owner) => owner switch
{
"The Eden Accord" => Color.FromArgb("#10b981"),
"The Keeper Council" => Color.FromArgb("#38bdf8"),
"The Obsidian Order" => Color.FromArgb("#ef4444"),
_ => Color.FromArgb("#9aa5b1"),
};
public static Color ColorForCategory(string category) => category switch
{
"Homeworld System" or "Homeworld" => Color.FromArgb("#43a047"),
"Core System" or "Core" => Color.FromArgb("#fb8c00"),
"Side System" or "Side" => Color.FromArgb("#4a90d9"),
_ => Color.FromArgb("#9aa5b1"),
};
}