SelectionOverlay.cs 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125
  1. using System.Linq;
  2. using System.Threading;
  3. using Avalonia;
  4. using Avalonia.Animation;
  5. using Avalonia.Media;
  6. using Avalonia.Styling;
  7. using PixiEditor.AvaloniaUI.Animation;
  8. using PixiEditor.DrawingApi.Core.Surface.Vector;
  9. namespace PixiEditor.AvaloniaUI.Views.Overlays.SelectionOverlay;
  10. #nullable enable
  11. internal class SelectionOverlay : Overlay
  12. {
  13. public static readonly StyledProperty<VectorPath?> PathProperty =
  14. AvaloniaProperty.Register<SelectionOverlay, VectorPath?>(nameof(Path));
  15. public VectorPath? Path
  16. {
  17. get => GetValue(PathProperty);
  18. set => SetValue(PathProperty, value);
  19. }
  20. public static readonly StyledProperty<bool> ShowFillProperty =
  21. AvaloniaProperty.Register<SelectionOverlay, bool>(nameof(ShowFill), defaultValue: true);
  22. public bool ShowFill
  23. {
  24. get => GetValue(ShowFillProperty);
  25. set => SetValue(ShowFillProperty, value);
  26. }
  27. public static readonly DirectProperty<SelectionOverlay, IDashStyle> BlackDashedPenProperty =
  28. AvaloniaProperty.RegisterDirect<SelectionOverlay, IDashStyle>("BlackDashedPen",
  29. overlay => overlay.blackDashedPen.DashStyle,
  30. (overlay, pen) => overlay.blackDashedPen.DashStyle = pen);
  31. static SelectionOverlay()
  32. {
  33. AffectsRender<SelectionOverlay>(PathProperty);
  34. ShowFillProperty.Changed.Subscribe(OnShowFillChanged);
  35. PathProperty.Changed.Subscribe(OnPathChanged);
  36. }
  37. private Pen whitePen = new Pen(Brushes.White, 1);
  38. private Pen blackDashedPen = new Pen(Brushes.Black, 1) { DashStyle = startingFrame };
  39. private Brush fillBrush = new SolidColorBrush(Color.FromArgb(80, 0, 80, 255));
  40. private static DashStyle startingFrame = new DashStyle(new double[] { 2, 4 }, 0);
  41. private Geometry renderPath = new PathGeometry();
  42. private Avalonia.Animation.Animation animation;
  43. private CancellationTokenSource cancelAnimationToken;
  44. public SelectionOverlay()
  45. {
  46. IsHitTestVisible = false;
  47. animation = new Avalonia.Animation.Animation()
  48. {
  49. Duration = new TimeSpan(0, 0, 0, 2, 0),
  50. IterationCount = IterationCount.Infinite,
  51. };
  52. int steps = 7;
  53. float step = 1f / steps;
  54. for (int i = 0; i < steps; i++)
  55. {
  56. Cue cue = new Cue(i * step);
  57. animation.Children.Add(new KeyFrame()
  58. {
  59. Cue = cue,
  60. Setters = { new Setter(BlackDashedPenProperty, SelectionDashAnimator.Interpolate(cue.CueValue, 6, blackDashedPen.DashStyle.Dashes.ToArray())) }
  61. });
  62. }
  63. }
  64. public override void Render(DrawingContext drawingContext)
  65. {
  66. base.Render(drawingContext);
  67. if (Path is null)
  68. return;
  69. try
  70. {
  71. renderPath = new PathGeometry()
  72. {
  73. FillRule = FillRule.EvenOdd,
  74. Figures = (PathFigures?)PathFigures.Parse(Path.ToSvgPathData()),
  75. };
  76. }
  77. catch (FormatException)
  78. {
  79. return;
  80. }
  81. drawingContext.DrawGeometry(null, whitePen, renderPath);
  82. drawingContext.DrawGeometry(fillBrush, blackDashedPen, renderPath);
  83. }
  84. protected override void ZoomChanged(double newZoom)
  85. {
  86. whitePen.Thickness = 1.0 / newZoom;
  87. blackDashedPen.Thickness = 1.0 / newZoom;
  88. }
  89. private static void OnShowFillChanged(AvaloniaPropertyChangedEventArgs<bool> args)
  90. {
  91. var self = (SelectionOverlay)args.Sender;
  92. self.fillBrush.Opacity = args.NewValue.Value ? 1 : 0;
  93. }
  94. private static void OnPathChanged(AvaloniaPropertyChangedEventArgs<VectorPath?> args)
  95. {
  96. var self = (SelectionOverlay)args.Sender;
  97. if (args.NewValue.HasValue && !args.NewValue.Value.IsEmpty && self.IsVisible)
  98. {
  99. self.cancelAnimationToken = new CancellationTokenSource();
  100. self.animation.RunAsync(self, self.cancelAnimationToken.Token);
  101. }
  102. else if (self.cancelAnimationToken != null)
  103. {
  104. self.cancelAnimationToken.Cancel();
  105. }
  106. }
  107. }