csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
217 lines
8.7 KiB
217 lines
8.7 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Text;
|
|
using Avalonia;
|
|
using Avalonia.Controls;
|
|
using Avalonia.Media;
|
|
using Avalonia.Input;
|
|
using Avalonia.Threading;
|
|
using Avalonia.Controls.Shapes;
|
|
|
|
namespace ControlCatalog.Pages
|
|
{
|
|
public class CustomDrawingExampleControl : Control
|
|
{
|
|
private Point _cursorPoint;
|
|
|
|
|
|
public StyledProperty<double> ScaleProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Scale), 1.0d);
|
|
public double Scale { get => GetValue(ScaleProperty); set => SetValue(ScaleProperty, value); }
|
|
|
|
public StyledProperty<double> RotationProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(Rotation));
|
|
/// <summary>
|
|
/// Rotation, measured in Radians!
|
|
/// </summary>
|
|
public double Rotation
|
|
{
|
|
get => GetValue(RotationProperty);
|
|
set
|
|
{
|
|
double valueToUse = value % (Math.PI * 2);
|
|
SetValue(RotationProperty, valueToUse);
|
|
}
|
|
}
|
|
|
|
public StyledProperty<double> ViewportCenterYProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(ViewportCenterY), 0.0d);
|
|
public double ViewportCenterY { get => GetValue(ViewportCenterYProperty); set => SetValue(ViewportCenterYProperty, value); }
|
|
|
|
public StyledProperty<double> ViewportCenterXProperty = AvaloniaProperty.Register<CustomDrawingExampleControl, double>(nameof(ViewportCenterX), 0.0d);
|
|
public double ViewportCenterX { get => GetValue(ViewportCenterXProperty); set => SetValue(ViewportCenterXProperty, value); }
|
|
|
|
private IPen _pen;
|
|
|
|
private System.Diagnostics.Stopwatch _timeKeeper = System.Diagnostics.Stopwatch.StartNew();
|
|
|
|
private bool _isPointerCaptured = false;
|
|
|
|
public CustomDrawingExampleControl()
|
|
{
|
|
_pen = new Pen(new SolidColorBrush(Colors.Black), lineCap: PenLineCap.Round);
|
|
|
|
var _arc = new ArcSegment()
|
|
{
|
|
IsLargeArc = false,
|
|
Point = new Point(0, 0),
|
|
RotationAngle = 0,
|
|
Size = new Size(25, 25),
|
|
SweepDirection = SweepDirection.Clockwise,
|
|
|
|
};
|
|
StreamGeometry sg = new StreamGeometry();
|
|
using (var cntx = sg.Open())
|
|
{
|
|
cntx.BeginFigure(new Point(-25.0d, -10.0d), false);
|
|
cntx.ArcTo(new Point(25.0d, -10.0d), new Size(10.0d, 10.0d), 0.0d, false, SweepDirection.Clockwise);
|
|
cntx.EndFigure(true);
|
|
}
|
|
_smileGeometry = sg.Clone();
|
|
}
|
|
|
|
private Geometry _smileGeometry;
|
|
|
|
protected override void OnPointerMoved(PointerEventArgs e)
|
|
{
|
|
base.OnPointerMoved(e);
|
|
|
|
Point previousPoint = _cursorPoint;
|
|
|
|
_cursorPoint = e.GetPosition(this);
|
|
|
|
if (_isPointerCaptured)
|
|
{
|
|
Point oldWorldPoint = UIPointToWorldPoint(previousPoint, ViewportCenterX, ViewportCenterY, Scale, Rotation);
|
|
Point newWorldPoint = UIPointToWorldPoint(_cursorPoint, ViewportCenterX, ViewportCenterY, Scale, Rotation);
|
|
|
|
Vector diff = newWorldPoint - oldWorldPoint;
|
|
|
|
ViewportCenterX -= diff.X;
|
|
ViewportCenterY -= diff.Y;
|
|
}
|
|
}
|
|
|
|
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
|
{
|
|
e.Handled = true;
|
|
e.Pointer.Capture(this);
|
|
_isPointerCaptured = true;
|
|
base.OnPointerPressed(e);
|
|
}
|
|
|
|
protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
|
|
{
|
|
base.OnPointerWheelChanged(e);
|
|
var oldScale = Scale;
|
|
Scale *= (1.0d + e.Delta.Y / 12.0d);
|
|
|
|
Point oldWorldPoint = UIPointToWorldPoint(_cursorPoint, ViewportCenterX, ViewportCenterY, oldScale, Rotation);
|
|
Point newWorldPoint = UIPointToWorldPoint(_cursorPoint, ViewportCenterX, ViewportCenterY, Scale, Rotation);
|
|
|
|
Vector diff = newWorldPoint - oldWorldPoint;
|
|
|
|
ViewportCenterX -= diff.X;
|
|
ViewportCenterY -= diff.Y;
|
|
}
|
|
|
|
protected override void OnPointerReleased(PointerReleasedEventArgs e)
|
|
{
|
|
e.Pointer.Capture(null);
|
|
_isPointerCaptured = false;
|
|
base.OnPointerReleased(e);
|
|
}
|
|
|
|
public override void Render(DrawingContext context)
|
|
{
|
|
var localBounds = new Rect(new Size(this.Bounds.Width, this.Bounds.Height));
|
|
var clip = context.PushClip(this.Bounds);
|
|
context.DrawRectangle(Brushes.White, _pen, localBounds, 1.0d);
|
|
|
|
var halfMax = Math.Max(this.Bounds.Width / 2.0d, this.Bounds.Height / 2.0d) * Math.Sqrt(2.0d);
|
|
var halfMin = Math.Min(this.Bounds.Width / 2.0d, this.Bounds.Height / 2.0d) / 1.3d;
|
|
var halfWidth = this.Bounds.Width / 2.0d;
|
|
var halfHeight = this.Bounds.Height / 2.0d;
|
|
|
|
// 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
|
|
|
|
var translateModifier = context.PushPreTransform(Avalonia.Matrix.CreateTranslation(new Avalonia.Vector(halfWidth, halfHeight)));
|
|
|
|
// now 0,0 refers to the ViewportCenter(X,Y).
|
|
var rotationMatrix = Avalonia.Matrix.CreateRotation(Rotation);
|
|
var rotationModifier = context.PushPreTransform(rotationMatrix);
|
|
|
|
// everything is rotated but not scaled
|
|
|
|
var scaleModifier = context.PushPreTransform(Avalonia.Matrix.CreateScale(Scale, -Scale));
|
|
|
|
var mapPositionModifier = context.PushPreTransform(Matrix.CreateTranslation(new Vector(-ViewportCenterX, -ViewportCenterY)));
|
|
|
|
// now everything is rotated and scaled, and at the right position, now we're drawing strictly in world coordinates
|
|
|
|
context.DrawEllipse(Brushes.White, _pen, new Point(0.0d, 0.0d), 50.0d, 50.0d);
|
|
context.DrawLine(_pen, new Point(-25.0d, -5.0d), new Point(-25.0d, 15.0d));
|
|
context.DrawLine(_pen, new Point(25.0d, -5.0d), new Point(25.0d, 15.0d));
|
|
context.DrawGeometry(null, _pen, _smileGeometry);
|
|
|
|
Point cursorInWorldPoint = UIPointToWorldPoint(_cursorPoint, ViewportCenterX, ViewportCenterY, Scale, Rotation);
|
|
context.DrawEllipse(Brushes.Gray, _pen, cursorInWorldPoint, 20.0d, 20.0d);
|
|
|
|
|
|
for (int i = 0; i < 10; i++)
|
|
{
|
|
double orbitRadius = i * 100 + 200;
|
|
var orbitInput = ((_timeKeeper.Elapsed.TotalMilliseconds + 987654d) / orbitRadius) / 10.0d;
|
|
if (i % 3 == 0)
|
|
orbitInput *= -1;
|
|
Point orbitPosition = new Point(Math.Sin(orbitInput) * orbitRadius, Math.Cos(orbitInput) * orbitRadius);
|
|
context.DrawEllipse(Brushes.Gray, _pen, orbitPosition, 20.0d, 20.0d);
|
|
}
|
|
|
|
|
|
// end drawing the world
|
|
|
|
mapPositionModifier.Dispose();
|
|
|
|
scaleModifier.Dispose();
|
|
|
|
rotationModifier.Dispose();
|
|
translateModifier.Dispose();
|
|
|
|
// this is prime time to draw gui stuff
|
|
|
|
context.DrawLine(_pen, _cursorPoint + new Vector(-20, 0), _cursorPoint + new Vector(20, 0));
|
|
context.DrawLine(_pen, _cursorPoint + new Vector(0, -20), _cursorPoint + new Vector(0, 20));
|
|
|
|
clip.Dispose();
|
|
|
|
// oh and draw again when you can, no rush, right?
|
|
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
|
|
}
|
|
|
|
private Point UIPointToWorldPoint(Point inPoint, double viewportCenterX, double viewportCenterY, double scale, double rotation)
|
|
{
|
|
Point workingPoint = new Point(inPoint.X, -inPoint.Y);
|
|
workingPoint += new Vector(-this.Bounds.Width / 2.0d, this.Bounds.Height / 2.0d);
|
|
workingPoint /= scale;
|
|
|
|
workingPoint = Matrix.CreateRotation(rotation).Transform(workingPoint);
|
|
|
|
workingPoint += new Vector(viewportCenterX, viewportCenterY);
|
|
|
|
return workingPoint;
|
|
}
|
|
|
|
private Point WorldPointToUIPoint(Point inPoint, double viewportCenterX, double viewportCenterY, double scale, double rotation)
|
|
{
|
|
Point workingPoint = new Point(inPoint.X, inPoint.Y);
|
|
|
|
workingPoint -= new Vector(viewportCenterX, viewportCenterY);
|
|
// undo rotation
|
|
workingPoint = Matrix.CreateRotation(-rotation).Transform(workingPoint);
|
|
workingPoint *= scale;
|
|
workingPoint -= new Vector(-this.Bounds.Width / 2.0d, this.Bounds.Height / 2.0d);
|
|
workingPoint = new Point(workingPoint.X, -workingPoint.Y);
|
|
|
|
return workingPoint;
|
|
}
|
|
}
|
|
}
|
|
|