Browse Source

Merge pull request #204 from PixiEditor/magicWandTool

Implemented Magic Wand Tool
CPKreuz 4 years ago
parent
commit
436c59d767
51 changed files with 450 additions and 132 deletions
  1. 19 0
      PixiEditor/Helpers/Extensions/EnumHelpers.cs
  2. 5 0
      PixiEditor/Helpers/Extensions/StringHelpers.cs
  3. 10 7
      PixiEditor/Helpers/SelectionHelpers.cs
  4. 0 0
      PixiEditor/Images/Tools/BrightnessImage.png
  5. 0 0
      PixiEditor/Images/Tools/CircleImage.png
  6. 0 0
      PixiEditor/Images/Tools/ColorPickerImage.png
  7. 0 0
      PixiEditor/Images/Tools/EraserImage.png
  8. 0 0
      PixiEditor/Images/Tools/FloodFillImage.png
  9. 0 0
      PixiEditor/Images/Tools/LineImage.png
  10. BIN
      PixiEditor/Images/Tools/MagicWandImage.png
  11. 0 0
      PixiEditor/Images/Tools/MoveImage.png
  12. 0 0
      PixiEditor/Images/Tools/MoveViewportImage.png
  13. 0 0
      PixiEditor/Images/Tools/PenImage.png
  14. 0 0
      PixiEditor/Images/Tools/RectangleImage.png
  15. 0 0
      PixiEditor/Images/Tools/SelectImage.png
  16. 0 0
      PixiEditor/Images/Tools/ZoomImage.png
  17. BIN
      PixiEditor/Images/UnknownFile.png
  18. BIN
      PixiEditor/Images/transparentbg.png
  19. 15 6
      PixiEditor/Models/Controllers/BitmapManager.cs
  20. 12 0
      PixiEditor/Models/Enums/DocumentScope.cs
  21. 22 0
      PixiEditor/Models/Events/SelectedToolEventArgs.cs
  22. 4 4
      PixiEditor/Models/ImageManipulation/BitmapUtils.cs
  23. 15 2
      PixiEditor/Models/Layers/GuidStructureItem.cs
  24. 5 7
      PixiEditor/Models/Layers/Layer.cs
  25. 7 0
      PixiEditor/Models/Tools/ICachedDocumentTool.cs
  26. 3 5
      PixiEditor/Models/Tools/Tool.cs
  27. 14 7
      PixiEditor/Models/Tools/ToolSettings/Settings/EnumSetting.cs
  28. 14 0
      PixiEditor/Models/Tools/ToolSettings/Toolbars/MagicWandToolbar.cs
  29. 6 2
      PixiEditor/Models/Tools/ToolSettings/Toolbars/SelectToolToolbar.cs
  30. 3 2
      PixiEditor/Models/Tools/Tools/BrightnessTool.cs
  31. 2 1
      PixiEditor/Models/Tools/Tools/CircleTool.cs
  32. 4 2
      PixiEditor/Models/Tools/Tools/ColorPickerTool.cs
  33. 2 1
      PixiEditor/Models/Tools/Tools/EraserTool.cs
  34. 2 1
      PixiEditor/Models/Tools/Tools/FloodFill.cs
  35. 2 1
      PixiEditor/Models/Tools/Tools/LineTool.cs
  36. 94 0
      PixiEditor/Models/Tools/Tools/MagicWandTool.cs
  37. 5 3
      PixiEditor/Models/Tools/Tools/MoveTool.cs
  38. 6 7
      PixiEditor/Models/Tools/Tools/MoveViewportTool.cs
  39. 3 2
      PixiEditor/Models/Tools/Tools/PenTool.cs
  40. 2 1
      PixiEditor/Models/Tools/Tools/RectangleTool.cs
  41. 2 1
      PixiEditor/Models/Tools/Tools/SelectTool.cs
  42. 9 6
      PixiEditor/Models/Tools/Tools/ZoomTool.cs
  43. 26 14
      PixiEditor/PixiEditor.csproj
  44. 2 1
      PixiEditor/ViewModels/SubViewModels/Main/DiscordViewModel.cs
  45. 42 18
      PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs
  46. 23 1
      PixiEditor/ViewModels/ViewModelMain.cs
  47. 29 7
      PixiEditor/Views/Dialogs/HelloTherePopup.xaml
  48. 14 0
      PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs
  49. 1 1
      PixiEditor/Views/UserControls/Zoombox.xaml.cs
  50. 3 1
      PixiEditorTests/ModelsTests/ControllersTests/MockedSinglePixelPenTool.cs
  51. 23 21
      PixiEditorTests/ModelsTests/ControllersTests/TestReadonlyTool.cs

+ 19 - 0
PixiEditor/Helpers/Extensions/EnumHelpers.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.ComponentModel;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
 using System.Threading.Tasks;
 using System.Threading.Tasks;
@@ -13,5 +14,23 @@ namespace PixiEditor.Helpers.Extensions
         {
         {
             return Enum.GetValues(e.GetType()).Cast<T>().Where(x => e.HasFlag(x));
             return Enum.GetValues(e.GetType()).Cast<T>().Where(x => e.HasFlag(x));
         }
         }
+
+        public static string GetDescription<T>(this T enumValue)
+            where T : struct, Enum
+        {
+            var description = enumValue.ToString();
+            var fieldInfo = enumValue.GetType().GetField(enumValue.ToString());
+
+            if (fieldInfo != null)
+            {
+                var attrs = fieldInfo.GetCustomAttributes(typeof(DescriptionAttribute), true);
+                if (attrs != null && attrs.Length > 0)
+                {
+                    description = ((DescriptionAttribute)attrs[0]).Description;
+                }
+            }
+
+            return description;
+        }
     }
     }
 }
 }

+ 5 - 0
PixiEditor/Helpers/Extensions/StringHelpers.cs

@@ -23,5 +23,10 @@ namespace PixiEditor.Helpers.Extensions
             }
             }
             return newText.ToString();
             return newText.ToString();
         }
         }
+
+        public static string Limit(this string value, int maxLenght)
+        {
+            return value.Length > maxLenght ? value.Substring(0, maxLenght) : value;
+        }
     }
     }
 }
 }

+ 10 - 7
PixiEditor/Helpers/SelectionHelpers.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Linq;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
@@ -10,14 +11,17 @@ namespace PixiEditor.Helpers
     {
     {
         public static void AddSelectionUndoStep(Document document, IEnumerable<Coordinates> oldPoints, SelectionType mode)
         public static void AddSelectionUndoStep(Document document, IEnumerable<Coordinates> oldPoints, SelectionType mode)
         {
         {
-#pragma warning disable SA1117 // Parameters should be on same line or separate lines. Justification: Making it readable
             if (mode == SelectionType.New && document.ActiveSelection.SelectedPoints.Count != 0)
             if (mode == SelectionType.New && document.ActiveSelection.SelectedPoints.Count != 0)
             {
             {
-                // Add empty selection as the old one get's fully deleted first
-                document.UndoManager.AddUndoChange(
-                    new Change(
-                        SetSelectionProcess, new object[] { document, new List<Coordinates>(oldPoints) },
-                        SetSelectionProcess, new object[] { document, new List<Coordinates>() }));
+                if (oldPoints.Any())
+                {
+                    // Add empty selection as the old one get's fully deleted first
+                    document.UndoManager.AddUndoChange(
+                        new Change(
+                            SetSelectionProcess, new object[] { document, new List<Coordinates>(oldPoints) },
+                            SetSelectionProcess, new object[] { document, new List<Coordinates>() }));
+                }
+
                 document.UndoManager.AddUndoChange(
                 document.UndoManager.AddUndoChange(
                     new Change(
                     new Change(
                         SetSelectionProcess, new object[] { document, new List<Coordinates>() },
                         SetSelectionProcess, new object[] { document, new List<Coordinates>() },
@@ -29,7 +33,6 @@ namespace PixiEditor.Helpers
                     new Change(
                     new Change(
                         SetSelectionProcess, new object[] { document, oldPoints is null ? new List<Coordinates>() : 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) }));
                         SetSelectionProcess, new object[] { document, new List<Coordinates>(document.ActiveSelection.SelectedPoints) }));
-#pragma warning restore SA1117 // Parameters should be on same line or separate lines
             }
             }
         }
         }
 
 

+ 0 - 0
PixiEditor/Images/BrightnessImage.png → PixiEditor/Images/Tools/BrightnessImage.png


+ 0 - 0
PixiEditor/Images/CircleImage.png → PixiEditor/Images/Tools/CircleImage.png


+ 0 - 0
PixiEditor/Images/ColorPickerImage.png → PixiEditor/Images/Tools/ColorPickerImage.png


+ 0 - 0
PixiEditor/Images/EraserImage.png → PixiEditor/Images/Tools/EraserImage.png


+ 0 - 0
PixiEditor/Images/FloodFillImage.png → PixiEditor/Images/Tools/FloodFillImage.png


+ 0 - 0
PixiEditor/Images/LineImage.png → PixiEditor/Images/Tools/LineImage.png


BIN
PixiEditor/Images/Tools/MagicWandImage.png


+ 0 - 0
PixiEditor/Images/MoveImage.png → PixiEditor/Images/Tools/MoveImage.png


+ 0 - 0
PixiEditor/Images/MoveViewportImage.png → PixiEditor/Images/Tools/MoveViewportImage.png


+ 0 - 0
PixiEditor/Images/PenImage.png → PixiEditor/Images/Tools/PenImage.png


+ 0 - 0
PixiEditor/Images/RectangleImage.png → PixiEditor/Images/Tools/RectangleImage.png


+ 0 - 0
PixiEditor/Images/SelectImage.png → PixiEditor/Images/Tools/SelectImage.png


+ 0 - 0
PixiEditor/Images/ZoomImage.png → PixiEditor/Images/Tools/ZoomImage.png


BIN
PixiEditor/Images/UnknownFile.png


BIN
PixiEditor/Images/transparentbg.png


+ 15 - 6
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -40,6 +40,8 @@ namespace PixiEditor.Models.Controllers
 
 
         public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
         public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
 
 
+        public event EventHandler<SelectedToolEventArgs> SelectedToolChanged;
+
         public MouseMovementController MouseController { get; set; }
         public MouseMovementController MouseController { get; set; }
 
 
         public Tool SelectedTool
         public Tool SelectedTool
@@ -47,8 +49,11 @@ namespace PixiEditor.Models.Controllers
             get => selectedTool;
             get => selectedTool;
             private set
             private set
             {
             {
-                selectedTool = value;
-                RaisePropertyChanged("SelectedTool");
+                Tool previousTool = selectedTool;
+                if (SetProperty(ref selectedTool, value))
+                {
+                    SelectedToolChanged?.Invoke(this, new SelectedToolEventArgs(previousTool, value));
+                }
             }
             }
         }
         }
 
 
@@ -123,14 +128,18 @@ namespace PixiEditor.Models.Controllers
                     startPosition = newPosition;
                     startPosition = newPosition;
                 }
                 }
 
 
-                if (IsOperationTool(SelectedTool))
+                if (SelectedTool is BitmapOperationTool operationTool)
                 {
                 {
-                    BitmapOperations.ExecuteTool(newPosition, MouseController.LastMouseMoveCoordinates, (BitmapOperationTool)SelectedTool);
+                    BitmapOperations.ExecuteTool(newPosition, MouseController.LastMouseMoveCoordinates, operationTool);
                 }
                 }
-                else
+                else if (SelectedTool is ReadonlyTool readonlyTool)
                 {
                 {
-                    ReadonlyToolUtility.ExecuteTool(MouseController.LastMouseMoveCoordinates, (ReadonlyTool)SelectedTool);
+                    ReadonlyToolUtility.ExecuteTool(MouseController.LastMouseMoveCoordinates, readonlyTool);
                 }
                 }
+                else
+                {
+                    throw new InvalidOperationException($"'{SelectedTool.GetType().Name}' is either not a Tool or can't inherit '{nameof(Tool)}' directly.\nChanges the base type to either '{nameof(BitmapOperationTool)}' or '{nameof(ReadonlyTool)}'");
+                }
             }
             }
         }
         }
 
 

+ 12 - 0
PixiEditor/Models/Enums/DocumentScope.cs

@@ -0,0 +1,12 @@
+using System.ComponentModel;
+
+namespace PixiEditor.Models.Enums
+{
+    public enum DocumentScope
+    {
+        [Description("Single Layer")]
+        SingleLayer,
+        [Description("All Layers")]
+        AllLayers
+    }
+}

+ 22 - 0
PixiEditor/Models/Events/SelectedToolEventArgs.cs

@@ -0,0 +1,22 @@
+using PixiEditor.Models.Tools;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.Models.Events
+{
+    public class SelectedToolEventArgs
+    {
+        public SelectedToolEventArgs(Tool oldTool, Tool newTool)
+        {
+            OldTool = oldTool;
+            NewTool = newTool;
+        }
+
+        public Tool OldTool { get; set; }
+
+        public Tool NewTool { get; set; }
+    }
+}

+ 4 - 4
PixiEditor/Models/ImageManipulation/BitmapUtils.cs

@@ -40,16 +40,16 @@ namespace PixiEditor.Models.ImageManipulation
         /// <param name="height">Height of final bitmap.</param>.
         /// <param name="height">Height of final bitmap.</param>.
         /// <param name="layers">Layers to combine.</param>
         /// <param name="layers">Layers to combine.</param>
         /// <returns>WriteableBitmap of layered bitmaps.</returns>
         /// <returns>WriteableBitmap of layered bitmaps.</returns>
-        public static WriteableBitmap CombineLayers(int width, int height, Layer[] layers, LayerStructure structure = null)
+        public static WriteableBitmap CombineLayers(int width, int height, IEnumerable<Layer> layers, LayerStructure structure = null)
         {
         {
             WriteableBitmap finalBitmap = BitmapFactory.New(width, height);
             WriteableBitmap finalBitmap = BitmapFactory.New(width, height);
 
 
             using (finalBitmap.GetBitmapContext())
             using (finalBitmap.GetBitmapContext())
             {
             {
-                for (int i = 0; i < layers.Length; i++)
+                for (int i = 0; i < layers.Count(); i++)
                 {
                 {
-                    float layerOpacity = structure == null ? layers[i].Opacity : LayerStructureUtils.GetFinalLayerOpacity(layers[i], structure);
-                    Layer layer = layers[i];
+                    Layer layer = layers.ElementAt(i);
+                    float layerOpacity = structure == null ? layer.Opacity : LayerStructureUtils.GetFinalLayerOpacity(layer, structure);
 
 
                     if (layer.OffsetX < 0 || layer.OffsetY < 0 ||
                     if (layer.OffsetX < 0 || layer.OffsetY < 0 ||
                         layer.Width + layer.OffsetX > layer.MaxWidth ||
                         layer.Width + layer.OffsetX > layer.MaxWidth ||

+ 15 - 2
PixiEditor/Models/Layers/GuidStructureItem.cs

@@ -4,6 +4,7 @@ using System.Collections.ObjectModel;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.Linq;
 using System.Linq;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
+using PixiEditor.ViewModels;
 
 
 namespace PixiEditor.Models.Layers
 namespace PixiEditor.Models.Layers
 {
 {
@@ -75,7 +76,13 @@ namespace PixiEditor.Models.Layers
         public bool IsVisible
         public bool IsVisible
         {
         {
             get => isVisible;
             get => isVisible;
-            set => SetProperty(ref isVisible, value);
+            set
+            {
+                if (SetProperty(ref isVisible, value))
+                {
+                    ViewModelMain.Current.ToolsSubViewModel.TriggerCacheOutdated();
+                }
+            }
         }
         }
 
 
         private float opacity = 1;
         private float opacity = 1;
@@ -83,7 +90,13 @@ namespace PixiEditor.Models.Layers
         public float Opacity
         public float Opacity
         {
         {
             get => opacity;
             get => opacity;
-            set => SetProperty(ref opacity, value);
+            set
+            {
+                if (SetProperty(ref opacity, value))
+                {
+                    ViewModelMain.Current.ToolsSubViewModel.TriggerCacheOutdated();
+                }
+            }
         }
         }
 
 
         public GuidStructureItem(
         public GuidStructureItem(

+ 5 - 7
PixiEditor/Models/Layers/Layer.cs

@@ -96,11 +96,10 @@ namespace PixiEditor.Models.Layers
             get => isVisible;
             get => isVisible;
             set
             set
             {
             {
-                if (isVisible != value)
+                if (SetProperty(ref isVisible, value))
                 {
                 {
-                    isVisible = value;
-                    RaisePropertyChanged(nameof(IsVisible));
                     RaisePropertyChanged(nameof(IsVisibleUndoTriggerable));
                     RaisePropertyChanged(nameof(IsVisibleUndoTriggerable));
+                    ViewModelMain.Current.ToolsSubViewModel.TriggerCacheOutdated();
                 }
                 }
             }
             }
         }
         }
@@ -151,12 +150,11 @@ namespace PixiEditor.Models.Layers
             get => opacity;
             get => opacity;
             set
             set
             {
             {
-                if (opacity != value)
+                if (SetProperty(ref opacity, value))
                 {
                 {
-                    opacity = value;
+                    RaisePropertyChanged(nameof(OpacityUndoTriggerable));
+                    ViewModelMain.Current.ToolsSubViewModel.TriggerCacheOutdated();
                 }
                 }
-                RaisePropertyChanged(nameof(Opacity));
-                RaisePropertyChanged(nameof(OpacityUndoTriggerable));
             }
             }
         }
         }
 
 

+ 7 - 0
PixiEditor/Models/Tools/ICachedDocumentTool.cs

@@ -0,0 +1,7 @@
+namespace PixiEditor.Models.Tools
+{
+    public interface ICachedDocumentTool
+    {
+        public void DocumentChanged();
+    }
+}

+ 3 - 5
PixiEditor/Models/Tools/Tool.cs

@@ -19,11 +19,11 @@ namespace PixiEditor.Models.Tools
 
 
         public virtual string DisplayName => ToolName.AddSpacesBeforeUppercaseLetters();
         public virtual string DisplayName => ToolName.AddSpacesBeforeUppercaseLetters();
 
 
-        public virtual string ImagePath => $"/Images/{ToolName}Image.png";
+        public virtual string ImagePath => $"/Images/Tools/{ToolName}Image.png";
 
 
-        public bool HideHighlight { get; set; } = false;
+        public virtual bool HideHighlight { get; }
 
 
-        public string Tooltip { get; set; }
+        public abstract string Tooltip { get; }
 
 
         public string ActionDisplay
         public string ActionDisplay
         {
         {
@@ -49,8 +49,6 @@ namespace PixiEditor.Models.Tools
 
 
         public Toolbar Toolbar { get; set; } = new EmptyToolbar();
         public Toolbar Toolbar { get; set; } = new EmptyToolbar();
 
 
-        public IServiceProvider Services { get; set; }
-
         public bool CanStartOutsideCanvas { get; set; } = false;
         public bool CanStartOutsideCanvas { get; set; } = false;
 
 
         public virtual void OnMouseDown(MouseEventArgs e)
         public virtual void OnMouseDown(MouseEventArgs e)

+ 14 - 7
PixiEditor/Models/Tools/ToolSettings/Settings/EnumSetting.cs

@@ -1,4 +1,5 @@
-using System;
+using PixiEditor.Helpers.Extensions;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
@@ -38,8 +39,15 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
             get => (TEnum)(SettingControl.SelectedItem as ComboBoxItem).Tag;
             get => (TEnum)(SettingControl.SelectedItem as ComboBoxItem).Tag;
             set
             set
             {
             {
-                SettingControl.SelectedItem = SettingControl.Items.Cast<ComboBoxItem>().First(x => x.Tag == (object)value);
-                RaisePropertyChanged(nameof(Value));
+                for (int i = 0; i < SettingControl.Items.Count; i++)
+                {
+                    ComboBoxItem item = SettingControl.Items[i] as ComboBoxItem;
+
+                    if (item.Tag.Equals(value))
+                    {
+                        SelectedIndex = i;
+                    }
+                }
             }
             }
         }
         }
 
 
@@ -78,15 +86,14 @@ namespace PixiEditor.Models.Tools.ToolSettings.Settings
 
 
         private static void GenerateItems(ComboBox comboBox)
         private static void GenerateItems(ComboBox comboBox)
         {
         {
-            string[] names = Enum.GetNames<TEnum>();
             TEnum[] values = Enum.GetValues<TEnum>();
             TEnum[] values = Enum.GetValues<TEnum>();
 
 
-            for (int i = 0; i < names.Length; i++)
+            foreach (TEnum value in values)
             {
             {
                 ComboBoxItem item = new ComboBoxItem
                 ComboBoxItem item = new ComboBoxItem
                 {
                 {
-                    Content = names[i],
-                    Tag = values[i]
+                    Content = value.GetDescription(),
+                    Tag = value
                 };
                 };
 
 
                 comboBox.Items.Add(item);
                 comboBox.Items.Add(item);

+ 14 - 0
PixiEditor/Models/Tools/ToolSettings/Toolbars/MagicWandToolbar.cs

@@ -0,0 +1,14 @@
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+
+namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
+{
+    public class MagicWandToolbar : SelectToolToolbar
+    {
+        public MagicWandToolbar()
+            : base(false)
+        {
+            Settings.Add(new EnumSetting<DocumentScope>(nameof(DocumentScope), "Scope"));
+        }
+    }
+}

+ 6 - 2
PixiEditor/Models/Tools/ToolSettings/Toolbars/SelectToolToolbar.cs

@@ -5,10 +5,14 @@ namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
 {
 {
     public class SelectToolToolbar : Toolbar
     public class SelectToolToolbar : Toolbar
     {
     {
-        public SelectToolToolbar()
+        public SelectToolToolbar(bool includeSelectionShape = true)
         {
         {
             Settings.Add(new EnumSetting<SelectionType>("SelectMode", "Selection type"));
             Settings.Add(new EnumSetting<SelectionType>("SelectMode", "Selection type"));
-            Settings.Add(new EnumSetting<SelectionShape>("SelectShape", "Selection shape"));
+
+            if (includeSelectionShape)
+            {
+                Settings.Add(new EnumSetting<SelectionShape>("SelectShape", "Selection shape"));
+            }
         }
         }
     }
     }
 }
 }

+ 3 - 2
PixiEditor/Models/Tools/Tools/BrightnessTool.cs

@@ -18,15 +18,16 @@ namespace PixiEditor.Models.Tools.Tools
     {
     {
         private const float CorrectionFactor = 5f; // Initial correction factor
         private const float CorrectionFactor = 5f; // Initial correction factor
 
 
-        private List<Coordinates> pixelsVisited = new List<Coordinates>();
+        private readonly List<Coordinates> pixelsVisited = new List<Coordinates>();
 
 
         public BrightnessTool()
         public BrightnessTool()
         {
         {
             ActionDisplay = "Draw on pixel to make it brighter. Hold Ctrl to darken.";
             ActionDisplay = "Draw on pixel to make it brighter. Hold Ctrl to darken.";
-            Tooltip = "Makes pixel brighter or darker pixel (U). Hold Ctrl to make pixel darker.";
             Toolbar = new BrightnessToolToolbar(CorrectionFactor);
             Toolbar = new BrightnessToolToolbar(CorrectionFactor);
         }
         }
 
 
+        public override string Tooltip => "Makes pixel brighter or darker pixel (U). Hold Ctrl to make pixel darker.";
+
         public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
         public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
 
 
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)

+ 2 - 1
PixiEditor/Models/Tools/Tools/CircleTool.cs

@@ -16,9 +16,10 @@ namespace PixiEditor.Models.Tools.Tools
         public CircleTool()
         public CircleTool()
         {
         {
             ActionDisplay = "Click and move mouse to draw a circle. Hold Shift to draw an even one.";
             ActionDisplay = "Click and move mouse to draw a circle. Hold Shift to draw an even one.";
-            Tooltip = "Draws circle on canvas (C). Hold Shift to draw even circle.";
         }
         }
 
 
+        public override string Tooltip => "Draws circle on canvas (C). Hold Shift to draw even circle.";
+
         public override void OnKeyDown(KeyEventArgs e)
         public override void OnKeyDown(KeyEventArgs e)
         {
         {
             if (e.Key == Key.LeftShift)
             if (e.Key == Key.LeftShift)

+ 4 - 2
PixiEditor/Models/Tools/Tools/ColorPickerTool.cs

@@ -11,11 +11,13 @@ namespace PixiEditor.Models.Tools.Tools
     {
     {
         public ColorPickerTool()
         public ColorPickerTool()
         {
         {
-            HideHighlight = true;
             ActionDisplay = "Press on pixel to make it the primary color.";
             ActionDisplay = "Press on pixel to make it the primary color.";
-            Tooltip = "Swaps primary color with selected on canvas. (O)";
         }
         }
 
 
+        public override bool HideHighlight => true;
+
+        public override string Tooltip => "Swaps primary color with selected on canvas. (O)";
+
         public override void OnMouseDown(MouseEventArgs e)
         public override void OnMouseDown(MouseEventArgs e)
         {
         {
             base.OnMouseDown(e);
             base.OnMouseDown(e);

+ 2 - 1
PixiEditor/Models/Tools/Tools/EraserTool.cs

@@ -16,11 +16,12 @@ namespace PixiEditor.Models.Tools.Tools
         public EraserTool(BitmapManager bitmapManager)
         public EraserTool(BitmapManager bitmapManager)
         {
         {
             ActionDisplay = "Draw to remove color from a pixel.";
             ActionDisplay = "Draw to remove color from a pixel.";
-            Tooltip = "Erasers color from pixel. (E)";
             Toolbar = new BasicToolbar();
             Toolbar = new BasicToolbar();
             pen = new PenTool(bitmapManager);
             pen = new PenTool(bitmapManager);
         }
         }
 
 
+        public override string Tooltip => "Erasers color from pixel. (E)";
+
         public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
         public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
         {
         {
             return Erase(layer, coordinates, Toolbar.GetSetting<SizeSetting>("ToolSize").Value);
             return Erase(layer, coordinates, Toolbar.GetSetting<SizeSetting>("ToolSize").Value);

+ 2 - 1
PixiEditor/Models/Tools/Tools/FloodFill.cs

@@ -14,10 +14,11 @@ namespace PixiEditor.Models.Tools.Tools
         public FloodFill(BitmapManager bitmapManager)
         public FloodFill(BitmapManager bitmapManager)
         {
         {
             ActionDisplay = "Press on a area to fill it.";
             ActionDisplay = "Press on a area to fill it.";
-            Tooltip = "Fills area with color. (G)";
             BitmapManager = bitmapManager;
             BitmapManager = bitmapManager;
         }
         }
 
 
+        public override string Tooltip => "Fills area with color. (G)";
+
         public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
         public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
         {
         {
             return Only(ForestFire(layer, coordinates[0], color), layer);
             return Only(ForestFire(layer, coordinates[0], color), layer);

+ 2 - 1
PixiEditor/Models/Tools/Tools/LineTool.cs

@@ -18,11 +18,12 @@ namespace PixiEditor.Models.Tools.Tools
         public LineTool()
         public LineTool()
         {
         {
             ActionDisplay = "Click and move to draw a line. Hold Shift to draw an even one.";
             ActionDisplay = "Click and move to draw a line. Hold Shift to draw an even one.";
-            Tooltip = "Draws line on canvas (L). Hold Shift to draw even line.";
             Toolbar = new BasicToolbar();
             Toolbar = new BasicToolbar();
             circleTool = new CircleTool();
             circleTool = new CircleTool();
         }
         }
 
 
+        public override string Tooltip => "Draws line on canvas (L). Hold Shift to draw even line.";
+
         public override void OnKeyDown(KeyEventArgs e)
         public override void OnKeyDown(KeyEventArgs e)
         {
         {
             if (e.Key == Key.LeftShift)
             if (e.Key == Key.LeftShift)

+ 94 - 0
PixiEditor/Models/Tools/Tools/MagicWandTool.cs

@@ -0,0 +1,94 @@
+using PixiEditor.Helpers;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.ImageManipulation;
+using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+using PixiEditor.ViewModels;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class MagicWandTool : ReadonlyTool, ICachedDocumentTool
+    {
+        private readonly FloodFill floodFill;
+
+        private static Selection ActiveSelection { get => ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection; }
+
+        private BitmapManager BitmapManager { get; }
+
+        private IEnumerable<Coordinates> oldSelection;
+
+        public override string Tooltip => "Magic Wand (W). Flood's the selection";
+
+        private Layer cachedDocument;
+
+        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
+        {
+            if (e.LeftButton != MouseButtonState.Pressed)
+            {
+                return;
+            }
+
+            oldSelection = new ReadOnlyCollection<Coordinates>(ActiveSelection.SelectedPoints);
+
+            SelectionType selectionType = Toolbar.GetEnumSetting<SelectionType>("SelectMode").Value;
+            DocumentScope documentScope = Toolbar.GetEnumSetting<DocumentScope>(nameof(DocumentScope)).Value;
+
+            Document document = BitmapManager.ActiveDocument;
+            Layer layer;
+
+            if (documentScope == DocumentScope.SingleLayer)
+            {
+                layer = BitmapManager.ActiveLayer;
+            }
+            else
+            {
+                ValidateCache(document);
+                layer = cachedDocument;
+            }
+
+            Selection selection = BitmapManager.ActiveDocument.ActiveSelection;
+
+            selection.SetSelection(
+                floodFill.ForestFire(
+                    layer,
+                    new Coordinates((int)document.MouseXOnCanvas, (int)document.MouseYOnCanvas),
+                    System.Windows.Media.Colors.White
+                    ).ChangedPixels.Keys,
+                selectionType);
+
+            SelectionHelpers.AddSelectionUndoStep(ViewModelMain.Current.BitmapManager.ActiveDocument, oldSelection, selectionType);
+        }
+
+        public MagicWandTool(BitmapManager manager)
+        {
+            floodFill = new FloodFill(manager);
+            BitmapManager = manager;
+
+            Toolbar = new MagicWandToolbar();
+
+            ActionDisplay = "Click to flood the selection.";
+        }
+
+        public override void Use(List<Coordinates> pixels)
+        {
+        }
+
+        public void DocumentChanged()
+        {
+            cachedDocument = null;
+        }
+
+        private void ValidateCache(Document document)
+        {
+            cachedDocument ??= new Layer("_CombinedLayers", BitmapUtils.CombineLayers(document.Width, document.Height, document.Layers, document.LayerStructure));
+        }
+    }
+}

+ 5 - 3
PixiEditor/Models/Tools/Tools/MoveTool.cs

@@ -32,16 +32,18 @@ namespace PixiEditor.Models.Tools.Tools
         public MoveTool(BitmapManager bitmapManager)
         public MoveTool(BitmapManager bitmapManager)
         {
         {
             ActionDisplay = "Hold mouse to move selected pixels. Hold Ctrl to move all layers.";
             ActionDisplay = "Hold mouse to move selected pixels. Hold Ctrl to move all layers.";
-            Tooltip = "Moves selected pixels (V). Hold Ctrl to move all layers.";
             Cursor = Cursors.Arrow;
             Cursor = Cursors.Arrow;
-            HideHighlight = true;
             RequiresPreviewLayer = true;
             RequiresPreviewLayer = true;
             UseDefaultUndoMethod = true;
             UseDefaultUndoMethod = true;
 
 
             BitmapManager = bitmapManager;
             BitmapManager = bitmapManager;
         }
         }
 
 
-        public bool MoveAll { get; set; } = false;
+        public override string Tooltip => "Moves selected pixels (V). Hold Ctrl to move all layers.";
+
+        public override bool HideHighlight => true;
+
+        public bool MoveAll { get; set; }
 
 
         private BitmapManager BitmapManager { get; }
         private BitmapManager BitmapManager { get; }
 
 

+ 6 - 7
PixiEditor/Models/Tools/Tools/MoveViewportTool.cs

@@ -8,20 +8,18 @@ namespace PixiEditor.Models.Tools.Tools
 {
 {
     public class MoveViewportTool : ReadonlyTool
     public class MoveViewportTool : ReadonlyTool
     {
     {
-        private BitmapManager BitmapManager { get; }
-
         private ToolsViewModel ToolsViewModel { get; }
         private ToolsViewModel ToolsViewModel { get; }
 
 
-        public MoveViewportTool(BitmapManager bitmapManager, ToolsViewModel toolsViewModel)
+        public MoveViewportTool(ToolsViewModel toolsViewModel)
         {
         {
-            HideHighlight = true;
             Cursor = Cursors.SizeAll;
             Cursor = Cursors.SizeAll;
             ActionDisplay = "Click and move to pan viewport.";
             ActionDisplay = "Click and move to pan viewport.";
-            Tooltip = "Move viewport. (H)";
 
 
-            BitmapManager = bitmapManager;
             ToolsViewModel = toolsViewModel;
             ToolsViewModel = toolsViewModel;
         }
         }
+
+        public override bool HideHighlight => true;
+        public override string Tooltip => "Move viewport. (H)";
 
 
         public override void OnMouseUp(MouseEventArgs e)
         public override void OnMouseUp(MouseEventArgs e)
         {
         {
@@ -32,7 +30,8 @@ namespace PixiEditor.Models.Tools.Tools
         }
         }
 
 
         public override void Use(List<Coordinates> pixels)
         public override void Use(List<Coordinates> pixels)
-        {
+        {
+            // Implemented inside Zoombox.xaml.cs
         }
         }
     }
     }
 }
 }

+ 3 - 2
PixiEditor/Models/Tools/Tools/PenTool.cs

@@ -21,7 +21,7 @@ namespace PixiEditor.Models.Tools.Tools
         private readonly List<Coordinates> confirmedPixels = new List<Coordinates>();
         private readonly List<Coordinates> confirmedPixels = new List<Coordinates>();
         private readonly LineTool lineTool;
         private readonly LineTool lineTool;
         private Coordinates[] lastChangedPixels = new Coordinates[3];
         private Coordinates[] lastChangedPixels = new Coordinates[3];
-        private byte changedPixelsindex = 0;
+        private byte changedPixelsindex;
 
 
         private BitmapManager BitmapManager { get; }
         private BitmapManager BitmapManager { get; }
 
 
@@ -29,7 +29,6 @@ namespace PixiEditor.Models.Tools.Tools
         {
         {
             Cursor = Cursors.Pen;
             Cursor = Cursors.Pen;
             ActionDisplay = "Click and move to draw.";
             ActionDisplay = "Click and move to draw.";
-            Tooltip = "Standard brush. (B)";
             Toolbar = new PenToolbar();
             Toolbar = new PenToolbar();
             toolSizeSetting = Toolbar.GetSetting<SizeSetting>("ToolSize");
             toolSizeSetting = Toolbar.GetSetting<SizeSetting>("ToolSize");
             pixelPerfectSetting = Toolbar.GetSetting<BoolSetting>("PixelPerfectEnabled");
             pixelPerfectSetting = Toolbar.GetSetting<BoolSetting>("PixelPerfectEnabled");
@@ -39,6 +38,8 @@ namespace PixiEditor.Models.Tools.Tools
             lineTool = new LineTool();
             lineTool = new LineTool();
         }
         }
 
 
+        public override string Tooltip => "Standard brush. (B)";
+
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
         {
             base.OnRecordingLeftMouseDown(e);
             base.OnRecordingLeftMouseDown(e);

+ 2 - 1
PixiEditor/Models/Tools/Tools/RectangleTool.cs

@@ -16,9 +16,10 @@ namespace PixiEditor.Models.Tools.Tools
         public RectangleTool()
         public RectangleTool()
         {
         {
             ActionDisplay = "Click and move to draw a rectangle.  Hold Shift to draw square.";
             ActionDisplay = "Click and move to draw a rectangle.  Hold Shift to draw square.";
-            Tooltip = "Draws rectangle on canvas (R). Hold Shift to draw square.";
         }
         }
 
 
+        public override string Tooltip => "Draws rectangle on canvas (R). Hold Shift to draw square.";
+
         public bool Filled { get; set; } = false;
         public bool Filled { get; set; } = false;
 
 
         public override void OnKeyDown(KeyEventArgs e)
         public override void OnKeyDown(KeyEventArgs e)

+ 2 - 1
PixiEditor/Models/Tools/Tools/SelectTool.cs

@@ -30,7 +30,6 @@ namespace PixiEditor.Models.Tools.Tools
         public SelectTool(BitmapManager bitmapManager)
         public SelectTool(BitmapManager bitmapManager)
         {
         {
             ActionDisplay = "Click and move to select an area.";
             ActionDisplay = "Click and move to select an area.";
-            Tooltip = "Selects area. (M)";
             Toolbar = new SelectToolToolbar();
             Toolbar = new SelectToolToolbar();
             BitmapManager = bitmapManager;
             BitmapManager = bitmapManager;
 
 
@@ -40,6 +39,8 @@ namespace PixiEditor.Models.Tools.Tools
 
 
         public SelectionType SelectionType { get; set; } = SelectionType.Add;
         public SelectionType SelectionType { get; set; } = SelectionType.Add;
 
 
+        public override string Tooltip => "Selects area. (M)";
+
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
         {
             SelectionType = Toolbar.GetEnumSetting<SelectionType>("SelectMode").Value;
             SelectionType = Toolbar.GetEnumSetting<SelectionType>("SelectMode").Value;

+ 9 - 6
PixiEditor/Models/Tools/Tools/ZoomTool.cs

@@ -11,31 +11,34 @@ namespace PixiEditor.Models.Tools.Tools
 
 
         public ZoomTool(BitmapManager bitmapManager)
         public ZoomTool(BitmapManager bitmapManager)
         {
         {
-            HideHighlight = true;
             CanStartOutsideCanvas = true;
             CanStartOutsideCanvas = true;
             ActionDisplay = "Click and move to zoom. Click to zoom in, hold alt and click to zoom out.";
             ActionDisplay = "Click and move to zoom. Click to zoom in, hold alt and click to zoom out.";
-            Tooltip = "Zooms viewport (Z). Click to zoom in, hold alt and click to zoom out.";
             BitmapManager = bitmapManager;
             BitmapManager = bitmapManager;
         }
         }
 
 
+        public override bool HideHighlight => true;
+
+        public override string Tooltip => "Zooms viewport (Z). Click to zoom in, hold alt and click to zoom out.";
+
         public override void OnKeyDown(KeyEventArgs e)
         public override void OnKeyDown(KeyEventArgs e)
         {
         {
-            if (e.Key == Key.LeftAlt)
+            if (e.Key == Key.LeftCtrl)
             {
             {
-                ActionDisplay = "Click and move to zoom. Click to zoom out, release alt and click to zoom in.";
+                ActionDisplay = "Click and move to zoom. Click to zoom out, release ctrl and click to zoom in.";
             }
             }
         }
         }
 
 
         public override void OnKeyUp(KeyEventArgs e)
         public override void OnKeyUp(KeyEventArgs e)
         {
         {
-            if (e.Key == Key.LeftAlt)
+            if (e.Key == Key.LeftCtrl)
             {
             {
-                ActionDisplay = "Click and move to zoom. Click to zoom in, hold alt and click to zoom out.";
+                ActionDisplay = "Click and move to zoom. Click to zoom in, hold ctrl and click to zoom out.";
             }
             }
         }
         }
 
 
         public override void Use(List<Coordinates> pixels)
         public override void Use(List<Coordinates> pixels)
         {
         {
+            // Implemented inside Zoombox.xaml.cs
         }
         }
     }
     }
 }
 }

+ 26 - 14
PixiEditor/PixiEditor.csproj

@@ -129,6 +129,19 @@
     <None Remove="Images\PixiEditorLogo.png" />
     <None Remove="Images\PixiEditorLogo.png" />
     <None Remove="Images\PixiParserLogo.png" />
     <None Remove="Images\PixiParserLogo.png" />
     <None Remove="Images\SelectImage.png" />
     <None Remove="Images\SelectImage.png" />
+    <None Remove="Images\Tools\BrightnessImage.png" />
+    <None Remove="Images\Tools\CircleImage.png" />
+    <None Remove="Images\Tools\ColorPickerImage.png" />
+    <None Remove="Images\Tools\EraserImage.png" />
+    <None Remove="Images\Tools\FloodFillImage.png" />
+    <None Remove="Images\Tools\LineImage.png" />
+    <None Remove="Images\Tools\MagicWandImage.png" />
+    <None Remove="Images\Tools\MoveImage.png" />
+    <None Remove="Images\Tools\MoveViewportImage.png" />
+    <None Remove="Images\Tools\PenImage.png" />
+    <None Remove="Images\Tools\RectangleImage.png" />
+    <None Remove="Images\Tools\SelectImage.png" />
+    <None Remove="Images\Tools\ZoomImage.png" />
     <None Remove="Images\Trash.png" />
     <None Remove="Images\Trash.png" />
     <None Remove="Images\UnknownFile.png" />
     <None Remove="Images\UnknownFile.png" />
     <None Remove="Images\ZoomImage.png" />
     <None Remove="Images\ZoomImage.png" />
@@ -164,30 +177,29 @@
     <Resource Include="Images\CheckerTile.png" />
     <Resource Include="Images\CheckerTile.png" />
     <Resource Include="Images\ChevronDown.png" />
     <Resource Include="Images\ChevronDown.png" />
     <Resource Include="Images\DiagonalRed.png" />
     <Resource Include="Images\DiagonalRed.png" />
-    <Resource Include="Images\FloodFillImage.png" />
-    <Resource Include="Images\CircleImage.png" />
-    <Resource Include="Images\EraserImage.png" />
-    <Resource Include="Images\BrightnessImage.png" />
     <Resource Include="Images\Eye-off.png" />
     <Resource Include="Images\Eye-off.png" />
     <Resource Include="Images\Eye.png" />
     <Resource Include="Images\Eye.png" />
     <Resource Include="Images\Folder-add.png" />
     <Resource Include="Images\Folder-add.png" />
     <Resource Include="Images\Folder.png" />
     <Resource Include="Images\Folder.png" />
     <Resource Include="Images\Layer-add.png" />
     <Resource Include="Images\Layer-add.png" />
-    <Resource Include="Images\LineImage.png" />
-    <Resource Include="Images\MoveImage.png" />
-    <Resource Include="Images\MoveViewportImage.png" />
-    <Resource Include="Images\PenImage.png" />
-    <Resource Include="Images\ColorPickerImage.png" />
     <Resource Include="Images\penMode.png" />
     <Resource Include="Images\penMode.png" />
     <Resource Include="Images\PixiBotLogo.png" />
     <Resource Include="Images\PixiBotLogo.png" />
     <Resource Include="Images\PixiEditorLogo.png" />
     <Resource Include="Images\PixiEditorLogo.png" />
     <Resource Include="Images\PixiParserLogo.png" />
     <Resource Include="Images\PixiParserLogo.png" />
-    <Resource Include="Images\RectangleImage.png" />
-    <Resource Include="Images\SelectImage.png" />
-    <Resource Include="Images\transparentbg.png" />
+    <Resource Include="Images\Tools\BrightnessImage.png" />
+    <Resource Include="Images\Tools\CircleImage.png" />
+    <Resource Include="Images\Tools\ColorPickerImage.png" />
+    <Resource Include="Images\Tools\EraserImage.png" />
+    <Resource Include="Images\Tools\FloodFillImage.png" />
+    <Resource Include="Images\Tools\LineImage.png" />
+    <Resource Include="Images\Tools\MagicWandImage.png" />
+    <Resource Include="Images\Tools\MoveImage.png" />
+    <Resource Include="Images\Tools\MoveViewportImage.png" />
+    <Resource Include="Images\Tools\PenImage.png" />
+    <Resource Include="Images\Tools\RectangleImage.png" />
+    <Resource Include="Images\Tools\SelectImage.png" />
+    <Resource Include="Images\Tools\ZoomImage.png" />
     <Resource Include="Images\Trash.png" />
     <Resource Include="Images\Trash.png" />
-    <Resource Include="Images\UnknownFile.png" />
-    <Resource Include="Images\ZoomImage.png" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <None Include="..\LICENSE">
     <None Include="..\LICENSE">

+ 2 - 1
PixiEditor/ViewModels/SubViewModels/Main/DiscordViewModel.cs

@@ -1,5 +1,6 @@
 using System;
 using System;
 using DiscordRPC;
 using DiscordRPC;
+using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.UserPreferences;
 using PixiEditor.Models.UserPreferences;
 
 
@@ -116,7 +117,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             {
             {
                 richPresence.WithTimestamps(new Timestamps(document.OpenedUTC));
                 richPresence.WithTimestamps(new Timestamps(document.OpenedUTC));
 
 
-                richPresence.Details = ShowDocumentName ? $"Editing {document.Name}" : "Editing something (incognito)";
+                richPresence.Details = ShowDocumentName ? $"Editing {document.Name}".Limit(128) : "Editing something (incognito)";
 
 
                 string state = string.Empty;
                 string state = string.Empty;
 
 

+ 42 - 18
PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs

@@ -4,6 +4,7 @@ using System.Linq;
 using System.Reflection;
 using System.Reflection;
 using System.Windows.Input;
 using System.Windows.Input;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
+using PixiEditor.Models.Enums;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.Tools.Tools;
 
 
@@ -36,18 +37,19 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         {
         {
             SelectToolCommand = new RelayCommand(SetTool, Owner.DocumentIsNotNull);
             SelectToolCommand = new RelayCommand(SetTool, Owner.DocumentIsNotNull);
             ChangeToolSizeCommand = new RelayCommand(ChangeToolSize);
             ChangeToolSizeCommand = new RelayCommand(ChangeToolSize);
+
+            Owner.BitmapManager.BitmapOperations.BitmapChanged += (_, _) => TriggerCacheOutdated();
+            Owner.BitmapManager.DocumentChanged += BitmapManager_DocumentChanged;
         }
         }
 
 
         public void SetupTools(IServiceProvider services)
         public void SetupTools(IServiceProvider services)
         {
         {
-            ToolBuilder builder = new ToolBuilder(services);
-
-            builder
-                .Add<MoveViewportTool>().Add<MoveTool>().Add<PenTool>().Add<SelectTool>().Add<FloodFill>()
-                .Add<LineTool>().Add<CircleTool>().Add<RectangleTool>().Add<EraserTool>().Add<ColorPickerTool>().Add<BrightnessTool>()
-                .Add<ZoomTool>();
-
-            ToolSet = new(builder.Build());
+            ToolSet = new ObservableCollection<Tool>(
+                new ToolBuilder(services)
+                .Add<MoveViewportTool>().Add<MoveTool>().Add<PenTool>().Add<SelectTool>().Add<MagicWandTool>().Add<FloodFill>()
+                .Add<LineTool>().Add<CircleTool>().Add<RectangleTool>().Add<EraserTool>().Add<ColorPickerTool>()
+                .Add<BrightnessTool>().Add<ZoomTool>()
+                .Build());
 
 
             SetActiveTool<MoveViewportTool>();
             SetActiveTool<MoveViewportTool>();
         }
         }
@@ -89,23 +91,45 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             SetActiveTool(tool.GetType());
             SetActiveTool(tool.GetType());
         }
         }
 
 
-        private static T CreateTool<T>(IServiceProvider provider)
-            where T : new()
+        public void TriggerCacheOutdated()
         {
         {
-            T tool = default;
-            Type toolType = typeof(T);
-
-            foreach (PropertyInfo info in toolType.GetProperties(BindingFlags.Public))
+            foreach (Tool tool in ToolSet)
             {
             {
-                if (!info.CanWrite)
+                if (tool is ICachedDocumentTool cachedTool)
                 {
                 {
-                    continue;
+                    cachedTool.DocumentChanged();
                 }
                 }
+            }
+        }
 
 
-                info.SetValue(tool, provider.GetService(info.PropertyType));
+        private void BitmapManager_DocumentChanged(object sender, Models.Events.DocumentChangedEventArgs e)
+        {
+            if (e.OldDocument != null)
+            {
+                e.OldDocument.DocumentSizeChanged -= Document_DocumentSizeChanged;
+                e.OldDocument.LayersChanged -= Document_LayersChanged;
             }
             }
 
 
-            return tool;
+            if (e.NewDocument != null)
+            {
+                e.NewDocument.DocumentSizeChanged += Document_DocumentSizeChanged;
+                e.NewDocument.LayersChanged += Document_LayersChanged;
+            }
+
+            TriggerCacheOutdated();
+
+            void Document_DocumentSizeChanged(object sender, Models.DataHolders.DocumentSizeChangedEventArgs e)
+            {
+                TriggerCacheOutdated();
+            }
+
+            void Document_LayersChanged(object sender, Models.Controllers.LayersChangedEventArgs e)
+            {
+                if (e.LayerChangeType is LayerAction.Add or LayerAction.Remove or LayerAction.Move)
+                {
+                    TriggerCacheOutdated();
+                }
+            }
         }
         }
 
 
         private void ChangeToolSize(object parameter)
         private void ChangeToolSize(object parameter)

+ 23 - 1
PixiEditor/ViewModels/ViewModelMain.cs

@@ -187,6 +187,7 @@ namespace PixiEditor.ViewModels
                         CreateToolShortcut<SelectTool>(Key.M, "Select Select Tool"),
                         CreateToolShortcut<SelectTool>(Key.M, "Select Select Tool"),
                         CreateToolShortcut<ZoomTool>(Key.Z, "Select Zoom Tool"),
                         CreateToolShortcut<ZoomTool>(Key.Z, "Select Zoom Tool"),
                         CreateToolShortcut<MoveViewportTool>(Key.H, "Select Viewport Move Tool"),
                         CreateToolShortcut<MoveViewportTool>(Key.H, "Select Viewport Move Tool"),
+                        CreateToolShortcut<MagicWandTool>(Key.W, "Select Magic Wand Tool"),
                         new Shortcut(Key.OemPlus, ViewportSubViewModel.ZoomCommand, "Zoom in", 1),
                         new Shortcut(Key.OemPlus, ViewportSubViewModel.ZoomCommand, "Zoom in", 1),
                         new Shortcut(Key.OemMinus, ViewportSubViewModel.ZoomCommand, "Zoom out", -1),
                         new Shortcut(Key.OemMinus, ViewportSubViewModel.ZoomCommand, "Zoom out", -1),
                         new Shortcut(Key.OemOpenBrackets, ToolsSubViewModel.ChangeToolSizeCommand, "Decrease Tool Size", -1),
                         new Shortcut(Key.OemOpenBrackets, ToolsSubViewModel.ChangeToolSizeCommand, "Decrease Tool Size", -1),
@@ -230,7 +231,7 @@ namespace PixiEditor.ViewModels
 
 
             BitmapManager.PrimaryColor = ColorsSubViewModel.PrimaryColor;
             BitmapManager.PrimaryColor = ColorsSubViewModel.PrimaryColor;
 
 
-            BitmapManager.AddPropertyChangedCallback(nameof(BitmapManager.SelectedTool), () => { if (!OverrideActionDisplay) RaisePropertyChanged(nameof(ActionDisplay)); });
+            BitmapManager.SelectedToolChanged += BitmapManager_SelectedToolChanged;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -258,6 +259,27 @@ namespace PixiEditor.ViewModels
             ((CancelEventArgs)property).Cancel = !RemoveDocumentsWithSaveConfirmation();
             ((CancelEventArgs)property).Cancel = !RemoveDocumentsWithSaveConfirmation();
         }
         }
 
 
+        private void BitmapManager_SelectedToolChanged(object sender, SelectedToolEventArgs e)
+        {
+            e.OldTool.PropertyChanged -= SelectedTool_PropertyChanged;
+            e.NewTool.PropertyChanged += SelectedTool_PropertyChanged;
+
+            NotifyToolActionDisplayChanged();
+        }
+
+        private void SelectedTool_PropertyChanged(object sender, PropertyChangedEventArgs e)
+        {
+            if (e.PropertyName == nameof(Tool.ActionDisplay))
+            {
+                NotifyToolActionDisplayChanged();
+            }
+        }
+
+        private void NotifyToolActionDisplayChanged()
+        {
+            if (!OverrideActionDisplay) RaisePropertyChanged(nameof(ActionDisplay));
+        }
+
         [Conditional("DEBUG")]
         [Conditional("DEBUG")]
         private void AddDebugOnlyViewModels()
         private void AddDebugOnlyViewModels()
         {
         {

+ 29 - 7
PixiEditor/Views/Dialogs/HelloTherePopup.xaml

@@ -110,7 +110,7 @@
                                                                     <DataTrigger.EnterActions>
                                                                     <DataTrigger.EnterActions>
                                                                         <BeginStoryboard Name="open">
                                                                         <BeginStoryboard Name="open">
                                                                             <Storyboard BeginTime="0:0:.1">
                                                                             <Storyboard BeginTime="0:0:.1">
-                                                                                <DoubleAnimation Storyboard.TargetProperty="Height" By="8" To="25" BeginTime="0:0:.1" Duration="0:0:.3">
+                                                                                <DoubleAnimation Storyboard.TargetProperty="Height" By="8" To="70" BeginTime="0:0:.1" Duration="0:0:.3">
                                                                                     <DoubleAnimation.EasingFunction>
                                                                                     <DoubleAnimation.EasingFunction>
                                                                                         <ExponentialEase/>
                                                                                         <ExponentialEase/>
                                                                                     </DoubleAnimation.EasingFunction>
                                                                                     </DoubleAnimation.EasingFunction>
@@ -131,7 +131,7 @@
                                                                     <DataTrigger.ExitActions>
                                                                     <DataTrigger.ExitActions>
                                                                         <BeginStoryboard Name="close">
                                                                         <BeginStoryboard Name="close">
                                                                             <Storyboard>
                                                                             <Storyboard>
-                                                                                <DoubleAnimation Storyboard.TargetProperty="Height" By="25" To="8"  Duration="0:0:.2">
+                                                                                <DoubleAnimation Storyboard.TargetProperty="Height" By="70" To="8"  Duration="0:0:.2">
                                                                                     <DoubleAnimation.EasingFunction>
                                                                                     <DoubleAnimation.EasingFunction>
                                                                                         <ExponentialEase/>
                                                                                         <ExponentialEase/>
                                                                                     </DoubleAnimation.EasingFunction>
                                                                                     </DoubleAnimation.EasingFunction>
@@ -154,9 +154,9 @@
                                                             </Style.Triggers>
                                                             </Style.Triggers>
                                                         </Style>
                                                         </Style>
                                                     </Border.Style>
                                                     </Border.Style>
-                                                    <TextBlock x:Name="extension" Text="{Binding FileExtension}" FontSize="15" TextAlignment="Center" VerticalAlignment="Center" Opacity="0">
-                                                        <TextBlock.Style>
-                                                            <Style TargetType="TextBlock">
+                                                    <Grid HorizontalAlignment="Center" Margin="0,10,0,0" Opacity="0">
+                                                        <Grid.Style>
+                                                            <Style TargetType="Grid">
                                                                 <Style.Triggers>
                                                                 <Style.Triggers>
                                                                     <DataTrigger Binding="{Binding IsMouseOver, ElementName=fileButton}" Value="True">
                                                                     <DataTrigger Binding="{Binding IsMouseOver, ElementName=fileButton}" Value="True">
                                                                         <DataTrigger.EnterActions>
                                                                         <DataTrigger.EnterActions>
@@ -176,8 +176,30 @@
                                                                     </DataTrigger>
                                                                     </DataTrigger>
                                                                 </Style.Triggers>
                                                                 </Style.Triggers>
                                                             </Style>
                                                             </Style>
-                                                        </TextBlock.Style>
-                                                    </TextBlock>
+                                                        </Grid.Style>
+                                                        <TextBlock x:Name="extension" VerticalAlignment="Top" Text="{Binding FileExtension}" FontSize="15" TextAlignment="Center"/>
+                                                        <StackPanel Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Center">
+                                                            <Button Margin="0,0,0,5" Height="25" Width="25"
+                                                                    Command="{Binding DataContext.OpenInExplorerCommand, RelativeSource={RelativeSource AncestorType=uc:AlignableWrapPanel}}"
+                                                                    CommandParameter="{Binding FilePath}"
+                                                                    ToolTip="Open in File Explorer">
+                                                                <TextBlock Text="&#xEC50;" FontFamily="Segoe MDL2 Assets"
+                                                                           TextAlignment="Center" FontSize="18"/>
+                                                                <Button.Style>
+                                                                    <Style TargetType="Button" BasedOn="{StaticResource BaseDarkButton}">
+                                                                        <Style.Triggers>
+                                                                            <Trigger Property="IsMouseOver" Value="False">
+                                                                                <Setter Property="Background" Value="Transparent"/>
+                                                                            </Trigger>
+                                                                            <Trigger Property="IsMouseOver" Value="True">
+                                                                                <Setter Property="Background" Value="#70FFFFFF"/>
+                                                                            </Trigger>
+                                                                        </Style.Triggers>
+                                                                    </Style>
+                                                                </Button.Style>
+                                                            </Button>
+                                                        </StackPanel>
+                                                    </Grid>
                                                 </Border>
                                                 </Border>
                                             </Grid>
                                             </Grid>
                                         </Button>
                                         </Button>

+ 14 - 0
PixiEditor/Views/Dialogs/HelloTherePopup.xaml.cs

@@ -1,6 +1,8 @@
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.ViewModels.SubViewModels.Main;
 using PixiEditor.ViewModels.SubViewModels.Main;
+using System.Diagnostics;
+using System.IO;
 using System.Windows;
 using System.Windows;
 using System.Windows.Input;
 using System.Windows.Input;
 
 
@@ -34,6 +36,8 @@ namespace PixiEditor.Views.Dialogs
 
 
         public RelayCommand OpenHyperlinkCommand { get => FileViewModel.Owner.MiscSubViewModel.OpenHyperlinkCommand; }
         public RelayCommand OpenHyperlinkCommand { get => FileViewModel.Owner.MiscSubViewModel.OpenHyperlinkCommand; }
 
 
+        public RelayCommand OpenInExplorerCommand { get; set; }
+
         public bool IsClosing { get; private set; }
         public bool IsClosing { get; private set; }
 
 
         public HelloTherePopup(FileViewModel fileViewModel)
         public HelloTherePopup(FileViewModel fileViewModel)
@@ -45,6 +49,7 @@ namespace PixiEditor.Views.Dialogs
             OpenFileCommand = new RelayCommand(OpenFile);
             OpenFileCommand = new RelayCommand(OpenFile);
             OpenNewFileCommand = new RelayCommand(OpenNewFile);
             OpenNewFileCommand = new RelayCommand(OpenNewFile);
             OpenRecentCommand = new RelayCommand(OpenRecent);
             OpenRecentCommand = new RelayCommand(OpenRecent);
+            OpenInExplorerCommand = new RelayCommand(OpenInExplorer, CanOpenInExplorer);
 
 
             RecentlyOpenedEmpty = RecentlyOpened.Count == 0;
             RecentlyOpenedEmpty = RecentlyOpened.Count == 0;
             RecentlyOpened.CollectionChanged += RecentlyOpened_CollectionChanged;
             RecentlyOpened.CollectionChanged += RecentlyOpened_CollectionChanged;
@@ -100,5 +105,14 @@ namespace PixiEditor.Views.Dialogs
             Close();
             Close();
             FileViewModel.OpenRecent(parameter);
             FileViewModel.OpenRecent(parameter);
         }
         }
+
+        private void OpenInExplorer(object parameter)
+        {
+            string path = Path.GetFullPath((string)parameter);
+
+            Process.Start("explorer.exe", $"/select,\"{path}\"");
+        }
+
+        private bool CanOpenInExplorer(object parameter) => File.Exists((string)parameter);
     }
     }
 }
 }

+ 1 - 1
PixiEditor/Views/UserControls/Zoombox.xaml.cs

@@ -285,7 +285,7 @@ namespace PixiEditor.Views.UserControls
             else
             else
             {
             {
                 if (ZoomMode == Mode.ZoomTool && e.ChangedButton == MouseButton.Left)
                 if (ZoomMode == Mode.ZoomTool && e.ChangedButton == MouseButton.Left)
-                    ZoomInto(e.GetPosition(mainCanvas), Keyboard.IsKeyDown(Key.LeftAlt) ? -1 : 1);
+                    ZoomInto(e.GetPosition(mainCanvas), Keyboard.IsKeyDown(Key.LeftCtrl) ? -1 : 1);
             }
             }
             activeMouseDownEventArgs = null;
             activeMouseDownEventArgs = null;
         }
         }

+ 3 - 1
PixiEditorTests/ModelsTests/ControllersTests/MockedSinglePixelPenTool.cs

@@ -9,9 +9,11 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
 {
 {
     public class MockedSinglePixelPenTool : BitmapOperationTool
     public class MockedSinglePixelPenTool : BitmapOperationTool
     {
     {
+        public override string Tooltip => "";
+    
         public override LayerChange[] Use(Layer layer, List<Coordinates> mouseMove, Color color)
         public override LayerChange[] Use(Layer layer, List<Coordinates> mouseMove, Color color)
         {
         {
             return Only(BitmapPixelChanges.FromSingleColoredArray(new[] { mouseMove[0] }, color), layer.LayerGuid);
             return Only(BitmapPixelChanges.FromSingleColoredArray(new[] { mouseMove[0] }, color), layer.LayerGuid);
         }
         }
     }
     }
-}
+}

+ 23 - 21
PixiEditorTests/ModelsTests/ControllersTests/TestReadonlyTool.cs

@@ -1,22 +1,24 @@
-using System;
+using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools;
-
-namespace PixiEditorTests.ModelsTests.ControllersTests
-{
-    public class TestReadonlyTool : ReadonlyTool
-    {
-        public TestReadonlyTool(Action toolAction)
-        {
-            ToolAction = toolAction;
-        }
-
-        public Action ToolAction { get; set; }
-
-        public override void Use(List<Coordinates> pixels)
-        {
-            ToolAction();
-        }
-    }
-}
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
+
+namespace PixiEditorTests.ModelsTests.ControllersTests
+{
+    public class TestReadonlyTool : ReadonlyTool
+    {
+        public override string Tooltip => "";
+    
+        public TestReadonlyTool(Action toolAction)
+        {
+            ToolAction = toolAction;
+        }
+
+        public Action ToolAction { get; set; }
+
+        public override void Use(List<Coordinates> pixels)
+        {
+            ToolAction();
+        }
+    }
+}