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 SourceProperty = AvaloniaProperty.Register(nameof(Source)); public static readonly StyledProperty PrimaryBrushProperty = AvaloniaProperty.Register(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 Objects = new(); private IImageEditorObject? CurrentObject; private Stack 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 line = new Polyline { Points = [new(0, 0), new(20, 8), new(5, 16), new(25, 25)], Width = 25, Height = 25 }; line.Bind(Polyline.StrokeProperty, new Binding(nameof(PrimaryBrush)) { Source = this }); ShapeButton.Content = line; 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; } } }