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); } } }