|  | @@ -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;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 |