Browse Source

Merge remote-tracking branch 'origin/kenric' into frank

# Conflicts:
#	InABox.Avalonia/InABox.Avalonia.csproj
frankvandenbos 2 weeks ago
parent
commit
90cf0e55d5

+ 66 - 0
InABox.Avalonia/Components/ImageEditor/ImageEditor.axaml

@@ -0,0 +1,66 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+			 xmlns:components="using:InABox.Avalonia.Components"
+			 xmlns:converters="using:InABox.Avalonia.Converters"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="InABox.Avalonia.Components.ImageEditor">
+	<Grid>
+		<Grid.RowDefinitions>
+			<RowDefinition Height="*"/>
+			<RowDefinition Height="Auto"/>
+		</Grid.RowDefinitions>
+		<Image Grid.Row="0" Source="{Binding $parent[components:ImageEditor].Source}"/>
+		<Canvas Name="Canvas"
+				Grid.Row="0"
+				Background="Transparent"
+				PointerPressed="Canvas_PointerPressed"
+				PointerMoved="Canvas_PointerMoved"
+				PointerReleased="Canvas_PointerReleased"/>
+		<Border Grid.Row="1" Classes="Standard">
+			<Grid>
+				<Grid.ColumnDefinitions>
+					<ColumnDefinition Width="*"/>
+					<ColumnDefinition Width="Auto"/>
+					<ColumnDefinition Width="*"/>
+				</Grid.ColumnDefinitions>
+				<UniformGrid Grid.Column="1" Margin="-10" Rows="1">
+					<UniformGrid.Styles>
+						<Style Selector="FlyoutPresenter">
+							<Setter Property="Padding" Value="{StaticResource PrsControlSpacing}"/>
+							<Setter Property="CornerRadius" Value="{StaticResource PrsCornerRadius}"/>
+							<Setter Property="BorderBrush" Value="Black"/>
+							<Setter Property="MinWidth" Value="0"/>
+						</Style>
+					</UniformGrid.Styles>
+					<Button Name="ShapeButton"
+							Width="40" Height="40"
+							Margin="10">
+						<Button.Flyout>
+							<Flyout Placement="Top" VerticalOffset="-5">
+								<UniformGrid Margin="-10" Rows="1">
+									<Button Width="40" Height="40" Margin="10"
+											CommandParameter="{x:Static components:ImageEditingMode.Polyline}"
+											Command="{Binding $parent[components:ImageEditor].SetModeCommand}"/>
+								</UniformGrid>
+							</Flyout>
+						</Button.Flyout>
+					</Button>
+					<Button Name="PrimaryColour" Width="40" Height="40">
+						<Ellipse Width="25" Height="25"
+								 Margin="10"
+								 HorizontalAlignment="Center" VerticalAlignment="Center"
+								 Fill="{Binding $parent[components:ImageEditor].PrimaryBrush}"
+								 Stroke="Black" StrokeThickness="1"/>
+						<Button.Flyout>
+							<Flyout Placement="Top" VerticalOffset="-5">
+								<ColorView Color="{Binding $parent[components:ImageEditor].PrimaryBrush,Converter={x:Static converters:BrushToColorConverter.Instance}}"/>
+							</Flyout>
+						</Button.Flyout>
+					</Button>
+				</UniformGrid>
+			</Grid>
+		</Border>
+	</Grid>
+</UserControl>

+ 163 - 0
InABox.Avalonia/Components/ImageEditor/ImageEditor.axaml.cs

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

+ 19 - 0
InABox.Avalonia/Components/ImageEditor/Objects/IImageEditorObject.cs

@@ -0,0 +1,19 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
+using Avalonia.Media;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace InABox.Avalonia.Components.ImageEditing;
+
+internal interface IImageEditorObject
+{
+    IBrush? PrimaryBrush { get; set; }
+
+    Control GetControl();
+
+    void Update();
+}

+ 29 - 0
InABox.Avalonia/Components/ImageEditor/Objects/Polyline.cs

@@ -0,0 +1,29 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
+using Avalonia.Media;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace InABox.Avalonia.Components.ImageEditing;
+
+internal class PolylineObject : IImageEditorObject
+{
+    public List<Point> Points { get; set; } = new List<Point>();
+    public double Thickness { get; set; } = 1.0;
+    public IBrush? PrimaryBrush { get; set; }
+
+    private Polyline Control = new();
+
+    public Control GetControl() => Control;
+
+    public void Update()
+    {
+        Control.Points = Points.ToList();
+        Control.Stroke = PrimaryBrush;
+        Control.StrokeThickness = Thickness;
+    }
+}

+ 23 - 0
InABox.Avalonia/Converters/BrushToColorConverter.cs

@@ -0,0 +1,23 @@
+using Avalonia.Media;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace InABox.Avalonia.Converters;
+
+public class BrushToColorConverter : AbstractConverter<Brush, Color>
+{
+    public static BrushToColorConverter Instance = new BrushToColorConverter();
+
+    protected override Color Convert(Brush? value, object? parameter = null)
+    {
+        return value is SolidColorBrush brush ? brush.Color : default;
+    }
+
+    protected override Brush? Deconvert(Color value, object? parameter = null)
+    {
+        return new SolidColorBrush(value);
+    }
+}

+ 2 - 0
InABox.Avalonia/InABox.Avalonia.csproj

@@ -22,8 +22,10 @@
     </ItemGroup>
 
     <ItemGroup>
+<<<<<<< HEAD
       <PackageReference Include="Autofac" Version="8.3.0" />
       <PackageReference Include="Avalonia" Version="11.2.2" />
+      <PackageReference Include="Avalonia.Controls.ColorPicker" Version="11.2.2" />
       <PackageReference Include="Avalonia.Controls.DataGrid" Version="11.2.2" />
       <PackageReference Include="AvaloniaDialogs" Version="3.6.1" />
       <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />

+ 7 - 7
InABox.Avalonia/Theme/Classes/TabItem.axaml

@@ -2,7 +2,7 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:classes="clr-namespace:InABox.Avalonia.Theme.Classes">
 	
-	<Style Selector="TabControl">
+	<Style Selector="TabControl.Standard">
 		<Style.Resources>
 			<classes:TabItemHeaderMarginConverter x:Key="TabItemHeaderMarginConverter" Padding="2"/>
 		</Style.Resources>
@@ -41,14 +41,14 @@
         </Setter>
 	</Style>
 	
-	<Style Selector="TabControl Border#PART_HeaderBorder">
+	<Style Selector="TabControl.Standard Border#PART_HeaderBorder">
 		<Setter Property="Background" Value="{DynamicResource PrsMenuBackground}"/>
 		<Setter Property="BorderBrush" Value="{DynamicResource PrsMenuBackground}"/>
 		<Setter Property="BorderThickness" Value="1"/>
 		<Setter Property="CornerRadius" Value="{DynamicResource PrsCornerRadius}"/>
 	</Style>
 	
-	<Style Selector="TabItem">
+	<Style Selector="TabControl.Standard TabItem">
 		<!-- <Setter Property="Height" Value="0"/> -->
 		<Setter Property="MinHeight" Value="30"/>
 		<Setter Property="FontSize" Value="{DynamicResource PrsFontSizeSmall}"/>
@@ -61,21 +61,21 @@
 		
 	</Style>
 	
-	<Style Selector="TabItem:pointerover /template/ Border#PART_LayoutRoot">
+	<Style Selector="TabControl.Standard TabItem:pointerover /template/ Border#PART_LayoutRoot">
 		<Setter Property="Background" Value="{DynamicResource PrsMenuBackground}"/>
 		<Setter Property="TextElement.Foreground" Value="White"/>
 	</Style>
 	
-	<Style Selector="TabItem:selected">
+	<Style Selector="TabControl.Standard TabItem:selected">
 		<Setter Property="Background" Value="{DynamicResource PrsTileBackground}"/>
 	</Style>
 	
-	<Style Selector="TabItem:selected:pointerover /template/ Border#PART_LayoutRoot">
+	<Style Selector="TabControl.Standard TabItem:selected:pointerover /template/ Border#PART_LayoutRoot">
 		<Setter Property="Background" Value="{DynamicResource PrsTileBackground}"/>
 		<Setter Property="TextElement.Foreground" Value="Black"/>
 	</Style>
 	
-	<Style Selector="TabItem:selected /template/ Border#PART_SelectedPipe">
+	<Style Selector="TabControl.Standard TabItem:selected /template/ Border#PART_SelectedPipe">
 		<Setter Property="IsVisible" Value="False"/>
 	</Style>
 	

+ 6 - 6
InABox.Avalonia/Theme/Classes/TabStrip.axaml

@@ -1,6 +1,6 @@
 <Styles xmlns="https://github.com/avaloniaui"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
-	<Style Selector="TabStrip">
+	<Style Selector="TabStrip.Standard">
 		<Setter Property="Background" Value="{DynamicResource PrsMenuBackground}"/>
 		<Setter Property="BorderBrush" Value="{DynamicResource PrsMenuBackground}"/>
 		<Setter Property="BorderThickness" Value="1"/>
@@ -13,7 +13,7 @@
 			</Setter.Value>
 		</Setter>
 	</Style>
-	<Style Selector="TabStripItem">
+	<Style Selector="TabStrip.Standard TabStripItem">
 		<Setter Property="MinHeight" Value="30"/>
 		<Setter Property="FontSize" Value="{DynamicResource PrsFontSizeSmall}"/>
 		<Setter Property="FontWeight" Value="{DynamicResource PrsFontWeightBold}"/>
@@ -26,20 +26,20 @@
 		<Setter Property="Padding" Value="0,10,0,10"/>
 	</Style>
 	
-	<Style Selector="TabStripItem:pointerover /template/ Border#PART_LayoutRoot">
+	<Style Selector="TabStrip.Standard TabStripItem:pointerover /template/ Border#PART_LayoutRoot">
 		<Setter Property="Background" Value="{DynamicResource PrsMenuBackground}"/>
 		<Setter Property="TextElement.Foreground" Value="White"/>
 	</Style>
 	
-	<Style Selector="TabStripItem:selected">
+	<Style Selector="TabStrip.Standard TabStripItem:selected">
 		<Setter Property="Background" Value="{DynamicResource PrsTileBackground}"/>
 	</Style>
-	<Style Selector="TabStripItem:selected:pointerover /template/ Border#PART_LayoutRoot">
+	<Style Selector="TabStrip.Standard TabStripItem:selected:pointerover /template/ Border#PART_LayoutRoot">
 		<Setter Property="Background" Value="{DynamicResource PrsTileBackground}"/>
 		<Setter Property="TextElement.Foreground" Value="Black"/>
 	</Style>
 	
-	<Style Selector="TabStripItem:selected /template/ Border#PART_SelectedPipe">
+	<Style Selector="TabStrip.Standard TabStripItem:selected /template/ Border#PART_SelectedPipe">
 		<Setter Property="IsVisible" Value="False"/>
 	</Style>