Sfoglia il codice sorgente

Merge pull request #202 from PixiEditor/reference-layer

Reference layer
Krzysztof Krysiński 4 anni fa
parent
commit
df7d7b39a7
32 ha cambiato i file con 707 aggiunte e 344 eliminazioni
  1. 1 1
      PixiEditor/Helpers/Converters/EmptyStringToVisibilityConverter.cs
  2. 2 2
      PixiEditor/Helpers/Converters/NotNullToVisibilityConverter.cs
  3. 35 0
      PixiEditor/Helpers/Converters/NullToVisibilityConverter.cs
  4. BIN
      PixiEditor/Images/ChevronDown.png
  5. 9 0
      PixiEditor/Models/DataHolders/Document/Document.cs
  6. 2 0
      PixiEditor/PixiEditor.csproj
  7. 1 0
      PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs
  8. 2 2
      PixiEditor/Views/Dialogs/HelloTherePopup.xaml
  9. 2 2
      PixiEditor/Views/Dialogs/ShortcutPopup.xaml
  10. 6 6
      PixiEditor/Views/MainWindow.xaml
  11. 41 0
      PixiEditor/Views/UserControls/AvalonDockWindows/ReferenceLayerWindow.xaml
  12. 82 0
      PixiEditor/Views/UserControls/AvalonDockWindows/ReferenceLayerWindow.xaml.cs
  13. 5 5
      PixiEditor/Views/UserControls/DiscordRPPreview.xaml
  14. 6 0
      PixiEditor/Views/UserControls/DrawingViewPort.xaml
  15. 1 1
      PixiEditor/Views/UserControls/Layers/LayerGroupControl.xaml
  16. 37 37
      PixiEditor/Views/UserControls/Layers/LayerGroupControl.xaml.cs
  17. 1 1
      PixiEditor/Views/UserControls/Layers/LayerItem.xaml
  18. 15 15
      PixiEditor/Views/UserControls/Layers/LayerItem.xaml.cs
  19. 2 2
      PixiEditor/Views/UserControls/Layers/LayerStructureItemContainer.xaml
  20. 1 1
      PixiEditor/Views/UserControls/Layers/LayerStructureItemContainer.xaml.cs
  21. 6 6
      PixiEditor/Views/UserControls/Layers/LayersManager.xaml
  22. 251 251
      PixiEditor/Views/UserControls/Layers/LayersManager.xaml.cs
  23. 1 1
      PixiEditor/Views/UserControls/Layers/RawLayersViewer.xaml
  24. 1 1
      PixiEditor/Views/UserControls/Layers/RawLayersViewer.xaml.cs
  25. 93 0
      PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml
  26. 70 0
      PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml.cs
  27. 14 4
      PixiEditor/Views/UserControls/PrependTextBlock.xaml
  28. 12 0
      PixiEditor/Views/UserControls/PrependTextBlock.xaml.cs
  29. 2 2
      PixiEditor/Views/UserControls/PreviewWindow.xaml
  30. 1 1
      PixiEditorTests/ModelsTests/DataHoldersTests/DocumentTests.cs
  31. 1 1
      PixiEditorTests/TestHelpers.cs
  32. 4 2
      PixiEditorTests/ViewModelsTests/ViewModelMainTests.cs

+ 1 - 1
PixiEditor/Helpers/Converters/EmptyStringToVisibiltyConverter.cs → PixiEditor/Helpers/Converters/EmptyStringToVisibilityConverter.cs

@@ -9,7 +9,7 @@ using System.Windows.Data;
 
 namespace PixiEditor.Helpers.Converters
 {
-    class EmptyStringToVisibiltyConverter : IValueConverter
+    public class EmptyStringToVisibilityConverter : IValueConverter
     {
         public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {

+ 2 - 2
PixiEditor/Helpers/Converters/NotNullToVisibiltyConverter.cs → PixiEditor/Helpers/Converters/NotNullToVisibilityConverter.cs

@@ -6,7 +6,7 @@ using System.Windows.Data;
 namespace PixiEditor.Helpers.Converters
 {
     [ValueConversion(typeof(object), typeof(Visibility))]
-    class NotNullToVisibiltyConverter : IValueConverter
+    public class NotNullToVisibilityConverter : IValueConverter
     {
         public bool Inverted { get; set; }
 
@@ -19,7 +19,7 @@ namespace PixiEditor.Helpers.Converters
                 isNull = !isNull;
             }
 
-            return isNull ? Visibility.Visible : Visibility.Hidden;
+            return isNull ? Visibility.Visible : Visibility.Collapsed;
         }
 
         public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)

+ 35 - 0
PixiEditor/Helpers/Converters/NullToVisibilityConverter.cs

@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Data;
+using System.Windows.Markup;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class NullToVisibilityConverter : MarkupExtension, IValueConverter
+    {
+        private static NullToVisibilityConverter converter;
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value != null ? Visibility.Collapsed : Visibility.Visible;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+
+        public override object ProvideValue(IServiceProvider serviceProvider)
+        {
+            if(converter == null)
+            {
+                converter = new NullToVisibilityConverter();
+            }
+            return converter;
+        }
+    }
+}

BIN
PixiEditor/Images/ChevronDown.png


+ 9 - 0
PixiEditor/Models/DataHolders/Document/Document.cs

@@ -14,6 +14,7 @@ using System.IO;
 using System.Linq;
 using System.Windows;
 using System.Windows.Media;
+using System.Windows.Media.Imaging;
 
 namespace PixiEditor.Models.DataHolders
 {
@@ -32,6 +33,14 @@ namespace PixiEditor.Models.DataHolders
             }
         }
 
+        private Layer referenceLayer;
+
+        public Layer ReferenceLayer
+        {
+            get => referenceLayer;
+            set => SetProperty(ref referenceLayer, value);
+        }
+
         public string Name
         {
             get => (string.IsNullOrEmpty(DocumentFilePath) ? "Untitled" : Path.GetFileName(DocumentFilePath))

+ 2 - 0
PixiEditor/PixiEditor.csproj

@@ -115,6 +115,7 @@
   <ItemGroup>
     <None Remove="Images\AnchorDot.png" />
     <None Remove="Images\CheckerTile.png" />
+    <None Remove="Images\ChevronDown.png" />
     <None Remove="Images\DiagonalRed.png" />
     <None Remove="Images\Eye-off.png" />
     <None Remove="Images\Eye.png" />
@@ -161,6 +162,7 @@
   <ItemGroup>
     <Resource Include="Images\AnchorDot.png" />
     <Resource Include="Images\CheckerTile.png" />
+    <Resource Include="Images\ChevronDown.png" />
     <Resource Include="Images\DiagonalRed.png" />
     <Resource Include="Images\FloodFillImage.png" />
     <Resource Include="Images\CircleImage.png" />

+ 1 - 0
PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs

@@ -2,6 +2,7 @@
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Views.UserControls;
+using PixiEditor.Views.UserControls.Layers;
 using System;
 using System.Linq;
 using System.Windows.Input;

+ 2 - 2
PixiEditor/Views/Dialogs/HelloTherePopup.xaml

@@ -11,7 +11,7 @@
         WindowStyle="None" WindowStartupLocation="CenterScreen">
 
     <Window.Resources>
-        <converters:EqualityBoolToVisibilityConverter x:Key="EqualBoolToVisibilty"/>
+        <converters:EqualityBoolToVisibilityConverter x:Key="EqualityBoolToVisibilityConverter"/>
         <converters:FileExtensionToColorConverter x:Key="FileExtensionToColorConverter"/>
 
         <Style TargetType="TextBlock">
@@ -76,7 +76,7 @@
                     <TextBlock Margin="0,12.5,0,0" Foreground="LightGray" HorizontalAlignment="Center">
                         <TextBlock.Visibility>
                             <Binding Path="RecentlyOpened.Count"
-                                     Converter="{StaticResource EqualBoolToVisibilty}">
+                                     Converter="{StaticResource EqualityBoolToVisibilityConverter}">
                                 <Binding.ConverterParameter>
                                     <sys:Int32/>
                                 </Binding.ConverterParameter>

+ 2 - 2
PixiEditor/Views/Dialogs/ShortcutPopup.xaml

@@ -12,7 +12,7 @@
         MinHeight="400" MinWidth="350" Topmost="{Binding IsTopmost}">
     <Window.Resources>
         <converters:KeyToStringConverter x:Key="KeyToStringConverter"/>
-        <BoolToVisibilityConverter x:Key="BoolToVisibilty"/>
+        <BoolToVisibilityConverter x:Key="BoolToVisibility"/>
         
         <Style TargetType="Border" x:Key="KeyBorder">
             <Setter Property="BorderThickness" Value="1"/>
@@ -79,7 +79,7 @@
                 <ItemsControl ItemsSource="{Binding Controller.ShortcutGroups}" Background="Transparent">
                     <ItemsControl.ItemTemplate>
                         <DataTemplate DataType="{x:Type shortcuts:ShortcutGroup}">
-                            <StackPanel Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisibilty}}">
+                            <StackPanel Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisibility}}">
                                 <TextBlock Text="{Binding Name}" Foreground="White" FontSize="18" FontWeight="Medium" Margin="10,8,0,0"/>
                                 <ItemsControl ItemsSource="{Binding Shortcuts}">
                                     <ItemsControl.ItemTemplate>

+ 6 - 6
PixiEditor/Views/MainWindow.xaml

@@ -13,7 +13,7 @@
         xmlns:cmd="http://www.galasoft.ch/mvvmlight" 
         xmlns:avalondock="https://github.com/Dirkster99/AvalonDock"
         xmlns:colorpicker="clr-namespace:ColorPicker;assembly=ColorPicker" xmlns:usercontrols="clr-namespace:PixiEditor.Views.UserControls" xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours" 
-        xmlns:avalonDockTheme="clr-namespace:PixiEditor.Styles.AvalonDock" d:DataContext="{d:DesignInstance Type=vm:ViewModelMain}"
+        xmlns:avalonDockTheme="clr-namespace:PixiEditor.Styles.AvalonDock" xmlns:layerUserControls="clr-namespace:PixiEditor.Views.UserControls.Layers" d:DataContext="{d:DesignInstance Type=vm:ViewModelMain}"
         mc:Ignorable="d" WindowStyle="None" Initialized="MainWindow_Initialized"
         Title="PixiEditor" Name="mainWindow" Height="1000" Width="1600" Background="{StaticResource MainColor}"
         WindowStartupLocation="CenterScreen" WindowState="Maximized">
@@ -331,20 +331,20 @@
                                     <LayoutAnchorable ContentId="layers" Title="Layers" CanHide="False"
                                                          CanClose="False" CanAutoHide="False"
                                                          CanDockAsTabbedDocument="True" CanFloat="True">
-                                        <usercontrols:LayersManager                                            
+                                        <layerUserControls:LayersManager                                            
                                             LayerCommandsViewModel="{Binding LayersSubViewModel}"
                                             OpacityInputEnabled="{Binding BitmapManager.ActiveDocument, 
                     Converter={StaticResource NotNullToBoolConverter}}">
-                                            <usercontrols:LayersManager.LayerTreeRoot>
+                                            <layerUserControls:LayersManager.LayerTreeRoot>
                                                 <MultiBinding Converter="{StaticResource LayersToStructuredLayersConverter}">
                                                     <Binding Path="BitmapManager.ActiveDocument.Layers" />
                                                     <Binding Path="BitmapManager.ActiveDocument.LayerStructure"/>
                                                 </MultiBinding>
-                                            </usercontrols:LayersManager.LayerTreeRoot>
-                                        </usercontrols:LayersManager>
+                                            </layerUserControls:LayersManager.LayerTreeRoot>
+                                        </layerUserControls:LayersManager>
                                     </LayoutAnchorable>
                                     <LayoutAnchorable x:Name="rawLayerAnchorable" ContentId="rawLayer" Title="Raw layers">
-                                        <usercontrols:RawLayersViewer Layers="{Binding BitmapManager.ActiveDocument.Layers}"
+                                        <layerUserControls:RawLayersViewer Layers="{Binding BitmapManager.ActiveDocument.Layers}"
                                                                       Structure="{Binding BitmapManager.ActiveDocument.LayerStructure}"/>
                                     </LayoutAnchorable>
                                 </LayoutAnchorablePane>

+ 41 - 0
PixiEditor/Views/UserControls/AvalonDockWindows/ReferenceLayerWindow.xaml

@@ -0,0 +1,41 @@
+<UserControl x:Class="PixiEditor.Views.UserControls.AvalonDockWindows.ReferenceLayerWindow"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:PixiEditor.Views.UserControls.AvalonDockWindows" xmlns:views="clr-namespace:PixiEditor.Views" xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+             mc:Ignorable="d" 
+             d:DesignHeight="200" d:DesignWidth="200"
+             Name="uc">
+    <UserControl.Resources>
+        <Style TargetType="TextBlock">
+            <Setter Property="Foreground" Value="White"/>
+        </Style>
+        <converters:NotNullToBoolConverter x:Key="NotNullToBoolConverter"/>
+    </UserControl.Resources>
+    
+    <StackPanel Margin="5">
+        <Grid>
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="Auto"/>
+                <ColumnDefinition/>
+                <ColumnDefinition Width="Auto"/>
+            </Grid.ColumnDefinitions>
+            <TextBlock Margin="0,0,5,0" VerticalAlignment="Center">Path:</TextBlock>
+            <TextBox Text="{Binding FilePath, ElementName=uc}" Grid.Column="1"
+                     Style="{StaticResource DarkTextBoxStyle}" FontSize="14"/>
+            <Button Grid.Column="2" Content="&#xE838;" VerticalAlignment="Center"
+                    Style="{StaticResource ToolSettingsGlyphButton}" Width="20"
+                    Command="{Binding OpenFilePickerCommand, ElementName=uc}"></Button>
+        </Grid>
+        <StackPanel Orientation="Horizontal" Margin="0,5,0,0">
+            <TextBlock Text="Opacity: " Foreground="White" VerticalAlignment="Center"/>
+            <views:NumberInput Min="0" Max="100" Value="{Binding LayerOpacity, ElementName=uc, Mode=TwoWay}"
+                               Width="40" Height="20" VerticalAlignment="Center"/>
+            <TextBlock Text=" %" Foreground="White" VerticalAlignment="Center"/>
+        </StackPanel>
+        <Button Command="{Binding UpdateLayerCommand, ElementName=uc}"
+                Style="{StaticResource DarkRoundButton}" FontSize="14" 
+                Height="25" Margin="0,5">Update</Button>
+    </StackPanel>
+</UserControl>

+ 82 - 0
PixiEditor/Views/UserControls/AvalonDockWindows/ReferenceLayerWindow.xaml.cs

@@ -0,0 +1,82 @@
+using Microsoft.Win32;
+using PixiEditor.Helpers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.IO;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace PixiEditor.Views.UserControls.AvalonDockWindows
+{
+    /// <summary>
+    /// Interaction logic for ReferenceLayerWindow.xaml
+    /// </summary>
+    public partial class ReferenceLayerWindow : UserControl
+    {
+        public static readonly DependencyProperty DocumentProperty =
+            DependencyProperty.Register(nameof(Document), typeof(Document), typeof(ReferenceLayerWindow));
+
+        public Document Document
+        {
+            get => (Document)GetValue(DocumentProperty);
+            set => SetValue(DocumentProperty, value);
+        }
+
+        public static readonly DependencyProperty FilePathProperty =
+            DependencyProperty.Register(nameof(FilePath), typeof(string), typeof(ReferenceLayerWindow));
+
+        public string FilePath
+        {
+            get => (string)GetValue(FilePathProperty);
+            set => SetValue(FilePathProperty, value);
+        }
+
+        public static readonly DependencyProperty LayerOpacityProperty =
+            DependencyProperty.Register(nameof(LayerOpacity), typeof(float), typeof(ReferenceLayerWindow), new PropertyMetadata(100f));
+
+        public float LayerOpacity
+        {
+            get => (float)GetValue(LayerOpacityProperty);
+            set => SetValue(LayerOpacityProperty, value);
+        }
+
+        public static readonly DependencyProperty HasDocumentProperty =
+            DependencyProperty.Register(nameof(HasDocument), typeof(bool), typeof(ReferenceLayerWindow));
+
+        public bool HasDocument
+        {
+            get => (bool)GetValue(HasDocumentProperty);
+            set => SetValue(HasDocumentProperty, value);
+        }
+
+        public RelayCommand UpdateLayerCommand { get; set; }
+
+        public RelayCommand OpenFilePickerCommand { get; set; }
+
+        public ReferenceLayerWindow()
+        {
+            UpdateLayerCommand = new RelayCommand(UpdateLayer);
+            OpenFilePickerCommand = new RelayCommand(OpenFilePicker);
+            InitializeComponent();
+        }
+
+        private void UpdateLayer(object obj)
+        {
+            Document.ReferenceLayer.LayerBitmap = Importer.ImportImage(FilePath);
+            Document.ReferenceLayer.Opacity = LayerOpacity;
+        }
+
+        private void OpenFilePicker(object obj)
+        {
+            OpenFileDialog dialog = new OpenFileDialog()
+            {
+                Filter = "PNG Files|*.png|JPEG Files|*.jpg;*.jpeg",
+                CheckFileExists = true
+            };
+
+            if ((bool)dialog.ShowDialog())
+            {
+                FilePath = dialog.FileName;
+            }
+        }
+    }
+}

+ 5 - 5
PixiEditor/Views/UserControls/DiscordRPPreview.xaml

@@ -8,8 +8,8 @@
              d:DesignHeight="280" d:DesignWidth="300"
              x:Name="uc">
     <UserControl.Resources>
-        <converters:EmptyStringToVisibiltyConverter x:Key="EmptyStringToVisibilty"/>
-        <BoolToVisibilityConverter x:Key="BoolToVisibilty"/>
+        <converters:EmptyStringToVisibilityConverter x:Key="EmptyStringToVisibility"/>
+        <BoolToVisibilityConverter x:Key="BoolToVisibility"/>
         <converters:BoolToBrushConverter x:Key="BoolToBrush"/>
         <converters:BrushTuple FirstBrush="#7289da" SecondBrush="#202225" x:Key="BackgroundBrushTuple"/>
         <converters:BrushTuple FirstBrush="White" SecondBrush="#7289da" x:Key="BotLabelTuple"/>
@@ -49,15 +49,15 @@
                         </StackPanel>
                     </StackPanel>
                 </Grid>
-                <Grid Grid.Row="1" Background="#0D000000" Visibility="{Binding ElementName=uc, Path=IsPlaying, Converter={StaticResource BoolToVisibilty}}">
+                <Grid Grid.Row="1" Background="#0D000000" Visibility="{Binding ElementName=uc, Path=IsPlaying, Converter={StaticResource BoolToVisibility}}">
                     <StackPanel Orientation="Vertical" Margin="15">
                         <TextBlock FontWeight="Bold" FontSize="12" Foreground="White" Margin="0,0,0,10">PLAYING A "GAME"</TextBlock>
                         <StackPanel Orientation="Horizontal">
                             <Image Source="../../Images/PixiEditorLogo.png" Height="70"/>
                             <StackPanel Margin="15,0,0,0" VerticalAlignment="Center">
                                 <TextBlock Foreground="White" FontSize="12" FontWeight="SemiBold">PixiEditor</TextBlock>
-                            <TextBlock Foreground="White" FontSize="12" Text="{Binding ElementName=uc, Path=Detail}" Visibility="{Binding ElementName=uc, Path=Detail, Converter={StaticResource EmptyStringToVisibilty}}"/>
-                            <TextBlock Foreground="White" FontSize="12" Text="{Binding ElementName=uc, Path=State}" Visibility="{Binding ElementName=uc, Path=State, Converter={StaticResource EmptyStringToVisibilty}}"/>
+                            <TextBlock Foreground="White" FontSize="12" Text="{Binding ElementName=uc, Path=Detail}" Visibility="{Binding ElementName=uc, Path=Detail, Converter={StaticResource EmptyStringToVisibility}}"/>
+                            <TextBlock Foreground="White" FontSize="12" Text="{Binding ElementName=uc, Path=State}" Visibility="{Binding ElementName=uc, Path=State, Converter={StaticResource EmptyStringToVisibility}}"/>
                             <TextBlock Foreground="White" FontSize="12">00:00 elapsed</TextBlock>
                             </StackPanel>
                         </StackPanel>

+ 6 - 0
PixiEditor/Views/UserControls/DrawingViewPort.xaml

@@ -53,6 +53,12 @@
                         </ImageBrush.Viewport>
                     </ImageBrush>
                 </Canvas.Background>
+                <Image Source="{Binding ReferenceLayer.LayerBitmap}"
+                       VerticalAlignment="Center" Stretch="Uniform"
+                       Visibility="{Binding ReferenceLayer.IsVisible, Converter={BoolToVisibilityConverter}}"
+                       HorizontalAlignment="Center" Width="{Binding Width}"
+                       Height="{Binding Height}"
+                       RenderOptions.BitmapScalingMode="NearestNeighbor"/>
                 <Image Source="{Binding PreviewLayer.LayerBitmap}" Panel.ZIndex="2"
                                    RenderOptions.BitmapScalingMode="NearestNeighbor" Stretch="Uniform"
                                    Width="{Binding PreviewLayer.Width}"

+ 1 - 1
PixiEditor/Views/UserControls/LayerGroupControl.xaml → PixiEditor/Views/UserControls/Layers/LayerGroupControl.xaml

@@ -1,4 +1,4 @@
-<UserControl x:Class="PixiEditor.Views.UserControls.LayerGroupControl"
+<UserControl x:Class="PixiEditor.Views.UserControls.Layers.LayerGroupControl"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 

+ 37 - 37
PixiEditor/Views/UserControls/LayerGroupControl.xaml.cs → PixiEditor/Views/UserControls/Layers/LayerGroupControl.xaml.cs

@@ -9,7 +9,7 @@ using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Media.Imaging;
 
-namespace PixiEditor.Views.UserControls
+namespace PixiEditor.Views.UserControls.Layers
 {
     /// <summary>
     /// Interaction logic for LayerFolder.xaml.
@@ -22,9 +22,6 @@ namespace PixiEditor.Views.UserControls
             set { SetValue(GroupGuidProperty, value); }
         }
 
-        public const string LayerGroupControlDataName = "PixiEditor.Views.UserControls.LayerGroupControl";
-        public const string LayerContainerDataName = "PixiEditor.Views.UserControls.LayerStructureItemContainer";
-
         public static readonly DependencyProperty GroupGuidProperty =
             DependencyProperty.Register("GroupGuid", typeof(Guid), typeof(LayerGroupControl), new PropertyMetadata(Guid.NewGuid()));
 
@@ -53,9 +50,12 @@ namespace PixiEditor.Views.UserControls
         }
 
         public static readonly DependencyProperty GroupOpacityProperty =
-            DependencyProperty.Register("GroupOpacity", typeof(float), typeof(LayerGroupControl), new PropertyMetadata(1f));
-
-
+            DependencyProperty.Register("GroupOpacity", typeof(float), typeof(LayerGroupControl), new PropertyMetadata(1f));
+
+
+        public static string LayerGroupControlDataName = typeof(LayerGroupControl).FullName;
+        public static string LayerContainerDataName = typeof(LayerStructureItemContainer).FullName;
+
         private static void LayersViewModelCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
         {
             LayerGroupControl control = (LayerGroupControl)d;
@@ -129,9 +129,9 @@ namespace PixiEditor.Views.UserControls
             item.Background = LayerItem.HighlightColor;
         }
 
-        private void Grid_CenterEnter(object sender, DragEventArgs e)
-        {
-            centerGrid.Background = LayerItem.HighlightColor;
+        private void Grid_CenterEnter(object sender, DragEventArgs e)
+        {
+            centerGrid.Background = LayerItem.HighlightColor;
         }
 
         private void Grid_DragLeave(object sender, DragEventArgs e)
@@ -205,10 +205,10 @@ namespace PixiEditor.Views.UserControls
             document.MoveGroupInStructure(group, tempLayer.LayerGuid, above);
             document.LayerStructure.AssignParent(tempLayer.LayerGuid, null);
             document.RemoveLayer(tempLayer, false);
-        }
-
-        private void HandleDropInside(IDataObject dataObj, Grid grid)
-        {
+        }
+
+        private void HandleDropInside(IDataObject dataObj, Grid grid)
+        {
             Guid referenceLayer = GroupData.EndLayerGuid;
             LayerItem.RemoveDragEffect(grid);
 
@@ -220,20 +220,20 @@ namespace PixiEditor.Views.UserControls
             if (dataObj.GetDataPresent(LayerGroupControlDataName))
             {
                 HandleGroupControlDrop(dataObj, referenceLayer, true, true);
-            }
+            }
         }
 
         private void Grid_Drop_Top(object sender, DragEventArgs e)
         {
             HandleDrop(e.Data, (Grid)sender, true);
-        }
-
-        private void Grid_Drop_Center(object sender, DragEventArgs e)
-        {
-            HandleDropInside(e.Data, (Grid)sender);
-            LayerItem.RemoveDragEffect(centerGrid);
-        }
-
+        }
+
+        private void Grid_Drop_Center(object sender, DragEventArgs e)
+        {
+            HandleDropInside(e.Data, (Grid)sender);
+            LayerItem.RemoveDragEffect(centerGrid);
+        }
+
         private void Grid_Drop_Bottom(object sender, DragEventArgs e)
         {
             HandleDrop(e.Data, (Grid)sender, false);
@@ -243,9 +243,9 @@ namespace PixiEditor.Views.UserControls
         {
             var doc = LayersViewModel.Owner.BitmapManager.ActiveDocument;
             var layer = doc.Layers.First(x => x.LayerGuid == GroupData.EndLayerGuid);
-            if (doc.ActiveLayerGuid != layer.LayerGuid)
-            {
-                doc.SetMainActiveLayer(doc.Layers.IndexOf(layer));
+            if (doc.ActiveLayerGuid != layer.LayerGuid)
+            {
+                doc.SetMainActiveLayer(doc.Layers.IndexOf(layer));
             }
         }
 
@@ -297,16 +297,16 @@ namespace PixiEditor.Views.UserControls
 
                 IsVisibleUndoTriggerable = value;
             }
-        }
-
-        private void GroupControl_DragEnter(object sender, DragEventArgs e)
-        {
-            middleDropGrid.Visibility = Visibility.Visible;
-        }
-
-        private void GroupControl_DragLeave(object sender, DragEventArgs e)
-        {
-            middleDropGrid.Visibility = Visibility.Collapsed;
-        }
+        }
+
+        private void GroupControl_DragEnter(object sender, DragEventArgs e)
+        {
+            middleDropGrid.Visibility = Visibility.Visible;
+        }
+
+        private void GroupControl_DragLeave(object sender, DragEventArgs e)
+        {
+            middleDropGrid.Visibility = Visibility.Collapsed;
+        }
     }
 }

+ 1 - 1
PixiEditor/Views/UserControls/LayerItem.xaml → PixiEditor/Views/UserControls/Layers/LayerItem.xaml

@@ -1,4 +1,4 @@
-<UserControl x:Class="PixiEditor.Views.LayerItem"
+<UserControl x:Class="PixiEditor.Views.UserControls.Layers.LayerItem"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 

+ 15 - 15
PixiEditor/Views/UserControls/LayerItem.xaml.cs → PixiEditor/Views/UserControls/Layers/LayerItem.xaml.cs

@@ -8,7 +8,7 @@ using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 
-namespace PixiEditor.Views
+namespace PixiEditor.Views.UserControls.Layers
 {
     /// <summary>
     /// Interaction logic for LayerItem.xaml.
@@ -156,23 +156,23 @@ namespace PixiEditor.Views
             Grid item = sender as Grid;
             RemoveDragEffect(item);
 
-            if (e.Data.GetDataPresent("PixiEditor.Views.UserControls.LayerStructureItemContainer"))
+            if (e.Data.GetDataPresent(LayerGroupControl.LayerContainerDataName))
             {
-                var data = (LayerStructureItemContainer)e.Data.GetData("PixiEditor.Views.UserControls.LayerStructureItemContainer");
+                var data = (LayerStructureItemContainer)e.Data.GetData(LayerGroupControl.LayerContainerDataName);
                 Guid layer = data.Layer.LayerGuid;
                 var doc = data.LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument;
 
                 doc.MoveLayerInStructure(layer, LayerGuid, above);
-                if (dropInParentFolder)
-                {
-                    Guid? groupGuid = doc.LayerStructure.GetGroupByLayer(layer)?.Parent?.GroupGuid;
-                    doc.LayerStructure.AssignParent(layer, groupGuid);
+                if (dropInParentFolder)
+                {
+                    Guid? groupGuid = doc.LayerStructure.GetGroupByLayer(layer)?.Parent?.GroupGuid;
+                    doc.LayerStructure.AssignParent(layer, groupGuid);
                 }
             }
 
-            if (e.Data.GetDataPresent("PixiEditor.Views.UserControls.LayerGroupControl"))
+            if (e.Data.GetDataPresent(LayerGroupControl.LayerGroupControlDataName))
             {
-                var data = (LayerGroupControl)e.Data.GetData("PixiEditor.Views.UserControls.LayerGroupControl");
+                var data = (LayerGroupControl)e.Data.GetData(LayerGroupControl.LayerGroupControlDataName);
                 Guid folder = data.GroupGuid;
 
                 var document = data.LayersViewModel.Owner.BitmapManager.ActiveDocument;
@@ -196,11 +196,11 @@ namespace PixiEditor.Views
         private void Grid_Drop_Bottom(object sender, DragEventArgs e)
         {
             HandleGridDrop(sender, e, false);
-        }
-
-        private void Grid_Drop_Below(object sender, DragEventArgs e)
-        {
-            HandleGridDrop(sender, e, false, true);
-        }
+        }
+
+        private void Grid_Drop_Below(object sender, DragEventArgs e)
+        {
+            HandleGridDrop(sender, e, false, true);
+        }
     }
 }

+ 2 - 2
PixiEditor/Views/UserControls/LayerStructureItemContainer.xaml → PixiEditor/Views/UserControls/Layers/LayerStructureItemContainer.xaml

@@ -1,9 +1,9 @@
-<UserControl x:Class="PixiEditor.Views.UserControls.LayerStructureItemContainer"
+<UserControl x:Class="PixiEditor.Views.UserControls.Layers.LayerStructureItemContainer"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
-             xmlns:vws="clr-namespace:PixiEditor.Views" xmlns:layers="clr-namespace:PixiEditor.Models.Layers" d:DataContext="{d:DesignInstance Type=layers:Layer}"
+             xmlns:vws="clr-namespace:PixiEditor.Views.UserControls.Layers" xmlns:layers="clr-namespace:PixiEditor.Models.Layers" d:DataContext="{d:DesignInstance Type=layers:Layer}"
              mc:Ignorable="d"
              d:DesignHeight="60" d:DesignWidth="250" Name="layerStructureContainer">
     <vws:LayerItem Tag="{Binding ElementName=layerStructureContainer}"

+ 1 - 1
PixiEditor/Views/UserControls/LayerStructureItemContainer.xaml.cs → PixiEditor/Views/UserControls/Layers/LayerStructureItemContainer.xaml.cs

@@ -3,7 +3,7 @@ using System.Windows.Controls;
 using PixiEditor.Models.Layers;
 using PixiEditor.ViewModels.SubViewModels.Main;
 
-namespace PixiEditor.Views.UserControls
+namespace PixiEditor.Views.UserControls.Layers
 {
     /// <summary>
     /// Interaction logic for LayerStructureItemContainer.xaml.

+ 6 - 6
PixiEditor/Views/UserControls/LayersManager.xaml → PixiEditor/Views/UserControls/Layers/LayersManager.xaml

@@ -1,13 +1,12 @@
-<UserControl x:Class="PixiEditor.Views.UserControls.LayersManager"
+<UserControl x:Class="PixiEditor.Views.UserControls.Layers.LayersManager"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
              xmlns:ui="clr-namespace:PixiEditor.Helpers.UI"
-             xmlns:local="clr-namespace:PixiEditor.Views.UserControls"
              xmlns:vws="clr-namespace:PixiEditor.Views" 
              xmlns:layers="clr-namespace:PixiEditor.Models.Layers"
-             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters" 
+             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters" xmlns:layerUserControls="clr-namespace:PixiEditor.Views.UserControls.Layers"
              mc:Ignorable="d"
              d:DesignHeight="450" d:DesignWidth="250" x:Name="layersManager">
     <UserControl.Resources>
@@ -18,7 +17,7 @@
             <RowDefinition Height="37.5"/>
             <RowDefinition Height="15"/>
             <RowDefinition Height="1*"/>
-            <RowDefinition Height="Auto"/>
+            <RowDefinition Height="40"/>
         </Grid.RowDefinitions>
         <DockPanel Background="{StaticResource MainColor}" Grid.Row="0" HorizontalAlignment="Stretch">
             <StackPanel Orientation="Horizontal" DockPanel.Dock="Left">
@@ -71,7 +70,7 @@
                 </TreeView.ItemsPanel>
                 <TreeView.Resources>
                     <HierarchicalDataTemplate DataType="{x:Type layers:LayerGroup}" ItemsSource="{Binding Items}">
-                        <local:LayerGroupControl GroupName="{Binding Name}" MouseDown="SelectActiveItem"
+                        <layerUserControls:LayerGroupControl GroupName="{Binding Name}" MouseDown="SelectActiveItem"
                                              IsVisibleUndoTriggerable="{Binding StructureData.IsVisible}" 
                                              GroupOpacity="{Binding StructureData.Opacity}"
                                              LayersViewModel="{Binding LayerCommandsViewModel, ElementName=layersManager}" 
@@ -80,7 +79,7 @@
                                              MouseMove="LayerGroup_MouseMove"/>
                     </HierarchicalDataTemplate>
                     <DataTemplate DataType="{x:Type layers:Layer}">
-                        <local:LayerStructureItemContainer    
+                        <layerUserControls:LayerStructureItemContainer    
                             MouseDown="SelectActiveItem"
                             MouseMove="LayerStructureItemContainer_MouseMove" 
                             ContainerIndex="{Binding Converter={StaticResource IndexOfConverter}}"
@@ -90,5 +89,6 @@
             </TreeView>
             <Border Name="dropBorder" DragEnter="Grid_DragEnter" DragLeave="Grid_DragLeave" AllowDrop="True" Drop="Grid_Drop" Background="Transparent" BorderThickness="0, 5, 0, 0"></Border>
         </DockPanel>
+        <layerUserControls:ReferenceLayer Visibility="{Binding Path=OpacityInputEnabled, ElementName=layersManager, Converter={BoolToVisibilityConverter}}" Background="{StaticResource MainColor}" Layer="{Binding LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument.ReferenceLayer, ElementName=layersManager, Mode=TwoWay}" Grid.Row="3" VerticalAlignment="Bottom"/>
     </Grid>
 </UserControl>

+ 251 - 251
PixiEditor/Views/UserControls/LayersManager.xaml.cs → PixiEditor/Views/UserControls/Layers/LayersManager.xaml.cs

@@ -1,6 +1,6 @@
-using PixiEditor.Helpers;
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
+using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Undo;
 using PixiEditor.ViewModels.SubViewModels.Main;
@@ -8,25 +8,25 @@ using System;
 using System.Collections.ObjectModel;
 using System.Windows;
 using System.Windows.Controls;
-using System.Windows.Media;
+using System.Windows.Media;
 
-namespace PixiEditor.Views.UserControls
+namespace PixiEditor.Views.UserControls.Layers
 {
     /// <summary>
     /// Interaction logic for LayersManager.xaml.
     /// </summary>
     public partial class LayersManager : UserControl
-    {
-        public object SelectedItem
-        {
-            get { return (object)GetValue(SelectedItemProperty); }
-            set { SetValue(SelectedItemProperty, value); }
-        }
-
-        public static readonly DependencyProperty SelectedItemProperty =
-            DependencyProperty.Register("SelectedItem", typeof(object), typeof(LayersManager), new PropertyMetadata(0));
-
-
+    {
+        public object SelectedItem
+        {
+            get { return (object)GetValue(SelectedItemProperty); }
+            set { SetValue(SelectedItemProperty, value); }
+        }
+
+        public static readonly DependencyProperty SelectedItemProperty =
+            DependencyProperty.Register("SelectedItem", typeof(object), typeof(LayersManager), new PropertyMetadata(0));
+
+
         public ObservableCollection<object> LayerTreeRoot
         {
             get { return (ObservableCollection<object>)GetValue(LayerTreeRootProperty); }
@@ -46,240 +46,240 @@ namespace PixiEditor.Views.UserControls
         }
 
         public static readonly DependencyProperty LayerCommandsViewModelProperty =
-            DependencyProperty.Register("LayerCommandsViewModel", typeof(LayersViewModel), typeof(LayersManager), new PropertyMetadata(default(LayersViewModel), ViewModelChanged));
-
-        public bool OpacityInputEnabled
-        {
-            get { return (bool)GetValue(OpacityInputEnabledProperty); }
-            set { SetValue(OpacityInputEnabledProperty, value); }
-        }
-
-        public static readonly DependencyProperty OpacityInputEnabledProperty =
-            DependencyProperty.Register("OpacityInputEnabled", typeof(bool), typeof(LayersManager), new PropertyMetadata(false));
-
-        public LayersManager()
-        {
-            InitializeComponent();
-        }
-
-        private static void ViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
-        {
-            if (e.NewValue is LayersViewModel vm)
-            {
-                LayersManager manager = (LayersManager)d;
-                vm.Owner.BitmapManager.AddPropertyChangedCallback(nameof(vm.Owner.BitmapManager.ActiveDocument), () =>
-                {
-                    var doc = vm.Owner.BitmapManager.ActiveDocument;
-                    if (doc != null)
-                    {
-                        if (doc.ActiveLayer != null)
-                        {
-                            manager.SetActiveLayerAsSelectedItem(doc);
-                        }
-                        doc.AddPropertyChangedCallback(nameof(doc.ActiveLayer), () =>
-                        {
-                            manager.SetActiveLayerAsSelectedItem(doc);
-                        });
-                    }
-                });
-            }
-        }
-
-        private void SetActiveLayerAsSelectedItem(Document doc)
-        {
-            SelectedItem = doc.ActiveLayer;
-            SetInputOpacity(SelectedItem);
-        }
-
-        private void SetInputOpacity(object item)
-        {
-            if (item is Layer layer)
-            {
-                numberInput.Value = layer.Opacity * 100f;
-            }
-            else if (item is LayerStructureItemContainer container)
-            {
-                numberInput.Value = container.Layer.Opacity * 100f;
-            }
-            else if (item is LayerGroup group)
-            {
-                numberInput.Value = group.StructureData.Opacity * 100f;
-            }
-            else if (item is LayerGroupControl groupControl)
-            {
-                numberInput.Value = groupControl.GroupData.Opacity * 100f;
-            }
-        }
-
-        private void LayerStructureItemContainer_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
-        {
-            if (sender is LayerStructureItemContainer container && e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
-            {
-                Dispatcher.InvokeAsync(() => DragDrop.DoDragDrop(container, container, DragDropEffects.Move));
-            }
-        }
-
-        private void HandleGroupOpacityChange(GuidStructureItem group, float value)
-        {
-            if (LayerCommandsViewModel.Owner?.BitmapManager?.ActiveDocument != null)
-            {
-                var doc = LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument;
-
-                var processArgs = new object[] { group.GroupGuid, value };
-                var reverseProcessArgs = new object[] { group.GroupGuid, group.Opacity };
-
-                ChangeGroupOpacityProcess(processArgs);
-
-                LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument.LayerStructure.ExpandParentGroups(group);
-
-                doc.UndoManager.AddUndoChange(
-                new Change(
-                    ChangeGroupOpacityProcess,
-                    reverseProcessArgs,
-                    ChangeGroupOpacityProcess,
-                    processArgs,
-                    $"Change {group.Name} opacity"), false);
-            }
-        }
-
-        private void ChangeGroupOpacityProcess(object[] processArgs)
-        {
-            if (processArgs.Length > 0 && processArgs[0] is Guid groupGuid && processArgs[1] is float opacity)
-            {
-                var structure = LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument.LayerStructure;
-                var group = structure.GetGroupByGuid(groupGuid);
-                group.Opacity = opacity;
-                var layers = structure.GetGroupLayers(group);
-                layers.ForEach(x => x.Opacity = x.Opacity); // This might seems stupid, but it raises property changed, without setting any value. This is used to trigger converters that use group opacity
-                numberInput.Value = opacity * 100;
-            }
-        }
-
-        private void LayerGroup_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
-        {
-            if (sender is LayerGroupControl container && e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
-            {
-                Dispatcher.InvokeAsync(() => DragDrop.DoDragDrop(container, container, DragDropEffects.Move));
-            }
-        }
-
-        private void NumberInput_LostFocus(object sender, RoutedEventArgs e)
-        {
-            float val = numberInput.Value / 100f;
-
-            object item = SelectedItem;
-
-            if (item is Layer || item is LayerStructureItemContainer)
-            {
-
-                Layer layer = null;
-
-                if (item is Layer lr)
-                {
-                    layer = lr;
-                }
-                else if (item is LayerStructureItemContainer container)
-                {
-                    layer = container.Layer;
-                }
-
-                HandleLayerOpacityChange(val, layer);
-            }
-            else if (item is LayerGroup group)
-            {
-                HandleGroupOpacityChange(group.StructureData, val);
-            }
-            else if (item is LayerGroupControl groupControl)
-            {
-                HandleGroupOpacityChange(groupControl.GroupData, val);
-            }
-        }
-
-        private void HandleLayerOpacityChange(float val, Layer layer)
-        {
-            float oldOpacity = layer.Opacity;
-
-            var doc = LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument;
-
-            doc.RaisePropertyChange(nameof(doc.LayerStructure));
-
-            layer.OpacityUndoTriggerable = val;
-
-            doc.LayerStructure.ExpandParentGroups(layer.LayerGuid);
-
-            doc.RaisePropertyChange(nameof(doc.LayerStructure));
-
-            UndoManager undoManager = doc.UndoManager;
-
-
-            undoManager.AddUndoChange(
-                new Change(
-                    UpdateNumberInputLayerOpacityProcess,
-                    new object[] { oldOpacity },
-                    UpdateNumberInputLayerOpacityProcess,
-                    new object[] { val }));
-            undoManager.SquashUndoChanges(2);
-        }
-
-        private void UpdateNumberInputLayerOpacityProcess(object[] args)
-        {
-            if (args.Length > 0 && args[0] is float opacity)
-            {
-                numberInput.Value = opacity * 100;
-            }
-        }
-
-        private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
-        {
-            SetInputOpacity(SelectedItem);
-        }
-
-        private void Grid_Drop(object sender, DragEventArgs e)
-        {
-            dropBorder.BorderBrush = Brushes.Transparent;
-
-            if (e.Data.GetDataPresent(LayerGroupControl.LayerContainerDataName))
-            {
-                HandleLayerDrop(e.Data);
-            }
-
-            if (e.Data.GetDataPresent(LayerGroupControl.LayerGroupControlDataName))
-            {
-                HandleGroupControlDrop(e.Data);
-            }
-        }
-
-        private void HandleLayerDrop(IDataObject data)
-        {
-            var doc = LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument;
-            if (doc.Layers.Count == 0) return;
-
-            var layerContainer = (LayerStructureItemContainer)data.GetData(LayerGroupControl.LayerContainerDataName);
-            var refLayer = doc.Layers[0].LayerGuid;
-            doc.MoveLayerInStructure(layerContainer.Layer.LayerGuid, refLayer);
-            doc.LayerStructure.AssignParent(layerContainer.Layer.LayerGuid, null);
-        }
-
-        private void HandleGroupControlDrop(IDataObject data)
-        {
-            var doc = LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument;
-            var groupContainer = (LayerGroupControl)data.GetData(LayerGroupControl.LayerGroupControlDataName);
-            doc.LayerStructure.MoveGroup(groupContainer.GroupGuid, 0);
-        }
-
-        private void Grid_DragEnter(object sender, DragEventArgs e)
-        {
-            ((Border)sender).BorderBrush = LayerItem.HighlightColor;
-        }
-
-        private void Grid_DragLeave(object sender, DragEventArgs e)
-        {
-            ((Border)sender).BorderBrush = Brushes.Transparent;
-        }
-
-        private void SelectActiveItem(object sender, System.Windows.Input.MouseButtonEventArgs e)
-        {
-            SelectedItem = sender;
-        }
+            DependencyProperty.Register("LayerCommandsViewModel", typeof(LayersViewModel), typeof(LayersManager), new PropertyMetadata(default(LayersViewModel), ViewModelChanged));
+
+        public bool OpacityInputEnabled
+        {
+            get { return (bool)GetValue(OpacityInputEnabledProperty); }
+            set { SetValue(OpacityInputEnabledProperty, value); }
+        }
+
+        public static readonly DependencyProperty OpacityInputEnabledProperty =
+            DependencyProperty.Register("OpacityInputEnabled", typeof(bool), typeof(LayersManager), new PropertyMetadata(false));
+
+        public LayersManager()
+        {
+            InitializeComponent();
+        }
+
+        private static void ViewModelChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            if (e.NewValue is LayersViewModel vm)
+            {
+                LayersManager manager = (LayersManager)d;
+                vm.Owner.BitmapManager.AddPropertyChangedCallback(nameof(vm.Owner.BitmapManager.ActiveDocument), () =>
+                {
+                    var doc = vm.Owner.BitmapManager.ActiveDocument;
+                    if (doc != null)
+                    {
+                        if (doc.ActiveLayer != null)
+                        {
+                            manager.SetActiveLayerAsSelectedItem(doc);
+                        }
+                        doc.AddPropertyChangedCallback(nameof(doc.ActiveLayer), () =>
+                        {
+                            manager.SetActiveLayerAsSelectedItem(doc);
+                        });
+                    }
+                });
+            }
+        }
+
+        private void SetActiveLayerAsSelectedItem(Document doc)
+        {
+            SelectedItem = doc.ActiveLayer;
+            SetInputOpacity(SelectedItem);
+        }
+
+        private void SetInputOpacity(object item)
+        {
+            if (item is Layer layer)
+            {
+                numberInput.Value = layer.Opacity * 100f;
+            }
+            else if (item is LayerStructureItemContainer container)
+            {
+                numberInput.Value = container.Layer.Opacity * 100f;
+            }
+            else if (item is LayerGroup group)
+            {
+                numberInput.Value = group.StructureData.Opacity * 100f;
+            }
+            else if (item is LayerGroupControl groupControl)
+            {
+                numberInput.Value = groupControl.GroupData.Opacity * 100f;
+            }
+        }
+
+        private void LayerStructureItemContainer_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
+        {
+            if (sender is LayerStructureItemContainer container && e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
+            {
+                Dispatcher.InvokeAsync(() => DragDrop.DoDragDrop(container, container, DragDropEffects.Move));
+            }
+        }
+
+        private void HandleGroupOpacityChange(GuidStructureItem group, float value)
+        {
+            if (LayerCommandsViewModel.Owner?.BitmapManager?.ActiveDocument != null)
+            {
+                var doc = LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument;
+
+                var processArgs = new object[] { group.GroupGuid, value };
+                var reverseProcessArgs = new object[] { group.GroupGuid, group.Opacity };
+
+                ChangeGroupOpacityProcess(processArgs);
+
+                LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument.LayerStructure.ExpandParentGroups(group);
+
+                doc.UndoManager.AddUndoChange(
+                new Change(
+                    ChangeGroupOpacityProcess,
+                    reverseProcessArgs,
+                    ChangeGroupOpacityProcess,
+                    processArgs,
+                    $"Change {group.Name} opacity"), false);
+            }
+        }
+
+        private void ChangeGroupOpacityProcess(object[] processArgs)
+        {
+            if (processArgs.Length > 0 && processArgs[0] is Guid groupGuid && processArgs[1] is float opacity)
+            {
+                var structure = LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument.LayerStructure;
+                var group = structure.GetGroupByGuid(groupGuid);
+                group.Opacity = opacity;
+                var layers = structure.GetGroupLayers(group);
+                layers.ForEach(x => x.Opacity = x.Opacity); // This might seems stupid, but it raises property changed, without setting any value. This is used to trigger converters that use group opacity
+                numberInput.Value = opacity * 100;
+            }
+        }
+
+        private void LayerGroup_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
+        {
+            if (sender is LayerGroupControl container && e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
+            {
+                Dispatcher.InvokeAsync(() => DragDrop.DoDragDrop(container, container, DragDropEffects.Move));
+            }
+        }
+
+        private void NumberInput_LostFocus(object sender, RoutedEventArgs e)
+        {
+            float val = numberInput.Value / 100f;
+
+            object item = SelectedItem;
+
+            if (item is Layer || item is LayerStructureItemContainer)
+            {
+
+                Layer layer = null;
+
+                if (item is Layer lr)
+                {
+                    layer = lr;
+                }
+                else if (item is LayerStructureItemContainer container)
+                {
+                    layer = container.Layer;
+                }
+
+                HandleLayerOpacityChange(val, layer);
+            }
+            else if (item is LayerGroup group)
+            {
+                HandleGroupOpacityChange(group.StructureData, val);
+            }
+            else if (item is LayerGroupControl groupControl)
+            {
+                HandleGroupOpacityChange(groupControl.GroupData, val);
+            }
+        }
+
+        private void HandleLayerOpacityChange(float val, Layer layer)
+        {
+            float oldOpacity = layer.Opacity;
+
+            var doc = LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument;
+
+            doc.RaisePropertyChange(nameof(doc.LayerStructure));
+
+            layer.OpacityUndoTriggerable = val;
+
+            doc.LayerStructure.ExpandParentGroups(layer.LayerGuid);
+
+            doc.RaisePropertyChange(nameof(doc.LayerStructure));
+
+            UndoManager undoManager = doc.UndoManager;
+
+
+            undoManager.AddUndoChange(
+                new Change(
+                    UpdateNumberInputLayerOpacityProcess,
+                    new object[] { oldOpacity },
+                    UpdateNumberInputLayerOpacityProcess,
+                    new object[] { val }));
+            undoManager.SquashUndoChanges(2);
+        }
+
+        private void UpdateNumberInputLayerOpacityProcess(object[] args)
+        {
+            if (args.Length > 0 && args[0] is float opacity)
+            {
+                numberInput.Value = opacity * 100;
+            }
+        }
+
+        private void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
+        {
+            SetInputOpacity(SelectedItem);
+        }
+
+        private void Grid_Drop(object sender, DragEventArgs e)
+        {
+            dropBorder.BorderBrush = Brushes.Transparent;
+
+            if (e.Data.GetDataPresent(LayerGroupControl.LayerContainerDataName))
+            {
+                HandleLayerDrop(e.Data);
+            }
+
+            if (e.Data.GetDataPresent(LayerGroupControl.LayerGroupControlDataName))
+            {
+                HandleGroupControlDrop(e.Data);
+            }
+        }
+
+        private void HandleLayerDrop(IDataObject data)
+        {
+            var doc = LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument;
+            if (doc.Layers.Count == 0) return;
+
+            var layerContainer = (LayerStructureItemContainer)data.GetData(LayerGroupControl.LayerContainerDataName);
+            var refLayer = doc.Layers[0].LayerGuid;
+            doc.MoveLayerInStructure(layerContainer.Layer.LayerGuid, refLayer);
+            doc.LayerStructure.AssignParent(layerContainer.Layer.LayerGuid, null);
+        }
+
+        private void HandleGroupControlDrop(IDataObject data)
+        {
+            var doc = LayerCommandsViewModel.Owner.BitmapManager.ActiveDocument;
+            var groupContainer = (LayerGroupControl)data.GetData(LayerGroupControl.LayerGroupControlDataName);
+            doc.LayerStructure.MoveGroup(groupContainer.GroupGuid, 0);
+        }
+
+        private void Grid_DragEnter(object sender, DragEventArgs e)
+        {
+            ((Border)sender).BorderBrush = LayerItem.HighlightColor;
+        }
+
+        private void Grid_DragLeave(object sender, DragEventArgs e)
+        {
+            ((Border)sender).BorderBrush = Brushes.Transparent;
+        }
+
+        private void SelectActiveItem(object sender, System.Windows.Input.MouseButtonEventArgs e)
+        {
+            SelectedItem = sender;
+        }
     }
 }

+ 1 - 1
PixiEditor/Views/UserControls/RawLayersViewer.xaml → PixiEditor/Views/UserControls/Layers/RawLayersViewer.xaml

@@ -1,4 +1,4 @@
-<UserControl x:Class="PixiEditor.Views.UserControls.RawLayersViewer"
+<UserControl x:Class="PixiEditor.Views.UserControls.Layers.RawLayersViewer"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 

+ 1 - 1
PixiEditor/Views/UserControls/RawLayersViewer.xaml.cs → PixiEditor/Views/UserControls/Layers/RawLayersViewer.xaml.cs

@@ -5,7 +5,7 @@ using System.Windows;
 using System.Windows.Controls;
 using PixiEditor.Models.Layers;
 
-namespace PixiEditor.Views.UserControls
+namespace PixiEditor.Views.UserControls.Layers
 {
     /// <summary>
     /// Interaction logic for RawLayersViewer.xaml.

+ 93 - 0
PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml

@@ -0,0 +1,93 @@
+<UserControl x:Class="PixiEditor.Views.UserControls.Layers.ReferenceLayer"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:PixiEditor.Views.UserControls.Layers" xmlns:behaviors="clr-namespace:PixiEditor.Helpers.Behaviours" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" xmlns:local1="clr-namespace:PixiEditor.Views.UserControls" xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+             mc:Ignorable="d" 
+             d:DesignHeight="60" d:DesignWidth="350" VerticalAlignment="Center" Name="uc">
+    <UserControl.Resources>
+        <converters:NotNullToVisibilityConverter x:Key="NotNullToVisibilityConverter"/>
+        <converters:NotNullToBoolConverter x:Key="NotNullToBoolConverter"/>
+    </UserControl.Resources>
+    <Border BorderBrush="{StaticResource DarkerAccentColor}" BorderThickness="0 2 0 0" MinWidth="60" Focusable="True">
+        <i:Interaction.Behaviors>
+            <behaviors:ClearFocusOnClickBehavior/>
+        </i:Interaction.Behaviors>
+        <Grid>
+            <Grid Background="Transparent"/>
+        <Grid Grid.Row="1" Grid.RowSpan="3" VerticalAlignment="Center">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="30"/>
+                    <ColumnDefinition Width="*"/>
+                </Grid.ColumnDefinitions>
+                <Grid Visibility="{Binding Layer, ElementName=uc, Converter={converters:NullToVisibilityConverter}}"  Grid.ColumnSpan="2" Grid.RowSpan="2" Panel.ZIndex="5">
+                    <Grid MouseDown="Grid_MouseDown" Cursor="Hand" Visibility="{Binding ElementName=visibilityCheckbox, Path=IsChecked, Converter={InverseBoolToVisibilityConverter}}"  Background="Transparent"/>
+                </Grid>
+                <Grid Grid.Column="0" Height="16" Name="layerVisibilityCheckboxGrid">
+                    <CheckBox Visibility="{Binding Layer, ElementName=uc, Converter={StaticResource NotNullToVisibilityConverter}}" Style="{StaticResource ImageCheckBox}" VerticalAlignment="Center"
+                      IsThreeState="False" HorizontalAlignment="Center" 
+                      IsChecked="{Binding Path=Layer.IsVisible, Mode=TwoWay, ElementName=uc}"/>
+                </Grid>
+                <StackPanel Name="middleStackPanel" Height="40" Orientation="Horizontal" Grid.Column="1" HorizontalAlignment="Center">
+                    <Border HorizontalAlignment="Left" Visibility="{Binding Layer, ElementName=uc, Converter={StaticResource NotNullToVisibilityConverter}}" Width="30" Height="30" BorderThickness="1" BorderBrush="Black" Background="{StaticResource MainColor}"
+                           Margin="5, 0, 10, 0">
+                        <Image Source="{Binding Layer.LayerBitmap, ElementName=uc}" Stretch="Uniform" Width="25" Height="25" 
+                       RenderOptions.BitmapScalingMode="NearestNeighbor"/>
+                    </Border>
+                    <Image Margin="0 0 5 0" Width="20" Source="/Images/Layer-add.png"  Visibility="{Binding Layer, ElementName=uc, Converter={converters:NullToVisibilityConverter}}"/>
+
+                    <local1:PrependTextBlock IsEnabled="{Binding ElementName=uc, Path=IsEnabled}" Margin="0 0 5 0" Prepend="Add " Foreground="White" HidePrepend="{Binding Layer, ElementName=uc, Converter={StaticResource NotNullToBoolConverter}}"
+                                             FontSize="15" VerticalAlignment="Center" Text="Reference Layer" />
+                    <Button Click="TrashButton_Click" Cursor="Hand" Grid.Column="1" Visibility="{Binding Layer, ElementName=uc, Converter={BoolToVisibilityConverter}}" Style="{StaticResource ImageButtonStyle}" Width="20" Height="20" HorizontalAlignment="Right">
+                        <Button.Background>
+                            <ImageBrush ImageSource="/Images/Trash.png"/>
+                        </Button.Background>
+                    </Button>
+                </StackPanel>
+                <CheckBox Panel.ZIndex="10" Name="visibilityCheckbox" Grid.Column="1" Margin="0,0,5,0" Height="16" HorizontalAlignment="Right">
+                    <CheckBox.Triggers>
+                        <EventTrigger RoutedEvent="CheckBox.Checked">
+                            <BeginStoryboard>
+                                <Storyboard>
+                                    <DoubleAnimation Storyboard.TargetName="middleStackPanel" Storyboard.TargetProperty="Height" From="40" To="0" Duration="0:0:0.15"/>
+                                    <DoubleAnimation Storyboard.TargetName="layerVisibilityCheckboxGrid" Storyboard.TargetProperty="Height" From="16" To="0" Duration="0:0:0.15"/>
+                                </Storyboard>
+                            </BeginStoryboard>
+                        </EventTrigger>
+                        <EventTrigger RoutedEvent="CheckBox.Unchecked">
+                        <BeginStoryboard>
+                            <Storyboard>
+                                <DoubleAnimation Storyboard.TargetName="middleStackPanel" Storyboard.TargetProperty="Height" From="0" To="40" Duration="0:0:0.15"/>
+                                    <DoubleAnimation Storyboard.TargetName="layerVisibilityCheckboxGrid" Storyboard.TargetProperty="Height" From="0" To="16" Duration="0:0:0.15"/>
+                                </Storyboard>
+                        </BeginStoryboard>
+                        </EventTrigger>
+
+                    </CheckBox.Triggers>
+                    <CheckBox.Template>
+                        <ControlTemplate TargetType="{x:Type CheckBox}">
+                            <StackPanel Orientation="Horizontal">
+                                <Image Width="14" Cursor="Hand" x:Name="checkboxImage" Source="/Images/ChevronDown.png">
+                                    <Image.RenderTransform>
+                                        <RotateTransform Angle="0"/>
+                                    </Image.RenderTransform>
+                                </Image>
+                                <ContentPresenter/>
+                            </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.Template>
+                </CheckBox>
+            </Grid>
+    </Grid>
+    </Border>
+</UserControl>

+ 70 - 0
PixiEditor/Views/UserControls/Layers/ReferenceLayer.xaml.cs

@@ -0,0 +1,70 @@
+using Microsoft.Win32;
+using PixiEditor.Models.IO;
+using PixiEditor.Models.Layers;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace PixiEditor.Views.UserControls.Layers
+{
+    /// <summary>
+    /// Interaction logic for ReferenceLayer.xaml
+    /// </summary>
+    public partial class ReferenceLayer : UserControl
+    {
+        public Layer Layer
+        {
+            get { return (Layer)GetValue(ReferenceLayerProperty); }
+            set { SetValue(ReferenceLayerProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for ReferenceLayer.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty ReferenceLayerProperty =
+            DependencyProperty.Register("Layer", typeof(Layer), typeof(ReferenceLayer), new PropertyMetadata(default(Layer)));
+
+
+        public ReferenceLayer()
+        {
+            InitializeComponent();
+        }
+
+        private void Grid_MouseDown(object sender, MouseButtonEventArgs e)
+        {
+            string path = OpenFilePicker();
+            if (path != null)
+            {
+                var bitmap = Importer.ImportImage(path);
+                Layer = new Layer("_Reference Layer", bitmap);
+            }
+        }
+
+        private string OpenFilePicker()
+        {
+
+            OpenFileDialog dialog = new OpenFileDialog
+            {
+                Title = "Reference layer path",
+                CheckPathExists = true,
+                Filter = "Image Files|*.png;*.jpeg;*.jpg|PNG Files|*.png|JPG Files|*.jpeg;*.jpg"
+            };
+
+            return (bool)dialog.ShowDialog() ? dialog.FileName : null;
+        }
+
+        private void TrashButton_Click(object sender, RoutedEventArgs e)
+        {
+            Layer = null;
+        }
+    }
+}

+ 14 - 4
PixiEditor/Views/UserControls/PrependTextBlock.xaml

@@ -8,9 +8,19 @@
              d:DesignHeight="450" d:DesignWidth="800"
              x:Name="uc">
     <StackPanel Orientation="Horizontal">
-        <TextBlock Text="{Binding Prepend, ElementName=uc}"/>
-        
-        <TextBlock>
+        <StackPanel.Resources>
+            <Style TargetType="{x:Type TextBox}">
+                <Setter Property="Foreground" Value="White" />
+                <Style.Triggers>
+                    <Trigger Property="IsEnabled" Value="False">
+                        <Setter Property="Foreground" Value="#88888A" />
+                    </Trigger>
+                </Style.Triggers>
+            </Style>
+        </StackPanel.Resources>
+        <TextBlock Visibility="{Binding HidePrepend, ElementName=uc, Converter={InverseBoolToVisibilityConverter}}" Text="{Binding Prepend, ElementName=uc}" IsEnabled="{Binding ElementName=uc, Path=IsEnabled}"/>
+
+        <TextBlock IsEnabled="{Binding ElementName=uc, Path=IsEnabled}">
             <TextBlock.Text>
                 <PriorityBinding>
                     <Binding Path="Text" ElementName="uc"/>
@@ -19,6 +29,6 @@
             </TextBlock.Text>
         </TextBlock>
 
-        <TextBlock Text="{Binding Append, ElementName=uc}"/>
+        <TextBlock IsEnabled="{Binding ElementName=uc, Path=IsEnabled}" Visibility="{Binding HidePrepend, ElementName=uc, Converter={InverseBoolToVisibilityConverter}}" Text="{Binding Append, ElementName=uc}"/>
     </StackPanel>
 </UserControl>

+ 12 - 0
PixiEditor/Views/UserControls/PrependTextBlock.xaml.cs

@@ -65,6 +65,18 @@ namespace PixiEditor.Views.UserControls
             set => SetValue(AppendColorProperty, value);
         }
 
+        public bool HidePrepend
+        {
+            get { return (bool)GetValue(HidePrependProperty); }
+            set { SetValue(HidePrependProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for HidePrepend.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty HidePrependProperty =
+            DependencyProperty.Register("HidePrepend", typeof(bool), typeof(PrependTextBlock), new PropertyMetadata(false));
+
+
+
         public PrependTextBlock()
         {
             InitializeComponent();

+ 2 - 2
PixiEditor/Views/UserControls/PreviewWindow.xaml

@@ -12,7 +12,7 @@
 
     <UserControl.Resources>
         <BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
-        <converters:NotNullToVisibiltyConverter x:Key="NullToVisibiltyConverter"/>
+        <converters:NotNullToVisibilityConverter x:Key="NullToVisibilityConverter"/>
     </UserControl.Resources>
     <Grid>
         <Grid.RowDefinitions>
@@ -23,7 +23,7 @@
 
         <Viewbox Margin="30" VerticalAlignment="Center">
             <Grid x:Name="imageGrid" RenderOptions.BitmapScalingMode="NearestNeighbor"
-              Visibility="{Binding Document, Converter={StaticResource NullToVisibiltyConverter}, ElementName=uc}"
+              Visibility="{Binding Document, Converter={StaticResource NullToVisibilityConverter}, ElementName=uc}"
               Height="{Binding Document.Height, ElementName=uc}" Width="{Binding Document.Width, ElementName=uc}"
               Background="{Binding ActiveItem.Value, ElementName=backgroundButton}" d:Width="8" d:Height="8">
                 <ItemsControl ItemsSource="{Binding Document.Layers, ElementName=uc}">

+ 1 - 1
PixiEditorTests/ModelsTests/DataHoldersTests/DocumentTests.cs

@@ -301,7 +301,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [StaFact]
         public void TestThatDocumentGetsAddedToRecentlyOpenedList()
         {
-            ViewModelMain viewModel = Helpers.MockedViewModelMain();
+            ViewModelMain viewModel = TestHelpers.MockedViewModelMain();
 
             Document document = new Document(1, 1)
             {

+ 1 - 1
PixiEditorTests/Helpers.cs → PixiEditorTests/TestHelpers.cs

@@ -5,7 +5,7 @@ using PixiEditor.ViewModels;
 
 namespace PixiEditorTests
 {
-    public static class Helpers
+    public static class TestHelpers
     {
         public static ViewModelMain MockedViewModelMain()
         {

+ 4 - 2
PixiEditorTests/ViewModelsTests/ViewModelMainTests.cs

@@ -18,8 +18,10 @@ namespace PixiEditorTests.ViewModelsTests
     [Collection("Application collection")]
     public class ViewModelMainTests
     {
-        public static IServiceProvider Services;
-
+        private static IServiceProvider services;
+
+        public static IServiceProvider Services { get => services; set => services = value; }
+
         public ViewModelMainTests()
         {
             Services = new ServiceCollection()