Browse Source

Layers control wip

Krzysztof Krysiński 1 year ago
parent
commit
43f6482f6d
47 changed files with 2010 additions and 55 deletions
  1. 1 0
      src/PixiEditor.AvaloniaUI/App.axaml
  2. 180 0
      src/PixiEditor.AvaloniaUI/Helpers/Behaviours/SliderUpdateBehavior.cs
  3. 1 2
      src/PixiEditor.AvaloniaUI/Helpers/Converters/ActiveToolToZoomModeConverter.cs
  4. 20 0
      src/PixiEditor.AvaloniaUI/Helpers/Converters/BlendModeToStringConverter.cs
  5. 2 7
      src/PixiEditor.AvaloniaUI/Helpers/Converters/BoolOrToVisibilityConverter.cs
  6. 34 0
      src/PixiEditor.AvaloniaUI/Helpers/Converters/BoolToBrushConverter.cs
  7. 1 2
      src/PixiEditor.AvaloniaUI/Helpers/Converters/BoolToIntConverter.cs
  8. 51 0
      src/PixiEditor.AvaloniaUI/Helpers/Converters/BoolToValueConverter.cs
  9. 1 3
      src/PixiEditor.AvaloniaUI/Helpers/Converters/GenericColorToMediaColorConverter.cs
  10. 21 0
      src/PixiEditor.AvaloniaUI/Helpers/Converters/IndentConverter.cs
  11. 1 3
      src/PixiEditor.AvaloniaUI/Helpers/Converters/IntToViewportRectConverter.cs
  12. 1 2
      src/PixiEditor.AvaloniaUI/Helpers/Converters/IsSelectionToolConverter.cs
  13. 40 0
      src/PixiEditor.AvaloniaUI/Helpers/Converters/MultiplyConverter.cs
  14. 1 2
      src/PixiEditor.AvaloniaUI/Helpers/Converters/NotNullToVisibilityConverter.cs
  15. 12 0
      src/PixiEditor.AvaloniaUI/Helpers/Converters/NullToVisibilityConverter.cs
  16. 1 2
      src/PixiEditor.AvaloniaUI/Helpers/Converters/PaletteItemsToWidthConverter.cs
  17. 1 2
      src/PixiEditor.AvaloniaUI/Helpers/Converters/RadiansToDegreesConverter.cs
  18. 1 3
      src/PixiEditor.AvaloniaUI/Helpers/Converters/ReciprocalConverter.cs
  19. 1 5
      src/PixiEditor.AvaloniaUI/Helpers/Converters/ScaleToBitmapScalingModeConverter.cs
  20. 23 0
      src/PixiEditor.AvaloniaUI/Helpers/Converters/StructureMemberSelectionTypeToColorConverter.cs
  21. 1 3
      src/PixiEditor.AvaloniaUI/Helpers/Converters/ThresholdVisibilityConverter.cs
  22. 1 3
      src/PixiEditor.AvaloniaUI/Helpers/Converters/ZoomModeToHitTestVisibleConverter.cs
  23. 1 2
      src/PixiEditor.AvaloniaUI/Helpers/Converters/ZoomToViewportConverter.cs
  24. 30 0
      src/PixiEditor.AvaloniaUI/Helpers/Extensions/BlendModeEx.cs
  25. 1 1
      src/PixiEditor.AvaloniaUI/Helpers/PointerHelpers.cs
  26. 20 0
      src/PixiEditor.AvaloniaUI/Helpers/UI/TreeViewItemHelper.cs
  27. 0 7
      src/PixiEditor.AvaloniaUI/PixiEditor.AvaloniaUI.csproj
  28. 93 0
      src/PixiEditor.AvaloniaUI/Styles/PixiEditor.Layers.axaml
  29. 20 0
      src/PixiEditor.AvaloniaUI/Styles/PortingWipStyles.axaml
  30. 2 0
      src/PixiEditor.AvaloniaUI/ViewLocator.cs
  31. 26 1
      src/PixiEditor.AvaloniaUI/ViewModels/Dock/DockFactory.cs
  32. 11 0
      src/PixiEditor.AvaloniaUI/ViewModels/Dock/LayersDockViewModel.cs
  33. 1 1
      src/PixiEditor.AvaloniaUI/Views/Dock/DocumentTemplate.axaml
  34. 101 0
      src/PixiEditor.AvaloniaUI/Views/Input/BlendModeComboBox.cs
  35. 154 0
      src/PixiEditor.AvaloniaUI/Views/Layers/FolderControl.axaml
  36. 115 0
      src/PixiEditor.AvaloniaUI/Views/Layers/FolderControl.axaml.cs
  37. 172 0
      src/PixiEditor.AvaloniaUI/Views/Layers/LayerControl.axaml
  38. 171 0
      src/PixiEditor.AvaloniaUI/Views/Layers/LayerControl.axaml.cs
  39. 185 0
      src/PixiEditor.AvaloniaUI/Views/Layers/LayersManager.axaml
  40. 248 0
      src/PixiEditor.AvaloniaUI/Views/Layers/LayersManager.axaml.cs
  41. 158 0
      src/PixiEditor.AvaloniaUI/Views/Layers/ReferenceLayer.axaml
  42. 57 0
      src/PixiEditor.AvaloniaUI/Views/Layers/ReferenceLayer.axaml.cs
  43. 1 1
      src/PixiEditor.AvaloniaUI/Views/Main/Viewport.axaml
  44. 2 1
      src/PixiEditor.AvaloniaUI/Views/MainView.axaml
  45. 2 1
      src/PixiEditor.AvaloniaUI/Views/Overlays/SymmetryOverlay/SymmetryOverlay.cs
  46. 1 1
      src/PixiEditor.AvaloniaUI/Views/Palettes/PaletteColorControl.axaml
  47. 42 0
      src/PixiEditor.AvaloniaUI/Views/Panels/ReversedOrderStackPanel.cs

+ 1 - 0
src/PixiEditor.AvaloniaUI/App.axaml

@@ -13,6 +13,7 @@
         <StyleInclude Source="/Styles/PixiEditor.Controls.axaml"/>
         <StyleInclude Source="/Styles/PixiEditor.Animators.axaml"/>
         <StyleInclude Source="/Styles/PixiEditor.Handles.axaml"/>
+        <StyleInclude Source="/Styles/PixiEditor.Layers.axaml"/>
     </Application.Styles>
 
 </Application>

+ 180 - 0
src/PixiEditor.AvaloniaUI/Helpers/Behaviours/SliderUpdateBehavior.cs

@@ -0,0 +1,180 @@
+using System.Windows.Input;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.VisualTree;
+using Avalonia.Xaml.Interactivity;
+
+namespace PixiEditor.AvaloniaUI.Helpers.Behaviours;
+#nullable enable
+internal class SliderUpdateBehavior : Behavior<Slider>
+{
+    public static readonly StyledProperty<double> BindingProperty
+        = AvaloniaProperty.Register<SliderUpdateBehavior, double>(nameof(Binding));
+
+    public double Binding
+    {
+        get => GetValue(BindingProperty);
+        set => SetValue(BindingProperty, value);
+    }
+
+    public static readonly StyledProperty<ICommand> DragValueChangedProperty = AvaloniaProperty.Register<SliderUpdateBehavior, ICommand>(
+        nameof(DragValueChanged));
+
+    public ICommand DragValueChanged
+    {
+        get => GetValue(DragValueChangedProperty);
+        set => SetValue(DragValueChangedProperty, value);
+    }
+
+    public static readonly StyledProperty<ICommand> DragEndedProperty = AvaloniaProperty.Register<SliderUpdateBehavior, ICommand>(
+        nameof(DragEnded));
+
+    public ICommand DragEnded
+    {
+        get => GetValue(DragEndedProperty);
+        set => SetValue(DragEndedProperty, value);
+    }
+
+    public static readonly StyledProperty<ICommand> DragStartedProperty = AvaloniaProperty.Register<SliderUpdateBehavior, ICommand>(
+        nameof(DragStarted));
+
+    public ICommand DragStarted
+    {
+        get => GetValue(DragStartedProperty);
+        set => SetValue(DragStartedProperty, value);
+    }
+
+    public static readonly StyledProperty<ICommand> SetOpacityProperty = AvaloniaProperty.Register<SliderUpdateBehavior, ICommand>(
+        nameof(SetOpacity));
+
+    public ICommand SetOpacity
+    {
+        get => GetValue(SetOpacityProperty);
+        set => SetValue(SetOpacityProperty, value);
+    }
+
+    public static readonly StyledProperty<double> ValueFromSliderProperty = AvaloniaProperty.Register<SliderUpdateBehavior, double>(
+        nameof(ValueFromSlider));
+    public double ValueFromSlider
+    {
+        get => (double)GetValue(ValueFromSliderProperty);
+        set => SetValue(ValueFromSliderProperty, value);
+    }
+
+    static SliderUpdateBehavior()
+    {
+        BindingProperty.Changed.Subscribe(OnBindingValuePropertyChange);
+        ValueFromSliderProperty.Changed.Subscribe(OnSliderValuePropertyChange);
+    }
+
+    private bool attached = false;
+    private bool dragging = false;
+
+    private bool bindingValueChangedWhileDragging = false;
+    private double bindingValueWhileDragging = 0.0;
+
+    private bool skipSetOpacity;
+    
+    protected override void OnAttached()
+    {
+        AssociatedObject.Loaded += AssociatedObject_Loaded;
+        AssociatedObject.Focusable = false;
+
+
+        if (AssociatedObject.IsLoaded)
+            AttachEvents();
+    }
+
+    private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
+    {
+        AttachEvents();
+    }
+
+    private void AttachEvents()
+    {
+        if (attached)
+            return;
+
+        Thumb? thumb = GetThumb(AssociatedObject);
+        if (thumb is null)
+            return;
+
+        attached = true;
+
+        thumb.DragStarted += Thumb_DragStarted;
+        thumb.DragCompleted += Thumb_DragCompleted;
+    }
+
+    protected override void OnDetaching()
+    {
+        AssociatedObject.Loaded -= AssociatedObject_Loaded;
+        if (!attached)
+            return;
+        Thumb? thumb = GetThumb(AssociatedObject);
+        if (thumb is null)
+            return;
+
+        thumb.DragStarted -= Thumb_DragStarted;
+        thumb.DragCompleted -= Thumb_DragCompleted;
+    }
+
+    private static void OnSliderValuePropertyChange(AvaloniaPropertyChangedEventArgs<double> e)
+    {
+        SliderUpdateBehavior obj = (SliderUpdateBehavior)e.Sender;
+        
+        if (obj.dragging)
+        {
+            if (obj.DragValueChanged is not null && obj.DragValueChanged.CanExecute(e.NewValue.Value))
+                obj.DragValueChanged.Execute(e.NewValue.Value);
+        }
+        else if (!obj.skipSetOpacity)
+        {
+            if (obj.SetOpacity is not null && obj.SetOpacity.CanExecute(e.NewValue.Value))
+                obj.SetOpacity.Execute(e.NewValue.Value);
+        }
+    }
+
+    private static void OnBindingValuePropertyChange(AvaloniaPropertyChangedEventArgs<double> args)
+    {
+        SliderUpdateBehavior obj = (SliderUpdateBehavior)args.Sender;
+        obj.skipSetOpacity = true;
+        if (obj.dragging)
+        {
+            obj.bindingValueChangedWhileDragging = true;
+            obj.bindingValueWhileDragging = args.NewValue.Value;
+            obj.skipSetOpacity = false;
+            return;
+        }
+        obj.ValueFromSlider = args.NewValue.Value;
+        obj.skipSetOpacity = false;
+    }
+
+    private void Thumb_DragCompleted(object sender, VectorEventArgs e)
+    {
+        dragging = false;
+        if (DragEnded is not null && DragEnded.CanExecute(null))
+            DragEnded.Execute(null);
+        if (bindingValueChangedWhileDragging)
+            ValueFromSlider = bindingValueWhileDragging;
+        bindingValueChangedWhileDragging = false;
+    }
+
+    private void Thumb_DragStarted(object sender, VectorEventArgs e)
+    {
+        dragging = true;
+        if (DragStarted is not null && DragStarted.CanExecute(null))
+            DragStarted.Execute(null);
+    }
+
+    private static Thumb? GetThumb(Slider slider)
+    {
+        /*Track? track = slider.Template.FindName("PART_Track", slider) as Track;
+        return track is null ? null : track.Thumb;*/
+        return slider.FindDescendantOfType<Thumb>();
+    }
+
+
+}

+ 1 - 2
src/PixiEditor.AvaloniaUI/Helpers/Converters/ActiveToolToZoomModeConverter.cs

@@ -1,9 +1,8 @@
 using System.Globalization;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 using PixiEditor.AvaloniaUI.ViewModels.Tools.Tools;
 using PixiEditor.Zoombox;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 internal class ActiveToolToZoomModeConverter : SingleInstanceConverter<ActiveToolToZoomModeConverter>
 {
     public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)

+ 20 - 0
src/PixiEditor.AvaloniaUI/Helpers/Converters/BlendModeToStringConverter.cs

@@ -0,0 +1,20 @@
+using System.Globalization;
+using PixiEditor.AvaloniaUI.Helpers.Extensions;
+using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Extensions.Common.Localization;
+
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
+internal class BlendModeToStringConverter : SingleInstanceConverter<BlendModeToStringConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value is not BlendMode mode)
+            return "<null>";
+        return new LocalizedString(mode.LocalizedKeys()).Value;
+    }
+
+    public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 2 - 7
src/PixiEditor.AvaloniaUI/Helpers/Converters/BoolOrToVisibilityConverter.cs

@@ -1,13 +1,8 @@
-using System;
-using System.Collections.Generic;
+using System.Collections.Generic;
 using System.Globalization;
 using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 // TODO: seems like this converter is doing the same as the avalonia built in {x:Static BoolConverters.Or}
 internal class BoolOrToVisibilityConverter : SingleInstanceMultiValueConverter<BoolOrToVisibilityConverter>
 {

+ 34 - 0
src/PixiEditor.AvaloniaUI/Helpers/Converters/BoolToBrushConverter.cs

@@ -0,0 +1,34 @@
+using System.Collections.Generic;
+using System.Globalization;
+using Avalonia;
+using Avalonia.Data.Converters;
+using Avalonia.Media;
+
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
+internal class BoolToBrushConverter : IMultiValueConverter
+{
+    public IBrush FalseBrush { get; set; } = new SolidColorBrush(Brushes.Black.Color);
+    public IBrush TrueBrush { get; set; } = new SolidColorBrush(Brushes.White.Color);
+
+    public object? Convert(IList<object?> values, Type targetType, object? parameter, CultureInfo culture)
+    {
+        if (values.Count == 1)
+        {
+            if (values[0] is not bool conv)
+                return AvaloniaProperty.UnsetValue;
+            return conv ? TrueBrush : FalseBrush;
+        }
+        else if (values.Count == 2)
+        {
+            if (values[0] is not bool conv || values[1] is not bool conv2)
+                return AvaloniaProperty.UnsetValue;
+            return (conv || !conv2) ? TrueBrush : FalseBrush;
+        }
+        return AvaloniaProperty.UnsetValue;
+    }
+
+    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+    {
+        throw new NotImplementedException();
+    }
+}

+ 1 - 2
src/PixiEditor.AvaloniaUI/Helpers/Converters/BoolToIntConverter.cs

@@ -1,7 +1,6 @@
 using System.Globalization;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 
 internal class BoolToIntConverter
     : SingleInstanceConverter<BoolToIntConverter>

+ 51 - 0
src/PixiEditor.AvaloniaUI/Helpers/Converters/BoolToValueConverter.cs

@@ -0,0 +1,51 @@
+using System.Globalization;
+using PixiEditor.Extensions.Common.Localization;
+
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
+
+internal class BoolToValueConverter : MarkupConverter
+{
+    public object FalseValue { get; set; }
+    
+    public object TrueValue { get; set; }
+    
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value is bool and true)
+        {
+            return GetValue(TrueValue);
+        }
+
+        return GetValue(FalseValue);
+    }
+
+    private object GetValue(object value)
+    {
+        if (value is string s && s.StartsWith("localized:"))
+        {
+            return new LocalizedString(s.Split("localized:")[1]);
+        }
+
+        return value;
+    }
+
+    public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        if (value == FalseValue)
+        {
+            return false;
+        }
+
+        if (value == TrueValue)
+        {
+            return true;
+        }
+
+        if (targetType == typeof(bool?))
+        {
+            return null;
+        }
+
+        throw new ArgumentException("value was neither FalseValue nor TrueValue and targetType was not a nullable bool");
+    }
+}

+ 1 - 3
src/PixiEditor.AvaloniaUI/Helpers/Converters/GenericColorToMediaColorConverter.cs

@@ -1,12 +1,10 @@
 using System.Globalization;
-using System.Windows.Media;
 using Avalonia.Media;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 using PixiEditor.AvaloniaUI.Helpers.Extensions;
 using PixiEditor.Extensions.Palettes;
 using BackendColor = PixiEditor.DrawingApi.Core.ColorsImpl.Color;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 
 internal class GenericColorToMediaColorConverter : SingleInstanceConverter<GenericColorToMediaColorConverter>
 {

+ 21 - 0
src/PixiEditor.AvaloniaUI/Helpers/Converters/IndentConverter.cs

@@ -0,0 +1,21 @@
+using System.Globalization;
+using Avalonia.Controls;
+using Avalonia.Data;
+
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
+
+internal class IndentConverter
+    : SingleInstanceConverter<IndentConverter>
+{
+    private const int IndentSize = 20;
+
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        return new GridLength(((GridLength)value).Value + IndentSize);
+    }
+
+    public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        return BindingOperations.DoNothing;
+    }
+}

+ 1 - 3
src/PixiEditor.AvaloniaUI/Helpers/Converters/IntToViewportRectConverter.cs

@@ -1,9 +1,7 @@
 using System.Globalization;
-using System.Windows;
 using Avalonia;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 
 internal class IntToViewportRectConverter
     : SingleInstanceConverter<IntToViewportRectConverter>

+ 1 - 2
src/PixiEditor.AvaloniaUI/Helpers/Converters/IsSelectionToolConverter.cs

@@ -1,8 +1,7 @@
 using System.Globalization;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 using PixiEditor.AvaloniaUI.ViewModels.Tools.Tools;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 
 internal class IsSelectionToolConverter : SingleInstanceConverter<IsSelectionToolConverter>
 {

+ 40 - 0
src/PixiEditor.AvaloniaUI/Helpers/Converters/MultiplyConverter.cs

@@ -0,0 +1,40 @@
+using System.Globalization;
+using Avalonia;
+
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
+internal class MultiplyConverter : SingleInstanceConverter<MultiplyConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        double? actuallyValue = NumberToDouble(value);
+        double? factor = NumberToDouble(parameter);
+        if (actuallyValue is null || factor is null)
+            return AvaloniaProperty.UnsetValue;
+        return actuallyValue * factor;
+    }
+
+    public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        double? actuallyValue = NumberToDouble(value);
+        double? factor = NumberToDouble(parameter);
+        if (actuallyValue is null || factor is null)
+            return AvaloniaProperty.UnsetValue;
+        return actuallyValue / factor;
+    }
+
+    private double? NumberToDouble(object number)
+    {
+        return number switch
+        {
+            int n => n,
+            uint n => n,
+            float n => n,
+            double n => n,
+            short n => n,
+            long n => n,
+            ulong n => n,
+            ushort n => n,
+            _ => null,
+        };
+    }
+}

+ 1 - 2
src/PixiEditor.AvaloniaUI/Helpers/Converters/NotNullToVisibilityConverter.cs

@@ -1,7 +1,6 @@
 using System.Globalization;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 
 // TODO: check if this converter can be replaced with StringConverters.IsNullOrEmpty, StringConverters.IsNotNullOrEmpty, ObjectConverters.IsNull, or ObjectConverters.IsNotNull
 internal class NotNullToVisibilityConverter

+ 12 - 0
src/PixiEditor.AvaloniaUI/Helpers/Converters/NullToVisibilityConverter.cs

@@ -0,0 +1,12 @@
+using System.Globalization;
+
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
+
+internal class NullToVisibilityConverter
+    : SingleInstanceConverter<NullToVisibilityConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        return value is null;
+    }
+}

+ 1 - 2
src/PixiEditor.AvaloniaUI/Helpers/Converters/PaletteItemsToWidthConverter.cs

@@ -1,9 +1,8 @@
 using System.Collections.Generic;
 using System.Globalization;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 
 internal class PaletteItemsToWidthConverter : SingleInstanceConverter<PaletteItemsToWidthConverter>
 {

+ 1 - 2
src/PixiEditor.AvaloniaUI/Helpers/Converters/RadiansToDegreesConverter.cs

@@ -1,7 +1,6 @@
 using System.Globalization;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 
 internal class RadiansToDegreesConverter : SingleInstanceConverter<RadiansToDegreesConverter>
 {

+ 1 - 3
src/PixiEditor.AvaloniaUI/Helpers/Converters/ReciprocalConverter.cs

@@ -1,9 +1,7 @@
 using System.Globalization;
-using System.Windows;
 using Avalonia;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 internal class ReciprocalConverter : SingleInstanceConverter<ReciprocalConverter>
 {
     public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)

+ 1 - 5
src/PixiEditor.AvaloniaUI/Helpers/Converters/ScaleToBitmapScalingModeConverter.cs

@@ -1,11 +1,7 @@
 using System.Globalization;
-using System.Windows;
-using System.Windows.Media;
-using Avalonia;
 using Avalonia.Media.Imaging;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 
 internal class ScaleToBitmapScalingModeConverter : SingleInstanceConverter<ScaleToBitmapScalingModeConverter>
 {

+ 23 - 0
src/PixiEditor.AvaloniaUI/Helpers/Converters/StructureMemberSelectionTypeToColorConverter.cs

@@ -0,0 +1,23 @@
+using System.Globalization;
+using Avalonia.Media;
+using PixiEditor.AvaloniaUI.Models.Layers;
+
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
+internal class StructureMemberSelectionTypeToColorConverter : SingleInstanceConverter<StructureMemberSelectionTypeToColorConverter>
+{
+    // Can't use DynamicResource, because properties are not AvaloniaProperty
+    public IBrush NoneColor { get; set; }
+    public IBrush SoftColor { get; set; }
+    public IBrush HardColor { get; set; }
+
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+    {
+        return value switch
+        {
+            StructureMemberSelectionType.Hard => HardColor,
+            StructureMemberSelectionType.Soft => SoftColor,
+            StructureMemberSelectionType.None => NoneColor,
+            _ => NoneColor,
+        };
+    }
+}

+ 1 - 3
src/PixiEditor.AvaloniaUI/Helpers/Converters/ThresholdVisibilityConverter.cs

@@ -1,8 +1,6 @@
 using System.Globalization;
-using System.Windows;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 
 internal class ThresholdVisibilityConverter
     : MarkupConverter

+ 1 - 3
src/PixiEditor.AvaloniaUI/Helpers/Converters/ZoomModeToHitTestVisibleConverter.cs

@@ -1,10 +1,8 @@
 using System.Globalization;
-using System.Windows;
 using Avalonia;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 using PixiEditor.Zoombox;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 
 internal class ZoomModeToHitTestVisibleConverter : SingleInstanceConverter<ZoomModeToHitTestVisibleConverter>
 {

+ 1 - 2
src/PixiEditor.AvaloniaUI/Helpers/Converters/ZoomToViewportConverter.cs

@@ -1,8 +1,7 @@
 using System.Globalization;
 using Avalonia;
-using PixiEditor.AvaloniaUI.Helpers.Converters;
 
-namespace PixiEditor.Helpers.Converters;
+namespace PixiEditor.AvaloniaUI.Helpers.Converters;
 
 internal class ZoomToViewportConverter
     : SingleInstanceConverter<ZoomToViewportConverter>

+ 30 - 0
src/PixiEditor.AvaloniaUI/Helpers/Extensions/BlendModeEx.cs

@@ -0,0 +1,30 @@
+using PixiEditor.ChangeableDocument.Enums;
+
+namespace PixiEditor.AvaloniaUI.Helpers.Extensions;
+internal static class BlendModeEx
+{
+    public static string LocalizedKeys(this BlendMode mode)
+    {
+        return mode switch
+        {
+            BlendMode.Normal => "NORMAL_BLEND_MODE",
+            BlendMode.Darken => "DARKEN_BLEND_MODE",
+            BlendMode.Multiply => "MULTIPLY_BLEND_MODE",
+            BlendMode.ColorBurn => "COLOR_BURN_BLEND_MODE",
+            BlendMode.Lighten => "LIGHTEN_BLEND_MODE",
+            BlendMode.Screen => "SCREEN_BLEND_MODE",
+            BlendMode.ColorDodge => "COLOR_DODGE_BLEND_MODE",
+            BlendMode.LinearDodge => "LINEAR_DODGE_BLEND_MODE",
+            BlendMode.Overlay => "OVERLAY_BLEND_MODE",
+            BlendMode.SoftLight => "SOFT_LIGHT_BLEND_MODE",
+            BlendMode.HardLight => "HARD_LIGHT_BLEND_MODE",
+            BlendMode.Difference => "DIFFERENCE_BLEND_MODE",
+            BlendMode.Exclusion => "EXCLUSION_BLEND_MODE",
+            BlendMode.Hue => "HUE_BLEND_MODE",
+            BlendMode.Saturation => "SATURATION_BLEND_MODE",
+            BlendMode.Luminosity => "LUMINOSITY_BLEND_MODE",
+            BlendMode.Color => "COLOR_BLEND_MODE",
+            _ => "NOT_SUPPORTED_BLEND_MODE"
+        };
+    }
+}

+ 1 - 1
src/PixiEditor.AvaloniaUI/Helpers/PointerHelpers.cs

@@ -5,7 +5,7 @@ namespace PixiEditor.AvaloniaUI.Helpers;
 
 public static class PointerHelpers
 {
-    public static MouseButton GetMouseButton(this PointerPressedEventArgs e, Visual visual)
+    public static MouseButton GetMouseButton(this PointerEventArgs e, Visual visual)
     {
         return e.GetCurrentPoint(visual).Properties.PointerUpdateKind switch
         {

+ 20 - 0
src/PixiEditor.AvaloniaUI/Helpers/UI/TreeViewItemHelper.cs

@@ -0,0 +1,20 @@
+using Avalonia;
+using Avalonia.Controls;
+
+namespace PixiEditor.AvaloniaUI.Helpers.UI;
+
+internal static class TreeViewItemHelper
+{
+    public static GridLength GetIndent(AvaloniaObject obj)
+    {
+        return obj.GetValue(IndentProperty);
+    }
+
+    public static void SetIndent(AvaloniaObject obj, GridLength value)
+    {
+        obj.SetValue(IndentProperty, value);
+    }
+
+    public static readonly AttachedProperty<GridLength> IndentProperty =
+        AvaloniaProperty.RegisterAttached<AvaloniaObject, GridLength>("Indent", typeof(TreeViewItemHelper), new GridLength(0));
+}

+ 0 - 7
src/PixiEditor.AvaloniaUI/PixiEditor.AvaloniaUI.csproj

@@ -75,11 +75,4 @@
       <UpToDateCheckInput Remove="Fonts\feather.ttf" />
     </ItemGroup>
   
-    <ItemGroup>
-      <Compile Update="Views\Main\Tools\ToolsPicker.axaml.cs">
-        <DependentUpon>ToolsPicker.axaml</DependentUpon>
-        <SubType>Code</SubType>
-      </Compile>
-    </ItemGroup>
-  
 </Project>

+ 93 - 0
src/PixiEditor.AvaloniaUI/Styles/PixiEditor.Layers.axaml

@@ -0,0 +1,93 @@
+<Styles xmlns="https://github.com/avaloniaui"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+                    xmlns:ui="clr-namespace:PixiEditor.Helpers.UI"
+                    xmlns:ui1="clr-namespace:PixiEditor.AvaloniaUI.Helpers.UI"
+                    xmlns:converters="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters">
+
+    <Styles.Resources>
+        <Color x:Key="SelectedLayerColor">#505056</Color>
+        <Color x:Key="SoftSelectedLayerColor">#7D505056</Color>
+
+        <SolidColorBrush x:Key="SelectedLayerBrush" Color="{DynamicResource SelectedLayerColor}" />
+        <SolidColorBrush x:Key="SoftSelectedLayerBrush" Color="{DynamicResource SoftSelectedLayerColor}" />
+
+        <ControlTheme x:Key="TreeViewItemTheme" TargetType="{x:Type TreeViewItem}">
+        <Setter Property="Background" Value="Transparent"/>
+        <Setter Property="Padding" Value="0,0,0,0"/>
+        <!--TODO: systemcolors-->
+        <!--<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>-->
+        <!--<Setter Property="FocusVisualStyle" Value="{StaticResource TreeViewItemFocusVisual}"/>-->
+        <Setter Property="ItemsPanel">
+            <Setter.Value>
+                <ItemsPanelTemplate>
+                    <ui:ReversedOrderStackPanel />
+                </ItemsPanelTemplate>
+            </Setter.Value>
+        </Setter>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="{x:Type TreeViewItem}">
+                    <Grid>
+                        <Grid.ColumnDefinitions>
+                            <ColumnDefinition MinWidth="15" Width="Auto"/>
+                            <ColumnDefinition Width="Auto"/>
+                            <ColumnDefinition Width="*"/>
+                        </Grid.ColumnDefinitions>
+                        <Grid.RowDefinitions>
+                            <RowDefinition Height="Auto"/>
+                            <RowDefinition/>
+                        </Grid.RowDefinitions>
+                        <ToggleButton x:Name="Expander"
+                                      Classes="ExpandCollapseToggleStyle"
+                                      ClickMode="Press"
+                                      IsChecked="{Binding Path=IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" />
+                        <Border x:Name="Bd" Grid.Column="1" Grid.ColumnSpan="2" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}">
+                            <ContentPresenter x:Name="PART_Header" HorizontalAlignment="{TemplateBinding HorizontalAlignment}" Content="{TemplateBinding Header}"/>
+                        </Border>
+                        <ItemsPresenter x:Name="ItemsHost" Grid.Column="0" Grid.ColumnSpan="3" Grid.Row="1"
+                                        ui1:TreeViewItemHelper.Indent="{Binding Path=(ui1:TreeViewItemHelper.Indent), Mode=OneWay, RelativeSource={RelativeSource AncestorType=ItemsPresenter}, Converter={converters:IndentConverter}}"/>
+                    </Grid>
+                    <!--TODO: Implement below-->
+                    <!--<Interaction.Behaviors>
+                        <DataTriggerBehavior Binding="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Value="False">
+                            <Style Selector="TreeViewItem #ItemsHost">
+                                <Setter Property="IsVisible" Value="False"/>
+                            </Style>
+                        </DataTriggerBehavior>
+                        <DataTriggerBehavior Binding="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}" Value="True">
+                        <Style Selector="TreeViewItem #ItemsHost">
+                            <Setter Property="IsVisible" Value="True"/>
+                        </Style>
+                        </DataTriggerBehavior>
+                        <DataTriggerBehavior Binding="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Items.Empty}" Value="True">
+                            <Style Selector="TreeViewItem #Expander">
+                                <Setter Property="IsVisible" Value="False"/>
+                            </Style>
+                        </DataTriggerBehavior>
+                    </Interaction.Behaviors>-->
+                    <!--<ControlTemplate.Triggers>
+                        <MultiTrigger>
+                            <MultiTrigger.Conditions>
+                                <Condition Property="HasItems" Value="True"/>
+                                <Condition Property="IsSelected" Value="True"/>
+                            </MultiTrigger.Conditions>
+                            <Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
+                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
+                        </MultiTrigger>
+                        <MultiTrigger>
+                            <MultiTrigger.Conditions>
+                                <Condition Property="IsSelected" Value="true"/>
+                                <Condition Property="IsSelectionActive" Value="false"/>
+                            </MultiTrigger.Conditions>
+                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.InactiveSelectionHighlightTextBrushKey}}"/>
+                        </MultiTrigger>
+                        <Trigger Property="IsEnabled" Value="false">
+                            <Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
+                        </Trigger>
+                    </ControlTemplate.Triggers>-->
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </ControlTheme>
+    </Styles.Resources>
+</Styles>

+ 20 - 0
src/PixiEditor.AvaloniaUI/Styles/PortingWipStyles.axaml

@@ -21,4 +21,24 @@
     <Style Selector="Button.OverlayButton">
 
     </Style>
+
+    <Style Selector="CheckBox.ImageCheckBox">
+
+    </Style>
+
+    <Style Selector="Button.ToolButtonStyle">
+
+    </Style>
+
+    <Style Selector="Button.ImageButtonStyle">
+
+    </Style>
+
+    <Style Selector="CheckBox.ImageCheckBox">
+
+    </Style>
+
+    <Style Selector="ToggleButton.ExpandCollapseToggleStyle">
+
+    </Style>
 </Styles>

+ 2 - 0
src/PixiEditor.AvaloniaUI/ViewLocator.cs

@@ -5,6 +5,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
 using Dock.Model.Core;
 using PixiEditor.AvaloniaUI.ViewModels.Dock;
 using PixiEditor.AvaloniaUI.Views.Dock;
+using PixiEditor.AvaloniaUI.Views.Layers;
 using PixiEditor.AvaloniaUI.Views.Main;
 
 namespace PixiEditor.AvaloniaUI;
@@ -14,6 +15,7 @@ public class ViewLocator : IDataTemplate
     public static Dictionary<Type, Type> ViewBindingsMap = new Dictionary<Type, Type>()
     {
         [typeof(DockDocumentViewModel)] = typeof(DocumentTemplate),
+        [typeof(LayersDockViewModel)] = typeof(LayersManager),
     };
 
     public Control Build(object? data)

+ 26 - 1
src/PixiEditor.AvaloniaUI/ViewModels/Dock/DockFactory.cs

@@ -98,7 +98,32 @@ internal class DockFactory : Factory
 
     private IDockable BuildPropertiesDock()
     {
-        return new ProportionalDock() { Proportion = 0.15 };
+        IDockable layersDock = BuildLayersDock();
+        return new ProportionalDock()
+        {
+            Proportion = 0.15,
+            VisibleDockables = CreateList(layersDock),
+            ActiveDockable = layersDock,
+        };
+    }
+
+    private IDockable BuildLayersDock()
+    {
+        LayersDockViewModel layersDock = new()
+        {
+            Id = "LayersPane",
+            Title = "LayersPane",
+        };
+
+        ToolDock layers = new()
+        {
+            Id = "LayersPane",
+            Title = "LayersPane",
+            VisibleDockables = new List<IDockable>() { layersDock },
+            ActiveDockable = layersDock,
+        };
+
+        return layers;
     }
 
     public override void InitLayout(IDockable layout)

+ 11 - 0
src/PixiEditor.AvaloniaUI/ViewModels/Dock/LayersDockViewModel.cs

@@ -0,0 +1,11 @@
+using Dock.Model.Avalonia.Controls;
+using PixiEditor.AvaloniaUI.Views.Layers;
+
+namespace PixiEditor.AvaloniaUI.ViewModels.Dock;
+
+public class LayersDockViewModel : Tool
+{
+    public LayersDockViewModel()
+    {
+    }
+}

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/Dock/DocumentTemplate.axaml

@@ -4,13 +4,13 @@
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
              xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
              xmlns:viewModels="clr-namespace:PixiEditor.ViewModels"
-             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
              xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
              xmlns:xaml="clr-namespace:PixiEditor.AvaloniaUI.Models.Commands.XAML"
              xmlns:palettes="clr-namespace:PixiEditor.Views.UserControls.Palettes"
              xmlns:document="clr-namespace:PixiEditor.AvaloniaUI.ViewModels.Document"
              xmlns:viewModels1="clr-namespace:PixiEditor.AvaloniaUI.ViewModels"
              xmlns:subViewModels="clr-namespace:PixiEditor.AvaloniaUI.ViewModels.SubViewModels"
+             xmlns:converters="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              x:Class="PixiEditor.AvaloniaUI.Views.Dock.DocumentTemplate">
     <userControls:Viewport

+ 101 - 0
src/PixiEditor.AvaloniaUI/Views/Input/BlendModeComboBox.cs

@@ -0,0 +1,101 @@
+using System.Collections.Generic;
+using Avalonia;
+using Avalonia.Controls;
+using PixiEditor.AvaloniaUI.Helpers.Extensions;
+using PixiEditor.ChangeableDocument.Enums;
+using PixiEditor.Extensions.UI;
+
+namespace PixiEditor.AvaloniaUI.Views.Input;
+internal class BlendModeComboBox : ComboBox
+{
+    public static readonly StyledProperty<BlendMode> SelectedBlendModeProperty =
+        AvaloniaProperty.Register<BlendModeComboBox, BlendMode>(
+            nameof(SelectedBlendMode),
+            BlendMode.Normal);
+
+    public BlendMode SelectedBlendMode
+    {
+        get { return GetValue(SelectedBlendModeProperty); }
+        set { SetValue(SelectedBlendModeProperty, value); }
+    }
+
+    private bool ignoreDepPropChange = false;
+    private bool ignoreSelectionChange = false;
+
+    static BlendModeComboBox()
+    {
+        SelectedBlendModeProperty.Changed.Subscribe(OnBlendModeChange);
+    }
+
+    public BlendModeComboBox()
+    {
+        AddItems();
+        SelectionChanged += OnSelectionChange;
+    }
+
+    private void OnSelectionChange(object sender, SelectionChangedEventArgs e)
+    {
+        if (ignoreSelectionChange || e.AddedItems.Count == 0 || e.AddedItems[0] is not ComboBoxItem item || item.Tag is not BlendMode mode)
+            return;
+        ignoreDepPropChange = true;
+        SelectedBlendMode = mode;
+        ignoreDepPropChange = false;
+    }
+
+    private static void OnBlendModeChange(AvaloniaPropertyChangedEventArgs<BlendMode> args)
+    {
+        var combobox = (BlendModeComboBox)args.Sender;
+        if (combobox.ignoreDepPropChange)
+            return;
+        foreach (var item in combobox.Items)
+        {
+            if (item is not ComboBoxItem cbItem)
+                continue;
+            if ((BlendMode)cbItem.Tag == args.NewValue.Value)
+            {
+                combobox.ignoreSelectionChange = true;
+                combobox.SelectedItem = item;
+                combobox.ignoreSelectionChange = false;
+                break;
+            }
+        }
+    }
+
+    private void AddItems()
+    {
+        var items = new List<AvaloniaObject>()
+        {
+            new ComboBoxItem() { Content = BlendMode.Normal.LocalizedKeys(), Tag = BlendMode.Normal },
+            new Separator(),
+            new ComboBoxItem() { Content = BlendMode.Darken.LocalizedKeys(), Tag = BlendMode.Darken },
+            new ComboBoxItem() { Content = BlendMode.Multiply.LocalizedKeys(), Tag = BlendMode.Multiply },
+            new ComboBoxItem() { Content = BlendMode.ColorBurn.LocalizedKeys(), Tag = BlendMode.ColorBurn },
+            new Separator(),
+            new ComboBoxItem() { Content = BlendMode.Lighten.LocalizedKeys(), Tag = BlendMode.Lighten },
+            new ComboBoxItem() { Content = BlendMode.Screen.LocalizedKeys(), Tag = BlendMode.Screen },
+            new ComboBoxItem() { Content = BlendMode.ColorDodge.LocalizedKeys(), Tag = BlendMode.ColorDodge },
+            new ComboBoxItem() { Content = BlendMode.LinearDodge.LocalizedKeys(), Tag = BlendMode.LinearDodge },
+            new Separator(),
+            new ComboBoxItem() { Content = BlendMode.Overlay.LocalizedKeys(), Tag = BlendMode.Overlay },
+            new ComboBoxItem() { Content = BlendMode.SoftLight.LocalizedKeys(), Tag = BlendMode.SoftLight },
+            new ComboBoxItem() { Content = BlendMode.HardLight.LocalizedKeys(), Tag = BlendMode.HardLight },
+            new Separator(),
+            new ComboBoxItem() { Content = BlendMode.Difference.LocalizedKeys(), Tag = BlendMode.Difference },
+            new ComboBoxItem() { Content = BlendMode.Exclusion.LocalizedKeys(), Tag = BlendMode.Exclusion },
+            new Separator(),
+            new ComboBoxItem() { Content = BlendMode.Hue.LocalizedKeys(), Tag = BlendMode.Hue },
+            new ComboBoxItem() { Content = BlendMode.Saturation.LocalizedKeys(), Tag = BlendMode.Saturation },
+            new ComboBoxItem() { Content = BlendMode.Luminosity.LocalizedKeys(), Tag = BlendMode.Luminosity },
+            new ComboBoxItem() { Content = BlendMode.Color.LocalizedKeys(), Tag = BlendMode.Color }
+        };
+        foreach (var item in items)
+        {
+            if (item is ComboBoxItem boxItem)
+            {
+                Translator.SetKey(boxItem, boxItem.Content.ToString());
+            }
+            Items.Add(item);
+        }
+        SelectedIndex = 0;
+    }
+}

+ 154 - 0
src/PixiEditor.AvaloniaUI/Views/Layers/FolderControl.axaml

@@ -0,0 +1,154 @@
+<UserControl x:Class="PixiEditor.AvaloniaUI.Views.Layers.FolderControl"
+             x:ClassModifier="internal"
+             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:i="http://schemas.microsoft.com/xaml/behaviors"
+             xmlns:sys="clr-namespace:System;assembly=mscorlib"
+             xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+             xmlns:behaviours="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Behaviours"
+             xmlns:xaml="clr-namespace:PixiEditor.AvaloniaUI.Models.Commands.XAML"
+             xmlns:converters="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters"
+             xmlns:input="clr-namespace:PixiEditor.AvaloniaUI.Views.Input"
+             xmlns:helpers="clr-namespace:PixiEditor.AvaloniaUI.Helpers.UI"
+             mc:Ignorable="d" 
+             Focusable="True"
+             d:DesignHeight="35" 
+             d:DesignWidth="250" 
+             x:Name="folderControl">
+    <Border BorderThickness="0 0 0 0.5" BorderBrush="Gray" MinWidth="60" Focusable="True" Tag="{Binding ElementName=folderControl}">
+        <Border.Background>
+            <Binding ElementName="folderControl" Path="Folder.Selection">
+                <Binding.Converter>
+                    <converters:StructureMemberSelectionTypeToColorConverter
+                        SoftColor="{StaticResource SoftSelectedLayerBrush}"
+                        HardColor="{StaticResource SelectedLayerBrush}"
+                        NoneColor="Transparent"
+                        />
+                </Binding.Converter>
+            </Binding>
+        </Border.Background>
+        <Interaction.Behaviors>
+            <behaviours:ClearFocusOnClickBehavior/>
+        </Interaction.Behaviors>
+        <Grid>
+            <Grid.RowDefinitions>
+                <RowDefinition Height="10"/>
+                <RowDefinition Height="15"/>
+                <RowDefinition Height="10"/>
+            </Grid.RowDefinitions>
+            <Grid DragDrop.AllowDrop="True"  Grid.Row="0" Grid.ColumnSpan="3" Background="Transparent" Panel.ZIndex="3"/>
+            <Grid IsVisible="False" x:Name="middleDropGrid" Grid.Row="1" DragDrop.AllowDrop="True" Panel.ZIndex="2" Background="Transparent"></Grid>
+            <Grid x:Name="centerGrid" Grid.Row="0" Grid.RowSpan="3" Background="Transparent">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="24"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+                <CheckBox Classes="ImageCheckBox" VerticalAlignment="Center"
+                      IsThreeState="False" HorizontalAlignment="Center"
+                      IsChecked="{Binding Path=Folder.IsVisibleBindable, ElementName=folderControl}" Grid.Column="0" Height="16"/>
+
+                <StackPanel Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Left">
+                    <Rectangle Width="{Binding Path=(helpers:TreeViewItemHelper.Indent).Value, Mode=OneWay, RelativeSource={RelativeSource AncestorType=ItemsPresenter}}" Fill="Transparent" StrokeThickness="0"/>
+                    <Border 
+                        IsVisible="{Binding Folder.ClipToMemberBelowEnabledBindable, ElementName=folderControl}"
+                        Background="{DynamicResource ThemeAccentBrush}" Width="3" Margin="1,1,2,1" CornerRadius="1"/>
+                    <StackPanel Grid.Row="1" Orientation="Horizontal" Grid.Column="0" HorizontalAlignment="Left">
+                        <Border Width="32" Height="32" BorderThickness="1" BorderBrush="Black" RenderOptions.BitmapInterpolationMode="None">
+                            <Border.Background>
+                                <ImageBrush Source="/Images/CheckerTile.png" TileMode="Tile" DestinationRect="0, 0, 0.20, 0.20"/>
+                            </Border.Background>
+                            <Image Source="{Binding Folder.PreviewBitmap, ElementName=folderControl}" Stretch="Uniform" Width="30" Height="30">
+                               <!--TODO: Avalonia throws error for below-->
+                                <!--<RenderOptions.BitmapInterpolationMode>
+                                    <MultiBinding Converter="{converters:WidthToBitmapScalingModeConverter}">
+                                        <Binding Path="Folder.PreviewBitmap.PixelSize.Width" ElementName="folderControl"/>
+                                        <Binding RelativeSource="{RelativeSource Mode=Self}" Path="Bounds.Width"/>
+                                    </MultiBinding>
+                                </RenderOptions.BitmapInterpolationMode>-->
+                            </Image>
+                        </Border>
+                        <Border 
+                            Width="32" Height="32" 
+                            BorderThickness="1"
+                            Margin="3,0,0,0"
+                            RenderOptions.BitmapInterpolationMode="None"
+                            IsVisible="{Binding Folder.HasMaskBindable, ElementName=folderControl}"
+                            BorderBrush="White">
+                            <Border.Background>
+                                <ImageBrush Source="/Images/CheckerTile.png" TileMode="Tile" DestinationRect="0, 0, 0.20, 0.20"/>
+                            </Border.Background>
+                            <Grid IsHitTestVisible="False">
+                                <Image Source="{Binding Folder.MaskPreviewBitmap,ElementName=folderControl}" Stretch="Uniform" Width="30" Height="30"
+                                    RenderOptions.BitmapInterpolationMode="None" IsHitTestVisible="False"/>
+                                <Path 
+                                Data="M 2 0 L 10 8 L 18 0 L 20 2 L 12 10 L 20 18 L 18 20 L 10 12 L 2 20 L 0 18 L 8 10 L 0 2 Z" 
+                                Fill="{StaticResource PixiRed}" HorizontalAlignment="Center" VerticalAlignment="Center"
+                                IsVisible="{Binding !Folder.MaskIsVisibleBindable, ElementName=folderControl}"/>
+                            </Grid>
+                        </Border>
+                        <StackPanel Orientation="Vertical"  Margin="3,0,5,0">
+                            <input:EditableTextBlock
+                                x:Name="editableTextBlock"
+                                d:Text="New Folder" FontSize="14"
+                                VerticalAlignment="Center"
+                                Text="{Binding Folder.NameBindable, ElementName=folderControl, Mode=TwoWay}" />
+                            
+                            <StackPanel Orientation="Horizontal">
+                                <TextBlock d:Text="100" Foreground="White" FontSize="11">
+                                    <TextBlock.Text>
+                                        <Binding ElementName="folderControl" Path="Folder.OpacityBindable" Converter="{converters:MultiplyConverter}" StringFormat="N0">
+                                            <Binding.ConverterParameter>
+                                                <sys:Double>100.0</sys:Double>
+                                            </Binding.ConverterParameter>
+                                        </Binding>
+                                    </TextBlock.Text>
+                                </TextBlock>
+                                <TextBlock Foreground="White" FontSize="11">%</TextBlock>
+                                <TextBlock 
+                                Margin="5,0,0,0" 
+                                d:Text="Normal" 
+                                Foreground="White"
+                                FontSize="11"
+                                Text="{Binding Folder.BlendModeBindable, ElementName=folderControl, Converter={converters:BlendModeToStringConverter}}"/>
+                            </StackPanel>
+                        </StackPanel>
+                    </StackPanel>
+                    <Image Source="/Images/Folder.png" Height="20" Margin="0,0,10,0" HorizontalAlignment="Right"/>
+                </StackPanel>
+            </Grid>
+            <Grid
+                  Grid.Row="2"
+                  DragDrop.AllowDrop="false"
+                  Grid.ColumnSpan="2" Background="Transparent"/>
+        </Grid>
+        <Border.ContextMenu>
+            <ContextMenu>
+                <MenuItem ui:Translator.Key="DELETE" Command="{xaml:Command PixiEditor.Layer.DeleteAllSelected}"/>
+                <MenuItem ui:Translator.Key="RENAME" Click="RenameMenuItem_Click"/>
+                <!--TODO: Add checkable menu item-->
+                <!--<MenuItem
+                    IsCheckable="True" 
+                    IsChecked="{Binding PlacementTarget.Tag.Folder.ClipToMemberBelowEnabledBindable, RelativeSource={RelativeSource AncestorType=ContextMenu}}" 
+                    ui:Translator.Key="CLIP_TO_BELOW"/>-->
+                <Separator/>
+                <MenuItem ui:Translator.Key="MOVE_UPWARDS" Command="{xaml:Command PixiEditor.Layer.MoveSelectedMemberUpwards}"/>
+                <MenuItem ui:Translator.Key="MOVE_DOWNWARDS" Command="{xaml:Command PixiEditor.Layer.MoveSelectedMemberDownwards}"/>
+                <Separator/>
+                <MenuItem ui:Translator.Key="CREATE_MASK" Command="{xaml:Command PixiEditor.Layer.CreateMask}"/>
+                <MenuItem ui:Translator.Key="DELETE_MASK" Command="{xaml:Command PixiEditor.Layer.DeleteMask}"/>
+                <!--TODO: Add checkable menu item-->
+                <!--<MenuItem
+                    IsCheckable="True" 
+                    IsChecked="{Binding PlacementTarget.Tag.Folder.MaskIsVisibleBindable, RelativeSource={RelativeSource AncestorType=ContextMenu}}" 
+                    IsEnabled="{Binding PlacementTarget.Tag.Folder.HasMaskBindable, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
+                    ui:Translator.Key="ENABLE_MASK"/>-->
+                <Separator/>
+                <MenuItem ui:Translator.Key="MERGE_SELECTED" Command="{xaml:Command PixiEditor.Layer.MergeSelected}"/>
+                <MenuItem ui:Translator.Key="MERGE_WITH_ABOVE" Command="{xaml:Command PixiEditor.Layer.MergeWithAbove}"/>
+                <MenuItem ui:Translator.Key="MERGE_WITH_BELOW" Command="{xaml:Command PixiEditor.Layer.MergeWithBelow}"/>
+            </ContextMenu>
+        </Border.ContextMenu>
+    </Border>
+</UserControl>

+ 115 - 0
src/PixiEditor.AvaloniaUI/Views/Layers/FolderControl.axaml.cs

@@ -0,0 +1,115 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using PixiEditor.AvaloniaUI.Models.Controllers.InputDevice;
+using PixiEditor.AvaloniaUI.Models.Layers;
+using PixiEditor.AvaloniaUI.ViewModels.Document;
+
+namespace PixiEditor.AvaloniaUI.Views.Layers;
+#nullable enable
+internal partial class FolderControl : UserControl
+{
+
+    public static readonly StyledProperty<FolderViewModel> FolderProperty =
+        AvaloniaProperty.Register<FolderControl, FolderViewModel>(nameof(Folder));
+
+    public FolderViewModel Folder
+    {
+        get => GetValue(FolderProperty);
+        set => SetValue(FolderProperty, value);
+    }
+
+    public static string? FolderControlDataName = typeof(FolderControl).FullName;
+    public static string? LayerControlDataName = typeof(LayerControl).FullName;
+
+    public static readonly StyledProperty<LayersManager> ManagerProperty =
+        AvaloniaProperty.Register<FolderControl, LayersManager>(nameof(Manager));
+
+    public LayersManager Manager
+    {
+        get { return GetValue(ManagerProperty); }
+        set { SetValue(ManagerProperty, value); }
+    }
+
+    private readonly IBrush? highlightColor;
+
+    
+    private MouseUpdateController mouseUpdateController;
+
+    public FolderControl()
+    {
+        InitializeComponent();
+        highlightColor = (Brush?)App.Current.Resources["SoftSelectedLayerColor"];
+        Loaded += OnLoaded;
+    }
+
+    private void OnLoaded(object sender, RoutedEventArgs e)
+    {
+        mouseUpdateController = new MouseUpdateController(this, Manager.FolderControl_MouseMove);
+    }
+
+    private void Grid_DragEnter(object sender, DragEventArgs e)
+    {
+        Grid item = (Grid)sender;
+        item.Background = highlightColor;
+    }
+
+    private void Grid_CenterEnter(object sender, DragEventArgs e)
+    {
+        centerGrid.Background = highlightColor;
+    }
+
+    private void Grid_DragLeave(object sender, DragEventArgs e)
+    {
+        Grid grid = (Grid)sender;
+        LayerControl.RemoveDragEffect(grid);
+    }
+
+    private void Grid_CenterLeave(object sender, DragEventArgs e)
+    {
+        LayerControl.RemoveDragEffect(centerGrid);
+    }
+
+    private void HandleDrop(IDataObject dataObj, StructureMemberPlacement placement)
+    {
+        Guid? droppedMemberGuid = LayerControl.ExtractMemberGuid(dataObj);
+        if (droppedMemberGuid is null)
+            return;
+        Folder.Document.Operations.MoveStructureMember((Guid)droppedMemberGuid, Folder.GuidValue, placement);
+    }
+
+    private void Grid_Drop_Top(object sender, DragEventArgs e)
+    {
+        LayerControl.RemoveDragEffect((Grid)sender);
+        HandleDrop(e.Data, StructureMemberPlacement.Above);
+    }
+
+    private void Grid_Drop_Center(object sender, DragEventArgs e)
+    {
+        LayerControl.RemoveDragEffect(centerGrid);
+        HandleDrop(e.Data, StructureMemberPlacement.Inside);
+    }
+
+    private void Grid_Drop_Bottom(object sender, DragEventArgs e)
+    {
+        LayerControl.RemoveDragEffect((Grid)sender);
+        HandleDrop(e.Data, StructureMemberPlacement.Below);
+    }
+
+    private void FolderControl_DragEnter(object sender, DragEventArgs e)
+    {
+        middleDropGrid.IsVisible = true;
+    }
+
+    private void FolderControl_DragLeave(object sender, DragEventArgs e)
+    {
+        middleDropGrid.IsVisible = false;
+    }
+
+    private void RenameMenuItem_Click(object sender, RoutedEventArgs e)
+    {
+        editableTextBlock.EnableEditing();
+    }
+}

+ 172 - 0
src/PixiEditor.AvaloniaUI/Views/Layers/LayerControl.axaml

@@ -0,0 +1,172 @@
+<UserControl x:Class="PixiEditor.AvaloniaUI.Views.Layers.LayerControl"
+             x:ClassModifier="internal"
+             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:controls="clr-namespace:PixiEditor.Views.UserControls"
+             xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
+             xmlns:sys="clr-namespace:System;assembly=mscorlib"
+             xmlns:views="clr-namespace:PixiEditor.Views"
+             xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+             xmlns:helpers="clr-namespace:PixiEditor.AvaloniaUI.Helpers.UI"
+             xmlns:converters="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters"
+             xmlns:behaviours="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Behaviours"
+             xmlns:input="clr-namespace:PixiEditor.AvaloniaUI.Views.Input"
+             xmlns:xaml="clr-namespace:PixiEditor.AvaloniaUI.Models.Commands.XAML"
+             mc:Ignorable="d" 
+             Focusable="True"
+             d:DesignHeight="35" d:DesignWidth="250" Name="uc"
+             PointerExited="LayerItem_OnMouseLeave" PointerEntered="LayerItem_OnMouseEnter">
+    <UserControl.Resources>
+        <converters:BoolToBrushConverter x:Key="LayerBorderConverter" FalseBrush="White" TrueBrush="Black"/>
+        <converters:BoolToBrushConverter x:Key="MaskBorderConverter" FalseBrush="Black" TrueBrush="White"/>
+    </UserControl.Resources>
+    <Border BorderThickness="0 0 0 0.5" BorderBrush="Gray" MinWidth="60" Focusable="True" Tag="{Binding ElementName=uc}">
+        <Border.Background>
+            <Binding ElementName="uc" Path="Layer.Selection">
+                <Binding.Converter>
+                    <converters:StructureMemberSelectionTypeToColorConverter
+                        SoftColor="{StaticResource SoftSelectedLayerBrush}"
+                        HardColor="{StaticResource SelectedLayerBrush}"
+                        NoneColor="Transparent"
+                        />
+                </Binding.Converter>
+            </Binding>
+        </Border.Background>
+        <Interaction.Behaviors>
+            <behaviours:ClearFocusOnClickBehavior/>
+        </Interaction.Behaviors>
+        <Grid>
+            <Grid.RowDefinitions>
+                <RowDefinition Height="10"/>
+                <RowDefinition Height="25"/>
+            </Grid.RowDefinitions>
+            <Grid DragDrop.AllowDrop="True" Grid.Row="0" Grid.ColumnSpan="3" Background="Transparent"/>
+            <Grid Grid.Row="1" Grid.RowSpan="3" Margin="0,-10,0,0" VerticalAlignment="Center" DragDrop.AllowDrop="False">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="24"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+                <CheckBox Classes="ImageCheckBox" VerticalAlignment="Center"
+                      IsThreeState="False" HorizontalAlignment="Center"
+                      IsChecked="{Binding Path=Layer.IsVisibleBindable, ElementName=uc}"
+                      Grid.Column="0" Height="16" />
+                <StackPanel Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Left">
+                    <Rectangle Width="{Binding Path=(helpers:TreeViewItemHelper.Indent).Value, Mode=OneWay, RelativeSource={RelativeSource AncestorType=ItemsPresenter}}" Fill="Transparent" StrokeThickness="0"/>
+                    <Border
+                        IsVisible="{Binding Layer.ClipToMemberBelowEnabledBindable, ElementName=uc}"
+                        Background="{DynamicResource ThemeAccentBrush}" Width="3" Margin="1,1,2,1" CornerRadius="1"/>
+                    <Border
+                        Width="32" Height="32"
+                        BorderThickness="1"
+                        RenderOptions.BitmapInterpolationMode="None"
+                        PointerPressed="LayerMouseDown">
+                        <Border.Background>
+                            <ImageBrush Source="/Images/CheckerTile.png" TileMode="Tile" DestinationRect="0, 0, 0.20, 0.20"/>
+                        </Border.Background>
+                        <Border.BorderBrush>
+                            <MultiBinding Converter="{StaticResource LayerBorderConverter}">
+                                <Binding ElementName="uc" Path="Layer.ShouldDrawOnMask"/>
+                                <Binding ElementName="uc" Path="Layer.HasMaskBindable"/>
+                            </MultiBinding>
+                        </Border.BorderBrush>
+                        <Image Source="{Binding Layer.PreviewBitmap,ElementName=uc}" Stretch="Uniform" Width="30" Height="30"
+                           RenderOptions.BitmapInterpolationMode="None" IsHitTestVisible="False"/>
+                    </Border>
+                    <Border
+                        Width="32" Height="32"
+                        BorderThickness="1"
+                        Margin="3,0,0,0"
+                        RenderOptions.BitmapInterpolationMode="None"
+                        IsVisible="{Binding Layer.HasMaskBindable, ElementName=uc}"
+                        PointerPressed="MaskMouseDown">
+                        <Border.Background>
+                            <ImageBrush Source="/Images/CheckerTile.png" TileMode="Tile" DestinationRect="0, 0, 0.20, 0.20"/>
+                        </Border.Background>
+                        <Border.BorderBrush>
+                            <MultiBinding Converter="{StaticResource MaskBorderConverter}">
+                                <Binding ElementName="uc" Path="Layer.ShouldDrawOnMask"/>
+                            </MultiBinding>
+                        </Border.BorderBrush>
+                        <Grid IsHitTestVisible="False">
+                            <Image Source="{Binding Layer.MaskPreviewBitmap,ElementName=uc}" Stretch="Uniform" Width="30" Height="30"
+                           RenderOptions.BitmapInterpolationMode="None" IsHitTestVisible="False"/>
+                            <Path
+                                Data="M 2 0 L 10 8 L 18 0 L 20 2 L 12 10 L 20 18 L 18 20 L 10 12 L 2 20 L 0 18 L 8 10 L 0 2 Z"
+                                Fill="{DynamicResource ThemeAccentBrush}" HorizontalAlignment="Center" VerticalAlignment="Center"
+                                IsVisible="{Binding !Layer.MaskIsVisibleBindable, ElementName=uc}"/>
+                        </Grid>
+                    </Border>
+                    <StackPanel Margin="3,0,5,0">
+                        <input:EditableTextBlock
+                            x:Name="editableTextBlock"
+                            VerticalAlignment="Center"
+                            d:Text="New Layer" FontSize="14"
+                            Text="{Binding Layer.NameBindable, ElementName=uc, Mode=TwoWay}" />
+
+                        <StackPanel Orientation="Horizontal">
+                            <TextBlock d:Text="100" Foreground="White" FontSize="11">
+                                <TextBlock.Text>
+                                    <Binding ElementName="uc" Path="Layer.OpacityBindable" Converter="{converters:MultiplyConverter}" StringFormat="N0">
+                                        <Binding.ConverterParameter>
+                                            <sys:Double>100.0</sys:Double>
+                                        </Binding.ConverterParameter>
+                                    </Binding>
+                                </TextBlock.Text>
+                            </TextBlock>
+                            <TextBlock Foreground="White" FontSize="11">%</TextBlock>
+                            <TextBlock  FontSize="11"
+                                        Margin="5,0,0,0"
+                                        d:Text="Normal"
+                                        Foreground="White"
+                                        Text="{Binding Layer.BlendModeBindable, ElementName=uc, Converter={converters:BlendModeToStringConverter}}"/>
+                        </StackPanel>
+                    </StackPanel>
+                    <WrapPanel Orientation="Vertical" Margin="0,3,3,3">
+                        <Image
+                            Source="/Images/Lock-alpha.png" Width="14" Height="14"
+                            IsVisible="{Binding Layer.LockTransparencyBindable, ElementName=uc}"/>
+                    </WrapPanel>
+                </StackPanel>
+                <Grid Margin="0, 0, 0, -2.5" VerticalAlignment="Bottom" Height="10" Grid.Row="2" Grid.Column="0" DragDrop.AllowDrop="True"  Background="Transparent" Name="dropBelowGrid"/>
+                <Grid Margin="0, 0, 0, -2.5" VerticalAlignment="Bottom" Height="10" Grid.Row="2" Grid.Column="1" Background="{Binding ElementName=dropBelowGrid, Path=Background}"/>
+
+                <Grid Margin="0, 0, 0, -2.5" VerticalAlignment="Bottom" Height="10" Grid.Row="2" Grid.Column="2" DragDrop.AllowDrop="True"  Background="Transparent"/>
+            </Grid>
+        </Grid>
+        <Border.ContextMenu>
+            <ContextMenu>
+                <MenuItem ui:Translator.Key="DUPLICATE" Command="{xaml:Command PixiEditor.Layer.DuplicateSelectedLayer}"/>
+                <MenuItem ui:Translator.Key="DELETE" Command="{xaml:Command PixiEditor.Layer.DeleteAllSelected}"/>
+                <MenuItem ui:Translator.Key="RENAME" Click="RenameMenuItem_Click"/>
+                <!--TODO: Add checkable menu item-->
+                <!--<MenuItem
+                    IsCheckable="True" 
+                    IsChecked="{Binding PlacementTarget.Tag.Layer.ClipToMemberBelowEnabledBindable, RelativeSource={RelativeSource AncestorType=ContextMenu}}" 
+                    ui:Translator.Key="CLIP_TO_BELOW"/>
+                <MenuItem
+                    IsCheckable="True" 
+                    IsChecked="{Binding PlacementTarget.Tag.Layer.LockTransparencyBindable, RelativeSource={RelativeSource AncestorType=ContextMenu}}" 
+                    ui:Translator.Key="LOCK_TRANSPARENCY"/>-->
+                <Separator/>
+                <MenuItem ui:Translator.Key="MOVE_UPWARDS" Command="{xaml:Command PixiEditor.Layer.MoveSelectedMemberUpwards}"/>
+                <MenuItem ui:Translator.Key="MOVE_DOWNWARDS" Command="{xaml:Command PixiEditor.Layer.MoveSelectedMemberDownwards}"/>
+                <Separator/>
+                <MenuItem ui:Translator.Key="CREATE_MASK" Command="{xaml:Command PixiEditor.Layer.CreateMask}"/>
+                <MenuItem ui:Translator.Key="DELETE_MASK" Command="{xaml:Command PixiEditor.Layer.DeleteMask}"/>
+                <!--TODO: Add checkable menu item-->
+                <!--<MenuItem
+                    IsCheckable="True" 
+                    IsChecked="{Binding PlacementTarget.Tag.Layer.MaskIsVisibleBindable, RelativeSource={RelativeSource AncestorType=ContextMenu}}" 
+                    IsEnabled="{Binding PlacementTarget.Tag.Layer.HasMaskBindable, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
+                    ui:Translator.Key="ENABLE_MASK"/>-->
+                <MenuItem ui:Translator.Key="APPLY_MASK" Command="{xaml:Command PixiEditor.Layer.ApplyMask}"/>
+                <Separator/>
+                <MenuItem ui:Translator.Key="MERGE_SELECTED" Command="{xaml:Command PixiEditor.Layer.MergeSelected}"/>
+                <MenuItem ui:Translator.Key="MERGE_WITH_ABOVE" Command="{xaml:Command PixiEditor.Layer.MergeWithAbove}"/>
+                <MenuItem ui:Translator.Key="MERGE_WITH_BELOW" Command="{xaml:Command PixiEditor.Layer.MergeWithBelow}"/>
+            </ContextMenu>
+        </Border.ContextMenu>
+    </Border>
+</UserControl>

+ 171 - 0
src/PixiEditor.AvaloniaUI/Views/Layers/LayerControl.axaml.cs

@@ -0,0 +1,171 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using CommunityToolkit.Mvvm.Input;
+using PixiEditor.AvaloniaUI.Models.Controllers.InputDevice;
+using PixiEditor.AvaloniaUI.Models.Layers;
+using PixiEditor.AvaloniaUI.ViewModels.Document;
+
+namespace PixiEditor.AvaloniaUI.Views.Layers;
+#nullable enable
+internal partial class LayerControl : UserControl
+{
+   public static readonly StyledProperty<LayerViewModel> LayerProperty =
+        AvaloniaProperty.Register<LayerControl, LayerViewModel>(nameof(Layer));
+
+   public LayerViewModel Layer
+    {
+        get => GetValue(LayerProperty);
+        set => SetValue(LayerProperty, value);
+    }
+
+   private readonly IBrush? highlightColor;
+
+   public static readonly StyledProperty<bool> ControlButtonsVisibleProperty =
+        AvaloniaProperty.Register<LayerControl, bool>(nameof(ControlButtonsVisible), false);
+
+   public bool ControlButtonsVisible
+    {
+        get => GetValue(ControlButtonsVisibleProperty);
+        set => SetValue(ControlButtonsVisibleProperty, value);
+    }
+
+   public string LayerColor
+    {
+        get => GetValue(LayerColorProperty);
+        set => SetValue(LayerColorProperty, value);
+    }
+
+   public static readonly StyledProperty<string> LayerColorProperty =
+        AvaloniaProperty.Register<LayerControl, string>(nameof(LayerColor), "#00000000");
+
+   public static readonly StyledProperty<LayersManager> ManagerProperty =
+        AvaloniaProperty.Register<LayerControl, LayersManager>(nameof(Manager));
+
+   public LayersManager Manager
+    {
+        get => GetValue(ManagerProperty);
+        set => SetValue(ManagerProperty, value);
+    }
+
+   public static readonly StyledProperty<RelayCommand> MoveToBackCommandProperty =
+        AvaloniaProperty.Register<LayerControl, RelayCommand>(nameof(MoveToBackCommand));
+
+   public RelayCommand MoveToBackCommand
+    {
+        get => GetValue(MoveToBackCommandProperty);
+        set => SetValue(MoveToBackCommandProperty, value);
+    }
+
+   public static readonly StyledProperty<RelayCommand> MoveToFrontCommandProperty =
+        AvaloniaProperty.Register<LayerControl, RelayCommand>(nameof(MoveToFrontCommand));
+
+   public RelayCommand MoveToFrontCommand
+    {
+        get => GetValue(MoveToFrontCommandProperty);
+        set => SetValue(MoveToFrontCommandProperty, value);
+    }
+
+
+   private MouseUpdateController mouseUpdateController;
+    
+   public LayerControl()
+    {
+        InitializeComponent();
+        Loaded += LayerControl_Loaded;
+        highlightColor = (Brush?)App.Current.Resources["SoftSelectedLayerColor"];
+    }
+
+   private void LayerControl_Loaded(object sender, RoutedEventArgs e)
+    {
+        mouseUpdateController = new MouseUpdateController(this, Manager.LayerControl_MouseMove);
+    }
+
+    public static void RemoveDragEffect(Grid grid)
+    {
+        grid.Background = Brushes.Transparent;
+    }
+
+    private void LayerItem_OnMouseEnter(object sender, PointerEventArgs e)
+    {
+        ControlButtonsVisible = true;
+    }
+
+    private void LayerItem_OnMouseLeave(object sender, PointerEventArgs e)
+    {
+        ControlButtonsVisible = false;
+
+    }
+
+    private void Grid_DragEnter(object? sender, DragEventArgs e)
+    {
+        Grid? item = sender as Grid;
+        if (item is not null)
+            item.Background = highlightColor;
+    }
+
+    private void Grid_DragLeave(object? sender, DragEventArgs e)
+    {
+        Grid? item = sender as Grid;
+        if (item is not null)
+            RemoveDragEffect(item);
+    }
+
+    public static Guid? ExtractMemberGuid(IDataObject droppedMemberDataObject)
+    {
+        object droppedLayer = droppedMemberDataObject.Get(FolderControl.LayerControlDataName);
+        object droppedFolder = droppedMemberDataObject.Get(AvaloniaUI.Views.Layers.FolderControl.FolderControlDataName);
+        if (droppedLayer is LayerControl layer)
+            return layer.Layer.GuidValue;
+        else if (droppedFolder is AvaloniaUI.Views.Layers.FolderControl folder)
+            return folder.Folder.GuidValue;
+        return null;
+    }
+
+    private void HandleDrop(IDataObject dataObj, StructureMemberPlacement placement)
+    {
+        if (placement == StructureMemberPlacement.Inside)
+            return;
+        Guid? droppedMemberGuid = ExtractMemberGuid(dataObj);
+        if (droppedMemberGuid is null)
+            return;
+        Layer.Document.Operations.MoveStructureMember((Guid)droppedMemberGuid, Layer.GuidValue, placement);
+    }
+
+    private void Grid_Drop_Top(object sender, DragEventArgs e)
+    {
+        RemoveDragEffect((Grid)sender);
+        HandleDrop(e.Data, StructureMemberPlacement.Above);
+    }
+
+    private void Grid_Drop_Bottom(object sender, DragEventArgs e)
+    {
+        RemoveDragEffect((Grid)sender);
+        HandleDrop(e.Data, StructureMemberPlacement.Below);
+    }
+
+    private void Grid_Drop_Below(object sender, DragEventArgs e)
+    {
+        RemoveDragEffect((Grid)sender);
+        HandleDrop(e.Data, StructureMemberPlacement.BelowOutsideFolder);
+    }
+
+   private void RenameMenuItem_Click(object sender, RoutedEventArgs e)
+    {
+        editableTextBlock.EnableEditing();
+    }
+
+   private void MaskMouseDown(object sender, PointerPressedEventArgs e)
+    {
+        if (Layer is not null)
+            Layer.ShouldDrawOnMask = true;
+    }
+
+   private void LayerMouseDown(object sender, PointerPressedEventArgs e)
+    {
+        if (Layer is not null)
+            Layer.ShouldDrawOnMask = false;
+    }
+}

+ 185 - 0
src/PixiEditor.AvaloniaUI/Views/Layers/LayersManager.axaml

@@ -0,0 +1,185 @@
+<UserControl x:Class="PixiEditor.AvaloniaUI.Views.Layers.LayersManager"
+             x:ClassModifier="internal"
+             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:i="http://schemas.microsoft.com/xaml/behaviors"
+             xmlns:vws="clr-namespace:PixiEditor.Views" 
+             xmlns:sys="clr-namespace:System;assembly=mscorlib"
+             xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
+             xmlns:uiExt="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+             xmlns:converters1="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters"
+             xmlns:layers="clr-namespace:PixiEditor.AvaloniaUI.Views.Layers"
+             xmlns:xaml="clr-namespace:PixiEditor.AvaloniaUI.Models.Commands.XAML"
+             xmlns:input="clr-namespace:PixiEditor.AvaloniaUI.Views.Input"
+             xmlns:behaviours="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Behaviours"
+             xmlns:ui="clr-namespace:PixiEditor.Helpers.UI"
+             xmlns:docVm="clr-namespace:PixiEditor.AvaloniaUI.ViewModels.Document"
+             mc:Ignorable="d"
+             d:DesignHeight="450" d:DesignWidth="250" x:Name="layersManager">
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="58"/>
+            <RowDefinition Height="15"/>
+            <RowDefinition Height="1*"/>
+        </Grid.RowDefinitions>
+        <StackPanel Grid.Row="0" HorizontalAlignment="Stretch" Background="{DynamicResource ThemeBackgroundBrush}">
+            <DockPanel HorizontalAlignment="Stretch" Margin="3,5,3,3">
+                <Button 
+                    Command="{xaml:Command PixiEditor.Layer.NewLayer}"
+                    DockPanel.Dock="Left"
+                    Height="24" Width="24" Cursor="Hand" uiExt:Translator.TooltipKey="NEW_LAYER"
+                    HorizontalAlignment="Stretch" Margin="0,0,5,0"
+                    Classes="ToolButtonStyle"
+                    FlowDirection="LeftToRight">
+                    <Button.Background>
+                        <ImageBrush Source="/Images/Layer-add.png"/>
+                    </Button.Background>
+                </Button>
+                <Button 
+                    Command="{xaml:Command PixiEditor.Layer.NewFolder}" 
+                    Height="24" Width="24" uiExt:Translator.TooltipKey="NEW_FOLDER" Cursor="Hand"
+                    DockPanel.Dock="Left"
+                    HorizontalAlignment="Stretch"  Margin="0,0,5,0"
+                    Classes="ToolButtonStyle"
+                    FlowDirection="LeftToRight">
+                    <Button.Background>
+                        <ImageBrush Source="/Images/Folder-add.png"/>
+                    </Button.Background>
+                </Button>
+                <Button 
+                    Command="{xaml:Command PixiEditor.Layer.DeleteSelected}" Height="24" Width="24" uiExt:Translator.TooltipKey="LAYER_DELETE_ALL_SELECTED"
+                    Cursor="Hand"
+                    HorizontalAlignment="Stretch" Margin="0,0,5,0"
+                    DockPanel.Dock="Left"
+                    Classes="ToolButtonStyle"
+                    FlowDirection="LeftToRight">
+                    <Button.Background>
+                        <ImageBrush Source="/Images/Trash.png"/>
+                    </Button.Background>
+                </Button>
+                <Button 
+                    Command="{xaml:Command PixiEditor.Layer.MergeWithBelow}" Height="24" Width="24" uiExt:Translator.TooltipKey="MERGE_WITH_BELOW" Cursor="Hand"
+                    DockPanel.Dock="Right"
+                    HorizontalAlignment="Stretch" Margin="5,0,0,0"
+                    Classes="ToolButtonStyle"
+                    FlowDirection="LeftToRight">
+                    <Button.Background>
+                        <ImageBrush Source="/Images/Merge-downwards.png"/>
+                    </Button.Background>
+                </Button>
+                <Button 
+                    Height="24" Width="24" uiExt:Translator.TooltipKey="CREATE_MASK" Cursor="Hand"
+                    DockPanel.Dock="Right"
+                    HorizontalAlignment="Stretch" Margin="5,0,0,0"
+                    Classes="ToolButtonStyle"
+                    Command="{xaml:Command PixiEditor.Layer.CreateMask}"
+                    FlowDirection="LeftToRight">
+                    <Button.Background>
+                        <ImageBrush Source="/Images/Create-mask.png"/>
+                    </Button.Background>
+                </Button>
+                <Button 
+                    Height="24" Width="24" uiExt:Translator.TooltipKey="LOCK_TRANSPARENCY" Cursor="Hand"
+                    DockPanel.Dock="Right"
+                    HorizontalAlignment="Stretch" Margin="5,0,0,0"
+                    Classes="ToolButtonStyle"
+                    Command="{xaml:Command PixiEditor.Layer.ToggleLockTransparency}"
+                    FlowDirection="LeftToRight">
+                    <Button.Background>
+                        <ImageBrush Source="/Images/Lock-alpha.png"/>
+                    </Button.Background>
+                </Button>
+                <Grid/>
+            </DockPanel>
+            <DockPanel Margin="3,0">
+                <input:BlendModeComboBox
+                    DockPanel.Dock="Left"
+                    Margin="0,0,5,0"
+                    Width="80"
+                    SelectedBlendMode="{Binding ActiveDocument.SelectedStructureMember.BlendModeBindable, Mode=TwoWay, ElementName=layersManager}" />
+                <input:NumberInput
+                    Min="0" Max="100"
+                    x:Name="numberInput"
+                    d:Value="100"
+                    DockPanel.Dock="Right"
+                    IsEnabled="{Binding Path=ActiveDocument, ElementName=layersManager, Converter={converters1:NotNullToVisibilityConverter}}"
+                    Width="35" Height="20"
+                    Margin="5,0,0,0"
+                    VerticalAlignment="Center"
+                    LostFocus="NumberInput_LostFocus">
+                    <input:NumberInput.Value>
+                        <Binding
+                            Mode="TwoWay"
+                            ElementName="layersManager"
+                            Path="ActiveDocument.SelectedStructureMember.OpacityBindable"
+                            Converter="{converters1:MultiplyConverter}">
+                            <Binding.ConverterParameter>
+                                <sys:Double>100</sys:Double>
+                            </Binding.ConverterParameter>
+                        </Binding>
+                    </input:NumberInput.Value>
+                </input:NumberInput>
+                <Slider
+                    Minimum="0"
+                    Maximum="1"
+                    SmallChange="0.01"
+                    LargeChange="0.1"
+                    IsSnapToTickEnabled="True"
+                    TickFrequency="0.01"
+                    x:Name="opacitySlider"
+                    VerticalAlignment="Center"
+                    HorizontalAlignment="Stretch">
+                    <Interaction.Behaviors>
+                        <behaviours:SliderUpdateBehavior
+                                Binding="{Binding ElementName=layersManager, Path=ActiveDocument.SelectedStructureMember.OpacityBindable, Mode=OneWay}"
+                                DragStarted="{xaml:Command PixiEditor.Layer.OpacitySliderDragStarted}"
+                                DragValueChanged="{xaml:Command PixiEditor.Layer.OpacitySliderDragged, UseProvided=True}"
+                                DragEnded="{xaml:Command PixiEditor.Layer.OpacitySliderDragEnded}"
+                                SetOpacity="{xaml:Command PixiEditor.Layer.OpacitySliderSet, UseProvided=True}"
+                                ValueFromSlider="{Binding ElementName=opacitySlider, Path=Value, Mode=TwoWay}" />
+                    </Interaction.Behaviors>
+                </Slider>
+            </DockPanel>
+        </StackPanel>
+        <Separator Grid.Row="1" Margin="0,-12, 0, 0" BorderBrush="{DynamicResource ThemeAccentBrush2}" BorderThickness="2" />
+        <DockPanel LastChildFill="True" Grid.Row="2" Margin="0, -12, 0, 0">
+            <layers:ReferenceLayer
+                DockPanel.Dock="Bottom"
+                Document="{Binding Path=ActiveDocument, ElementName=layersManager}"
+                IsVisible="{Binding Path=ActiveDocument, ElementName=layersManager, Converter={converters1:NotNullToVisibilityConverter}}"
+                Background="{DynamicResource ThemeBackgroundBrush}"
+                Grid.Row="3" VerticalAlignment="Bottom"/>
+            <TreeView ItemContainerTheme="{StaticResource TreeViewItemTheme}" DockPanel.Dock="Top" Name="treeView"
+                      ItemsSource="{Binding ActiveDocument.StructureRoot.Children, ElementName=layersManager}">
+                <TreeView.ItemsPanel>
+                    <ItemsPanelTemplate>
+                        <ui:ReversedOrderStackPanel />
+                    </ItemsPanelTemplate>
+                </TreeView.ItemsPanel>
+                <TreeView.Resources>
+                    <!--<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}" Color="Transparent"/>
+                    <SolidColorBrush x:Key="{x:Static SystemColors.InactiveSelectionHighlightBrushKey}" Color="Transparent" />-->
+                    <TreeDataTemplate x:Key="TreeDataTemplate" DataType="{x:Type docVm:FolderViewModel}" ItemsSource="{Binding Children}">
+                        <layers:FolderControl
+                            Folder="{Binding}"
+                            Manager="{Binding ElementName=layersManager}"
+                            PointerPressed="FolderControl_MouseDown"
+                            PointerReleased="FolderControl_MouseUp"/>
+                    </TreeDataTemplate>
+                </TreeView.Resources>
+                <TreeView.DataTemplates>
+                    <DataTemplate DataType="{x:Type docVm:LayerViewModel}">
+                        <layers:LayerControl
+                            Layer="{Binding}"
+                            Manager="{Binding ElementName=layersManager}"
+                            PointerPressed="LayerControl_MouseDown"
+                            PointerReleased="LayerControl_MouseUp"/>
+                    </DataTemplate>
+                </TreeView.DataTemplates>
+            </TreeView>
+            <Border Name="dropBorder" DragDrop.AllowDrop="True" Background="Transparent" BorderThickness="0, 5, 0, 0"></Border>
+        </DockPanel>
+    </Grid>
+</UserControl>

+ 248 - 0
src/PixiEditor.AvaloniaUI/Views/Layers/LayersManager.axaml.cs

@@ -0,0 +1,248 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using Avalonia.Threading;
+using Hardware.Info;
+using PixiEditor.AvaloniaUI.Helpers;
+using PixiEditor.AvaloniaUI.Models.Controllers;
+using PixiEditor.AvaloniaUI.Models.Layers;
+using PixiEditor.AvaloniaUI.ViewModels;
+using PixiEditor.AvaloniaUI.ViewModels.Document;
+
+namespace PixiEditor.AvaloniaUI.Views.Layers;
+#nullable enable
+internal partial class LayersManager : UserControl
+{
+    public static readonly StyledProperty<DocumentViewModel> ActiveDocumentProperty = AvaloniaProperty.Register<LayersManager, DocumentViewModel>(
+        nameof(ActiveDocument));
+
+    public DocumentViewModel ActiveDocument
+    {
+        get => GetValue(ActiveDocumentProperty);
+        set => SetValue(ActiveDocumentProperty, value);
+    }
+
+    private readonly IBrush? highlightColor;
+    public LayersManager()
+    {
+        InitializeComponent();
+        numberInput.OnScrollAction = () => NumberInput_LostFocus(null, null);
+        highlightColor = (Brush?)App.Current.Resources["SoftSelectedLayerColor"];
+    }
+
+    private void LayerControl_MouseDown(object sender, PointerPressedEventArgs e)
+    {
+        LayerControl control = (LayerControl)sender;
+        if (e.GetMouseButton(this) == MouseButton.Left)
+        {
+            HandleMouseDown(control.Layer, e);
+            e.Pointer.Capture(control);
+        }
+        else
+        {
+            if (control.Layer is not null && control.Layer.Selection == StructureMemberSelectionType.None)
+            {
+                control.Layer.Document.Operations.SetSelectedMember(control.Layer.GuidValue);
+                control.Layer.Document.Operations.ClearSoftSelectedMembers();
+            }
+        }
+    }
+    
+    public void LayerControl_MouseMove(PointerEventArgs e)
+    {
+        if (e is null)
+            return;
+
+        bool isLeftPressed = e.GetCurrentPoint(this).Properties.IsLeftButtonPressed;
+
+        if (e.Source is LayerControl container && isLeftPressed && Equals(e.Pointer.Captured, container))
+        {
+            DataObject data = new();
+            //TODO: Check what dataformat to use
+            data.Set(DataFormats.Text, container);
+            Dispatcher.UIThread.InvokeAsync(() => DragDrop.DoDragDrop(e, data, DragDropEffects.Move));
+        }
+    }
+
+    private void LayerControl_MouseUp(object sender, PointerReleasedEventArgs e)
+    {
+        if (sender is not LayerControl)
+            return;
+
+        e.Pointer.Capture(null);
+    }
+    
+    private void FolderControl_MouseDown(object sender, PointerPressedEventArgs e)
+    {
+        AvaloniaUI.Views.Layers.FolderControl control = (AvaloniaUI.Views.Layers.FolderControl)sender;
+        if (e.GetMouseButton(control) == MouseButton.Left)
+        {
+            HandleMouseDown(control.Folder, e);
+            e.Pointer.Capture(control);
+        }
+        else
+        {
+            if (control.Folder is not null && control.Folder.Selection == StructureMemberSelectionType.None)
+            {
+                control.Folder.Document.Operations.SetSelectedMember(control.Folder.GuidValue);
+                control.Folder.Document.Operations.ClearSoftSelectedMembers();
+            }
+        }
+    }
+
+    public void FolderControl_MouseMove(PointerEventArgs e)
+    {
+        if (e is null)
+            return;
+
+        bool isLeftPressed = e.GetCurrentPoint(this).Properties.IsLeftButtonPressed;
+        if (e.Source is AvaloniaUI.Views.Layers.FolderControl container &&
+            isLeftPressed && Equals(e.Pointer.Captured, container))
+        {
+            DataObject data = new();
+            data.Set(DataFormats.Text, container);
+            Dispatcher.UIThread.InvokeAsync(() => DragDrop.DoDragDrop(e, data, DragDropEffects.Move));
+        }
+    }
+    
+    private void FolderControl_MouseUp(object sender, PointerReleasedEventArgs e)
+    {
+        if (sender is not AvaloniaUI.Views.Layers.FolderControl folderControl)
+            return;
+
+        e.Pointer.Capture(null);
+    }
+
+    private void NumberInput_LostFocus(object? sender, RoutedEventArgs? e)
+    {
+        if (ActiveDocument?.SelectedStructureMember is null)
+            return;
+        //ActiveDocument.SetMemberOpacity(ActiveDocument.SelectedStructureMember.GuidValue, numberInput.Value / 100f);
+
+        // does anyone know why this is here?
+        ShortcutController.UnblockShortcutExecutionAll();
+    }
+
+    private void Grid_Drop(object sender, DragEventArgs e)
+    {
+        ViewModelMain.Current.ActionDisplays[nameof(LayersManager)] = null;
+        
+        if (ActiveDocument == null)
+        {
+            return;
+        }
+
+        dropBorder.BorderBrush = Brushes.Transparent;
+        Guid? droppedGuid = LayerControl.ExtractMemberGuid(e.Data);
+
+        if (droppedGuid is not null && ActiveDocument is not null)
+        {
+            ActiveDocument.Operations.MoveStructureMember((Guid)droppedGuid,
+                ActiveDocument.StructureRoot.Children[0].GuidValue, StructureMemberPlacement.Below);
+            e.Handled = true;
+        }
+
+        if (ClipboardController.TryPaste(ActiveDocument, (DataObject)e.Data, true))
+        {
+            e.Handled = true;
+        }
+    }
+
+    private void Grid_DragEnter(object sender, DragEventArgs e)
+    {
+        if (ActiveDocument == null)
+        {
+            return;
+        }
+        
+        var member = LayerControl.ExtractMemberGuid(e.Data);
+
+        if (member == null)
+        {
+            if (!ClipboardController.IsImage((DataObject)e.Data))
+            {
+                return;
+            }
+
+            ViewModelMain.Current.ActionDisplays[nameof(LayersManager)] = "IMPORT_AS_NEW_LAYER";
+            e.DragEffects = DragDropEffects.Copy;
+        }
+        else
+        {
+            e.DragEffects = DragDropEffects.Move;
+        }
+        
+        ((Border)sender).BorderBrush = highlightColor;
+        e.Handled = true;
+    }
+
+    private void Grid_DragLeave(object sender, DragEventArgs e)
+    {
+        ViewModelMain.Current.ActionDisplays[nameof(LayersManager)] = null;
+        ((Border)sender).BorderBrush = Brushes.Transparent;
+    }
+
+    private static int TraverseRange(Guid bound1, Guid bound2, FolderViewModel root, Action<StructureMemberViewModel> action, int matches = 0)
+    {
+        if (matches == 2)
+            return 2;
+        foreach (StructureMemberViewModel child in root.Children)
+        {
+            if (child is FolderViewModel innerFolder)
+            {
+                matches = TraverseRange(bound1, bound2, innerFolder, action, matches);
+            }
+            if (matches == 1)
+                action(child);
+            if (matches == 2)
+                return 2;
+            if (child.GuidValue == bound1 || child.GuidValue == bound2)
+            {
+                matches++;
+                if (matches == 1)
+                    action(child);
+                if (matches == 2)
+                    return 2;
+            }
+        }
+        return matches;
+    }
+
+    private void HandleMouseDown(StructureMemberViewModel memberVM, PointerPressedEventArgs pointerPressedEventArgs)
+    {
+        if (ActiveDocument is null)
+            return;
+
+        if (pointerPressedEventArgs.KeyModifiers.HasFlag(KeyModifiers.Control))
+        {
+            if (memberVM.Selection == StructureMemberSelectionType.Hard)
+                return;
+            else if (memberVM.Selection == StructureMemberSelectionType.Soft)
+                ActiveDocument.Operations.RemoveSoftSelectedMember(memberVM.GuidValue);
+            else if (memberVM.Selection == StructureMemberSelectionType.None)
+                ActiveDocument.Operations.AddSoftSelectedMember(memberVM.GuidValue);
+        }
+        else if (pointerPressedEventArgs.KeyModifiers.HasFlag(KeyModifiers.Shift))
+        {
+            if (ActiveDocument.SelectedStructureMember is null || ActiveDocument.SelectedStructureMember.GuidValue == memberVM.GuidValue)
+                return;
+            ActiveDocument.Operations.ClearSoftSelectedMembers();
+            TraverseRange(
+                ActiveDocument.SelectedStructureMember.GuidValue,
+                memberVM.GuidValue,
+                ActiveDocument.StructureRoot,
+                static member =>
+                {
+                    if (member.Selection == StructureMemberSelectionType.None)
+                        member.Document.Operations.AddSoftSelectedMember(member.GuidValue);
+                });
+        }
+        else
+        {
+            ActiveDocument.Operations.SetSelectedMember(memberVM.GuidValue);
+            ActiveDocument.Operations.ClearSoftSelectedMembers();
+        }
+    }
+}

+ 158 - 0
src/PixiEditor.AvaloniaUI/Views/Layers/ReferenceLayer.axaml

@@ -0,0 +1,158 @@
+<UserControl x:Class="PixiEditor.AvaloniaUI.Views.Layers.ReferenceLayer"
+             x:ClassModifier="internal"
+             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:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
+             xmlns:converters="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters"
+             xmlns:behaviours="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Behaviours"
+             xmlns:cmds="clr-namespace:PixiEditor.AvaloniaUI.Models.Commands.XAML"
+             mc:Ignorable="d"
+             d:DesignHeight="60" d:DesignWidth="350" VerticalAlignment="Center" x:Name="uc">
+    <Border BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderThickness="0 2 0 0" MinWidth="60"
+            Focusable="True" DragDrop.AllowDrop="True">
+        <Interaction.Behaviors>
+            <behaviours:ClearFocusOnClickBehavior/>
+        </Interaction.Behaviors>
+        <DockPanel Background="Transparent">
+            <CheckBox Focusable="False" ZIndex="10" Name="visibilityCheckbox" Margin="0,0,5,0" Height="16" HorizontalAlignment="Right" DockPanel.Dock="Right">
+              <!--TODO: Recreate this animation-->
+                <!--<CheckBox.Triggers>
+                    <EventTrigger RoutedEvent="CheckBox.Checked">
+                        <BeginStoryboard>
+                            <Storyboard>
+                                <DoubleAnimation Storyboard.TargetName="mainDockPanel" Storyboard.TargetProperty="Height" From="40" To="0" Duration="0:0:0.15"/>
+                            </Storyboard>
+                        </BeginStoryboard>
+                    </EventTrigger>
+                    <EventTrigger RoutedEvent="CheckBox.Unchecked">
+                        <BeginStoryboard>
+                            <Storyboard>
+                                <DoubleAnimation Storyboard.TargetName="mainDockPanel" Storyboard.TargetProperty="Height" From="0" To="40" Duration="0:0:0.15"/>
+                            </Storyboard>
+                        </BeginStoryboard>
+                    </EventTrigger>
+
+                </CheckBox.Triggers>-->
+                <CheckBox.Template>
+                    <ControlTemplate TargetType="{x:Type CheckBox}">
+                        <StackPanel Orientation="Horizontal" Focusable="False">
+                            <Image Focusable="False" Width="14" Cursor="Hand" x:Name="checkboxImage" Source="/Images/ChevronDown.png">
+                                <Image.RenderTransform>
+                                    <RotateTransform Angle="0"/>
+                                </Image.RenderTransform>
+                            </Image>
+                            <ContentPresenter Focusable="False"/>
+                        </StackPanel>
+                        <!--<ControlTemplate.Triggers>
+                            <Trigger Property="IsChecked" Value="True">
+                                <Setter TargetName="checkboxImage" Property="RenderTransform">
+                                    <Setter.Value>
+                                        <RotateTransform Angle="180" CenterX="7" CenterY="4"/>
+                                    </Setter.Value>
+                                </Setter>
+                            </Trigger>
+                        </ControlTemplate.Triggers>-->
+                    </ControlTemplate>
+                    <!--<CheckBox.Styles>
+                        <Style Selector="CheckBox:checked #checkboxImage">
+                            <Setter Property="RenderTransform">
+                                <Setter.Value>
+                                    <RotateTransform Angle="180" CenterX="7" CenterY="4"/>
+                                </Setter.Value>
+                            </Setter>
+                        </Style>
+                    </CheckBox.Styles>-->
+                </CheckBox.Template>
+            </CheckBox>
+
+            <Grid Height="40" x:Name="mainDockPanel">
+                <Grid 
+                    IsVisible="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NullToVisibilityConverter}}"
+                    Panel.ZIndex="5">
+                    <Grid Cursor="Hand" IsVisible="{Binding ElementName=visibilityCheckbox, Path=!IsChecked}" Background="Transparent">
+                        <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" DockPanel.Dock="Left">
+                            <Image Margin="5 0 5 0" Width="20" Source="/Images/Add-reference.png"
+                               IsVisible="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NullToVisibilityConverter}}"/>
+
+                            <TextBlock IsEnabled="{Binding ElementName=uc, Path=IsEnabled}" 
+                                        Margin="0 0 5 0" Foreground="White" 
+                                        FontSize="15" VerticalAlignment="Center" ui:Translator.Key="ADD_REFERENCE_LAYER"/>
+                        </StackPanel>
+                        <Interaction.Behaviors>
+                            <EventTriggerBehavior EventName="PointerReleased">
+                                <InvokeCommandAction Command="{cmds:Command PixiEditor.Layer.ImportReferenceLayer}"
+                                        PassEventArgsToCommand="True"/>
+                            </EventTriggerBehavior>
+                        </Interaction.Behaviors>
+                    </Grid>
+                </Grid>
+
+                <DockPanel Grid.Row="0" VerticalAlignment="Center" Height="40"
+                           IsVisible="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap, ElementName=uc, Converter={converters:NotNullToVisibilityConverter}}" >
+                    <Grid Height="16" Name="layerVisibilityCheckboxGrid" DockPanel.Dock="Left" Margin="10,0,5,0">
+                        <CheckBox
+                            Classes="ImageCheckBox"
+                            VerticalAlignment="Center"
+                            IsThreeState="False" HorizontalAlignment="Center"
+                            IsChecked="{Binding Path=Document.ReferenceLayerViewModel.IsVisibleBindable, Mode=TwoWay, ElementName=uc}"/>
+                    </Grid>
+                    <Button Cursor="Hand" DockPanel.Dock="Left"
+                            Command="{cmds:Command PixiEditor.Layer.ToggleReferenceLayerTopMost}"
+                            Classes="ImageButtonStyle"
+                            ToolTip.Tip="{Binding Document.ReferenceLayerViewModel.IsTopMost, ElementName=uc, Converter={converters:BoolToValueConverter FalseValue='localized:PUT_REFERENCE_LAYER_ABOVE', TrueValue='localized:PUT_REFERENCE_LAYER_BELOW'}}"
+                            RenderOptions.BitmapInterpolationMode="HighQuality"
+                            Width="20" Height="20" HorizontalAlignment="Right">
+                        <Button.Background>
+                            <ImageBrush Source="{Binding Document.ReferenceLayerViewModel.IsTopMost, ElementName=uc, Converter={converters:BoolToValueConverter FalseValue='Images/ReferenceLayerBelow.png', TrueValue='Images/ReferenceLayerAbove.png'}}"/>
+                        </Button.Background>
+                    </Button>
+                    <Border 
+                        HorizontalAlignment="Left" DockPanel.Dock="Left"
+                        Width="30" Height="30"
+                        BorderThickness="1" 
+                        BorderBrush="Black"
+                        Background="{DynamicResource ThemeBackgroundBrush}"
+                        Margin="5, 0, 10, 0">
+                        <Image Source="{Binding Document.ReferenceLayerViewModel.ReferenceBitmap,ElementName=uc}" Stretch="Uniform" Width="26" Height="26"
+                               RenderOptions.BitmapInterpolationMode="HighQuality" IsHitTestVisible="False"/>
+                    </Border>
+                    <Button Cursor="Hand" Grid.Column="1" DockPanel.Dock="Right"
+                                Command="{cmds:Command PixiEditor.Layer.DeleteReferenceLayer}"
+                                Classes="ImageButtonStyle"
+                                RenderOptions.BitmapInterpolationMode="HighQuality"
+                                Margin="3,0,5,0"
+                                Width="20" Height="20" HorizontalAlignment="Right">
+                        <Button.Background>
+                            <ImageBrush Source="/Images/Trash.png"/>
+                        </Button.Background>
+                    </Button>
+                    <Button Cursor="Hand" DockPanel.Dock="Right"
+                            Command="{cmds:Command PixiEditor.Layer.ResetReferenceLayerPosition}"
+                            Classes="ImageButtonStyle"
+                            ui:Translator.TooltipKey="RESET_REFERENCE_LAYER_POS"
+                            RenderOptions.BitmapInterpolationMode="HighQuality"
+                            Width="20" Height="20" HorizontalAlignment="Right">
+                        <Button.Background>
+                            <ImageBrush Source="/Images/Layout.png"/>
+                        </Button.Background>
+                    </Button>
+                    <Button Cursor="Hand" DockPanel.Dock="Right"
+                            Command="{cmds:Command PixiEditor.Layer.TransformReferenceLayer}"
+                            Classes="ImageButtonStyle"
+                            ui:Translator.TooltipKey="TRANSFORM_REFERENCE_LAYER"
+                            RenderOptions.BitmapInterpolationMode="HighQuality"
+                            Width="20" Height="20" HorizontalAlignment="Right">
+                        <Button.Background>
+                            <ImageBrush Source="/Images/Crop.png"/>
+                        </Button.Background>
+                    </Button>
+                    <TextBlock IsEnabled="{Binding ElementName=uc, Path=IsEnabled}" HorizontalAlignment="Center"
+                               Margin="0 0 5 0" Foreground="White" 
+                               FontSize="15" VerticalAlignment="Center" ui:Translator.Key="REFERENCE"/>
+                </DockPanel>
+            </Grid>
+        </DockPanel>
+    </Border>
+</UserControl>

+ 57 - 0
src/PixiEditor.AvaloniaUI/Views/Layers/ReferenceLayer.axaml.cs

@@ -0,0 +1,57 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using PixiEditor.AvaloniaUI.Models.Commands;
+using PixiEditor.AvaloniaUI.Models.Commands.Commands;
+using PixiEditor.AvaloniaUI.ViewModels;
+using PixiEditor.AvaloniaUI.ViewModels.Document;
+
+namespace PixiEditor.AvaloniaUI.Views.Layers;
+
+#nullable enable
+internal partial class ReferenceLayer : UserControl
+{
+    private Command command;
+
+    public static readonly StyledProperty<DocumentViewModel> DocumentProperty = AvaloniaProperty.Register<ReferenceLayer, DocumentViewModel>(
+        nameof(Document));
+
+    public DocumentViewModel Document
+    {
+        get => GetValue(DocumentProperty);
+        set => SetValue(DocumentProperty, value);
+    }
+
+    public ReferenceLayer()
+    {
+        command = CommandController.Current.Commands["PixiEditor.Clipboard.PasteReferenceLayer"];
+        InitializeComponent();
+    }
+
+    private void ReferenceLayer_DragEnter(object sender, DragEventArgs e)
+    {
+        if (!command.Methods.CanExecute(e.Data))
+        {
+            return;
+        }
+
+        ViewModelMain.Current.ActionDisplays[nameof(ReferenceLayer_Drop)] = "IMPORT_AS_REFERENCE_LAYER";
+        e.Handled = true;
+    }
+
+    private void ReferenceLayer_DragLeave(object sender, DragEventArgs e)
+    {
+        ViewModelMain.Current.ActionDisplays[nameof(ReferenceLayer_Drop)] = null;
+    }
+
+    private void ReferenceLayer_Drop(object sender, DragEventArgs e)
+    {
+        if (!command.Methods.CanExecute(e.Data))
+        {
+            return;
+        }
+
+        command.Methods.Execute(e.Data);
+        e.Handled = true;
+    }
+}

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/Main/Viewport.axaml

@@ -13,7 +13,6 @@
     xmlns:ui="clr-namespace:PixiEditor.Extensions.UI;assembly=PixiEditor.Extensions"
     xmlns:xaml="clr-namespace:PixiEditor.AvaloniaUI.Models.Commands.XAML"
     xmlns:zoombox="clr-namespace:PixiEditor.Zoombox;assembly=PixiEditor.Zoombox"
-    xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
     xmlns:subviews="clr-namespace:PixiEditor.AvaloniaUI.ViewModels.Document"
     xmlns:brushShapeOverlay="clr-namespace:PixiEditor.AvaloniaUI.Views.Overlays.BrushShapeOverlay"
     xmlns:viewModels="clr-namespace:PixiEditor.AvaloniaUI.ViewModels"
@@ -23,6 +22,7 @@
     xmlns:transformOverlay="clr-namespace:PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay"
     xmlns:main="clr-namespace:PixiEditor.AvaloniaUI.Views.Main"
     xmlns:viewModels1="clr-namespace:PixiEditor.ViewModels"
+    xmlns:converters="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters"
     mc:Ignorable="d"
     x:Name="vpUc"
     d:DesignHeight="450"

+ 2 - 1
src/PixiEditor.AvaloniaUI/Views/MainView.axaml

@@ -8,10 +8,10 @@
              xmlns:xaml="clr-namespace:PixiEditor.AvaloniaUI.Models.Commands.XAML"
              xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
              xmlns:viewModels="clr-namespace:PixiEditor.ViewModels"
-             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
              xmlns:palettes="clr-namespace:PixiEditor.Views.UserControls.Palettes"
              xmlns:dock="clr-namespace:PixiEditor.AvaloniaUI.ViewModels.Dock"
              xmlns:zoombox="clr-namespace:PixiEditor.Zoombox;assembly=PixiEditor.Zoombox"
+             xmlns:layers="clr-namespace:PixiEditor.AvaloniaUI.Views.Layers"
              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
              x:Class="PixiEditor.AvaloniaUI.Views.MainView"
              x:DataType="viewModels1:ViewModelMain" Background="{DynamicResource ThemeBackgroundBrush}">
@@ -32,5 +32,6 @@
                            VerticalAlignment="Center"
                            Tools="{Binding Path=ToolsSubViewModel.ToolSet}"/>
         <DockControl Grid.Row="1" Layout="{Binding LayoutDockSubViewModel.Layout}"/>
+        <layers:LayersManager Grid.Row="1" Width="200" Height="400" ActiveDocument="{Binding Path=DocumentManagerSubViewModel.ActiveDocument}"/>
     </Grid>
 </UserControl>

+ 2 - 1
src/PixiEditor.AvaloniaUI/Views/Overlays/SymmetryOverlay/SymmetryOverlay.cs

@@ -8,6 +8,7 @@ using Avalonia.Media;
 using Hardware.Info;
 using PixiEditor.AvaloniaUI.Helpers;
 using PixiEditor.AvaloniaUI.Models.Controllers.InputDevice;
+using PixiEditor.AvaloniaUI.Views.Overlays.Handles;
 using PixiEditor.ChangeableDocument.Enums;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Extensions.Common.Localization;
@@ -99,7 +100,7 @@ internal class SymmetryOverlay : Overlay
     }
 
     private const double HandleSize = 12;
-    private Geometry handleGeometry /*= GetHandleGeometry("MarkerHandle")*/;
+    private Geometry handleGeometry = Handle.GetHandleGeometry("MarkerHandle");
 
     private const double DashWidth = 10.0;
     const int RulerOffset = -35;

+ 1 - 1
src/PixiEditor.AvaloniaUI/Views/Palettes/PaletteColorControl.axaml

@@ -4,8 +4,8 @@
              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:converters="clr-namespace:PixiEditor.Helpers.Converters"
              xmlns:palettes="clr-namespace:PixiEditor.Views.UserControls.Palettes"
+             xmlns:converters="clr-namespace:PixiEditor.AvaloniaUI.Helpers.Converters"
              mc:Ignorable="d" 
              d:DesignHeight="45" d:DesignWidth="45" 
              x:Name="uc" 

+ 42 - 0
src/PixiEditor.AvaloniaUI/Views/Panels/ReversedOrderStackPanel.cs

@@ -0,0 +1,42 @@
+using System.Linq;
+using Avalonia;
+using Avalonia.Controls;
+
+namespace PixiEditor.Helpers.UI;
+
+internal class ReversedOrderStackPanel : StackPanel
+{
+    protected override Size ArrangeOverride(Size arrangeSize)
+    {
+        bool fHorizontal = Orientation == Avalonia.Layout.Orientation.Horizontal;
+        Rect rcChild = new Rect(arrangeSize);
+        double previousChildSize = 0.0;
+
+        System.Collections.Generic.IEnumerable<Control> children = Children.Reverse();
+        foreach (Control child in children)
+        {
+            if (child == null)
+            {
+                continue;
+            }
+
+            if (fHorizontal)
+            {
+                rcChild = rcChild.WithX(rcChild.X + previousChildSize);
+                previousChildSize = child.DesiredSize.Width;
+                rcChild = rcChild.WithWidth(previousChildSize).WithHeight(Math.Max(arrangeSize.Height, child.DesiredSize.Height));
+            }
+            else
+            {
+                rcChild = rcChild.WithY(rcChild.Y + previousChildSize);
+                previousChildSize = child.DesiredSize.Height;
+                rcChild = rcChild.WithHeight(previousChildSize)
+                    .WithWidth(Math.Max(arrangeSize.Width, child.DesiredSize.Width));
+            }
+
+            child.Arrange(rcChild);
+        }
+
+        return arrangeSize;
+    }
+}