Переглянути джерело

Merge pull request #187 from PixiEditor/master

Version 0.1.6.0
Krzysztof Krysiński 4 роки тому
батько
коміт
2dd894a344
38 змінених файлів з 1033 додано та 182 видалено
  1. 3 3
      PixiEditor.MSIX/Package.appxmanifest
  2. 20 0
      PixiEditor/Helpers/Converters/NotNullToVisibiltyConverter.cs
  3. 8 8
      PixiEditor/Helpers/Extensions/ParserHelpers.cs
  4. 1 1
      PixiEditor/Helpers/SelectionHelpers.cs
  5. 5 0
      PixiEditor/Models/DataHolders/BitmapPixelChanges.cs
  6. 1 1
      PixiEditor/Models/DataHolders/Document/Document.Constructors.cs
  7. 7 1
      PixiEditor/Models/DataHolders/Document/Document.Layers.cs
  8. 14 0
      PixiEditor/Models/Enums/SelectionShape.cs
  9. 46 21
      PixiEditor/Models/ImageManipulation/BitmapUtils.cs
  10. 18 18
      PixiEditor/Models/Tools/ShapeTool.cs
  11. 1 0
      PixiEditor/Models/Tools/ToolSettings/Toolbars/SelectToolToolbar.cs
  12. 22 16
      PixiEditor/Models/Tools/Tools/CircleTool.cs
  13. 27 4
      PixiEditor/Models/Tools/Tools/SelectTool.cs
  14. 59 1
      PixiEditor/Models/UserPreferences/IPreferences.cs
  15. 19 4
      PixiEditor/Models/UserPreferences/PreferencesSettings.cs
  16. 22 0
      PixiEditor/NotifyableObject.cs
  17. 1 1
      PixiEditor/PixiEditor.csproj
  18. 3 3
      PixiEditor/Properties/AssemblyInfo.cs
  19. 7 0
      PixiEditor/ViewModels/ISettableOwner.cs
  20. 17 17
      PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs
  21. 50 0
      PixiEditor/ViewModels/SubViewModels/Main/WindowViewModel.cs
  22. 19 0
      PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/GeneralSettings.cs
  23. 2 0
      PixiEditor/ViewModels/SubViewModels/UserPreferences/SettingsViewModel.cs
  24. 2 9
      PixiEditor/ViewModels/ViewModelBase.cs
  25. 40 0
      PixiEditor/ViewModels/ViewModelMain.cs
  26. 1 1
      PixiEditor/Views/Dialogs/HelloTherePopup.xaml
  27. 2 0
      PixiEditor/Views/Dialogs/SettingsWindow.xaml
  28. 29 62
      PixiEditor/Views/MainWindow.xaml
  29. 62 3
      PixiEditor/Views/MainWindow.xaml.cs
  30. 24 0
      PixiEditor/Views/UserControls/PrependTextBlock.xaml
  31. 73 0
      PixiEditor/Views/UserControls/PrependTextBlock.xaml.cs
  32. 152 0
      PixiEditor/Views/UserControls/PreviewWindow.xaml
  33. 142 0
      PixiEditor/Views/UserControls/PreviewWindow.xaml.cs
  34. 66 0
      PixiEditor/Views/UserControls/SwatchesView.xaml
  35. 55 0
      PixiEditor/Views/UserControls/SwatchesView.xaml.cs
  36. 4 0
      PixiEditorTests/Mocks/PreferenceSettingsMock.cs
  37. 9 8
      README.md
  38. BIN
      Screenshot.png

+ 3 - 3
PixiEditor.MSIX/Package.appxmanifest

@@ -7,13 +7,13 @@
   IgnorableNamespaces="uap rescap">
 
   <Identity
-    Name="6018c504-49c2-4830-ac0f-fab30b922158"
-    Publisher="CN=PixiEditor"
+    Name="56069PixiEditorOrganizati.PixiEditor"
+    Publisher="CN=0AFA75AD-56A3-481D-B5E4-D3C6274DD38A"
     Version="0.2.0.0" />
 
   <Properties>
     <DisplayName>PixiEditor</DisplayName>
-    <PublisherDisplayName>PixiEditor</PublisherDisplayName>
+    <PublisherDisplayName>PixiEditor Organization</PublisherDisplayName>
     <Logo>Images\StoreLogo.png</Logo>
   </Properties>
 

+ 20 - 0
PixiEditor/Helpers/Converters/NotNullToVisibiltyConverter.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Data;
+
+namespace PixiEditor.Helpers.Converters
+{
+    class NotNullToVisibiltyConverter : IValueConverter
+    {
+        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            return value != null ? Visibility.Visible : Visibility.Hidden;
+        }
+
+        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 8 - 8
PixiEditor/Helpers/Extensions/ParserHelpers.cs

@@ -1,11 +1,11 @@
-using System;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.Layers;
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Windows;
 using System.Windows.Media;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.ImageManipulation;
-using PixiEditor.Models.Layers;
+using SDColor = System.Drawing.Color;
 
 namespace PixiEditor.Helpers.Extensions
 {
@@ -17,7 +17,7 @@ namespace PixiEditor.Helpers.Extensions
             {
                 Layers = serializableDocument.ToLayers(),
                 Swatches = new ObservableCollection<Color>(serializableDocument.Swatches.Select(x =>
-                    Color.FromArgb(x.Item1, x.Item2, x.Item3, x.Item4)))
+                    Color.FromArgb(x.A, x.R, x.G, x.B)))
             };
 
             if (document.Layers.Count > 0)
@@ -31,7 +31,7 @@ namespace PixiEditor.Helpers.Extensions
         public static ObservableCollection<Layer> ToLayers(this Parser.SerializableDocument serializableDocument)
         {
             ObservableCollection<Layer> layers = new ObservableCollection<Layer>();
-            for (int i = 0; i < serializableDocument.Layers.Length; i++)
+            for (int i = 0; i < serializableDocument.Layers.Count; i++)
             {
                 Parser.SerializableLayer serLayer = serializableDocument.Layers[i];
                 Layer layer =
@@ -55,8 +55,8 @@ namespace PixiEditor.Helpers.Extensions
             {
                 Width = document.Width,
                 Height = document.Height,
-                Layers = document.Layers.Select(x => x.ToSerializable()).ToArray(),
-                Swatches = document.Swatches.Select(x => new Tuple<byte, byte, byte, byte>(x.A, x.R, x.G, x.B)).ToArray()
+                Layers = document.Layers.Select(x => x.ToSerializable()).ToList(),
+                Swatches = document.Swatches.Select(x => SDColor.FromArgb(x.A, x.R, x.G, x.B)).ToList()
             };
 
             return serializable;

+ 1 - 1
PixiEditor/Helpers/SelectionHelpers.cs

@@ -27,7 +27,7 @@ namespace PixiEditor.Helpers
             {
                 document.UndoManager.AddUndoChange(
                     new Change(
-                        SetSelectionProcess, new object[] { document, new List<Coordinates>(oldPoints) },
+                        SetSelectionProcess, new object[] { document, oldPoints is null ? new List<Coordinates>() : new List<Coordinates>(oldPoints) },
                         SetSelectionProcess, new object[] { document, new List<Coordinates>(document.ActiveSelection.SelectedPoints) }));
 #pragma warning restore SA1117 // Parameters should be on same line or separate lines
             }

+ 5 - 0
PixiEditor/Models/DataHolders/BitmapPixelChanges.cs

@@ -35,6 +35,11 @@ namespace PixiEditor.Models.DataHolders
             Dictionary<Coordinates, Color> dict = new Dictionary<Coordinates, Color>();
             foreach (Coordinates coordinate in coordinates)
             {
+                if (dict.ContainsKey(coordinate))
+                {
+                    continue;
+                }
+
                 dict.Add(coordinate, color);
             }
 

+ 1 - 1
PixiEditor/Models/DataHolders/Document/Document.Constructors.cs

@@ -17,7 +17,7 @@ namespace PixiEditor.Models.DataHolders
         {
             SetRelayCommands();
             UndoManager = new UndoManager();
-            XamlAccesibleViewModel = ViewModelMain.Current ?? null;
+            XamlAccesibleViewModel = ViewModelMain.Current;
             GeneratePreviewLayer();
         }
     }

+ 7 - 1
PixiEditor/Models/DataHolders/Document/Document.Layers.cs

@@ -4,11 +4,12 @@ using System.Collections.ObjectModel;
 using System.Linq;
 using System.Text.RegularExpressions;
 using System.Windows;
+using System.Windows.Media;
 using System.Windows.Media.Imaging;
-using GalaSoft.MvvmLight.Messaging;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Enums;
+using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Undo;
@@ -339,6 +340,11 @@ namespace PixiEditor.Models.DataHolders
             return layer;
         }
 
+        public Color GetColorAtPoint(int x, int y)
+        {
+            return BitmapUtils.GetColorAtPointCombined(x, y, Layers.ToArray());
+        }
+
         private void InjectRemoveActiveLayersUndo(object[] guidArgs, StorageBasedChange change)
         {
             Action<Layer[], UndoLayer[]> undoAction = RestoreLayersProcess;

+ 14 - 0
PixiEditor/Models/Enums/SelectionShape.cs

@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.Models.Enums
+{
+    public enum SelectionShape
+    {
+        Rectangle,
+        Circle
+    }
+}

+ 46 - 21
PixiEditor/Models/ImageManipulation/BitmapUtils.cs

@@ -5,6 +5,7 @@ using PixiEditor.Parser;
 using System;
 using System.Collections.Generic;
 using System.Linq;
+using System.Runtime.CompilerServices;
 using System.Windows;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
@@ -56,30 +57,14 @@ namespace PixiEditor.Models.ImageManipulation
                         throw new InvalidOperationException("Layers must not extend beyond canvas borders");
                     }
 
-                    for (int y = 0; y < layers[i].Height; y++)
+                    for (int y = 0; y < layer.Height; y++)
                     {
-                        for (int x = 0; x < layers[i].Width; x++)
+                        for (int x = 0; x < layer.Width; x++)
                         {
+                            Color previousColor = finalBitmap.GetPixel(x + layer.OffsetX, y + layer.OffsetY);
                             Color color = layer.GetPixel(x, y);
-                            if (i > 0 && ((color.A < 255 && color.A > 0) || (layerOpacity < 1f && layerOpacity > 0 && color.A > 0)))
-                            {
-                                var lastLayerPixel = finalBitmap.GetPixel(x + layer.OffsetX, y + layer.OffsetY);
-                                byte pixelA = (byte)(color.A * layerOpacity);
-                                byte r = (byte)((color.R * pixelA / 255) + (lastLayerPixel.R * lastLayerPixel.A * (255 - pixelA) / (255 * 255)));
-                                byte g = (byte)((color.G * pixelA / 255) + (lastLayerPixel.G * lastLayerPixel.A * (255 - pixelA) / (255 * 255)));
-                                byte b = (byte)((color.B * pixelA / 255) + (lastLayerPixel.B * lastLayerPixel.A * (255 - pixelA) / (255 * 255)));
-                                byte a = (byte)(pixelA + (lastLayerPixel.A * (255 - pixelA) / 255));
-                                color = Color.FromArgb(a, r, g, b);
-                            }
-                            else
-                            {
-                                color = Color.FromArgb(color.A, color.R, color.G, color.B);
-                            }
-
-                            if (color.A > 0)
-                            {
-                                finalBitmap.SetPixel(x + layer.OffsetX, y + layer.OffsetY, color);
-                            }
+
+                            finalBitmap.SetPixel(x + layer.OffsetX, y + layer.OffsetY, BlendColor(previousColor, color, layerOpacity));
                         }
                     }
                 }
@@ -88,6 +73,21 @@ namespace PixiEditor.Models.ImageManipulation
             return finalBitmap;
         }
 
+        public static Color GetColorAtPointCombined(int x, int y, params Layer[] layers)
+        {
+            Color prevColor = Color.FromArgb(0, 0, 0, 0);
+
+            for (int i = 0; i < layers.Length; i++)
+            {
+                Color color = layers[i].GetPixelWithOffset(x, y);
+                float layerOpacity = layers[i].Opacity;
+
+                prevColor = BlendColor(prevColor, color, layerOpacity);
+            }
+
+            return prevColor;
+        }
+
         /// <summary>
         /// Generates simplified preview from Document, very fast, great for creating small previews. Creates uniform streched image.
         /// </summary>
@@ -152,6 +152,31 @@ namespace PixiEditor.Models.ImageManipulation
             return result;
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static Color BlendColor(Color previousColor, Color color, float opacity)
+        {
+            if ((color.A < 255 && color.A > 0) || (opacity < 1f && opacity > 0 && color.A > 0))
+            {
+                byte pixelA = (byte)(color.A * opacity);
+                byte r = (byte)((color.R * pixelA / 255) + (previousColor.R * previousColor.A * (255 - pixelA) / (255 * 255)));
+                byte g = (byte)((color.G * pixelA / 255) + (previousColor.G * previousColor.A * (255 - pixelA) / (255 * 255)));
+                byte b = (byte)((color.B * pixelA / 255) + (previousColor.B * previousColor.A * (255 - pixelA) / (255 * 255)));
+                byte a = (byte)(pixelA + (previousColor.A * (255 - pixelA) / 255));
+                color = Color.FromArgb(a, r, g, b);
+            }
+            else
+            {
+                color = Color.FromArgb(color.A, color.R, color.G, color.B);
+            }
+
+            if (color.A > 0)
+            {
+                return color;
+            }
+
+            return previousColor;
+        }
+
         private static WriteableBitmap GeneratePreviewBitmap(
             IEnumerable<WriteableBitmap> layerBitmaps,
             IEnumerable<int> offsetsX,

+ 18 - 18
PixiEditor/Models/Tools/ShapeTool.cs

@@ -16,25 +16,9 @@ namespace PixiEditor.Models.Tools
             RequiresPreviewLayer = true;
             Cursor = Cursors.Cross;
             Toolbar = new BasicShapeToolbar();
-        }
-
-        // TODO: Add cache for lines 31, 32 (hopefully it would speed up calculation)
-        public abstract override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color);
-
-        protected IEnumerable<Coordinates> GetThickShape(IEnumerable<Coordinates> shape, int thickness)
-        {
-            List<Coordinates> output = new List<Coordinates>();
-            foreach (Coordinates item in shape)
-            {
-                output.AddRange(
-                    CoordinatesCalculator.RectangleToCoordinates(
-                        CoordinatesCalculator.CalculateThicknessCenter(item, thickness)));
-            }
-
-            return output.Distinct();
-        }
+        }
 
-        protected DoubleCords CalculateCoordinatesForShapeRotation(
+        public static DoubleCords CalculateCoordinatesForShapeRotation(
             Coordinates startingCords,
             Coordinates secondCoordinates)
         {
@@ -70,5 +54,21 @@ namespace PixiEditor.Models.Tools
 
             return new DoubleCords(startingCords, secondCoordinates);
         }
+
+        // TODO: Add cache for lines 31, 32 (hopefully it would speed up calculation)
+        public abstract override LayerChange[] Use(Layer layer, Coordinates[] coordinates, Color color);
+
+        protected IEnumerable<Coordinates> GetThickShape(IEnumerable<Coordinates> shape, int thickness)
+        {
+            List<Coordinates> output = new List<Coordinates>();
+            foreach (Coordinates item in shape)
+            {
+                output.AddRange(
+                    CoordinatesCalculator.RectangleToCoordinates(
+                        CoordinatesCalculator.CalculateThicknessCenter(item, thickness)));
+            }
+
+            return output.Distinct();
+        }
     }
 }

+ 1 - 0
PixiEditor/Models/Tools/ToolSettings/Toolbars/SelectToolToolbar.cs

@@ -8,6 +8,7 @@ namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
         public SelectToolToolbar()
         {
             Settings.Add(new EnumSetting<SelectionType>("SelectMode", "Selection type"));
+            Settings.Add(new EnumSetting<SelectionShape>("SelectShape", "Selection shape"));
         }
     }
 }

+ 22 - 16
PixiEditor/Models/Tools/Tools/CircleTool.cs

@@ -158,27 +158,15 @@ namespace PixiEditor.Models.Tools.Tools
             return outputCoordinates;
         }
 
-        private Coordinates[] FallbackRectangle(double halfWidth, double halfHeight, double centerX, double centerY)
+        public IEnumerable<Coordinates> CalculateFillForEllipse(IEnumerable<Coordinates> outlineCoordinates)
         {
-            List<Coordinates> coordinates = new List<Coordinates>();
-            for (double x = centerX - halfWidth; x <= centerX + halfWidth; x++)
-            {
-                coordinates.Add(new Coordinates((int)x, (int)(centerY - halfHeight)));
-                coordinates.Add(new Coordinates((int)x, (int)(centerY + halfHeight)));
-            }
+            List<Coordinates> finalCoordinates = new List<Coordinates>();
 
-            for (double y = centerY - halfHeight + 1; y <= centerY + halfHeight - 1; y++)
+            if (!outlineCoordinates.Any())
             {
-                coordinates.Add(new Coordinates((int)(centerX - halfWidth), (int)y));
-                coordinates.Add(new Coordinates((int)(centerX + halfWidth), (int)y));
+                return finalCoordinates;
             }
 
-            return coordinates.ToArray();
-        }
-
-        private IEnumerable<Coordinates> CalculateFillForEllipse(IEnumerable<Coordinates> outlineCoordinates)
-        {
-            List<Coordinates> finalCoordinates = new List<Coordinates>();
             int bottom = outlineCoordinates.Max(x => x.Y);
             int top = outlineCoordinates.Min(x => x.Y);
             for (int i = top + 1; i < bottom; i++)
@@ -195,6 +183,24 @@ namespace PixiEditor.Models.Tools.Tools
             return finalCoordinates;
         }
 
+        private Coordinates[] FallbackRectangle(double halfWidth, double halfHeight, double centerX, double centerY)
+        {
+            List<Coordinates> coordinates = new List<Coordinates>();
+            for (double x = centerX - halfWidth; x <= centerX + halfWidth; x++)
+            {
+                coordinates.Add(new Coordinates((int)x, (int)(centerY - halfHeight)));
+                coordinates.Add(new Coordinates((int)x, (int)(centerY + halfHeight)));
+            }
+
+            for (double y = centerY - halfHeight + 1; y <= centerY + halfHeight - 1; y++)
+            {
+                coordinates.Add(new Coordinates((int)(centerX - halfWidth), (int)y));
+                coordinates.Add(new Coordinates((int)(centerX + halfWidth), (int)y));
+            }
+
+            return coordinates.ToArray();
+        }
+
         private Coordinates[] GetRegionPoints(double x, double xc, double y, double yc)
         {
             Coordinates[] outputCoordinates = new Coordinates[4];

+ 27 - 4
PixiEditor/Models/Tools/Tools/SelectTool.cs

@@ -19,8 +19,9 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class SelectTool : ReadonlyTool
     {
+        private readonly RectangleTool rectangleTool = new RectangleTool();
+        private readonly CircleTool circleTool = new CircleTool();
         private IEnumerable<Coordinates> oldSelectedPoints;
-        private RectangleTool rectangleTool = new RectangleTool();
 
         private static Selection ActiveSelection { get => ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection; }
 
@@ -53,7 +54,7 @@ namespace PixiEditor.Models.Tools.Tools
 
         public override void Use(Coordinates[] pixels)
         {
-            Select(pixels);
+            Select(pixels, Toolbar.GetEnumSetting<SelectionShape>("SelectShape").Value);
         }
 
         public IEnumerable<Coordinates> GetRectangleSelectionForPoints(Coordinates start, Coordinates end)
@@ -63,6 +64,14 @@ namespace PixiEditor.Models.Tools.Tools
             return selection;
         }
 
+        public IEnumerable<Coordinates> GetCircleSelectionForPoints(Coordinates start, Coordinates end)
+        {
+            DoubleCords fixedCoordinates = ShapeTool.CalculateCoordinatesForShapeRotation(start, end);
+            List<Coordinates> selection = circleTool.CreateEllipse(fixedCoordinates.Coords1, fixedCoordinates.Coords2, 1).ToList();
+            selection.AddRange(circleTool.CalculateFillForEllipse(selection));
+            return selection;
+        }
+
         /// <summary>
         ///     Gets coordinates of every pixel in root layer.
         /// </summary>
@@ -81,9 +90,23 @@ namespace PixiEditor.Models.Tools.Tools
             return GetRectangleSelectionForPoints(new Coordinates(0, 0), new Coordinates(document.Width - 1, document.Height - 1));
         }
 
-        private void Select(Coordinates[] pixels)
+        private void Select(Coordinates[] pixels, SelectionShape shape)
         {
-            IEnumerable<Coordinates> selection = GetRectangleSelectionForPoints(pixels[^1], pixels[0]);
+            IEnumerable<Coordinates> selection;
+
+            if (shape == SelectionShape.Circle)
+            {
+                selection = GetCircleSelectionForPoints(pixels[^1], pixels[0]);
+            }
+            else if (shape == SelectionShape.Rectangle)
+            {
+                selection = GetRectangleSelectionForPoints(pixels[^1], pixels[0]);
+            }
+            else
+            {
+                throw new NotImplementedException($"Selection shape '{shape}' has not been implemented");
+            }
+
             ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(selection, SelectionType);
         }
     }

+ 59 - 1
PixiEditor/Models/UserPreferences/IPreferences.cs

@@ -7,26 +7,84 @@ namespace PixiEditor.Models.UserPreferences
     {
         public static IPreferences Current => ViewModelMain.Current.Preferences;
 
+        /// <summary>
+        /// Saves the preferences to be stored permanently.
+        /// </summary>
         public void Save();
 
-        public void AddCallback(string setting, Action<object> action);
+        /// <summary>
+        /// Adds a callback that will be executed when the setting called <paramref name="name"/> changes.
+        /// </summary>
+        /// <param name="name">The name of the setting</param>
+        /// <param name="action">The action that will be executed when the setting changes</param>
+        public void AddCallback(string name, Action<object> action);
 
+        /// <summary>
+        /// Adds a callback that will be executed when the setting called <paramref name="name"/> changes.
+        /// </summary>
+        /// <typeparam name="T">The <see cref="Type"/> of the setting</typeparam>
+        /// <param name="name">The name of the setting</param>
+        /// <param name="action">The action that will be executed when the setting changes</param>
+        public void AddCallback<T>(string name, Action<T> action);
+
+        /// <summary>
+        /// Initializes the preferences.
+        /// </summary>
         public void Init();
 
+        /// <summary>
+        /// Initializes the preferences using the <paramref name="path"/> and <paramref name="localPath"/>
+        /// </summary>
         public void Init(string path, string localPath);
 
+        /// <summary>
+        /// Updates a user preference and calls all added callbacks.
+        /// </summary>
+        /// <typeparam name="T">The <see cref="Type"/> of the setting</typeparam>
+        /// <param name="name">The name of the setting.</param>
+        /// <param name="value">The new value.</param>
         public void UpdatePreference<T>(string name, T value);
 
+        /// <summary>
+        /// Updates a editor setting and calls all added callbacks.
+        /// </summary>
+        /// <typeparam name="T">The <see cref="Type"/> of the setting</typeparam>
+        /// <param name="name">The name of the setting</param>
+        /// <param name="value">The new value</param>
         public void UpdateLocalPreference<T>(string name, T value);
 
 #nullable enable
 
+        /// <summary>
+        /// Reads the user preference that is called <paramref name="name"/>, if the setting does not exist the default of <typeparamref name="T"/> will be used
+        /// </summary>
+        /// <typeparam name="T">The <see cref="Type"/> of the setting</typeparam>
+        /// <param name="name">The name of the setting</param>
+        /// <returns>The setting or the default of <typeparamref name="T"/> if it has not been set yet</returns>
         public T? GetPreference<T>(string name);
 
+        /// <summary>
+        /// Reads the user preference that is called <paramref name="name"/>, if the setting does not exist the default of <paramref name="fallbackValue"/> will be used
+        /// </summary>
+        /// <typeparam name="T">The <see cref="Type"/> of the setting</typeparam>
+        /// <param name="name">The name of the setting</param>
+        /// <returns>The setting or the <paramref name="fallbackValue"/> if it has not been set yet</returns>
         public T? GetPreference<T>(string name, T? fallbackValue);
 
+        /// <summary>
+        /// Reads the editor setting that is called <paramref name="name"/>, if the setting does not exist the deafult of <typeparamref name="T"/> will be used
+        /// </summary>
+        /// <typeparam name="T">The <see cref="Type"/> of the setting</typeparam>
+        /// <param name="name">The name of the setting</param>
+        /// <returns>The editor setting or the default of <typeparamref name="T"/> if it has not been set yet</returns>
         public T? GetLocalPreference<T>(string name);
 
+        /// <summary>
+        /// Reads the editor setting that is called <paramref name="name"/>, if the setting does not exist the <paramref name="fallbackValue"/> will be used
+        /// </summary>
+        /// <typeparam name="T">The <see cref="Type"/> of the setting</typeparam>
+        /// <param name="name">The name of the setting</param>
+        /// <returns>The editor setting or the <paramref name="fallbackValue"/> if it has not been set yet</returns>
         public T? GetLocalPreference<T>(string name, T? fallbackValue);
     }
 }

+ 19 - 4
PixiEditor/Models/UserPreferences/PreferencesSettings.cs

@@ -94,15 +94,30 @@ namespace PixiEditor.Models.UserPreferences
 
         public Dictionary<string, List<Action<object>>> Callbacks { get; set; } = new Dictionary<string, List<Action<object>>>();
 
-        public void AddCallback(string setting, Action<object> action)
+        public void AddCallback(string name, Action<object> action)
         {
-            if (Callbacks.ContainsKey(setting))
+            if (action == null)
             {
-                Callbacks[setting].Add(action);
+                throw new ArgumentNullException(nameof(action));
+            }
+
+            if (Callbacks.ContainsKey(name))
+            {
+                Callbacks[name].Add(action);
                 return;
             }
 
-            Callbacks.Add(setting, new List<Action<object>>() { action });
+            Callbacks.Add(name, new List<Action<object>>() { action });
+        }
+
+        public void AddCallback<T>(string name, Action<T> action)
+        {
+            if (action == null)
+            {
+                throw new ArgumentNullException(nameof(action));
+            }
+
+            AddCallback(name, new Action<object>(o => action((T)o)));
         }
 
 #nullable enable

+ 22 - 0
PixiEditor/NotifyableObject.cs

@@ -11,6 +11,28 @@ namespace PixiEditor.Helpers
         [field: NonSerialized]
         public event PropertyChangedEventHandler PropertyChanged = (sender, e) => { };
 
+        public void AddPropertyChangedCallback(string propertyName, Action action)
+        {
+            if (action == null)
+            {
+                throw new ArgumentNullException(nameof(propertyName));
+            }
+
+            if (string.IsNullOrWhiteSpace(propertyName))
+            {
+                PropertyChanged += (_, _) => action();
+                return;
+            }
+
+            PropertyChanged += (sender, e) =>
+            {
+                if (e.PropertyName == propertyName)
+                {
+                    action();
+                }
+            };
+        }
+
         protected void RaisePropertyChanged(string property)
         {
             if (property != null)

+ 1 - 1
PixiEditor/PixiEditor.csproj

@@ -147,7 +147,7 @@
     <PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
     <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
     <PackageReference Include="PixiEditor.ColorPicker" Version="3.1.0" />
-    <PackageReference Include="PixiEditor.Parser" Version="1.1.1.1" />
+    <PackageReference Include="PixiEditor.Parser" Version="1.1.2.1" />
     <PackageReference Include="WriteableBitmapEx">
       <Version>1.6.7</Version>
     </PackageReference>

+ 3 - 3
PixiEditor/Properties/AssemblyInfo.cs

@@ -10,7 +10,7 @@ using System.Windows;
 [assembly: AssemblyConfiguration("")]
 [assembly: AssemblyCompany("PixiEditor")]
 [assembly: AssemblyProduct("PixiEditor")]
-[assembly: AssemblyCopyright("Copyright PixiEditor © 2018 - 2021")]
+[assembly: AssemblyCopyright("Copyright PixiEditor © 2017 - 2021")]
 [assembly: AssemblyTrademark("")]
 [assembly: AssemblyCulture("")]
 
@@ -50,5 +50,5 @@ using System.Windows;
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.1.5.0")]
-[assembly: AssemblyFileVersion("0.1.5.0")]
+[assembly: AssemblyVersion("0.1.6.0")]
+[assembly: AssemblyFileVersion("0.1.6.0")]

+ 7 - 0
PixiEditor/ViewModels/ISettableOwner.cs

@@ -0,0 +1,7 @@
+namespace PixiEditor.ViewModels
+{
+    public interface ISettableOwner<TOwner>
+    {
+        public void SetOwner(TOwner owner);
+    }
+}

+ 17 - 17
PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -162,23 +162,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Open((object)null);
         }
 
-        private void Owner_OnStartupEvent(object sender, System.EventArgs e)
-        {
-            var lastArg = Environment.GetCommandLineArgs().Last();
-            if (Importer.IsSupportedFile(lastArg) && File.Exists(lastArg))
-            {
-                Open(lastArg);
-            }
-            else
-            {
-                if (IPreferences.Current.GetPreference("ShowStartupWindow", true))
-                {
-                    OpenHelloTherePopup();
-                }
-            }
-        }
-
-        private void Open(string path)
+        public void Open(string path)
         {
             try
             {
@@ -203,6 +187,22 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
+        private void Owner_OnStartupEvent(object sender, System.EventArgs e)
+        {
+            var lastArg = Environment.GetCommandLineArgs().Last();
+            if (Importer.IsSupportedFile(lastArg) && File.Exists(lastArg))
+            {
+                Open(lastArg);
+            }
+            else
+            {
+                if (IPreferences.Current.GetPreference("ShowStartupWindow", true))
+                {
+                    OpenHelloTherePopup();
+                }
+            }
+        }
+
         private void Open(object property)
         {
             OpenFileDialog dialog = new OpenFileDialog

+ 50 - 0
PixiEditor/ViewModels/SubViewModels/Main/WindowViewModel.cs

@@ -0,0 +1,50 @@
+using AvalonDock.Layout;
+using PixiEditor.Helpers;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace PixiEditor.ViewModels.SubViewModels.Main
+{
+    public class WindowViewModel : SubViewModel<ViewModelMain>, ISettableOwner<ViewModelMain>
+    {
+        public MainWindow MainWindow { get; private set; }
+
+        public RelayCommand ShowAvalonDockWindowCommand { get; set; }
+
+        public WindowViewModel()
+            : this(null)
+        {
+        }
+
+        public WindowViewModel(ViewModelMain owner)
+            : base(owner)
+        {
+            ShowAvalonDockWindowCommand = new RelayCommand(ShowAvalonDockWindow);
+
+            MainWindow = (MainWindow)System.Windows.Application.Current?.MainWindow;
+        }
+
+        public void SetOwner(ViewModelMain owner)
+        {
+            Owner = owner;
+        }
+
+        private void ShowAvalonDockWindow(object parameter)
+        {
+            string id = (string)parameter;
+
+            var anchorables = new List<LayoutAnchorable>(MainWindow.LayoutRoot.Manager.Layout
+                    .Descendents()
+                    .OfType<LayoutAnchorable>());
+
+            foreach (var la in anchorables)
+            {
+                if (la.ContentId == id)
+                {
+                    la.Show();
+                    la.IsActive = true;
+                }
+            }
+        }
+    }
+}

+ 19 - 0
PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/GeneralSettings.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings
+{
+    public class GeneralSettings : SettingsGroup
+    {
+        private bool imagePreviewInTaskbar = GetPreference(nameof(ImagePreviewInTaskbar), false);
+
+        public bool ImagePreviewInTaskbar
+        {
+            get => imagePreviewInTaskbar;
+            set => RaiseAndUpdatePreference(ref imagePreviewInTaskbar, value);
+        }
+    }
+}

+ 2 - 0
PixiEditor/ViewModels/SubViewModels/UserPreferences/SettingsViewModel.cs

@@ -7,6 +7,8 @@ namespace PixiEditor.ViewModels.SubViewModels.UserPreferences
 {
     public class SettingsViewModel : SubViewModel<SettingsWindowViewModel>
     {
+        public GeneralSettings General { get; set; } = new GeneralSettings();
+
         public FileSettings File { get; set; } = new FileSettings();
 
         public UpdateSettings Update { get; set; } = new UpdateSettings();

+ 2 - 9
PixiEditor/ViewModels/ViewModelBase.cs

@@ -1,19 +1,12 @@
-using System.ComponentModel;
+using PixiEditor.Helpers;
 using System.Linq;
 using System.Windows;
 using System.Windows.Input;
 
 namespace PixiEditor.ViewModels
 {
-    public class ViewModelBase : INotifyPropertyChanged
+    public class ViewModelBase : NotifyableObject
     {
-        public event PropertyChangedEventHandler PropertyChanged = delegate { };
-
-        protected void RaisePropertyChanged(string property)
-        {
-            if (property != null) PropertyChanged(this, new PropertyChangedEventArgs(property));
-        }
-
         protected void CloseButton(object parameter)
         {
             ((Window)parameter).Close();

+ 40 - 0
PixiEditor/ViewModels/ViewModelMain.cs

@@ -24,6 +24,9 @@ namespace PixiEditor.ViewModels
 {
     public class ViewModelMain : ViewModelBase
     {
+        private string actionDisplay;
+        private bool overrideActionDisplay;
+
         public static ViewModelMain Current { get; set; }
 
         public Action CloseAction { get; set; }
@@ -70,8 +73,40 @@ namespace PixiEditor.ViewModels
 
         public StylusViewModel StylusSubViewModel { get; set; }
 
+        public WindowViewModel WindowSubViewModel { get; set; }
+
         public IPreferences Preferences { get; set; }
 
+        public string ActionDisplay
+        {
+            get
+            {
+                if (OverrideActionDisplay)
+                {
+                    return actionDisplay;
+                }
+
+                return BitmapManager.SelectedTool.ActionDisplay;
+            }
+            set
+            {
+                actionDisplay = value;
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets a value indicating whether a custom action display should be used. If false the action display of the selected tool will be used.
+        /// </summary>
+        public bool OverrideActionDisplay
+        {
+            get => overrideActionDisplay;
+            set
+            {
+                SetProperty(ref overrideActionDisplay, value);
+                RaisePropertyChanged(nameof(ActionDisplay));
+            }
+        }
+
         public bool IsDebug
         {
             get =>
@@ -113,6 +148,9 @@ namespace PixiEditor.ViewModels
             DiscordViewModel = new DiscordViewModel(this, "764168193685979138");
             UpdateSubViewModel = new UpdateViewModel(this);
 
+            WindowSubViewModel = services.GetService<WindowViewModel>();
+            WindowSubViewModel?.SetOwner(this);
+
             StylusSubViewModel = services.GetService<StylusViewModel>();
             StylusSubViewModel?.SetOwner(this);
 
@@ -176,6 +214,8 @@ namespace PixiEditor.ViewModels
                         new Shortcut(Key.F1, MiscSubViewModel.OpenShortcutWindowCommand, "Open the shortcut window", true)));
 
             BitmapManager.PrimaryColor = ColorsSubViewModel.PrimaryColor;
+
+            BitmapManager.AddPropertyChangedCallback(nameof(BitmapManager.SelectedTool), () => { if (!OverrideActionDisplay) RaisePropertyChanged(nameof(ActionDisplay)); });
         }
 
         /// <summary>

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

@@ -6,7 +6,7 @@
         xmlns:dataHolders="clr-namespace:PixiEditor.Models.DataHolders" xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
         xmlns:sys="clr-namespace:System;assembly=System.Runtime"
         xmlns:uc="clr-namespace:PixiEditor.Views.UserControls"
-        mc:Ignorable="d"
+        mc:Ignorable="d" ShowInTaskbar="False"
         Title="Hello there!" Height="662" Width="632"
         WindowStyle="None" WindowStartupLocation="CenterScreen">
 

+ 2 - 0
PixiEditor/Views/Dialogs/SettingsWindow.xaml

@@ -57,6 +57,8 @@
             <Grid Visibility="{Binding SelectedCategory, Converter={StaticResource EqualityBoolToVisibilityConverter},
             ConverterParameter='General'}">
                 <StackPanel Orientation="Vertical">
+                    <CheckBox Content="Show Document Preview in Taskbar" Margin="25,30,0,0"
+                                  IsChecked="{Binding SettingsSubViewModel.General.ImagePreviewInTaskbar}"/>
                     <Label Content="File" Style="{StaticResource Header1}"/>
                     <StackPanel Orientation="Vertical" Margin="50 0 50 0">
                         <CheckBox Content="Show Startup Window" 

+ 29 - 62
PixiEditor/Views/MainWindow.xaml

@@ -3,6 +3,7 @@
         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:local="clr-namespace:PixiEditor"
         xmlns:vm="clr-namespace:PixiEditor.ViewModels"
         xmlns:vws="clr-namespace:PixiEditor.Views"
         xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
@@ -14,7 +15,8 @@
         xmlns:avalonDockTheme="clr-namespace:PixiEditor.Styles.AvalonDock" d:DataContext="{d:DesignInstance Type=vm:ViewModelMain}" xmlns:dataHolders="clr-namespace:PixiEditor.Models.DataHolders"
         mc:Ignorable="d" WindowStyle="None" Initialized="MainWindow_Initialized"
         Title="PixiEditor" Name="mainWindow" Height="1000" Width="1600" Background="{StaticResource MainColor}"
-        WindowStartupLocation="CenterScreen" WindowState="Maximized">
+        WindowStartupLocation="CenterScreen" WindowState="Maximized"
+        AllowDrop="True" Drop="MainWindow_Drop">
     <WindowChrome.WindowChrome>
         <WindowChrome CaptionHeight="32"
                       ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
@@ -134,8 +136,10 @@
                 <MenuItem Header="_View">
                     <MenuItem Header="_Show Grid Lines" IsChecked="{Binding ViewportSubViewModel.GridLinesEnabled, Mode=TwoWay}"
                               IsCheckable="True" InputGestureText="Ctrl+`"/>
-                    <MenuItem Header="_Open Startup Window" ToolTip="Hello there!"
+                    <MenuItem Header="Open _Startup Window" ToolTip="Hello there!"
                               Command="{Binding MiscSubViewModel.OpenHelloThereWindowCommand}"/>
+                    <MenuItem Header="Open _Navigation Window"
+                              Command="{Binding WindowSubViewModel.ShowAvalonDockWindowCommand}" CommandParameter="navigation"/>
                 </MenuItem>
                 <MenuItem Header="_Help">
                     <MenuItem Header="_Documentation" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
@@ -203,16 +207,6 @@
             </ItemsControl>
         </StackPanel>
         <Grid Grid.Column="1" Grid.Row="2" Background="#303030">
-            <Grid.ContextMenu>
-                <ContextMenu>
-                    <MenuItem Header="_Select All" Command="{Binding SelectionSubViewModel.SelectAllCommand}" InputGestureText="Ctrl+A" />
-                    <MenuItem Header="_Deselect" Command="{Binding SelectionSubViewModel.DeselectCommand}" InputGestureText="Ctrl+D" />
-                    <Separator/>
-                    <MenuItem Header="_Cut" Command="{Binding ClipboardSubViewModel.CutCommand}" InputGestureText="Ctrl+X" />
-                    <MenuItem Header="_Copy" Command="{Binding ClipboardSubViewModel.CopyCommand}" InputGestureText="Ctrl+C" />
-                    <MenuItem Header="_Paste" Command="{Binding ClipboardSubViewModel.PasteCommand}" InputGestureText="Ctrl+V" />
-                </ContextMenu>
-            </Grid.ContextMenu>
             <Grid>
                 <DockingManager ActiveContent="{Binding BitmapManager.ActiveDocument, Mode=TwoWay}" 
                                            DocumentsSource="{Binding BitmapManager.Documents}">
@@ -232,7 +226,7 @@
                     <DockingManager.LayoutItemTemplateSelector>
                         <ui:DocumentsTemplateSelector>
                             <ui:DocumentsTemplateSelector.DocumentsViewTemplate>
-                                <DataTemplate DataType="{x:Type vm:ViewModelMain}">
+                                <DataTemplate DataType="{x:Type dataHolders:Document}">
                                     <usercontrols:DrawingViewPort
                                         ZoomPercentage="{Binding ZoomPercentage}"
                                         RecenterZoombox="{Binding RecenterZoombox}"
@@ -250,6 +244,16 @@
                                                 <i:InvokeCommandAction Command="{Binding SetAsActiveOnClickCommand}"/>
                                             </i:EventTrigger>
                                         </i:Interaction.Triggers>
+                                        <usercontrols:DrawingViewPort.ContextMenu>
+                                            <ContextMenu>
+                                                <MenuItem Header="_Select All" Command="{Binding XamlAccesibleViewModel.SelectionSubViewModel.SelectAllCommand}" InputGestureText="Ctrl+A" />
+                                                <MenuItem Header="_Deselect" Command="{Binding XamlAccesibleViewModel.SelectionSubViewModel.DeselectCommand}" InputGestureText="Ctrl+D" />
+                                                <Separator/>
+                                                <MenuItem Header="_Cut" Command="{Binding XamlAccesibleViewModel.ClipboardSubViewModel.CutCommand}" InputGestureText="Ctrl+X" />
+                                                <MenuItem Header="_Copy" Command="{Binding XamlAccesibleViewModel.ClipboardSubViewModel.CopyCommand}" InputGestureText="Ctrl+C" />
+                                                <MenuItem Header="_Paste" Command="{Binding XamlAccesibleViewModel.ClipboardSubViewModel.PasteCommand}" InputGestureText="Ctrl+V" />
+                                            </ContextMenu>
+                                        </usercontrols:DrawingViewPort.ContextMenu>
                                     </usercontrols:DrawingViewPort>
                                 </DataTemplate>
                             </ui:DocumentsTemplateSelector.DocumentsViewTemplate>
@@ -284,54 +288,9 @@
                                     <avalondock:LayoutAnchorable ContentId="swatches" Title="Swatches" CanHide="False"
                                                                  CanClose="False" CanAutoHide="False"
                                                                  CanDockAsTabbedDocument="False" CanFloat="True">
-                                        <ScrollViewer HorizontalScrollBarVisibility="Disabled"
-                                              VerticalScrollBarVisibility="Auto">
-                                            <ItemsControl ItemsSource="{Binding BitmapManager.ActiveDocument.Swatches}">
-                                                <ItemsControl.ItemsPanel>
-                                                    <ItemsPanelTemplate>
-                                                        <WrapPanel Margin="10,10,0,10" Orientation="Horizontal"
-                                                           VerticalAlignment="Top" HorizontalAlignment="Left" />
-                                                    </ItemsPanelTemplate>
-                                                </ItemsControl.ItemsPanel>
-                                                <ItemsControl.ItemTemplate>
-                                                    <DataTemplate>
-                                                        <Grid Width="45" Height="45" Margin="0 5 5 5">
-                                                            <Border CornerRadius="5.5" Width="44" Height="44">
-                                                                <Border.Background>
-                                                                    <ImageBrush ImageSource="../Images/transparentbg.png"
-                                                                        Stretch="UniformToFill">
-                                                                        <ImageBrush.RelativeTransform>
-                                                                            <ScaleTransform ScaleX="6" ScaleY="6" CenterX="0.5"
-                                                                                    CenterY="0.5" />
-                                                                        </ImageBrush.RelativeTransform>
-                                                                    </ImageBrush>
-                                                                </Border.Background>
-                                                            </Border>
-                                                            <Border CornerRadius="5.5" BorderThickness="0 0 0 0.1" BorderBrush="White" Cursor="Hand">
-                                                                <Border.Background>
-                                                                    <SolidColorBrush Color="{Binding}" />
-                                                                </Border.Background>
-                                                            </Border>
-                                                            <i:Interaction.Triggers>
-                                                                <i:EventTrigger EventName="MouseDown">
-                                                                    <i:InvokeCommandAction
-                                                                Command="{Binding
-                                                                    RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ColorsSubViewModel.SelectColorCommand}"
-                                                                CommandParameter="{Binding}" />
-                                                                </i:EventTrigger>
-                                                            </i:Interaction.Triggers>
-                                                            <Grid.ContextMenu>
-                                                                <ContextMenu>
-                                                                    <MenuItem Header="Remove" Foreground="White"
-                                                                      Command="{Binding ColorsSubViewModel.RemoveSwatchCommand}"
-                                                                      CommandParameter="{Binding}" />
-                                                                </ContextMenu>
-                                                            </Grid.ContextMenu>
-                                                        </Grid>
-                                                    </DataTemplate>
-                                                </ItemsControl.ItemTemplate>
-                                            </ItemsControl>
-                                        </ScrollViewer>
+                                        <usercontrols:SwatchesView
+                                            SelectSwatchCommand="{Binding ColorsSubViewModel.SelectColorCommand}" RemoveSwatchCommand="{Binding ColorsSubViewModel.RemoveSwatchCommand}"
+                                            Swatches="{Binding BitmapManager.ActiveDocument.Swatches}"/>
                                     </avalondock:LayoutAnchorable>
                                 </LayoutAnchorablePane>
                                 <LayoutAnchorablePane>
@@ -431,6 +390,14 @@
                                         </Grid>
                                     </LayoutAnchorable>
                                 </LayoutAnchorablePane>
+                                <LayoutAnchorablePane>
+                                    <LayoutAnchorable ContentId="navigation" Title="Navigation" 
+                                                      CanHide="True" CanAutoHide="False"
+                                                      CanDockAsTabbedDocument="False" CanFloat="True">
+                                        <usercontrols:PreviewWindow Document="{Binding BitmapManager.ActiveDocument}"
+                                                                    PrimaryColor="{Binding ColorsSubViewModel.PrimaryColor, Mode=TwoWay}"/>
+                                    </LayoutAnchorable>
+                                </LayoutAnchorablePane>
                             </LayoutAnchorablePaneGroup>
                         </LayoutPanel>
                     </avalondock:LayoutRoot>
@@ -465,7 +432,7 @@
                 <ColumnDefinition Width="290"/>
             </Grid.ColumnDefinitions>
             <DockPanel>
-                <TextBlock Text="{Binding BitmapManager.SelectedTool.ActionDisplay}" Foreground="White" FontSize="15" Margin="10,0,0,0" VerticalAlignment="Center"/>
+                <TextBlock Text="{Binding ActionDisplay}" Foreground="White" FontSize="15" Margin="10,0,0,0" VerticalAlignment="Center"/>
                 <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
                     <TextBlock Text="X:" Foreground="White" FontSize="16"/>
                     <TextBlock Margin="4,0,10,0" Text="{Binding BitmapManager.ActiveDocument.MouseXOnCanvas, Converter={StaticResource DoubleToIntConverter}}" Foreground="White" FontSize="16"/>

+ 62 - 3
PixiEditor/Views/MainWindow.xaml.cs

@@ -9,6 +9,9 @@ using PixiEditor.ViewModels.SubViewModels.Main;
 using System.Diagnostics;
 using System.Linq;
 using PixiEditor.Views.Dialogs;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using PixiEditor.Models.DataHolders;
 
 namespace PixiEditor
 {
@@ -17,24 +20,46 @@ namespace PixiEditor
     /// </summary>
     public partial class MainWindow : Window
     {
+        private static WriteableBitmap pixiEditorLogo;
+
+        private PreferencesSettings preferences;
+
         public new ViewModelMain DataContext { get => (ViewModelMain)base.DataContext; set => base.DataContext = value; }
 
         public MainWindow()
         {
-            InitializeComponent();
+            preferences = new PreferencesSettings();
 
             IServiceCollection services = new ServiceCollection()
-                .AddSingleton<IPreferences>(new PreferencesSettings())
-                .AddSingleton(new StylusViewModel());
+                .AddSingleton<IPreferences>(preferences)
+                .AddSingleton<StylusViewModel>()
+                .AddSingleton<WindowViewModel>();
 
             DataContext = new ViewModelMain(services.BuildServiceProvider());
 
+            InitializeComponent();
+
+            pixiEditorLogo = BitmapFactory.FromResource(@"/Images/PixiEditorLogo.png");
+
             StateChanged += MainWindowStateChangeRaised;
             Activated += MainWindow_Activated;
 
             MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight;
             DataContext.CloseAction = Close;
             Application.Current.ShutdownMode = ShutdownMode.OnMainWindowClose;
+
+            DataContext.BitmapManager.DocumentChanged += BitmapManager_DocumentChanged;
+            preferences.AddCallback<bool>("ImagePreviewInTaskbar", x =>
+            {
+                if (x)
+                {
+                    UpdateTaskbarIcon(DataContext.BitmapManager.ActiveDocument);
+                }
+                else
+                {
+                    UpdateTaskbarIcon(null);
+                }
+            });
         }
 
         protected override void OnClosing(CancelEventArgs e)
@@ -49,6 +74,30 @@ namespace PixiEditor
             Application.Current.Windows.OfType<HelloTherePopup>().ToList().ForEach(x => { if (!x.IsClosing) x.Close(); });
         }
 
+        private void BitmapManager_DocumentChanged(object sender, Models.Events.DocumentChangedEventArgs e)
+        {
+            if (preferences.GetPreference("ImagePreviewInTaskbar", false))
+            {
+                UpdateTaskbarIcon(e.NewDocument);
+            }
+        }
+
+        private void UpdateTaskbarIcon(Document document)
+        {
+            if (document?.PreviewImage == null)
+            {
+                Icon = pixiEditorLogo;
+                return;
+            }
+
+            var previewCopy = document.PreviewImage.Clone()
+                .Resize(512, 512, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
+
+            previewCopy.Blit(new Rect(256, 256, 256, 256), pixiEditorLogo, new Rect(0, 0, 512, 512));
+
+            Icon = previewCopy;
+        }
+
         private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
         {
             e.CanExecute = true;
@@ -97,5 +146,15 @@ namespace PixiEditor
         {
             AppDomain.CurrentDomain.UnhandledException += (sender, e) => Helpers.CrashHelper.SaveCrashInfo((Exception)e.ExceptionObject);
         }
+
+        private void MainWindow_Drop(object sender, DragEventArgs e)
+        {
+            if (e.Data.GetDataPresent(DataFormats.FileDrop))
+            {
+                string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);
+
+                DataContext.FileSubViewModel.Open(files[0]);
+            }
+        }
     }
 }

+ 24 - 0
PixiEditor/Views/UserControls/PrependTextBlock.xaml

@@ -0,0 +1,24 @@
+<UserControl x:Class="PixiEditor.Views.UserControls.PrependTextBlock"
+             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"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800"
+             x:Name="uc">
+    <StackPanel Orientation="Horizontal">
+        <TextBlock Text="{Binding Prepend, ElementName=uc}"/>
+        
+        <TextBlock>
+            <TextBlock.Text>
+                <PriorityBinding>
+                    <Binding Path="Text" ElementName="uc"/>
+                    <Binding Path="Content" ElementName="uc"/>
+                </PriorityBinding>
+            </TextBlock.Text>
+        </TextBlock>
+
+        <TextBlock Text="{Binding Append, ElementName=uc}"/>
+    </StackPanel>
+</UserControl>

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

@@ -0,0 +1,73 @@
+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
+{
+    /// <summary>
+    /// Interaction logic for PrependTextBlock.xaml
+    /// </summary>
+    public partial class PrependTextBlock : UserControl
+    {
+        public static readonly DependencyProperty TextProperty =
+            DependencyProperty.Register(nameof(Text), typeof(string), typeof(PrependTextBlock));
+
+        public string Text
+        {
+            get => (string)GetValue(TextProperty);
+            set => SetValue(TextProperty, value);
+        }
+
+        public static readonly DependencyProperty PrependProperty =
+            DependencyProperty.Register(nameof(Prepend), typeof(string), typeof(PrependTextBlock));
+
+        public string Prepend
+        {
+            get => (string)GetValue(PrependProperty);
+            set => SetValue(PrependProperty, value);
+        }
+
+        public static readonly DependencyProperty AppendProperty =
+            DependencyProperty.Register(nameof(Append), typeof(string), typeof(PrependTextBlock));
+
+        public string Append
+        {
+            get => (string)GetValue(AppendProperty);
+            set => SetValue(AppendProperty, value);
+        }
+
+        public static readonly DependencyProperty PrependColorProperty =
+            DependencyProperty.Register(nameof(PrependColor), typeof(Brush), typeof(PrependTextBlock));
+
+        public Brush PrependColor
+        {
+            get => (Brush)GetValue(PrependColorProperty);
+            set => SetValue(PrependColorProperty, value);
+        }
+
+        public static readonly DependencyProperty AppendColorProperty =
+            DependencyProperty.Register(nameof(AppendColor), typeof(Brush), typeof(PrependTextBlock));
+
+        public Brush AppendColor
+        {
+            get => (Brush)GetValue(AppendColorProperty);
+            set => SetValue(AppendColorProperty, value);
+        }
+
+        public PrependTextBlock()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 152 - 0
PixiEditor/Views/UserControls/PreviewWindow.xaml

@@ -0,0 +1,152 @@
+<UserControl x:Class="PixiEditor.Views.UserControls.PreviewWindow"
+             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"
+             xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+             mc:Ignorable="d" 
+             d:DesignHeight="400" d:DesignWidth="400" x:Name="uc"
+             Foreground="White" Background="Transparent">
+
+    <UserControl.Resources>
+        <BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
+        <converters:NotNullToVisibiltyConverter x:Key="NullToVisibiltyConverter"/>
+    </UserControl.Resources>
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="*"/>
+            <RowDefinition Height="5"/>
+            <RowDefinition Height="Auto"/>
+        </Grid.RowDefinitions>
+        
+        <Viewbox Margin="30" VerticalAlignment="Top">
+            <Grid x:Name="imageGrid"
+              Visibility="{Binding Document, Converter={StaticResource NullToVisibiltyConverter}, ElementName=uc}"
+              Height="{Binding Document.Height, ElementName=uc}" Width="{Binding Document.Width, ElementName=uc}"
+              Background="{Binding SelectedItem.Tag, ElementName=backgroundComboBox}" d:Width="8" d:Height="8">
+                <ItemsControl ItemsSource="{Binding Document.Layers, ElementName=uc}">
+                    <ItemsControl.ItemsPanel>
+                        <ItemsPanelTemplate>
+                            <Grid/>
+                        </ItemsPanelTemplate>
+                    </ItemsControl.ItemsPanel>
+                    <ItemsControl.ItemTemplate>
+                        <DataTemplate>
+                            <Image VerticalAlignment="Top" HorizontalAlignment="Left" Source="{Binding LayerBitmap}"
+                                               Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}"
+                                               RenderOptions.BitmapScalingMode="NearestNeighbor" Stretch="Uniform"
+                                               Opacity="{Binding Opacity}"
+                                               Width="{Binding Width}" Height="{Binding Height}" Margin="{Binding Offset}"  />
+                        </DataTemplate>
+                    </ItemsControl.ItemTemplate>
+                </ItemsControl>
+                <Border x:Name="colorCursor" Width="1" Height="1"
+                    Margin="{Binding ColorCursorPosition, ElementName=uc}"
+                    HorizontalAlignment="Left" VerticalAlignment="Top"
+                    BorderBrush="Black" BorderThickness=".1"
+                    Visibility="{Binding IsMouseOver, ElementName=imageGrid, Converter={StaticResource BoolToVisibilityConverter}}">
+                    <Border BorderThickness=".1" BorderBrush="White"/>
+                </Border>
+            </Grid>
+        </Viewbox>
+
+        <Grid Grid.Row="1">
+            <Grid.Background>
+                <SolidColorBrush Color="{Binding ColorCursorColor, ElementName=uc, FallbackValue=Black}"/>
+            </Grid.Background>
+        </Grid>
+        <StackPanel Grid.Row="2" Orientation="Horizontal" MinHeight="30"
+                    Background="{StaticResource MainColor}" MaxHeight="60">
+            <StackPanel.Resources>
+                <Style TargetType="local:PrependTextBlock">
+                    <Setter Property="VerticalAlignment" Value="Center"/>
+                </Style>
+            </StackPanel.Resources>
+
+            <local:PrependTextBlock Prepend=" X: " Text="{Binding ColorCursorPosition.Left, ElementName=uc}"/>
+            <local:PrependTextBlock Prepend=" Y: " Text="{Binding ColorCursorPosition.Top, ElementName=uc}"/>
+
+            <Grid Width="15"/>
+
+            <local:PrependTextBlock Prepend=" R: " Text="{Binding ColorCursorColor.R, ElementName=uc}"/>
+            <local:PrependTextBlock Prepend=" G: " Text="{Binding ColorCursorColor.G, ElementName=uc}"/>
+            <local:PrependTextBlock Prepend=" B: " Text="{Binding ColorCursorColor.B, ElementName=uc}"/>
+            <local:PrependTextBlock Prepend=" A: " Text="{Binding ColorCursorColor.A, ElementName=uc}"/>
+
+            <local:PrependTextBlock Prepend="  (" Text="{Binding ColorCursorColor, ElementName=uc, FallbackValue=#00000000}" Append=")"/>
+        </StackPanel>
+
+
+        <StackPanel Visibility="{Binding OptionsOpen, ElementName=uc, Converter={StaticResource BoolToVisibilityConverter}, FallbackValue=Hidden}"
+                    Background="{StaticResource AccentColor}" Grid.RowSpan="3">
+            <Grid Margin="5">
+                <Grid.ColumnDefinitions>
+                    <ColumnDefinition Width="Auto"/>
+                    <ColumnDefinition Width="*"/>
+                    <ColumnDefinition Width="50"/>
+                </Grid.ColumnDefinitions>
+                <TextBlock Text="Background: " VerticalAlignment="Center"/>
+                <ComboBox SelectedIndex="1" x:Name="backgroundComboBox" Grid.Column="1">
+                    <ComboBoxItem Content="None">
+                        <ComboBoxItem.Tag>
+                            <SolidColorBrush Color="Transparent"/>
+                        </ComboBoxItem.Tag>
+                    </ComboBoxItem>
+                    <ComboBoxItem Content="Checkered">
+                        <ComboBoxItem.Tag>
+                            <ImageBrush ImageSource="/Images/transparentbg.png"/>
+                        </ComboBoxItem.Tag>
+                    </ComboBoxItem>
+                    <ComboBoxItem Content="Black">
+                        <ComboBoxItem.Tag>
+                            <SolidColorBrush Color="Black"/>
+                        </ComboBoxItem.Tag>
+                    </ComboBoxItem>
+                    <ComboBoxItem Content="White">
+                        <ComboBoxItem.Tag>
+                            <SolidColorBrush Color="White"/>
+                        </ComboBoxItem.Tag>
+                    </ComboBoxItem>
+                </ComboBox>
+            </Grid>
+        </StackPanel>
+
+        <ToggleButton Height="28" VerticalAlignment="Top" HorizontalAlignment="Right"
+                 Margin="5" Background="Transparent" BorderThickness="0" IsChecked="{Binding OptionsOpen, ElementName=uc, Mode=TwoWay, FallbackValue=True}">
+            <ToggleButton.Style>
+                <Style TargetType="ToggleButton">
+                    <Setter Property="Template">
+                        <Setter.Value>
+                            <ControlTemplate TargetType="ToggleButton">
+                                <Border BorderBrush="{TemplateBinding BorderBrush}" 
+                                        Background="{TemplateBinding Background}">
+                                    <ContentPresenter HorizontalAlignment="Center"
+                                              VerticalAlignment="Center"/>
+                                </Border>
+                            </ControlTemplate>
+                        </Setter.Value>
+                    </Setter>
+                    <Style.Triggers>
+                        <Trigger Property="IsChecked" Value="False">
+                            <Setter Property="Foreground" Value="LightGray"/>
+                        </Trigger>
+                        <Trigger Property="IsChecked" Value="True">
+                            <Setter Property="Foreground" Value="White"/>
+                        </Trigger>
+                        <Trigger Property="IsMouseOver" Value="True">
+                            <Setter Property="Foreground" Value="Gray"/>
+                        </Trigger>
+                    </Style.Triggers>
+                </Style>
+            </ToggleButton.Style>
+            <Viewbox>
+                <Grid>
+                    <Path Stroke="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType=ToggleButton}, Mode=OneWay}" StrokeThickness="1.5" Data="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"/>
+                    <Ellipse Stroke="{Binding Foreground, RelativeSource={RelativeSource FindAncestor, AncestorType=ToggleButton}, Mode=OneWay}" StrokeThickness="1.5" Height="8" Width="8" HorizontalAlignment="Center" VerticalAlignment="Stretch"/>
+                </Grid>
+            </Viewbox>
+        </ToggleButton>
+
+    </Grid>
+</UserControl>

+ 142 - 0
PixiEditor/Views/UserControls/PreviewWindow.xaml.cs

@@ -0,0 +1,142 @@
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.ImageManipulation;
+using PixiEditor.ViewModels;
+using System.Linq;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace PixiEditor.Views.UserControls
+{
+    /// <summary>
+    /// Interaction logic for PreviewWindow.xaml
+    /// </summary>
+    public partial class PreviewWindow : UserControl
+    {
+        public static readonly DependencyProperty DocumentProperty =
+            DependencyProperty.Register(nameof(Document), typeof(Document), typeof(PreviewWindow));
+
+        public Document Document
+        { 
+            get => (Document)GetValue(DocumentProperty);
+            set => SetValue(DocumentProperty, value);
+        }
+
+        public static readonly DependencyProperty ColorCursorPositionProperty =
+            DependencyProperty.Register(nameof(ColorCursorPosition), typeof(Thickness), typeof(PreviewWindow));
+
+        public Thickness ColorCursorPosition
+        {
+            get => (Thickness)GetValue(ColorCursorPositionProperty);
+            private set => SetValue(ColorCursorPositionProperty, value);
+        }
+
+        public static readonly DependencyProperty ColorCursorColorProperty =
+            DependencyProperty.Register(nameof(ColorCursorColor), typeof(Color), typeof(PreviewWindow));
+
+        public Color ColorCursorColor
+        {
+            get => (Color)GetValue(ColorCursorColorProperty);
+            set => SetValue(ColorCursorColorProperty, value);
+        }
+
+        public static readonly DependencyProperty PrimaryColorProperty =
+            DependencyProperty.Register(nameof(PrimaryColor), typeof(Color), typeof(PreviewWindow));
+
+        public Color PrimaryColor
+        {
+            get => (Color)GetValue(PrimaryColorProperty);
+            set => SetValue(PrimaryColorProperty, value);
+        }
+
+        public static readonly DependencyProperty OptionsOpenProperty =
+            DependencyProperty.Register(nameof(OptionsOpen), typeof(bool), typeof(PreviewWindow));
+
+        public bool OptionsOpen
+        {
+            get => (bool)GetValue(OptionsOpenProperty);
+            set => SetValue(OptionsOpenProperty, value);
+        }
+
+        public PreviewWindow()
+        {
+            InitializeComponent();
+
+            imageGrid.MouseMove += ImageGrid_MouseMove;
+            imageGrid.MouseRightButtonDown += ImageGrid_MouseRightButtonDown;
+            imageGrid.MouseEnter += ImageGrid_MouseEnter;
+            imageGrid.MouseLeave += ImageGrid_MouseLeave;
+        }
+
+        private void ImageGrid_MouseLeave(object sender, MouseEventArgs e)
+        {
+            if (ViewModelMain.Current != null)
+            {
+                ViewModelMain.Current.OverrideActionDisplay = false;
+            }
+        }
+
+        private void ImageGrid_MouseEnter(object sender, MouseEventArgs e)
+        {
+            if (ViewModelMain.Current != null)
+            {
+                ViewModelMain.Current.ActionDisplay = "Right-click to pick color, Shift-right-click to copy color to clipboard";
+                ViewModelMain.Current.OverrideActionDisplay = true;
+            }
+        }
+
+        private void ImageGrid_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
+        {
+            if (Keyboard.IsKeyDown(Key.LeftShift))
+            {
+                CopyColorToClipboard();
+            }
+            else
+            {
+                CopyColorToPrimary();
+            }
+        }
+
+        private void CopyColorToPrimary()
+        {
+            PrimaryColor = ColorCursorColor;
+        }
+
+        private void CopyColorToClipboard()
+        {
+            if (ColorCursorColor.A == 255)
+            {
+                Clipboard.SetText(string.Format("#{0:X2}{1:X2}{2:X2}", ColorCursorColor.R, ColorCursorColor.G, ColorCursorColor.B));
+            }
+            else
+            {
+                Clipboard.SetText(ColorCursorColor.ToString());
+            }
+        }
+
+        private void ImageGrid_MouseMove(object sender, MouseEventArgs e)
+        {
+            if (Document == null)
+            {
+                return;
+            }
+
+            Point mousePos = e.GetPosition(imageGrid);
+
+            int x = (int)mousePos.X;
+            int y = (int)mousePos.Y;
+
+            Thickness newPos = new Thickness(x, y, 0, 0);
+
+            if (ColorCursorPosition == newPos)
+            {
+                return;
+            }
+
+            ColorCursorPosition = newPos;
+
+            ColorCursorColor = BitmapUtils.GetColorAtPointCombined(x, y, Document.Layers.ToArray());
+        }
+    }
+}

+ 66 - 0
PixiEditor/Views/UserControls/SwatchesView.xaml

@@ -0,0 +1,66 @@
+<UserControl x:Class="PixiEditor.Views.UserControls.SwatchesView"
+             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" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
+             mc:Ignorable="d" Name="uc"
+             d:DesignHeight="450" d:DesignWidth="300">
+    <ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
+        <ItemsControl ItemsSource="{Binding Swatches, ElementName=uc}">
+            <d:ItemsControl.ItemsSource>
+                <x:Array Type="{x:Type Color}">
+                    <Color R="0" G="0" B="0" A="255"/>
+                    <Color R="255" G="255" B="255" A="255"/>
+                    <Color R="255" G="255" B="255" A="150"/>
+                    <Color R="255" G="255" B="255" A="0"/>
+                    <Color R="255" G="0" B="0" A="255"/>
+                    <Color R="0" G="255" B="0" A="255"/>
+                    <Color R="0" G="0" B="255" A="255"/>
+                </x:Array>
+            </d:ItemsControl.ItemsSource>
+            <ItemsControl.ItemsPanel>
+                <ItemsPanelTemplate>
+                    <WrapPanel Margin="10,10,0,10" Orientation="Horizontal"
+                               HorizontalAlignment="Center" VerticalAlignment="Top"/>
+                </ItemsPanelTemplate>
+            </ItemsControl.ItemsPanel>
+            <ItemsControl.ItemTemplate>
+                <DataTemplate>
+                    <Grid Width="45" Height="45" Margin="0 5 5 5">
+                        <Border CornerRadius="5.5" Width="44" Height="44">
+                            <Border.Background>
+                                <ImageBrush ImageSource="../../Images/transparentbg.png"
+                                                                        Stretch="UniformToFill">
+                                    <ImageBrush.RelativeTransform>
+                                        <ScaleTransform ScaleX="6" ScaleY="6" CenterX="0.5"
+                                                                                    CenterY="0.5" />
+                                    </ImageBrush.RelativeTransform>
+                                </ImageBrush>
+                            </Border.Background>
+                        </Border>
+                        <Border CornerRadius="5.5" BorderThickness="0 0 0 0.1" BorderBrush="White" Cursor="Hand">
+                            <Border.Background>
+                                <SolidColorBrush Color="{Binding}" />
+                            </Border.Background>
+                        </Border>
+                        <i:Interaction.Triggers>
+                            <i:EventTrigger EventName="MouseDown">
+                                <i:InvokeCommandAction 
+                                    Command="{Binding SelectSwatchCommand, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:SwatchesView}}}"
+                                    CommandParameter="{Binding}" />
+                            </i:EventTrigger>
+                        </i:Interaction.Triggers>
+                        <Grid.ContextMenu>
+                            <ContextMenu>
+                                <MenuItem Header="Remove" Foreground="White"
+                                          Command="{Binding RemoveSwatchCommand, Source={x:Reference uc}}"
+                                          CommandParameter="{Binding}" />
+                            </ContextMenu>
+                        </Grid.ContextMenu>
+                    </Grid>
+                </DataTemplate>
+            </ItemsControl.ItemTemplate>
+        </ItemsControl>
+    </ScrollViewer>
+</UserControl>

+ 55 - 0
PixiEditor/Views/UserControls/SwatchesView.xaml.cs

@@ -0,0 +1,55 @@
+using System.Collections.ObjectModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace PixiEditor.Views.UserControls
+{
+    /// <summary>
+    /// Interaction logic for SwatchesView.xaml
+    /// </summary>
+    public partial class SwatchesView : UserControl
+    {
+        public static readonly DependencyProperty SwatchesProperty =
+            DependencyProperty.Register(nameof(Swatches), typeof(ObservableCollection<Color>), typeof(SwatchesView));
+
+        public ObservableCollection<Color> Swatches
+        {
+            get => (ObservableCollection<Color>)GetValue(SwatchesProperty);
+            set => SetValue(SwatchesProperty, value);
+        }
+
+        public static readonly DependencyProperty MouseDownCommandProperty =
+            DependencyProperty.Register(nameof(MouseDownCommand), typeof(ICommand), typeof(SwatchesView));
+
+        public ICommand MouseDownCommand
+        {
+            get => (ICommand)GetValue(MouseDownCommandProperty);
+            set => SetValue(MouseDownCommandProperty, value);
+        }
+
+        public static readonly DependencyProperty SelectSwatchCommandProperty =
+            DependencyProperty.Register(nameof(SelectSwatchCommand), typeof(ICommand), typeof(SwatchesView));
+
+        public ICommand SelectSwatchCommand
+        {
+            get => (ICommand)GetValue(SelectSwatchCommandProperty);
+            set => SetValue(SelectSwatchCommandProperty, value);
+        }
+
+        public static readonly DependencyProperty RemoveSwatchCommandProperty =
+            DependencyProperty.Register(nameof(RemoveSwatchCommand), typeof(ICommand), typeof(SwatchesView));
+
+        public ICommand RemoveSwatchCommand
+        {
+            get => (ICommand)GetValue(RemoveSwatchCommandProperty);
+            set => SetValue(RemoveSwatchCommandProperty, value);
+        }
+
+        public SwatchesView()
+        {
+            InitializeComponent();
+        }
+    }
+}

+ 4 - 0
PixiEditorTests/Mocks/PreferenceSettingsMock.cs

@@ -9,6 +9,10 @@ namespace PixiEditorTests.Mocks
         {
         }
 
+        public void AddCallback<T>(string name, Action<T> action)
+        {
+        }
+
 #nullable enable
 
         public T? GetLocalPreference<T>(string name)

+ 9 - 8
README.md

@@ -37,6 +37,10 @@ PixiEditor started in 2018 and it's been actively developed since. We continuous
 
 ## Installation
 
+<a href='//www.microsoft.com/store/apps/9NDDRHS8PBRN?cid=storebadge&ocid=badge'><img src='https://developer.microsoft.com/store/badges/images/English_get-it-from-MS.png' alt='Microsoft Store badge' width="184"/></a>
+
+**Or**
+
 Follow these instructions to get PixiEditor working on your machine.
 
 1. Download the .exe file from [here](https://github.com/flabbet/PixiEditor/releases)
@@ -54,6 +58,10 @@ Follow these instructions to get PixiEditor working on your machine.
 
 [![Landscape timelapse](https://img.youtube.com/vi/bzC-wy6HCB8/0.jpg)](https://www.youtube.com/watch?v=bzC-wy6HCB8)
 
+### Gallery
+
+Check out some pixel arts made with PixiEditor [here](https://github.com/PixiEditor/PixiEditor/wiki/Gallery).
+
 
 ## Support
 
@@ -63,6 +71,7 @@ Struggling with something? You can find support in a few places:
 
 * Ask on [Discord](https://discord.gg/qSRMYmq)
 * Open new [Issue](https://github.com/flabbet/PixiEditor/issues)
+* Check out [FAQ](https://github.com/PixiEditor/PixiEditor/wiki/FAQ). 
 
 
 
@@ -86,14 +95,6 @@ Struggling with something? You can find support in a few places:
 
 Please read [CONTRIBUTING.md](https://github.com/flabbet/PixiEditor/blob/master/CONTRIBUTING.md) for details on our code of conduct, and the process for submitting pull requests to us.
 
-## FAQ
-
-You can find [FAQ here](https://github.com/PixiEditor/PixiEditor/wiki/FAQ).
-
-## Gallery
-
-Check out some pixel arts made with PixiEditor [here](https://github.com/PixiEditor/PixiEditor/wiki/Gallery).
-
 ## License
 
 This project is licensed under the MIT License - see the [LICENSE.md](https://github.com/flabbet/PixiEditor/blob/master/LICENSE) - file for details

BIN
Screenshot.png