CustomDrawingExampleControl.cs 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Globalization;
  4. using System.Text;
  5. using Avalonia;
  6. using Avalonia.Controls;
  7. using Avalonia.Media;
  8. using Avalonia.Input;
  9. using Avalonia.Threading;
  10. using Avalonia.Controls.Shapes;
  11. namespace ControlCatalog.Pages
  12. {
  13. public class CustomDrawingExampleControl : Control
  14. {
  15. private Point _cursorPoint;
  16. public StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Scale), 1.0d);
  17. public double Scale { get => GetValue(ScaleProperty); set => SetValue(ScaleProperty, value); }
  18. public StyledProperty<double> RotationProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Rotation));
  19. /// <summary>
  20. /// Rotation, measured in Radians!
  21. /// </summary>
  22. public double Rotation
  23. {
  24. get => GetValue(RotationProperty);
  25. set
  26. {
  27. double valueToUse = value % (Math.PI * 2);
  28. SetValue(RotationProperty, valueToUse);
  29. }
  30. }
  31. public StyledProperty<double> ViewportCenterYProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(ViewportCenterY), 0.0d);
  32. public double ViewportCenterY { get => GetValue(ViewportCenterYProperty); set => SetValue(ViewportCenterYProperty, value); }
  33. public StyledProperty<double> ViewportCenterXProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(ViewportCenterX), 0.0d);
  34. public double ViewportCenterX { get => GetValue(ViewportCenterXProperty); set => SetValue(ViewportCenterXProperty, value); }
  35. private IPen _pen;
  36. private System.Diagnostics.Stopwatch _timeKeeper = System.Diagnostics.Stopwatch.StartNew();
  37. private bool _isPointerCaptured = false;
  38. public CustomDrawingExampleControl()
  39. {
  40. _pen = new Pen(new SolidColorBrush(Colors.Black), lineCap: PenLineCap.Round);
  41. var _arc = new ArcSegment()
  42. {
  43. IsLargeArc = false,
  44. Point = new Point(0, 0),
  45. RotationAngle = 0,
  46. Size = new Size(25, 25),
  47. SweepDirection = SweepDirection.Clockwise,
  48. };
  49. StreamGeometry sg = new StreamGeometry();
  50. var cntx = sg.Open();
  51. cntx.BeginFigure(new Point(-25.0d, -10.0d), false);
  52. cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise);
  53. cntx.EndFigure(true);
  54. _smileGeometry = sg.Clone();
  55. }
  56. private Geometry _smileGeometry;
  57. protected override void OnPointerMoved(PointerEventArgs e)
  58. {
  59. base.OnPointerMoved(e);
  60. Point previousPoint = _cursorPoint;
  61. _cursorPoint = e.GetPosition(this);
  62. if (_isPointerCaptured)
  63. {
  64. Point oldWorldPoint = UIPointToWorldPoint(previousPoint, ViewportCenterX, ViewportCenterY, Scale, Rotation);
  65. Point newWorldPoint = UIPointToWorldPoint(_cursorPoint, ViewportCenterX, ViewportCenterY, Scale, Rotation);
  66. Vector diff = newWorldPoint - oldWorldPoint;
  67. ViewportCenterX -= diff.X;
  68. ViewportCenterY -= diff.Y;
  69. }
  70. }
  71. protected override void OnPointerPressed(PointerPressedEventArgs e)
  72. {
  73. e.Handled = true;
  74. e.Pointer.Capture(this);
  75. _isPointerCaptured = true;
  76. base.OnPointerPressed(e);
  77. }
  78. protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
  79. {
  80. base.OnPointerWheelChanged(e);
  81. var oldScale = Scale;
  82. Scale *= (1.0d + e.Delta.Y / 12.0d);
  83. Point oldWorldPoint = UIPointToWorldPoint(_cursorPoint, ViewportCenterX, ViewportCenterY, oldScale, Rotation);
  84. Point newWorldPoint = UIPointToWorldPoint(_cursorPoint, ViewportCenterX, ViewportCenterY, Scale, Rotation);
  85. Vector diff = newWorldPoint - oldWorldPoint;
  86. ViewportCenterX -= diff.X;
  87. ViewportCenterY -= diff.Y;
  88. }
  89. protected override void OnPointerReleased(PointerReleasedEventArgs e)
  90. {
  91. e.Pointer.Capture(null);
  92. _isPointerCaptured = false;
  93. base.OnPointerReleased(e);
  94. }
  95. public override void Render(DrawingContext context)
  96. {
  97. var localBounds = new Rect(new Size(this.Bounds.Width, this.Bounds.Height));
  98. var clip = context.PushClip(this.Bounds);
  99. context.DrawRectangle(Brushes.White, _pen, localBounds, 1.0d);
  100. var halfMax = Math.Max(this.Bounds.Width / 2.0d, this.Bounds.Height / 2.0d) * Math.Sqrt(2.0d);
  101. var halfMin = Math.Min(this.Bounds.Width / 2.0d, this.Bounds.Height / 2.0d) / 1.3d;
  102. var halfWidth = this.Bounds.Width / 2.0d;
  103. var halfHeight = this.Bounds.Height / 2.0d;
  104. // 0,0 refers to the top-left of the control now. It is not prime time to draw gui stuff because it'll be under the world
  105. var translateModifier = context.PushPreTransform(Avalonia.Matrix.CreateTranslation(new Avalonia.Vector(halfWidth, halfHeight)));
  106. // now 0,0 refers to the ViewportCenter(X,Y).
  107. var rotationMatrix = Avalonia.Matrix.CreateRotation(Rotation);
  108. var rotationModifier = context.PushPreTransform(rotationMatrix);
  109. // everything is rotated but not scaled
  110. var scaleModifier = context.PushPreTransform(Avalonia.Matrix.CreateScale(Scale, -Scale));
  111. var mapPositionModifier = context.PushPreTransform(Matrix.CreateTranslation(new Vector(-ViewportCenterX, -ViewportCenterY)));
  112. // now everything is rotated and scaled, and at the right position, now we're drawing strictly in world coordinates
  113. context.DrawEllipse(Brushes.White, _pen, new Point(0.0d, 0.0d), 50.0d, 50.0d);
  114. context.DrawLine(_pen, new Point(-25.0d, -5.0d), new Point(-25.0d, 15.0d));
  115. context.DrawLine(_pen, new Point(25.0d, -5.0d), new Point(25.0d, 15.0d));
  116. context.DrawGeometry(null, _pen, _smileGeometry);
  117. Point cursorInWorldPoint = UIPointToWorldPoint(_cursorPoint, ViewportCenterX, ViewportCenterY, Scale, Rotation);
  118. context.DrawEllipse(Brushes.Gray, _pen, cursorInWorldPoint, 20.0d, 20.0d);
  119. for (int i = 0; i < 10; i++)
  120. {
  121. double orbitRadius = i * 100 + 200;
  122. var orbitInput = ((_timeKeeper.Elapsed.TotalMilliseconds + 987654d) / orbitRadius) / 10.0d;
  123. if (i % 3 == 0)
  124. orbitInput *= -1;
  125. Point orbitPosition = new Point(Math.Sin(orbitInput) * orbitRadius, Math.Cos(orbitInput) * orbitRadius);
  126. context.DrawEllipse(Brushes.Gray, _pen, orbitPosition, 20.0d, 20.0d);
  127. }
  128. // end drawing the world
  129. mapPositionModifier.Dispose();
  130. scaleModifier.Dispose();
  131. rotationModifier.Dispose();
  132. translateModifier.Dispose();
  133. // this is prime time to draw gui stuff
  134. context.DrawLine(_pen, _cursorPoint + new Vector(-20, 0), _cursorPoint + new Vector(20, 0));
  135. context.DrawLine(_pen, _cursorPoint + new Vector(0, -20), _cursorPoint + new Vector(0, 20));
  136. clip.Dispose();
  137. // oh and draw again when you can, no rush, right?
  138. Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
  139. }
  140. private Point UIPointToWorldPoint(Point inPoint, double viewportCenterX, double viewportCenterY, double scale, double rotation)
  141. {
  142. Point workingPoint = new Point(inPoint.X, -inPoint.Y);
  143. workingPoint += new Vector(-this.Bounds.Width / 2.0d, this.Bounds.Height / 2.0d);
  144. workingPoint /= scale;
  145. workingPoint = Matrix.CreateRotation(rotation).Transform(workingPoint);
  146. workingPoint += new Vector(viewportCenterX, viewportCenterY);
  147. return workingPoint;
  148. }
  149. private Point WorldPointToUIPoint(Point inPoint, double viewportCenterX, double viewportCenterY, double scale, double rotation)
  150. {
  151. Point workingPoint = new Point(inPoint.X, inPoint.Y);
  152. workingPoint -= new Vector(viewportCenterX, viewportCenterY);
  153. // undo rotation
  154. workingPoint = Matrix.CreateRotation(-rotation).Transform(workingPoint);
  155. workingPoint *= scale;
  156. workingPoint -= new Vector(-this.Bounds.Width / 2.0d, this.Bounds.Height / 2.0d);
  157. workingPoint = new Point(workingPoint.X, -workingPoint.Y);
  158. return workingPoint;
  159. }
  160. }
  161. }