using Microsoft.UI;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;
using Windows.UI;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Controls;
public sealed class RouteMap : UserControl
{
private readonly Canvas canvas = new() { MinHeight = 230 };
public static readonly DependencyProperty PlanProperty = DependencyProperty.Register(nameof(Plan), typeof(RoutePlan), typeof(RouteMap), new PropertyMetadata(null, (d, _) => ((RouteMap)d).Draw()));
public RoutePlan? Plan { get => (RoutePlan?)GetValue(PlanProperty); set => SetValue(PlanProperty, value); }
public RouteMap() { Content = canvas; SizeChanged += (_, _) => Draw(); }
private static SolidColorBrush Brush(byte r, byte g, byte b, byte alpha = 255) => new(Color.FromArgb(alpha, r, g, b));
private void Draw()
{
canvas.Children.Clear();
var width = Math.Max(100, ActualWidth); var height = Math.Max(230, ActualHeight);
canvas.Clip = new RectangleGeometry { Rect = new(0, 0, width, height) };
for (var i = 0; i < 65; i++)
{
var dot = new Ellipse { Width = i % 7 == 0 ? 2 : 1, Height = i % 7 == 0 ? 2 : 1, Fill = Brush(116, 151, 187, (byte)(50 + i % 6 * 15)) };
Canvas.SetLeft(dot, (i * 127 + 17) % width); Canvas.SetTop(dot, (i * 73 + 29) % height); canvas.Children.Add(dot);
}
var stations = Plan?.Legs.SelectMany(l => new[] { l.From, l.To }).DistinctBy(s => s.MarketId).ToArray() ?? [];
if (stations.Length == 0)
{
var ring = new Ellipse { Width = 135, Height = 135, Stroke = Brush(49, 79, 111), StrokeThickness = 1 };
Canvas.SetLeft(ring, width / 2 - 67); Canvas.SetTop(ring, height / 2 - 87); canvas.Children.Add(ring);
var icon = new GameIcon { IconKey = Core.Rendering.GameUiIcon.FeatureRoute, Width = 34, Height = 34 };
Canvas.SetLeft(icon, width / 2 - 17); Canvas.SetTop(icon, height / 2 - 35); canvas.Children.Add(icon);
var hint = new TextBlock { Text = "计算路线后,在这里预览站点与航段", Foreground = Brush(145, 163, 185), FontSize = 12 };
Canvas.SetLeft(hint, Math.Max(16, width / 2 - 120)); Canvas.SetTop(hint, height / 2 + 70); canvas.Children.Add(hint);
return;
}
var minX = stations.Min(s => s.Position?.X ?? 0); var maxX = stations.Max(s => s.Position?.X ?? 0);
var minY = stations.Min(s => s.Position?.Z + s.Position?.Y ?? 0); var maxY = stations.Max(s => s.Position?.Z + s.Position?.Y ?? 0);
var points = stations.ToDictionary(s => s.MarketId, s => new Windows.Foundation.Point(60 + ((s.Position?.X ?? 0) - minX) / Math.Max(1, maxX - minX) * Math.Max(50, width - 190), 48 + ((s.Position?.Z + s.Position?.Y ?? 0) - minY) / Math.Max(1, maxY - minY) * Math.Max(50, height - 115)));
foreach (var leg in Plan!.Legs)
{
var a = points[leg.From.MarketId]; var b = points[leg.To.MarketId];
var offset = leg.Number % 2 == 0 ? 15d : -15d;
var geometry = new PathGeometry(); var figure = new PathFigure { StartPoint = a };
figure.Segments.Add(new QuadraticBezierSegment { Point1 = new((a.X + b.X) / 2 + offset, (a.Y + b.Y) / 2 - offset), Point2 = b }); geometry.Figures.Add(figure);
canvas.Children.Add(new Microsoft.UI.Xaml.Shapes.Path { Data = geometry, Stroke = leg.Number == 1 ? Brush(255, 174, 72) : Brush(84, 222, 200, 160), StrokeThickness = 2, StrokeDashArray = leg.Number == 1 ? [] : [4, 4] });
var label = new TextBlock { Text = $"{leg.Number:00}", FontSize = 11, Foreground = Brush(255, 174, 72), FontWeight = Microsoft.UI.Text.FontWeights.SemiBold };
Canvas.SetLeft(label, (a.X + b.X) / 2 + offset); Canvas.SetTop(label, (a.Y + b.Y) / 2 - offset); canvas.Children.Add(label);
}
foreach (var station in stations)
{
var p = points[station.MarketId]; var first = station.MarketId == stations[0].MarketId;
var outer = new Ellipse { Width = 24, Height = 24, Fill = first ? Brush(255, 174, 72, 35) : Brush(84, 222, 200, 30), Stroke = first ? Brush(255, 174, 72) : Brush(84, 222, 200), StrokeThickness = 1 };
Canvas.SetLeft(outer, p.X - 12); Canvas.SetTop(outer, p.Y - 12); canvas.Children.Add(outer);
var dot = new Ellipse { Width = 7, Height = 7, Fill = first ? Brush(255, 174, 72) : Brush(84, 222, 200) };
Canvas.SetLeft(dot, p.X - 3.5); Canvas.SetTop(dot, p.Y - 3.5); canvas.Children.Add(dot);
var label = new StackPanel { Spacing = 3 };
label.Children.Add(new TextBlock { Text = station.SystemName, FontSize = 13, FontWeight = Microsoft.UI.Text.FontWeights.SemiBold });
label.Children.Add(new TextBlock { Text = station.Name, FontSize = 10, Foreground = Brush(145, 163, 185), MaxWidth = 140, TextTrimming = TextTrimming.CharacterEllipsis });
Canvas.SetLeft(label, p.X + 18); Canvas.SetTop(label, p.Y - 5); canvas.Children.Add(label);
}
}
}
using Microsoft.UI;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Shapes;
using Windows.UI;
using XFEEDToolBox.Core.Models;
namespace XFEEDToolBox.Controls;
public sealed class RouteMap : UserControl
{
private readonly Canvas canvas = new() { MinHeight = 230 };
public static readonly DependencyProperty PlanProperty = DependencyProperty.Register(nameof(Plan), typeof(RoutePlan), typeof(RouteMap), new PropertyMetadata(null, (d, _) => ((RouteMap)d).Draw()));
public RoutePlan? Plan { get => (RoutePlan?)GetValue(PlanProperty); set => SetValue(PlanProperty, value); }
public RouteMap() { Content = canvas; SizeChanged += (_, _) => Draw(); }
private static SolidColorBrush Brush(byte r, byte g, byte b, byte alpha = 255) => new(Color.FromArgb(alpha, r, g, b));
private void Draw()
{
canvas.Children.Clear();
var width = Math.Max(100, ActualWidth); var height = Math.Max(230, ActualHeight);
canvas.Clip = new RectangleGeometry { Rect = new(0, 0, width, height) };
for (var i = 0; i < 65; i++)
{
var dot = new Ellipse { Width = i % 7 == 0 ? 2 : 1, Height = i % 7 == 0 ? 2 : 1, Fill = Brush(116, 151, 187, (byte)(50 + i % 6 * 15)) };
Canvas.SetLeft(dot, (i * 127 + 17) % width); Canvas.SetTop(dot, (i * 73 + 29) % height); canvas.Children.Add(dot);
}
var stations = Plan?.Legs.SelectMany(l => new[] { l.From, l.To }).DistinctBy(s => s.MarketId).ToArray() ?? [];
if (stations.Length == 0)
{
var ring = new Ellipse { Width = 135, Height = 135, Stroke = Brush(49, 79, 111), StrokeThickness = 1 };
Canvas.SetLeft(ring, width / 2 - 67); Canvas.SetTop(ring, height / 2 - 87); canvas.Children.Add(ring);
var icon = new GameIcon { IconKey = Core.Rendering.GameUiIcon.FeatureRoute, Width = 34, Height = 34 };
Canvas.SetLeft(icon, width / 2 - 17); Canvas.SetTop(icon, height / 2 - 35); canvas.Children.Add(icon);
var hint = new TextBlock { Text = "计算路线后,在这里预览站点与航段", Foreground = Brush(145, 163, 185), FontSize = 12 };
Canvas.SetLeft(hint, Math.Max(16, width / 2 - 120)); Canvas.SetTop(hint, height / 2 + 70); canvas.Children.Add(hint);
return;
}
var minX = stations.Min(s => s.Position?.X ?? 0); var maxX = stations.Max(s => s.Position?.X ?? 0);
var minY = stations.Min(s => s.Position?.Z + s.Position?.Y ?? 0); var maxY = stations.Max(s => s.Position?.Z + s.Position?.Y ?? 0);
var points = stations.ToDictionary(s => s.MarketId, s => new Windows.Foundation.Point(60 + ((s.Position?.X ?? 0) - minX) / Math.Max(1, maxX - minX) * Math.Max(50, width - 190), 48 + ((s.Position?.Z + s.Position?.Y ?? 0) - minY) / Math.Max(1, maxY - minY) * Math.Max(50, height - 115)));
foreach (var leg in Plan!.Legs)
{
var a = points[leg.From.MarketId]; var b = points[leg.To.MarketId];
var offset = leg.Number % 2 == 0 ? 15d : -15d;
var geometry = new PathGeometry(); var figure = new PathFigure { StartPoint = a };
figure.Segments.Add(new QuadraticBezierSegment { Point1 = new((a.X + b.X) / 2 + offset, (a.Y + b.Y) / 2 - offset), Point2 = b }); geometry.Figures.Add(figure);
canvas.Children.Add(new Microsoft.UI.Xaml.Shapes.Path { Data = geometry, Stroke = leg.Number == 1 ? Brush(255, 174, 72) : Brush(84, 222, 200, 160), StrokeThickness = 2, StrokeDashArray = leg.Number == 1 ? [] : [4, 4] });
var label = new TextBlock { Text = $"{leg.Number:00}", FontSize = 11, Foreground = Brush(255, 174, 72), FontWeight = Microsoft.UI.Text.FontWeights.SemiBold };
Canvas.SetLeft(label, (a.X + b.X) / 2 + offset); Canvas.SetTop(label, (a.Y + b.Y) / 2 - offset); canvas.Children.Add(label);
}
foreach (var station in stations)
{
var p = points[station.MarketId]; var first = station.MarketId == stations[0].MarketId;
var outer = new Ellipse { Width = 24, Height = 24, Fill = first ? Brush(255, 174, 72, 35) : Brush(84, 222, 200, 30), Stroke = first ? Brush(255, 174, 72) : Brush(84, 222, 200), StrokeThickness = 1 };
Canvas.SetLeft(outer, p.X - 12); Canvas.SetTop(outer, p.Y - 12); canvas.Children.Add(outer);
var dot = new Ellipse { Width = 7, Height = 7, Fill = first ? Brush(255, 174, 72) : Brush(84, 222, 200) };
Canvas.SetLeft(dot, p.X - 3.5); Canvas.SetTop(dot, p.Y - 3.5); canvas.Children.Add(dot);
var label = new StackPanel { Spacing = 3 };
label.Children.Add(new TextBlock { Text = station.SystemName, FontSize = 13, FontWeight = Microsoft.UI.Text.FontWeights.SemiBold });
label.Children.Add(new TextBlock { Text = station.Name, FontSize = 10, Foreground = Brush(145, 163, 185), MaxWidth = 140, TextTrimming = TextTrimming.CharacterEllipsis });
Canvas.SetLeft(label, p.X + 18); Canvas.SetTop(label, p.Y - 5); canvas.Children.Add(label);
}
}
}