From 43b73764663bd7837da65092710d29abb8f55d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wies=C5=82aw=20S=CC=8Colte=CC=81s?= Date: Sun, 31 Dec 2023 17:50:06 +0100 Subject: [PATCH] Update canvas --- VectorPaint/Controls/GridLines.cs | 111 ++++++++++++++++++ VectorPaint/ViewModels/Core/Drawing.cs | 3 +- VectorPaint/ViewModels/Core/SnapHelper.cs | 29 +++++ .../ViewModels/Core/Tools/EllipseTool.cs | 5 + VectorPaint/ViewModels/Core/Tools/LineTool.cs | 7 +- .../ViewModels/Core/Tools/RectangleTool.cs | 5 + .../ViewModels/Core/Tools/SelectionTool.cs | 7 +- VectorPaint/ViewModels/MainWindowViewModel.cs | 6 +- VectorPaint/Views/MainView.axaml | 24 +++- VectorPaint/Views/MainWindow.axaml | 2 +- 10 files changed, 185 insertions(+), 14 deletions(-) create mode 100644 VectorPaint/Controls/GridLines.cs create mode 100644 VectorPaint/ViewModels/Core/SnapHelper.cs diff --git a/VectorPaint/Controls/GridLines.cs b/VectorPaint/Controls/GridLines.cs new file mode 100644 index 0000000..868c55b --- /dev/null +++ b/VectorPaint/Controls/GridLines.cs @@ -0,0 +1,111 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Media; + +namespace VectorPaint.Controls; + +public sealed class GridLines : Control +{ + public static readonly StyledProperty CellWidthProperty = + AvaloniaProperty.Register(nameof(CellWidth), 10); + + public static readonly StyledProperty CellHeightProperty = + AvaloniaProperty.Register(nameof(CellHeight), 10); + + public static readonly StyledProperty BoldSeparatorHorizontalSpacingProperty = + AvaloniaProperty.Register(nameof(BoldSeparatorHorizontalSpacing), 10); + + public static readonly StyledProperty BoldSeparatorVerticalSpacingProperty = + AvaloniaProperty.Register(nameof(BoldSeparatorVerticalSpacing), 10); + + public static readonly StyledProperty IsGridEnabledProperty = + AvaloniaProperty.Register(nameof(IsGridEnabled), true); + + private readonly Pen _pen; + private readonly Pen _penBold; + + public int CellWidth + { + get => GetValue(CellWidthProperty); + set => SetValue(CellWidthProperty, value); + } + + public int CellHeight + { + get => GetValue(CellHeightProperty); + set => SetValue(CellHeightProperty, value); + } + + public int BoldSeparatorHorizontalSpacing + { + get => GetValue(BoldSeparatorHorizontalSpacingProperty); + set => SetValue(BoldSeparatorHorizontalSpacingProperty, value); + } + + public int BoldSeparatorVerticalSpacing + { + get => GetValue(BoldSeparatorVerticalSpacingProperty); + set => SetValue(BoldSeparatorVerticalSpacingProperty, value); + } + + public bool IsGridEnabled + { + get => GetValue(IsGridEnabledProperty); + set => SetValue(IsGridEnabledProperty, value); + } + + public GridLines() + { + _pen = new Pen(new SolidColorBrush(Color.FromArgb((byte)(255.0 * 0.1), 14, 94, 253))); + _penBold = new Pen(new SolidColorBrush(Color.FromArgb((byte)(255.0 * 0.3), 14, 94, 253))); + } + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == CellWidthProperty + || change.Property == CellHeightProperty + || change.Property == BoldSeparatorHorizontalSpacingProperty + || change.Property == BoldSeparatorVerticalSpacingProperty + || change.Property == IsGridEnabledProperty) + { + InvalidateVisual(); + } + } + + public override void Render(DrawingContext context) + { + base.Render(context); + + if (!IsGridEnabled) + { + return; + } + + var cellWidth = CellWidth; + var cellHeight = CellHeight; + var boldSeparatorHorizontalSpacing = BoldSeparatorHorizontalSpacing; + var boldSeparatorVerticalSpacing = BoldSeparatorVerticalSpacing; + var width = Bounds.Width; + var height = Bounds.Height; + + for(var i = 1; i < height / cellHeight; i++) + { + var pen = i % boldSeparatorVerticalSpacing == 0 ? _penBold : _pen; + context.DrawLine( + pen, + new Point(0 + 0.5, i * cellHeight + 0.5), + new Point(width + 0.5, i * cellHeight + 0.5)); + } + + for (var i = 1; i < width / cellWidth; i++) + { + var pen = i % boldSeparatorHorizontalSpacing == 0 ? _penBold : _pen; + context.DrawLine( + pen, + new Point(i * cellWidth + 0.5, 0 + 0.5), + new Point(i * cellWidth + 0.5, height + 0.5)); + } + } +} diff --git a/VectorPaint/ViewModels/Core/Drawing.cs b/VectorPaint/ViewModels/Core/Drawing.cs index 44c33de..7e8f439 100644 --- a/VectorPaint/ViewModels/Core/Drawing.cs +++ b/VectorPaint/ViewModels/Core/Drawing.cs @@ -72,7 +72,8 @@ public class Drawing : ReactiveObject, IDrawing public void Draw(DrawingContext context, Rect bounds) { - context.DrawRectangle(Brushes.WhiteSmoke, null, bounds); + // context.DrawRectangle(Brushes.WhiteSmoke, null, bounds); + context.DrawRectangle(Brushes.Transparent, null, bounds); if (_drawables is { }) { diff --git a/VectorPaint/ViewModels/Core/SnapHelper.cs b/VectorPaint/ViewModels/Core/SnapHelper.cs new file mode 100644 index 0000000..762a327 --- /dev/null +++ b/VectorPaint/ViewModels/Core/SnapHelper.cs @@ -0,0 +1,29 @@ +using Avalonia; + +namespace VectorPaint.ViewModels.Core; + +public static class SnapHelper +{ + public static double SnapValue(double value, double snap) + { + if (snap == 0.0) + { + return value; + } + var c = value % snap; + var r = c >= snap / 2.0 ? value + snap - c : value - c; + return r; + } + + public static Point SnapPoint(Point point, double snapX = 5, double snapY = 5, bool enabled = true) + { + if (enabled) + { + var pointX = SnapValue(point.X, snapX); + var pointY = SnapValue(point.Y, snapY); + return new Point(pointX, pointY); + } + + return point; + } +} diff --git a/VectorPaint/ViewModels/Core/Tools/EllipseTool.cs b/VectorPaint/ViewModels/Core/Tools/EllipseTool.cs index 6911e48..44859df 100644 --- a/VectorPaint/ViewModels/Core/Tools/EllipseTool.cs +++ b/VectorPaint/ViewModels/Core/Tools/EllipseTool.cs @@ -1,4 +1,5 @@ using Avalonia.Input; +using VectorPaint.ViewModels.Core; using VectorPaint.ViewModels.Drawables; namespace VectorPaint.ViewModels.Tools; @@ -17,6 +18,8 @@ public class EllipseTool : Tool } var point = e.GetCurrentPoint(drawing.Input).Position; + + point = SnapHelper.SnapPoint(point); _ellipse = new EllipseDrawable() { @@ -57,6 +60,8 @@ public class EllipseTool : Tool if (_ellipse?.BottomRight is { }) { var point = e.GetCurrentPoint(drawing.Input).Position; + + point = SnapHelper.SnapPoint(point); _ellipse.BottomRight.X = point.X; _ellipse.BottomRight.Y = point.Y; diff --git a/VectorPaint/ViewModels/Core/Tools/LineTool.cs b/VectorPaint/ViewModels/Core/Tools/LineTool.cs index 825f748..e9e0ed2 100644 --- a/VectorPaint/ViewModels/Core/Tools/LineTool.cs +++ b/VectorPaint/ViewModels/Core/Tools/LineTool.cs @@ -1,4 +1,5 @@ using Avalonia.Input; +using VectorPaint.ViewModels.Core; using VectorPaint.ViewModels.Drawables; namespace VectorPaint.ViewModels.Tools; @@ -18,6 +19,8 @@ public class LineTool : Tool var point = e.GetCurrentPoint(drawing.Input).Position; + point = SnapHelper.SnapPoint(point); + _line = new LineDrawable() { Fill = null, @@ -57,7 +60,9 @@ public class LineTool : Tool if (_line?.End is { }) { var point = e.GetCurrentPoint(drawing.Input).Position; - + + point = SnapHelper.SnapPoint(point); + _line.End.X = point.X; _line.End.Y = point.Y; _line.Invalidate(); diff --git a/VectorPaint/ViewModels/Core/Tools/RectangleTool.cs b/VectorPaint/ViewModels/Core/Tools/RectangleTool.cs index 06cdbb0..97f6245 100644 --- a/VectorPaint/ViewModels/Core/Tools/RectangleTool.cs +++ b/VectorPaint/ViewModels/Core/Tools/RectangleTool.cs @@ -1,4 +1,5 @@ using Avalonia.Input; +using VectorPaint.ViewModels.Core; using VectorPaint.ViewModels.Drawables; namespace VectorPaint.ViewModels.Tools; @@ -17,6 +18,8 @@ public class RectangleTool : Tool } var point = e.GetCurrentPoint(drawing.Input).Position; + + point = SnapHelper.SnapPoint(point); _rectangle = new RectangleDrawable() { @@ -58,6 +61,8 @@ public class RectangleTool : Tool { var point = e.GetCurrentPoint(drawing.Input).Position; + point = SnapHelper.SnapPoint(point); + _rectangle.BottomRight.X = point.X; _rectangle.BottomRight.Y = point.Y; _rectangle.Invalidate(); diff --git a/VectorPaint/ViewModels/Core/Tools/SelectionTool.cs b/VectorPaint/ViewModels/Core/Tools/SelectionTool.cs index ca12d16..e1141a3 100644 --- a/VectorPaint/ViewModels/Core/Tools/SelectionTool.cs +++ b/VectorPaint/ViewModels/Core/Tools/SelectionTool.cs @@ -4,6 +4,7 @@ using Avalonia; using Avalonia.Input; using Avalonia.Media; using Avalonia.Media.Immutable; +using VectorPaint.ViewModels.Core; using VectorPaint.ViewModels.Drawables; namespace VectorPaint.ViewModels.Tools; @@ -79,7 +80,7 @@ public class SelectionTool : Tool if (!_moving) { var point = e.GetCurrentPoint(drawing.Input).Position; - + var rect = new Rect(_start, point); var selected = drawing.HitTest(rect).ToList(); @@ -110,13 +111,15 @@ public class SelectionTool : Tool } var point = e.GetCurrentPoint(drawing.Input).Position; - + if (_moving) { if (_selected.Count > 0) { foreach (var drawable in _selected) { + point = SnapHelper.SnapPoint(point); + drawable.Move(point - _start); } diff --git a/VectorPaint/ViewModels/MainWindowViewModel.cs b/VectorPaint/ViewModels/MainWindowViewModel.cs index 8cbb9b0..4a19719 100644 --- a/VectorPaint/ViewModels/MainWindowViewModel.cs +++ b/VectorPaint/ViewModels/MainWindowViewModel.cs @@ -18,8 +18,8 @@ public class MainWindowViewModel : ViewModelBase { Drawing = new Drawing { - DefaultFill = new ImmutableSolidColorBrush(Colors.Yellow), - DefaultStroke = new ImmutablePen(new ImmutableSolidColorBrush(Colors.Red), 2, null, PenLineCap.Round), + DefaultFill = new ImmutableSolidColorBrush(Colors.Transparent), + DefaultStroke = new ImmutablePen(new ImmutableSolidColorBrush(Colors.Black), 10, null, PenLineCap.Round), Drawables = new ObservableCollection(), OverlayDrawables = new ObservableCollection() }; @@ -37,7 +37,7 @@ public class MainWindowViewModel : ViewModelBase Editor.CurrentTool = Editor.Tools[0]; - Demo(Drawing); + // Demo(Drawing); } private void Demo(IDrawing drawing) diff --git a/VectorPaint/Views/MainView.axaml b/VectorPaint/Views/MainView.axaml index c4876b9..0fcfaa6 100644 --- a/VectorPaint/Views/MainView.axaml +++ b/VectorPaint/Views/MainView.axaml @@ -30,12 +30,24 @@