Dispenser/DispenserUI/Views/Controls/CoordinateViewer.axaml.cs

187 lines
5.5 KiB
C#

using System;
using System.Linq;
using System.Reactive.Linq;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Threading;
using Masuit.Tools;
namespace DispenserUI.Views.Controls;
public partial class CoordinateViewer : UserControl
{
public static readonly StyledProperty<double[][]> CoordsProperty =
AvaloniaProperty.Register<CoordinateViewer,
double[][]>(nameof(Coords));
public static readonly StyledProperty<double[][]> PathProperty =
AvaloniaProperty.Register<CoordinateViewer,
double[][]>(nameof(Path));
public static readonly StyledProperty<bool> RotateProperty =
AvaloniaProperty.Register<CoordinateViewer,
bool>(nameof(Rotate));
private readonly object _lock = new();
public CoordinateViewer()
{
InitializeComponent();
this.GetObservable(CoordsProperty).Throttle(TimeSpan.FromMilliseconds(1000))
.Subscribe(val =>
{
if (val.IsNullOrEmpty()) return;
Dispatcher.UIThread.InvokeAsync(InvalidateVisual);
});
this.GetObservable(PathProperty).Throttle(TimeSpan.FromSeconds(1))
.Subscribe(val =>
{
if (val.IsNullOrEmpty()) return;
Dispatcher.UIThread.InvokeAsync(InvalidateVisual);
});
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
public double[][] Coords
{
get => GetValue(CoordsProperty);
set => SetValue(CoordsProperty, value);
}
public double[][] Path
{
get => GetValue(PathProperty);
set => SetValue(PathProperty, value);
}
public bool Rotate
{
get => GetValue(RotateProperty);
set => SetValue(RotateProperty, value);
}
public override void Render(DrawingContext context)
{
lock (_lock)
{
base.Render(context);
if (Coords.IsNullOrEmpty())
{
return;
}
var coords = CloneCoords(Coords);
var boundsWidth = DesiredSize.Width;
var boundsHeight = DesiredSize.Height;
if (Rotate)
{
// 将坐标进行旋转90°
Rotate90Degrees(coords);
}
// 将坐标进行Y轴翻转
var points = coords.Select(point => new Point(-point[0], point[1])).ToList();
// 找到坐标中最小的X、Y坐标
var minX = points.Min(point => point.X);
// 查找最小的Y
var minY = points.Min(point => point.Y);
// 找到最大的X
var maxX = points.Max(point => point.X);
// 找到最大的Y
var maxY = points.Max(point => point.Y);
var aspectRatio = (maxY - minY) / (maxX - minX);
// 更改缩放比例,使得容器的宽高比尽量接近坐标的宽高比
var scaleBoundsWidth = boundsHeight / aspectRatio;
var scaleX = scaleBoundsWidth / (maxX - minX);
var scaleY = boundsHeight / (maxY - minY);
var offsetX = (boundsWidth - (maxX - minX) * scaleX) / 2;
foreach (var point in points)
{
// 将X 进行居中对齐
var x = (point.X - minX) * scaleX + offsetX;
var y = (point.Y - minY) * scaleY;
var rect = new Rect(x, y, 2, 5);
var pen = new Pen(Brushes.Black, 0.5);
context.DrawRectangle(pen, rect);
}
// 如果路径不为空的话,绘制路径
if (Path.IsNullOrEmpty()) return;
// 计算出路径的坐标点
var paths = Path
.Select(point => new Point((-point[0] - minX) * scaleX + offsetX, (point[1] - minY) * scaleY)).ToList();
var geometry = new StreamGeometry();
using (var ctx = geometry.Open())
{
ctx.BeginFigure(paths[0], false);
foreach (var point in paths.Skip(1))
{
ctx.LineTo(point);
}
ctx.EndFigure(false);
}
// 绘制路径
context.DrawGeometry(null, new Pen(Brushes.Red, 1), geometry);
// 在开始的第一个点标注为绿色
var startPoint = paths[0];
context.DrawEllipse(new SolidColorBrush(Colors.Green), new Pen(Brushes.Green, 1),
new Rect(new Point(startPoint.X - 2, startPoint.Y - 2), new Size(5, 5)));
// 最后一个点标注为黄色
var endPoint = paths[^1];
context.DrawEllipse(new SolidColorBrush(Colors.Yellow), new Pen(Brushes.Yellow, 1),
new Rect(new Point(endPoint.X - 2, endPoint.Y - 2), new Size(5, 5)));
}
}
private static void Rotate90Degrees(double[][] coords)
{
foreach (var coord in coords)
{
var tempX = coord[0];
var tempY = coord[1];
coord[0] = -tempY;
coord[1] = tempX;
}
}
private static double[][] CloneCoords(double[][] original)
{
var copy = new double[original.Length][];
// 逐行复制
for (var i = 0; i < original.Length; i++)
{
copy[i] = new double[original[i].Length];
Array.Copy(original[i], copy[i], original[i].Length);
}
return copy;
}
}