03 - LayoutPanels例子 - SimpleInkCanvas
C# maui暂时没有官方支持InkCanvas,但是不影响,自己实现一个就行了。目前支持画图,选择,移动和删除。同时支持自定义橡皮擦形状,也支持绑定自定义的形状列表。实现一个Converter类,以后所有的绑定类型转换都在这个类中实现。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Shares.Utility
{
public class Converter : IValueConverter
{
<local:Converter x:Key="Converter"/>public object? Convert(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> // Implement conversion logic here
<local:Converter x:Key="Converter"/> if (value is List<string> list)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>return string.Join(", ", list); // 自定义分隔符
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> else if (value is int intValue && targetType.IsEnum)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>return Enum.ToObject(targetType, intValue); // 将整数转换为枚举类型
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> return value;
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>public object? ConvertBack(object? value, Type targetType, object? parameter, System.Globalization.CultureInfo culture)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> // Implement conversion back logic here
<local:Converter x:Key="Converter"/> return value;
<local:Converter x:Key="Converter"/>}
}
}然后在MyStyles.xaml中添加Converter类的引用,这样以后所有项目都可以使用了,local是
xmlns:local="clr-namespace:Shares.Utility;assembly=Shares"
<local:Converter x:Key="Converter"/>InkCanvas重写GraphicsView
public class InkCanvas : GraphicsView, IDrawable {
<local:Converter x:Key="Converter"/>public class DrawingPath
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> private RectF? cachedBounds;
<local:Converter x:Key="Converter"/> private bool isDirty = true;
<local:Converter x:Key="Converter"/> public Guid Id { get; } = Guid.NewGuid();
<local:Converter x:Key="Converter"/> public PathF Path { get; set; } = new PathF();
<local:Converter x:Key="Converter"/> public Color? StrokeColor { get; set; }
<local:Converter x:Key="Converter"/> public float StrokeThickness { get; set; }
<local:Converter x:Key="Converter"/> public bool IsSelected { get; set; }
<local:Converter x:Key="Converter"/> public PointF Pos { get; set; }
<local:Converter x:Key="Converter"/> public RectF Bounds
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>get
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> if (!isDirty && cachedBounds.HasValue)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>return cachedBounds.Value;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> if (Path.Count == 0)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>cachedBounds = RectF.Zero;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>return RectF.Zero;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> var points = Path.Points;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> float minX = float.MaxValue, minY = float.MaxValue;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> float maxX = float.MinValue, maxY = float.MinValue;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> foreach (var point in points)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>float x = point.X + Pos.X;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>float y = point.Y + Pos.Y;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>minX = Math.Min(minX, x);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>minY = Math.Min(minY, y);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>maxX = Math.Max(maxX, x);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>maxY = Math.Max(maxY, y);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> cachedBounds = new RectF(minX, minY, maxX - minX, maxY - minY);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> isDirty = false;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> return cachedBounds.Value;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> public void InvalidateBounds() => isDirty = true;
<local:Converter x:Key="Converter"/> public void LineTo(float x, float y)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>Path.LineTo(x, y);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>InvalidateBounds();
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> public bool IntersectAt(PointF eraserPos, float eraserRadius)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>if (Path.Count == 0)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> return false;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>// 优化点接触检查
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>foreach (var point in Path.Points)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> float dx = point.X + Pos.X - eraserPos.X;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> float dy = point.Y + Pos.Y - eraserPos.Y;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> if (dx * dx + dy * dy = 2)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> var points = Path.Points;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> for (int i = 1; i < points.Count(); i++)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>var start = new PointF(points.ElementAt(i - 1).X + Pos.X, points.ElementAt(i - 1).Y + Pos.Y);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>var end = new PointF(points.ElementAt(i).X + Pos.X, points.ElementAt(i).Y + Pos.Y);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>if (PointToLineDistance(start, end, eraserPos) = 0 && minDistance1)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> var newPath = new DrawingPath
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>StrokeColor = StrokeColor,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>StrokeThickness = StrokeThickness,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>Pos = Pos
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> };
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> newPath.Path.MoveTo(points.ElementAt(1));
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> for (int i = 2; i < points.Count(); i++)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>newPath.Path.LineTo(points.ElementAt(i));
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> newPaths.Add(newPath);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>return newPaths;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> // 终点处理
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> if (bestIndex == points.Count() - 1)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>if (points.Count() > 1)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> var newPath = new DrawingPath
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>StrokeColor = StrokeColor,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>StrokeThickness = StrokeThickness,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>Pos = Pos
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> };
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> newPath.Path.MoveTo(points.ElementAt(0));
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> for (int i = 1; i < points.Count() - 1; i++)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>newPath.Path.LineTo(points.ElementAt(i));
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> newPaths.Add(newPath);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>return newPaths;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> // 中间点处理
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> if (bestIndex > 0 && bestIndex < points.Count() - 1)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>// 第一段路径
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>var path1 = new DrawingPath
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> StrokeColor = StrokeColor,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> StrokeThickness = StrokeThickness,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> Pos = Pos
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>};
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>path1.Path.MoveTo(points.ElementAt(0));
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>for (int i = 1; i0 && minDistance1)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>var path1 = new DrawingPath
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> StrokeColor = StrokeColor,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> StrokeThickness = StrokeThickness,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> Pos = Pos
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>};
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>path1.Path.MoveTo(points.ElementAt(0));
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>for (int i = 1; i < bestIndex; i++)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> path1.Path.LineTo(points.ElementAt(i));
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>newPaths.Add(path1);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> // 第二段路径
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> if (bestIndex < points.Count() - 1)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>var path2 = new DrawingPath
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> StrokeColor = StrokeColor,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> StrokeThickness = StrokeThickness,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> Pos = Pos
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>};
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>path2.Path.MoveTo(points.ElementAt(bestIndex));
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>for (int i = bestIndex + 1; i < points.Count(); i++)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> path2.Path.LineTo(points.ElementAt(i));
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>newPaths.Add(path2);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>return newPaths;
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>public enum InkCanvasEditingMode { Ink, Select, Erase }
<local:Converter x:Key="Converter"/>public static readonly BindableProperty EditingModeProperty =
<local:Converter x:Key="Converter"/> BindableProperty.Create(nameof(EditingMode), typeof(InkCanvasEditingMode), typeof(InkCanvas),
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>InkCanvasEditingMode.Ink, BindingMode.TwoWay, propertyChanged: OnEditingModeChanged);
<local:Converter x:Key="Converter"/>private static void OnEditingModeChanged(BindableObject bindable, object oldValue, object newValue)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> if (bindable is InkCanvas canvas)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>canvas.ClearSelection();
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>canvas.Invalidate();
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>public InkCanvasEditingMode EditingMode
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> get => (InkCanvasEditingMode)GetValue(EditingModeProperty);
<local:Converter x:Key="Converter"/> set => SetValue(EditingModeProperty, value);
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>public ObservableCollection Paths { get; set; } = new ObservableCollection();
<local:Converter x:Key="Converter"/>public DrawingPath Eraser { get; }
<local:Converter x:Key="Converter"/>public float EraserRadius { get; set; } = 15f; // 增大橡皮擦半径
<local:Converter x:Key="Converter"/>private DrawingPath? currentPath;
<local:Converter x:Key="Converter"/>private RectF? selectionRect;
<local:Converter x:Key="Converter"/>private PointF lastTouchPoint;
<local:Converter x:Key="Converter"/>private bool isMovingSelection;
<local:Converter x:Key="Converter"/>// 橡皮擦轨迹跟踪
<local:Converter x:Key="Converter"/>private readonly List eraserTrail = new List();
<local:Converter x:Key="Converter"/>private const int MaxEraserTrailPoints = 5;
<local:Converter x:Key="Converter"/>public Color StrokeColor { get; set; } = Colors.Black;
<local:Converter x:Key="Converter"/>public Color SelectionColor { get; set; } = Colors.Red;
<local:Converter x:Key="Converter"/>public float SelectionStrokeThickness { get; set; } = 1f;
<local:Converter x:Key="Converter"/>public float StrokeThickness { get; set; } = 1f;
<local:Converter x:Key="Converter"/>public InkCanvas()
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> Drawable = this;
<local:Converter x:Key="Converter"/> BackgroundColor = Colors.Transparent;
<local:Converter x:Key="Converter"/> Eraser = CreateEraserPath();
<local:Converter x:Key="Converter"/> StartInteraction += OnTouchStarted;
<local:Converter x:Key="Converter"/> DragInteraction += OnTouchMoved;
<local:Converter x:Key="Converter"/> EndInteraction += OnTouchEnded;
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private DrawingPath CreateEraserPath()
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> var path = new PathF();
<local:Converter x:Key="Converter"/> var points = new[]
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/> new PointF(107.4f, 13), new PointF(113.7f, 28.8f),
<local:Converter x:Key="Converter"/> new PointF(127.9f, 31.3f), new PointF(117.6f, 43.5f),
<local:Converter x:Key="Converter"/> new PointF(120.1f, 60.8f), new PointF(107.4f, 52.6f),
<local:Converter x:Key="Converter"/> new PointF(94.6f, 60.8f), new PointF(97.1f, 43.5f),
<local:Converter x:Key="Converter"/> new PointF(86.8f, 31.3f), new PointF(101f, 28.8f)
<local:Converter x:Key="Converter"/>};
<local:Converter x:Key="Converter"/> path.MoveTo(points);
<local:Converter x:Key="Converter"/> for (int i = 1; i < points.Length; i++)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>path.LineTo(points);
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> path.Close();
<local:Converter x:Key="Converter"/> return new DrawingPath { Path = path, StrokeColor = Colors.Black, StrokeThickness = 1f };
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void OnTouchStarted(object? sender, TouchEventArgs e)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> if (e.Touches.Length == 0) return;
<local:Converter x:Key="Converter"/> var point = e.Touches;
<local:Converter x:Key="Converter"/> lastTouchPoint = new PointF(point.X, point.Y);
<local:Converter x:Key="Converter"/> eraserTrail.Clear(); // 清除历史轨迹
<local:Converter x:Key="Converter"/> switch (EditingMode)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Ink:
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> StartInking(lastTouchPoint);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> break;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Select:
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> StartSelection(lastTouchPoint);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> break;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Erase:
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> StartErase(lastTouchPoint);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> eraserTrail.Add(lastTouchPoint); // 添加起始点
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> break;
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> Invalidate();
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void StartInking(PointF startPoint)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> currentPath = new DrawingPath
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>StrokeColor = StrokeColor,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>StrokeThickness = StrokeThickness,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>Pos = PointF.Zero
<local:Converter x:Key="Converter"/> };
<local:Converter x:Key="Converter"/> currentPath.Path.MoveTo(startPoint.X, startPoint.Y);
<local:Converter x:Key="Converter"/> Paths.Add(currentPath);
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void StartSelection(PointF startPoint)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> isMovingSelection = Paths.Any(p => p.IsSelected && p.Bounds.Contains(startPoint));
<local:Converter x:Key="Converter"/> if (!isMovingSelection)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>ClearSelection();
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>var clickedPath = Paths.LastOrDefault(p => p.Bounds.Contains(startPoint));
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>if (clickedPath != null)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> clickedPath.IsSelected = true;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> isMovingSelection = true;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>else
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> selectionRect = new RectF(startPoint, SizeF.Zero);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void StartErase(PointF startPoint)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> Eraser.Pos = new PointF(startPoint.X - Eraser.Path.Bounds.Width / 2,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> startPoint.Y - Eraser.Path.Bounds.Height / 4);
<local:Converter x:Key="Converter"/> Eraser.IsSelected = true;
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void OnTouchMoved(object? sender, TouchEventArgs e)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> if (e.Touches.Length == 0) return;
<local:Converter x:Key="Converter"/> var currentPoint = new PointF(e.Touches.X, e.Touches.Y);
<local:Converter x:Key="Converter"/> switch (EditingMode)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Ink:
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> ContinueInking(currentPoint);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> break;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Select:
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> UpdateSelection(currentPoint);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> break;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Erase:
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> UpdateEraser(currentPoint);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> ErasePaths();
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> break;
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> Invalidate();
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void ContinueInking(PointF currentPoint)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> if (currentPath == null) return;
<local:Converter x:Key="Converter"/> const float minDistance = 1.0f;
<local:Converter x:Key="Converter"/> float dx = currentPoint.X - lastTouchPoint.X;
<local:Converter x:Key="Converter"/> float dy = currentPoint.Y - lastTouchPoint.Y;
<local:Converter x:Key="Converter"/> if (dx * dx + dy * dy > minDistance * minDistance)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>currentPath.LineTo(currentPoint.X, currentPoint.Y);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>lastTouchPoint = currentPoint;
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void UpdateSelection(PointF currentPoint)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> if (isMovingSelection)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>MoveSelectedPaths(currentPoint);
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> else if (selectionRect.HasValue)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>UpdateSelectionRect(currentPoint);
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void UpdateEraser(PointF currentPoint)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> Eraser.Pos = new PointF(currentPoint.X - Eraser.Path.Bounds.Width / 2,
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> currentPoint.Y - Eraser.Path.Bounds.Height / 4);
<local:Converter x:Key="Converter"/> // 添加到橡皮擦轨迹
<local:Converter x:Key="Converter"/> eraserTrail.Add(Eraser.Pos);
<local:Converter x:Key="Converter"/> if (eraserTrail.Count > MaxEraserTrailPoints)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>eraserTrail.RemoveAt(0);
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> lastTouchPoint = currentPoint;
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>// 优化擦除逻辑
<local:Converter x:Key="Converter"/>private void ErasePaths()
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> // 倒序遍历所有路径
<local:Converter x:Key="Converter"/> for (int i = Paths.Count - 1; i >= 0; i--)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>var path = Paths;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>// 检查橡皮擦轨迹上的所有点
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>foreach (var trailPoint in eraserTrail)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> if (path.IntersectAt(trailPoint, EraserRadius))
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>var newPaths = path.SplitAt(trailPoint, EraserRadius);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>if (newPaths.Count > 0)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> Paths.RemoveAt(i);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> foreach (var newPath in newPaths)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>if (newPath.Path.Count >= 2) // 只添加有效路径
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> Paths.Add(newPath);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> break; // 路径已被处理,跳出循环
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>else
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> // 没有新路径表示整个路径应被删除
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> Paths.RemoveAt(i);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> break;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void MoveSelectedPaths(PointF currentPoint)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> float deltaX = currentPoint.X - lastTouchPoint.X;
<local:Converter x:Key="Converter"/> float deltaY = currentPoint.Y - lastTouchPoint.Y;
<local:Converter x:Key="Converter"/> foreach (var path in Paths)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>if (path.IsSelected)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> path.Pos = new PointF(path.Pos.X + deltaX, path.Pos.Y + deltaY);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> path.InvalidateBounds();
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> lastTouchPoint = currentPoint;
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void UpdateSelectionRect(PointF currentPoint)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> float x = Math.Min(lastTouchPoint.X, currentPoint.X);
<local:Converter x:Key="Converter"/> float y = Math.Min(lastTouchPoint.Y, currentPoint.Y);
<local:Converter x:Key="Converter"/> float width = Math.Abs(currentPoint.X - lastTouchPoint.X);
<local:Converter x:Key="Converter"/> float height = Math.Abs(currentPoint.Y - lastTouchPoint.Y);
<local:Converter x:Key="Converter"/> selectionRect = new RectF(x, y, width, height);
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void OnTouchEnded(object? sender, TouchEventArgs e)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> switch (EditingMode)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>case InkCanvasEditingMode.Select when selectionRect.HasValue:
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> FinalizeSelection();
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> break;
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> currentPath = null;
<local:Converter x:Key="Converter"/> selectionRect = null;
<local:Converter x:Key="Converter"/> isMovingSelection = false;
<local:Converter x:Key="Converter"/> Eraser.IsSelected = false;
<local:Converter x:Key="Converter"/> eraserTrail.Clear(); // 清除橡皮擦轨迹
<local:Converter x:Key="Converter"/> Invalidate();
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void FinalizeSelection()
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> var selection = selectionRect!.Value;
<local:Converter x:Key="Converter"/> foreach (var path in Paths)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>if (!selection.IntersectsWith(path.Bounds)) continue;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>if (selection.Contains(path.Bounds))
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> path.IsSelected = true;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> continue;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>foreach (var point in path.Path.Points)
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> var absolutePoint = new PointF(point.X + path.Pos.X, point.Y + path.Pos.Y);
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> if (selection.Contains(absolutePoint))
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>path.IsSelected = true;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>break;
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>public void ClearSelection()
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> foreach (var path in Paths)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>path.IsSelected = false;
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>public void Draw(ICanvas canvas, RectF dirtyRect)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> canvas.FillColor = BackgroundColor;
<local:Converter x:Key="Converter"/> canvas.FillRectangle(dirtyRect);
<local:Converter x:Key="Converter"/> canvas.StrokeLineCap = LineCap.Round;
<local:Converter x:Key="Converter"/> canvas.StrokeLineJoin = LineJoin.Round;
<local:Converter x:Key="Converter"/> // 绘制所有路径
<local:Converter x:Key="Converter"/> foreach (var path in Paths)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>DrawPath(canvas, path);
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> // 绘制橡皮擦(如果被选中)
<local:Converter x:Key="Converter"/> if (Eraser.IsSelected)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>DrawEraser(canvas);
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> // 绘制选择框
<local:Converter x:Key="Converter"/> if (selectionRect.HasValue)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>DrawSelectionRect(canvas, selectionRect.Value);
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void DrawPath(ICanvas canvas, DrawingPath path)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> var strokeColor = path.StrokeColor ?? Colors.Black;
<local:Converter x:Key="Converter"/> float strokeSize = path.IsSelected ? path.StrokeThickness * 1.5f : path.StrokeThickness;
<local:Converter x:Key="Converter"/> if (!path.IsSelected)
<local:Converter x:Key="Converter"/> {
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>strokeColor = strokeColor.WithAlpha(0.5f);
<local:Converter x:Key="Converter"/> }
<local:Converter x:Key="Converter"/> canvas.StrokeColor = strokeColor;
<local:Converter x:Key="Converter"/> canvas.StrokeSize = strokeSize;
<local:Converter x:Key="Converter"/> canvas.SaveState();
<local:Converter x:Key="Converter"/> canvas.Translate(path.Pos.X, path.Pos.Y);
<local:Converter x:Key="Converter"/> canvas.DrawPath(path.Path);
<local:Converter x:Key="Converter"/> canvas.RestoreState();
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void DrawEraser(ICanvas canvas)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> canvas.SaveState();
<local:Converter x:Key="Converter"/> canvas.Translate(Eraser.Pos.X, Eraser.Pos.Y);
<local:Converter x:Key="Converter"/> canvas.Scale(0.2f, 0.2f);
<local:Converter x:Key="Converter"/> canvas.StrokeColor = Eraser.StrokeColor ?? Colors.Black;
<local:Converter x:Key="Converter"/> canvas.StrokeSize = Eraser.StrokeThickness;
<local:Converter x:Key="Converter"/> canvas.FillColor = Color.FromArgb("#FFD700");
<local:Converter x:Key="Converter"/> canvas.FillPath(Eraser.Path);
<local:Converter x:Key="Converter"/> canvas.DrawPath(Eraser.Path);
<local:Converter x:Key="Converter"/> canvas.RestoreState();
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>private void DrawSelectionRect(ICanvas canvas, RectF rect)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> canvas.SaveState();
<local:Converter x:Key="Converter"/> canvas.StrokeColor = SelectionColor;
<local:Converter x:Key="Converter"/> canvas.StrokeSize = SelectionStrokeThickness;
<local:Converter x:Key="Converter"/> canvas.StrokeDashPattern = new float[] { 5, 3 };
<local:Converter x:Key="Converter"/> canvas.DrawRectangle(rect);
<local:Converter x:Key="Converter"/> canvas.RestoreState();
<local:Converter x:Key="Converter"/>}
<local:Converter x:Key="Converter"/>// 静态工具方法
<local:Converter x:Key="Converter"/>public static float Distance(PointF a, PointF b)
<local:Converter x:Key="Converter"/> => (float)Math.Sqrt(Math.Pow(a.X - b.X, 2) + Math.Pow(a.Y - b.Y, 2));
<local:Converter x:Key="Converter"/>public static float DistanceSquared(PointF a, PointF b)
<local:Converter x:Key="Converter"/> => (a.X - b.X) * (a.X - b.X) + (a.Y - b.Y) * (a.Y - b.Y);
<local:Converter x:Key="Converter"/>public static float PointToLineDistance(PointF lineStart, PointF lineEnd, PointF point)
<local:Converter x:Key="Converter"/>{
<local:Converter x:Key="Converter"/> float l2 = DistanceSquared(lineStart, lineEnd);
<local:Converter x:Key="Converter"/> if (l2 == 0) return Distance(point, lineStart);
<local:Converter x:Key="Converter"/> float t = Math.Max(0, Math.Min(1, Vector2.Dot(
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>new Vector2(point.X - lineStart.X, point.Y - lineStart.Y),
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>new Vector2(lineEnd.X - lineStart.X, lineEnd.Y - lineStart.Y)) / l2));
<local:Converter x:Key="Converter"/> PointF projection = new PointF(
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>lineStart.X + t * (lineEnd.X - lineStart.X),
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>lineStart.Y + t * (lineEnd.Y - lineStart.Y)
<local:Converter x:Key="Converter"/> );
<local:Converter x:Key="Converter"/> return Distance(point, projection);
<local:Converter x:Key="Converter"/>} }SimpleInkCanvas.xaml
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>
<local:Converter x:Key="Converter"/>对应的cs代码
using static Shares.Utility.InkCanvas;namespace MauiViews.MauiDemos.Book._03;public partial class SimpleInkCanvas : ContentPage{ public SimpleInkCanvas() { InitializeComponent(); foreach (InkCanvasEditingMode mode in Enum.GetValues(typeof(InkCanvasEditingMode))) {
<local:Converter x:Key="Converter"/> lstEditingMode.Items.Add(mode.ToString()); lstEditingMode.SelectedIndex = 0;
<local:Converter x:Key="Converter"/>} }}运行效果
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页:
[1]