|
@@ -0,0 +1,163 @@
|
|
|
+using Avalonia;
|
|
|
+using Avalonia.Controls;
|
|
|
+using Avalonia.Controls.Shapes;
|
|
|
+using Avalonia.Data;
|
|
|
+using Avalonia.Input;
|
|
|
+using Avalonia.Markup.Xaml;
|
|
|
+using Avalonia.Media;
|
|
|
+using CommunityToolkit.Mvvm.Input;
|
|
|
+using InABox.Avalonia.Components.ImageEditing;
|
|
|
+using System.Collections.ObjectModel;
|
|
|
+
|
|
|
+namespace InABox.Avalonia.Components;
|
|
|
+
|
|
|
+public enum ImageEditingMode
|
|
|
+{
|
|
|
+ Polyline
|
|
|
+}
|
|
|
+
|
|
|
+// TODO: Make it so we don't re-render everything everytime 'Objects' changes.
|
|
|
+
|
|
|
+public partial class ImageEditor : UserControl
|
|
|
+{
|
|
|
+ public static readonly StyledProperty<IImage?> SourceProperty =
|
|
|
+ AvaloniaProperty.Register<ImageEditor, IImage?>(nameof(Source));
|
|
|
+
|
|
|
+ public static readonly StyledProperty<IBrush?> PrimaryBrushProperty =
|
|
|
+ AvaloniaProperty.Register<ImageEditor, IBrush?>(nameof(PrimaryBrush), new SolidColorBrush(Colors.Black));
|
|
|
+
|
|
|
+ public IImage? Source
|
|
|
+ {
|
|
|
+ get => GetValue(SourceProperty);
|
|
|
+ set => SetValue(SourceProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ #region Editing Properties
|
|
|
+
|
|
|
+ public ImageEditingMode Mode { get; set; } = ImageEditingMode.Polyline;
|
|
|
+
|
|
|
+ public IBrush? PrimaryBrush
|
|
|
+ {
|
|
|
+ get => GetValue(PrimaryBrushProperty);
|
|
|
+ set => SetValue(PrimaryBrushProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public double LineThickness { get; set; } = 1.0;
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ private ObservableCollection<IImageEditorObject> Objects = new();
|
|
|
+
|
|
|
+ private IImageEditorObject? CurrentObject;
|
|
|
+
|
|
|
+ private Stack<IImageEditorObject> RedoStack = new();
|
|
|
+
|
|
|
+ public ImageEditor()
|
|
|
+ {
|
|
|
+ InitializeComponent();
|
|
|
+
|
|
|
+ Objects.CollectionChanged += Objects_CollectionChanged;
|
|
|
+
|
|
|
+ SetMode(Mode);
|
|
|
+ }
|
|
|
+
|
|
|
+ #region Editing Commands
|
|
|
+
|
|
|
+ private void Undo()
|
|
|
+ {
|
|
|
+ RedoStack.Push(Objects[^1]);
|
|
|
+ Objects.RemoveAt(Objects.Count - 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Redo()
|
|
|
+ {
|
|
|
+ if (!RedoStack.TryPop(out var top)) return;
|
|
|
+
|
|
|
+ Objects.Add(top);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void AddObject(IImageEditorObject obj)
|
|
|
+ {
|
|
|
+ Objects.Add(obj);
|
|
|
+ RedoStack.Clear();
|
|
|
+ }
|
|
|
+
|
|
|
+ [RelayCommand]
|
|
|
+ private void SetMode(ImageEditingMode mode)
|
|
|
+ {
|
|
|
+ Mode = mode;
|
|
|
+ switch (mode)
|
|
|
+ {
|
|
|
+ case ImageEditingMode.Polyline:
|
|
|
+ var canvas = new Canvas();
|
|
|
+ var points = new Point[] { new(0, 0), new(20, 8), new(5, 16), new(25, 25) };
|
|
|
+ var line1 = new Polyline { Points = points, Width = 25, Height = 25 };
|
|
|
+ var line2 = new Polyline { Points = points, Width = 25, Height = 25 };
|
|
|
+ line1.StrokeThickness = 4;
|
|
|
+ line1.StrokeLineCap = PenLineCap.Round;
|
|
|
+ line2.StrokeThickness = 1.5;
|
|
|
+ line1.Stroke = new SolidColorBrush(Colors.Black);
|
|
|
+ line2.Bind(Polyline.StrokeProperty, new Binding(nameof(PrimaryBrush)) { Source = this });
|
|
|
+ canvas.Children.Add(line1);
|
|
|
+ canvas.Children.Add(line2);
|
|
|
+ ShapeButton.Content = canvas;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ private void Objects_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
|
|
+ {
|
|
|
+ Canvas.Children.Clear();
|
|
|
+ foreach(var item in Objects)
|
|
|
+ {
|
|
|
+ item.Update();
|
|
|
+ Canvas.Children.Add(item.GetControl());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ Point ConvertToImageCoordinates(Point canvasCoordinates)
|
|
|
+ {
|
|
|
+ return canvasCoordinates;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Canvas_PointerPressed(object? sender, PointerPressedEventArgs e)
|
|
|
+ {
|
|
|
+ var position = ConvertToImageCoordinates(e.GetPosition(Canvas));
|
|
|
+ switch (Mode)
|
|
|
+ {
|
|
|
+ case ImageEditingMode.Polyline:
|
|
|
+ CurrentObject = new PolylineObject
|
|
|
+ {
|
|
|
+ Points = [position],
|
|
|
+ PrimaryBrush = PrimaryBrush,
|
|
|
+ Thickness = LineThickness
|
|
|
+ };
|
|
|
+ AddObject(CurrentObject);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Canvas_PointerMoved(object? sender, PointerEventArgs e)
|
|
|
+ {
|
|
|
+ var position = ConvertToImageCoordinates(e.GetPosition(Canvas));
|
|
|
+ if(CurrentObject is PolylineObject polyline)
|
|
|
+ {
|
|
|
+ polyline.Points.Add(position);
|
|
|
+ polyline.Update();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Canvas_PointerReleased(object? sender, PointerReleasedEventArgs e)
|
|
|
+ {
|
|
|
+ var position = ConvertToImageCoordinates(e.GetPosition(Canvas));
|
|
|
+ if(CurrentObject is PolylineObject polyline)
|
|
|
+ {
|
|
|
+ polyline.Points.Add(position);
|
|
|
+ polyline.Update();
|
|
|
+ CurrentObject = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+}
|