Browse Source

Merge pull request #194 from PixiEditor/toolDependencyInjection

Tool Dependency Injection
CPKreuz 4 years ago
parent
commit
aa6c4b7e19
27 changed files with 447 additions and 151 deletions
  1. 61 0
      PixiEditor/Helpers/DependencyInjectionHelper.cs
  2. 1 1
      PixiEditor/Models/Tools/ShapeTool.cs
  3. 2 0
      PixiEditor/Models/Tools/Tool.cs
  4. 71 0
      PixiEditor/Models/Tools/ToolBuilder.cs
  5. 2 3
      PixiEditor/Models/Tools/ToolSettings/Toolbars/BrightnessToolToolbar.cs
  6. 2 2
      PixiEditor/Models/Tools/Tools/BrightnessTool.cs
  7. 4 2
      PixiEditor/Models/Tools/Tools/EraserTool.cs
  8. 8 5
      PixiEditor/Models/Tools/Tools/FloodFill.cs
  9. 4 4
      PixiEditor/Models/Tools/Tools/LineTool.cs
  10. 31 25
      PixiEditor/Models/Tools/Tools/MoveTool.cs
  11. 13 5
      PixiEditor/Models/Tools/Tools/MoveViewportTool.cs
  12. 8 6
      PixiEditor/Models/Tools/Tools/PenTool.cs
  13. 10 4
      PixiEditor/Models/Tools/Tools/SelectTool.cs
  14. 8 4
      PixiEditor/Models/Tools/Tools/ZoomTool.cs
  15. 6 3
      PixiEditor/ViewModels/SubViewModels/Main/SelectionViewModel.cs
  16. 1 2
      PixiEditor/ViewModels/SubViewModels/Main/StylusViewModel.cs
  17. 33 7
      PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs
  18. 39 8
      PixiEditor/ViewModels/ViewModelMain.cs
  19. 16 6
      PixiEditor/Views/MainWindow.xaml.cs
  20. 0 24
      PixiEditorTests/Helpers.cs
  21. 58 0
      PixiEditorTests/Helpers/ViewModelHelper.cs
  22. 1 1
      PixiEditorTests/HelpersTests/ConvertersTests/DoubleToIntConverterTest.cs
  23. 41 0
      PixiEditorTests/HelpersTests/DependencyInjectionTests.cs
  24. 2 1
      PixiEditorTests/ModelsTests/DataHoldersTests/DocumentTests.cs
  25. 2 1
      PixiEditorTests/ModelsTests/ToolsTests/PenToolTests.cs
  26. 4 4
      PixiEditorTests/ModelsTests/ToolsTests/ZoomToolTests.cs
  27. 19 33
      PixiEditorTests/ViewModelsTests/ViewModelMainTests.cs

+ 61 - 0
PixiEditor/Helpers/DependencyInjectionHelper.cs

@@ -0,0 +1,61 @@
+using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+
+namespace PixiEditor.Helpers
+{
+    public static class DependencyInjectionHelper
+    {
+        public static T Inject<T>(this IServiceProvider provider)
+            => (T)Inject(provider, typeof(T));
+
+#nullable enable
+
+        public static object Inject(this IServiceProvider provider, Type type)
+        {
+            ConstructorInfo constructor = FindConstructorOrDefault(provider, type);
+
+            List<object?> parameters = new List<object?>();
+
+            foreach (Type argumentType in constructor.GetParameters().Select(x => x.ParameterType))
+            {
+                parameters.Add(provider.GetRequiredService(argumentType));
+            }
+
+            return constructor.Invoke(parameters.ToArray());
+        }
+
+#nullable disable
+
+        private static ConstructorInfo FindConstructorOrDefault(IServiceProvider provider, Type type)
+        {
+            ConstructorInfo foundConstructor = default;
+
+            foreach (ConstructorInfo info in type.GetConstructors())
+            {
+                if (HasParameters(provider, info.GetParameters()))
+                {
+                    foundConstructor = info;
+                    break;
+                }
+            }
+
+            return foundConstructor;
+        }
+
+        private static bool HasParameters(IServiceProvider provider, IEnumerable<ParameterInfo> parameters)
+        {
+            foreach (ParameterInfo parameter in parameters)
+            {
+                if (provider.GetService(parameter.ParameterType) is null)
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+    }
+}

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

@@ -58,7 +58,7 @@ namespace PixiEditor.Models.Tools
         // TODO: Add cache for lines 31, 32 (hopefully it would speed up calculation)
         // TODO: Add cache for lines 31, 32 (hopefully it would speed up calculation)
         public abstract override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color);
         public abstract override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color);
 
 
-        protected IEnumerable<Coordinates> GetThickShape(IEnumerable<Coordinates> shape, int thickness)
+        protected static IEnumerable<Coordinates> GetThickShape(IEnumerable<Coordinates> shape, int thickness)
         {
         {
             List<Coordinates> output = new List<Coordinates>();
             List<Coordinates> output = new List<Coordinates>();
             foreach (Coordinates item in shape)
             foreach (Coordinates item in shape)

+ 2 - 0
PixiEditor/Models/Tools/Tool.cs

@@ -49,6 +49,8 @@ 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)

+ 71 - 0
PixiEditor/Models/Tools/ToolBuilder.cs

@@ -0,0 +1,71 @@
+using PixiEditor.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace PixiEditor.Models.Tools
+{
+    /// <summary>
+    /// Handles Depdency Injection of tools
+    /// </summary>
+    public class ToolBuilder
+    {
+        private readonly IServiceProvider services;
+
+        private readonly List<Type> toBuild = new List<Type>();
+
+        public ToolBuilder(IServiceProvider services)
+        {
+            this.services = services;
+        }
+
+        /// <summary>
+        /// Constructs a new tool of type <typeparamref name="T"/> and injects all services of <paramref name="services"/>
+        /// </summary>
+        public static T BuildTool<T>(IServiceProvider services)
+            where T : Tool
+            => (T)BuildTool(typeof(T), services);
+
+        /// <summary>
+        /// Constructs a new tool of type <paramref name="type"/> and injects all services of <paramref name="services"/>
+        /// </summary>
+        public static Tool BuildTool(Type type, IServiceProvider services)
+        {
+            Tool tool = (Tool)services.Inject(type);
+
+            return tool;
+        }
+
+        /// <summary>
+        /// Adds a new tool of type <typeparamref name="T"/> to the building chain.
+        /// </summary>
+        public ToolBuilder Add<T>()
+            where T : Tool
+            => Add(typeof(T));
+
+        /// <summary>
+        /// Adds a new tool of type <paramref name="type"/> to the building chain.
+        /// </summary>
+        public ToolBuilder Add(Type type)
+        {
+            toBuild.Add(type);
+
+            return this;
+        }
+
+        /// <summary>
+        /// Builds all added tools.
+        /// </summary>
+        public IEnumerable<Tool> Build()
+        {
+            List<Tool> tools = new List<Tool>();
+
+            foreach (Type type in toBuild)
+            {
+                tools.Add(BuildTool(type, services));
+            }
+
+            return tools;
+        }
+    }
+}

+ 2 - 3
PixiEditor/Models/Tools/ToolSettings/Toolbars/BrightnessToolToolbar.cs

@@ -1,5 +1,4 @@
-using System;
-using PixiEditor.Models.Enums;
+using PixiEditor.Models.Enums;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 
 
 namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
 namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
@@ -9,7 +8,7 @@ namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
         public BrightnessToolToolbar(float initialValue)
         public BrightnessToolToolbar(float initialValue)
         {
         {
             Settings.Add(new FloatSetting("CorrectionFactor", initialValue, "Strength:", 0f, 100f));
             Settings.Add(new FloatSetting("CorrectionFactor", initialValue, "Strength:", 0f, 100f));
-            Settings.Add(new DropdownSetting("BrightnessMode", Enum.GetNames(typeof(BrightnessMode)), "Mode"));
+            Settings.Add(new EnumSetting<BrightnessMode>("BrightnessMode", "Mode"));
         }
         }
     }
     }
 }
 }

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

@@ -3,6 +3,7 @@ using System.Collections.Generic;
 using System.Windows.Controls;
 using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Media;
+using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.Colors;
 using PixiEditor.Models.Colors;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Enums;
@@ -53,8 +54,7 @@ namespace PixiEditor.Models.Tools.Tools
         {
         {
             int toolSize = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
             int toolSize = Toolbar.GetSetting<SizeSetting>("ToolSize").Value;
             float correctionFactor = Toolbar.GetSetting<FloatSetting>("CorrectionFactor").Value;
             float correctionFactor = Toolbar.GetSetting<FloatSetting>("CorrectionFactor").Value;
-            Enum.TryParse((Toolbar.GetSetting<DropdownSetting>("BrightnessMode")?.Value as ComboBoxItem)?.Content as string, out BrightnessMode mode);
-            Mode = mode;
+            Mode = Toolbar.GetEnumSetting<BrightnessMode>("BrightnessMode").Value;
 
 
             LayerChange[] layersChanges = new LayerChange[1];
             LayerChange[] layersChanges = new LayerChange[1];
             if (Keyboard.IsKeyDown(Key.LeftCtrl))
             if (Keyboard.IsKeyDown(Key.LeftCtrl))

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

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Windows.Media;
 using System.Windows.Media;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
@@ -10,13 +11,14 @@ namespace PixiEditor.Models.Tools.Tools
 {
 {
     public class EraserTool : BitmapOperationTool
     public class EraserTool : BitmapOperationTool
     {
     {
-        private readonly PenTool pen = new PenTool();
+        private readonly PenTool pen;
 
 
-        public EraserTool()
+        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)";
             Tooltip = "Erasers color from pixel. (E)";
             Toolbar = new BasicToolbar();
             Toolbar = new BasicToolbar();
+            pen = new PenTool(bitmapManager);
         }
         }
 
 
         public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
         public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)

+ 8 - 5
PixiEditor/Models/Tools/Tools/FloodFill.cs

@@ -1,18 +1,21 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Windows.Media;
 using System.Windows.Media;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
-using PixiEditor.ViewModels;
 
 
 namespace PixiEditor.Models.Tools.Tools
 namespace PixiEditor.Models.Tools.Tools
 {
 {
     public class FloodFill : BitmapOperationTool
     public class FloodFill : BitmapOperationTool
     {
     {
-        public FloodFill()
+        private BitmapManager BitmapManager { get; }
+
+        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)";
             Tooltip = "Fills area with color. (G)";
+            BitmapManager = bitmapManager;
         }
         }
 
 
         public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
         public override LayerChange[] Use(Layer layer, List<Coordinates> coordinates, Color color)
@@ -20,12 +23,12 @@ namespace PixiEditor.Models.Tools.Tools
             return Only(ForestFire(layer, coordinates[0], color), layer);
             return Only(ForestFire(layer, coordinates[0], color), layer);
         }
         }
 
 
-        private BitmapPixelChanges ForestFire(Layer layer, Coordinates startingCoords, Color newColor)
+        public BitmapPixelChanges ForestFire(Layer layer, Coordinates startingCoords, Color newColor)
         {
         {
             List<Coordinates> changedCoords = new List<Coordinates>();
             List<Coordinates> changedCoords = new List<Coordinates>();
 
 
-            int width = ViewModelMain.Current.BitmapManager.ActiveDocument.Width;
-            int height = ViewModelMain.Current.BitmapManager.ActiveDocument.Height;
+            int width = BitmapManager.ActiveDocument.Width;
+            int height = BitmapManager.ActiveDocument.Height;
 
 
             var visited = new bool[width, height];
             var visited = new bool[width, height];
 
 

+ 4 - 4
PixiEditor/Models/Tools/Tools/LineTool.cs

@@ -58,13 +58,13 @@ namespace PixiEditor.Models.Tools.Tools
 
 
         public IEnumerable<Coordinates> CreateLine(Coordinates start, Coordinates end, int thickness, CapType startCap, CapType endCap)
         public IEnumerable<Coordinates> CreateLine(Coordinates start, Coordinates end, int thickness, CapType startCap, CapType endCap)
         {
         {
-            return CreateLine(new() { end, start }, thickness, startCap, endCap);
+            return CreateLine(new List<Coordinates>() { end, start }, thickness, startCap, endCap);
         }
         }
 
 
-        private IEnumerable<Coordinates> CreateLine(List<Coordinates> coordinates, int thickness, CapType startCap, CapType endCap)
+        private IEnumerable<Coordinates> CreateLine(IEnumerable<Coordinates> coordinates, int thickness, CapType startCap, CapType endCap)
         {
         {
-            Coordinates startingCoordinates = coordinates[^1];
-            Coordinates latestCoordinates = coordinates[0];
+            Coordinates startingCoordinates = coordinates.Last();
+            Coordinates latestCoordinates = coordinates.First();
             if (thickness == 1)
             if (thickness == 1)
             {
             {
                 return BresenhamLine(startingCoordinates.X, startingCoordinates.Y, latestCoordinates.X, latestCoordinates.Y);
                 return BresenhamLine(startingCoordinates.X, startingCoordinates.Y, latestCoordinates.X, latestCoordinates.Y);

+ 31 - 25
PixiEditor/Models/Tools/Tools/MoveTool.cs

@@ -29,7 +29,7 @@ namespace PixiEditor.Models.Tools.Tools
         private Coordinates[] startSelection;
         private Coordinates[] startSelection;
         private bool updateViewModelSelection = true;
         private bool updateViewModelSelection = true;
 
 
-        public MoveTool()
+        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.";
             Tooltip = "Moves selected pixels (V). Hold Ctrl to move all layers.";
@@ -37,10 +37,14 @@ namespace PixiEditor.Models.Tools.Tools
             HideHighlight = true;
             HideHighlight = true;
             RequiresPreviewLayer = true;
             RequiresPreviewLayer = true;
             UseDefaultUndoMethod = true;
             UseDefaultUndoMethod = true;
+
+            BitmapManager = bitmapManager;
         }
         }
 
 
         public bool MoveAll { get; set; } = false;
         public bool MoveAll { get; set; } = false;
 
 
+        private BitmapManager BitmapManager { get; }
+
         public override void OnKeyDown(KeyEventArgs e)
         public override void OnKeyDown(KeyEventArgs e)
         {
         {
             if (e.Key == Key.LeftCtrl)
             if (e.Key == Key.LeftCtrl)
@@ -59,31 +63,33 @@ namespace PixiEditor.Models.Tools.Tools
 
 
         public override void AfterAddedUndo(UndoManager undoManager)
         public override void AfterAddedUndo(UndoManager undoManager)
         {
         {
-            if (currentSelection != null && currentSelection.Length > 0)
+            if (currentSelection == null || currentSelection.Length == 0)
             {
             {
-                Change changes = undoManager.UndoStack.Peek();
+                return;
+            }
 
 
-                // Inject to default undo system change custom changes made by this tool
-                foreach (var item in startPixelColors)
-                {
-                    BitmapPixelChanges beforeMovePixels = BitmapPixelChanges.FromArrays(startSelection, item.Value);
-                    BitmapPixelChanges afterMovePixels = BitmapPixelChanges.FromArrays(currentSelection, endPixelColors[item.Key]);
-                    Guid layerGuid = item.Key;
-                    var oldValue = (LayerChange[])changes.OldValue;
+            Change changes = undoManager.UndoStack.Peek();
 
 
-                    if (oldValue.Any(x => x.LayerGuid == layerGuid))
-                    {
-                        var layer = oldValue.First(x => x.LayerGuid == layerGuid);
-                        layer.PixelChanges.ChangedPixels.AddRangeOverride(afterMovePixels.ChangedPixels);
-                        layer.PixelChanges.ChangedPixels
-                            .AddRangeOverride(beforeMovePixels.ChangedPixels);
+            // Inject to default undo system change custom changes made by this tool
+            foreach (var item in startPixelColors)
+            {
+                BitmapPixelChanges beforeMovePixels = BitmapPixelChanges.FromArrays(startSelection, item.Value);
+                BitmapPixelChanges afterMovePixels = BitmapPixelChanges.FromArrays(currentSelection, endPixelColors[item.Key]);
+                Guid layerGuid = item.Key;
+                var oldValue = (LayerChange[])changes.OldValue;
 
 
-                        ((LayerChange[])changes.NewValue).First(x => x.LayerGuid == layerGuid).PixelChanges.ChangedPixels
-                            .AddRangeNewOnly(BitmapPixelChanges
-                                .FromSingleColoredArray(startSelection, System.Windows.Media.Colors.Transparent)
-                                .ChangedPixels);
-                    }
-                }
+                if (oldValue.Any(x => x.LayerGuid == layerGuid))
+                {
+                    var layer = oldValue.First(x => x.LayerGuid == layerGuid);
+                    layer.PixelChanges.ChangedPixels.AddRangeOverride(afterMovePixels.ChangedPixels);
+                    layer.PixelChanges.ChangedPixels
+                        .AddRangeOverride(beforeMovePixels.ChangedPixels);
+
+                    ((LayerChange[])changes.NewValue).First(x => x.LayerGuid == layerGuid).PixelChanges.ChangedPixels
+                        .AddRangeNewOnly(BitmapPixelChanges
+                            .FromSingleColoredArray(startSelection, System.Windows.Media.Colors.Transparent)
+                            .ChangedPixels);
+                }
             }
             }
         }
         }
 
 
@@ -93,7 +99,7 @@ namespace PixiEditor.Models.Tools.Tools
         {
         {
             if (currentSelection != null && currentSelection.Length == 0)
             if (currentSelection != null && currentSelection.Length == 0)
             {
             {
-                ViewModelMain.Current.BitmapManager.ActiveDocument.UndoManager.AddUndoChange(new Change(
+                BitmapManager.ActiveDocument.UndoManager.AddUndoChange(new Change(
                     ApplyOffsets,
                     ApplyOffsets,
                     new object[] { startingOffsets },
                     new object[] { startingOffsets },
                     ApplyOffsets,
                     ApplyOffsets,
@@ -107,7 +113,7 @@ namespace PixiEditor.Models.Tools.Tools
             ResetSelectionValues(startPos);
             ResetSelectionValues(startPos);
 
 
             // Move offset if no selection
             // Move offset if no selection
-            var doc = ViewModelMain.Current.BitmapManager.ActiveDocument;
+            Document doc = BitmapManager.ActiveDocument;
             Selection selection = doc.ActiveSelection;
             Selection selection = doc.ActiveSelection;
             if (selection != null && selection.SelectedPoints.Count > 0)
             if (selection != null && selection.SelectedPoints.Count > 0)
             {
             {
@@ -232,7 +238,7 @@ namespace PixiEditor.Models.Tools.Tools
             Guid layerGuid = layer.LayerGuid;
             Guid layerGuid = layer.LayerGuid;
             if (!clearedPixels.ContainsKey(layerGuid) || clearedPixels[layerGuid] == false)
             if (!clearedPixels.ContainsKey(layerGuid) || clearedPixels[layerGuid] == false)
             {
             {
-                ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.First(x => x == layer)
+                BitmapManager.ActiveDocument.Layers.First(x => x == layer)
                     .SetPixels(BitmapPixelChanges.FromSingleColoredArray(selection, System.Windows.Media.Colors.Transparent));
                     .SetPixels(BitmapPixelChanges.FromSingleColoredArray(selection, System.Windows.Media.Colors.Transparent));
 
 
                 clearedPixels[layerGuid] = true;
                 clearedPixels[layerGuid] = true;

+ 13 - 5
PixiEditor/Models/Tools/Tools/MoveViewportTool.cs

@@ -1,21 +1,29 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Drawing;
 using System.Drawing;
 using System.Windows.Input;
 using System.Windows.Input;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
-using PixiEditor.ViewModels;
-
+using PixiEditor.ViewModels.SubViewModels.Main;
+
 namespace PixiEditor.Models.Tools.Tools
 namespace PixiEditor.Models.Tools.Tools
 {
 {
     public class MoveViewportTool : ReadonlyTool
     public class MoveViewportTool : ReadonlyTool
     {
     {
         private Point clickPoint;
         private Point clickPoint;
 
 
-        public MoveViewportTool()
+        private BitmapManager BitmapManager { get; }
+
+        private ToolsViewModel ToolsViewModel { get; }
+
+        public MoveViewportTool(BitmapManager bitmapManager, ToolsViewModel toolsViewModel)
         {
         {
             HideHighlight = true;
             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)";
             Tooltip = "Move viewport. (H)";
+
+            BitmapManager = bitmapManager;
+            ToolsViewModel = toolsViewModel;
         }
         }
 
 
         public override void OnMouseDown(MouseEventArgs e)
         public override void OnMouseDown(MouseEventArgs e)
@@ -31,7 +39,7 @@ namespace PixiEditor.Models.Tools.Tools
             if (e.LeftButton == MouseButtonState.Pressed || e.MiddleButton == MouseButtonState.Pressed)
             if (e.LeftButton == MouseButtonState.Pressed || e.MiddleButton == MouseButtonState.Pressed)
             {
             {
                 var point = MousePositionConverter.GetCursorPosition();
                 var point = MousePositionConverter.GetCursorPosition();
-                ViewModelMain.Current.BitmapManager.ActiveDocument.ViewportPosition = new System.Windows.Point(
+                BitmapManager.ActiveDocument.ViewportPosition = new System.Windows.Point(
                     point.X - clickPoint.X,
                     point.X - clickPoint.X,
                     point.Y - clickPoint.Y);
                     point.Y - clickPoint.Y);
             }
             }
@@ -41,7 +49,7 @@ namespace PixiEditor.Models.Tools.Tools
         {
         {
             if (e.MiddleButton == MouseButtonState.Pressed)
             if (e.MiddleButton == MouseButtonState.Pressed)
             {
             {
-                ViewModelMain.Current.ToolsSubViewModel.SetActiveTool(ViewModelMain.Current.ToolsSubViewModel.LastActionTool);
+                ToolsViewModel.SetActiveTool(ToolsViewModel.LastActionTool);
             }
             }
         }
         }
 
 

+ 8 - 6
PixiEditor/Models/Tools/Tools/PenTool.cs

@@ -1,11 +1,11 @@
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings;
 using PixiEditor.Models.Tools.ToolSettings;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
-using PixiEditor.ViewModels;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
@@ -18,12 +18,14 @@ namespace PixiEditor.Models.Tools.Tools
     {
     {
         private readonly SizeSetting toolSizeSetting;
         private readonly SizeSetting toolSizeSetting;
         private readonly BoolSetting pixelPerfectSetting;
         private readonly BoolSetting pixelPerfectSetting;
-        private readonly LineTool lineTool;
         private readonly List<Coordinates> confirmedPixels = new List<Coordinates>();
         private readonly List<Coordinates> confirmedPixels = new List<Coordinates>();
+        private readonly LineTool lineTool;
         private Coordinates[] lastChangedPixels = new Coordinates[3];
         private Coordinates[] lastChangedPixels = new Coordinates[3];
         private byte changedPixelsindex = 0;
         private byte changedPixelsindex = 0;
 
 
-        public PenTool()
+        private BitmapManager BitmapManager { get; }
+
+        public PenTool(BitmapManager bitmapManager)
         {
         {
             Cursor = Cursors.Pen;
             Cursor = Cursors.Pen;
             ActionDisplay = "Click and move to draw.";
             ActionDisplay = "Click and move to draw.";
@@ -32,9 +34,9 @@ namespace PixiEditor.Models.Tools.Tools
             toolSizeSetting = Toolbar.GetSetting<SizeSetting>("ToolSize");
             toolSizeSetting = Toolbar.GetSetting<SizeSetting>("ToolSize");
             pixelPerfectSetting = Toolbar.GetSetting<BoolSetting>("PixelPerfectEnabled");
             pixelPerfectSetting = Toolbar.GetSetting<BoolSetting>("PixelPerfectEnabled");
             pixelPerfectSetting.ValueChanged += PixelPerfectSettingValueChanged;
             pixelPerfectSetting.ValueChanged += PixelPerfectSettingValueChanged;
-            RequiresPreviewLayer = pixelPerfectSetting.Value;
-            lineTool = new LineTool();
             ClearPreviewLayerOnEachIteration = false;
             ClearPreviewLayerOnEachIteration = false;
+            BitmapManager = bitmapManager;
+            lineTool = new LineTool();
         }
         }
 
 
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
@@ -54,7 +56,7 @@ namespace PixiEditor.Models.Tools.Tools
                 color,
                 color,
                 toolSizeSetting.Value,
                 toolSizeSetting.Value,
                 pixelPerfectSetting.Value,
                 pixelPerfectSetting.Value,
-                ViewModelMain.Current.BitmapManager.ActiveDocument.PreviewLayer);
+                BitmapManager.ActiveDocument.PreviewLayer);
             return Only(pixels, layer);
             return Only(pixels, layer);
         }
         }
 
 

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

@@ -19,17 +19,23 @@ namespace PixiEditor.Models.Tools.Tools
 {
 {
     public class SelectTool : ReadonlyTool
     public class SelectTool : ReadonlyTool
     {
     {
-        private readonly RectangleTool rectangleTool = new RectangleTool();
-        private readonly CircleTool circleTool = new CircleTool();
+        private readonly RectangleTool rectangleTool;
+        private readonly CircleTool circleTool;
         private IEnumerable<Coordinates> oldSelectedPoints;
         private IEnumerable<Coordinates> oldSelectedPoints;
 
 
         private static Selection ActiveSelection { get => ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection; }
         private static Selection ActiveSelection { get => ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection; }
 
 
-        public SelectTool()
+        private BitmapManager BitmapManager { get; }
+
+        public SelectTool(BitmapManager bitmapManager)
         {
         {
             ActionDisplay = "Click and move to select an area.";
             ActionDisplay = "Click and move to select an area.";
             Tooltip = "Selects area. (M)";
             Tooltip = "Selects area. (M)";
             Toolbar = new SelectToolToolbar();
             Toolbar = new SelectToolToolbar();
+            BitmapManager = bitmapManager;
+
+            rectangleTool = new RectangleTool();
+            circleTool = new CircleTool();
         }
         }
 
 
         public SelectionType SelectionType { get; set; } = SelectionType.Add;
         public SelectionType SelectionType { get; set; } = SelectionType.Add;
@@ -107,7 +113,7 @@ namespace PixiEditor.Models.Tools.Tools
                 throw new NotImplementedException($"Selection shape '{shape}' has not been implemented");
                 throw new NotImplementedException($"Selection shape '{shape}' has not been implemented");
             }
             }
 
 
-            ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(selection, SelectionType);
+            BitmapManager.ActiveDocument.ActiveSelection.SetSelection(selection, SelectionType);
         }
         }
     }
     }
 }
 }

+ 8 - 4
PixiEditor/Models/Tools/Tools/ZoomTool.cs

@@ -2,8 +2,8 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Windows;
 using System.Windows;
 using System.Windows.Input;
 using System.Windows.Input;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
-using PixiEditor.ViewModels;
 
 
 namespace PixiEditor.Models.Tools.Tools
 namespace PixiEditor.Models.Tools.Tools
 {
 {
@@ -16,13 +16,17 @@ namespace PixiEditor.Models.Tools.Tools
         private double workAreaWidth = SystemParameters.WorkArea.Width;
         private double workAreaWidth = SystemParameters.WorkArea.Width;
         private double pixelsPerZoomMultiplier;
         private double pixelsPerZoomMultiplier;
 
 
-        public ZoomTool()
+        private BitmapManager BitmapManager { get; }
+
+        public ZoomTool(BitmapManager bitmapManager)
         {
         {
             HideHighlight = true;
             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.";
             Tooltip = "Zooms viewport (Z). Click to zoom in, hold alt and click to zoom out.";
             pixelsPerZoomMultiplier = workAreaWidth / ZoomSensitivityMultiplier;
             pixelsPerZoomMultiplier = workAreaWidth / ZoomSensitivityMultiplier;
+
+            BitmapManager = bitmapManager;
         }
         }
 
 
         public override void OnKeyDown(KeyEventArgs e)
         public override void OnKeyDown(KeyEventArgs e)
@@ -44,7 +48,7 @@ namespace PixiEditor.Models.Tools.Tools
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
         {
             startingX = MousePositionConverter.GetCursorPosition().X;
             startingX = MousePositionConverter.GetCursorPosition().X;
-            ViewModelMain.Current.BitmapManager.ActiveDocument.ZoomPercentage = 100; // This resest the value, so callback in MainDrawingPanel can fire again later
+            BitmapManager.ActiveDocument.ZoomPercentage = 100; // This resest the value, so callback in MainDrawingPanel can fire again later
         }
         }
 
 
         public override void OnMouseMove(MouseEventArgs e)
         public override void OnMouseMove(MouseEventArgs e)
@@ -77,7 +81,7 @@ namespace PixiEditor.Models.Tools.Tools
 
 
         public void Zoom(double percentage)
         public void Zoom(double percentage)
         {
         {
-            ViewModelMain.Current.BitmapManager.ActiveDocument.ZoomPercentage = percentage;
+            BitmapManager.ActiveDocument.ZoomPercentage = percentage;
         }
         }
 
 
         public override void Use(List<Coordinates> pixels)
         public override void Use(List<Coordinates> pixels)

+ 6 - 3
PixiEditor/ViewModels/SubViewModels/Main/SelectionViewModel.cs

@@ -4,6 +4,7 @@ using PixiEditor.Helpers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.Tools.Tools;
 
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 namespace PixiEditor.ViewModels.SubViewModels.Main
@@ -14,20 +15,22 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         public RelayCommand SelectAllCommand { get; set; }
         public RelayCommand SelectAllCommand { get; set; }
 
 
+        private readonly SelectTool selectTool;
+
         public SelectionViewModel(ViewModelMain owner)
         public SelectionViewModel(ViewModelMain owner)
             : base(owner)
             : base(owner)
         {
         {
             DeselectCommand = new RelayCommand(Deselect, SelectionIsNotEmpty);
             DeselectCommand = new RelayCommand(Deselect, SelectionIsNotEmpty);
             SelectAllCommand = new RelayCommand(SelectAll, CanSelectAll);
             SelectAllCommand = new RelayCommand(SelectAll, CanSelectAll);
+
+            selectTool = new SelectTool(Owner.BitmapManager);
         }
         }
 
 
         public void SelectAll(object parameter)
         public void SelectAll(object parameter)
         {
         {
-            SelectTool select = new SelectTool();
-
             var oldSelection = new List<Coordinates>(Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints);
             var oldSelection = new List<Coordinates>(Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints);
 
 
-            Owner.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(select.GetAllSelection(), SelectionType.New);
+            Owner.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(selectTool.GetAllSelection(), SelectionType.New);
             SelectionHelpers.AddSelectionUndoStep(Owner.BitmapManager.ActiveDocument, oldSelection, SelectionType.New);
             SelectionHelpers.AddSelectionUndoStep(Owner.BitmapManager.ActiveDocument, oldSelection, SelectionType.New);
         }
         }
 
 

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

@@ -19,14 +19,13 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         public StylusViewModel(ViewModelMain owner)
         public StylusViewModel(ViewModelMain owner)
             : base(owner)
             : base(owner)
         {
         {
-            SetOwner(owner);
         }
         }
 
 
         public void SetOwner(ViewModelMain owner)
         public void SetOwner(ViewModelMain owner)
         {
         {
             if (Owner is not null)
             if (Owner is not null)
             {
             {
-                throw new System.Exception("StylusViewModel already has an owner");
+                throw new System.Exception($"{nameof(StylusViewModel)} already has an owner");
             }
             }
 
 
             Owner = owner;
             Owner = owner;

+ 33 - 7
PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs

@@ -1,6 +1,7 @@
 using System;
 using System;
 using System.Collections.ObjectModel;
 using System.Collections.ObjectModel;
 using System.Linq;
 using System.Linq;
+using System.Reflection;
 using System.Windows.Input;
 using System.Windows.Input;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools;
@@ -35,14 +36,20 @@ 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);
+        }
 
 
-            ToolSet = new ObservableCollection<Tool>
-            {
-                new MoveViewportTool(), new MoveTool(), new PenTool(), new SelectTool(), new FloodFill(), new LineTool(),
-                new CircleTool(), new RectangleTool(), new EraserTool(), new ColorPickerTool(), new BrightnessTool(),
-                new ZoomTool()
-            };
-            SetActiveTool(typeof(MoveViewportTool));
+        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());
+
+            SetActiveTool<MoveViewportTool>();
         }
         }
 
 
         public void SetActiveTool<T>()
         public void SetActiveTool<T>()
@@ -82,6 +89,25 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             SetActiveTool(tool.GetType());
             SetActiveTool(tool.GetType());
         }
         }
 
 
+        private static T CreateTool<T>(IServiceProvider provider)
+            where T : new()
+        {
+            T tool = default;
+            Type toolType = typeof(T);
+
+            foreach (PropertyInfo info in toolType.GetProperties(BindingFlags.Public))
+            {
+                if (!info.CanWrite)
+                {
+                    continue;
+                }
+
+                info.SetValue(tool, provider.GetService(info.PropertyType));
+            }
+
+            return tool;
+        }
+
         private void ChangeToolSize(object parameter)
         private void ChangeToolSize(object parameter)
         {
         {
             int increment = (int)parameter;
             int increment = (int)parameter;

+ 39 - 8
PixiEditor/ViewModels/ViewModelMain.cs

@@ -29,6 +29,8 @@ namespace PixiEditor.ViewModels
 
 
         public static ViewModelMain Current { get; set; }
         public static ViewModelMain Current { get; set; }
 
 
+        public IServiceProvider Services { get; private set; }
+
         public Action CloseAction { get; set; }
         public Action CloseAction { get; set; }
 
 
         public event EventHandler OnStartupEvent;
         public event EventHandler OnStartupEvent;
@@ -117,15 +119,28 @@ namespace PixiEditor.ViewModels
 #endif
 #endif
         }
         }
 
 
-        public ViewModelMain(IServiceProvider services)
+        public ViewModelMain(IServiceCollection services)
         {
         {
             Current = this;
             Current = this;
 
 
+            ConfigureServices(services);
+            Setup(services.BuildServiceProvider());
+        }
+
+        public void ConfigureServices(IServiceCollection collection)
+        {
+            collection.AddSingleton(this);
+        }
+
+        public void Setup(IServiceProvider services)
+        {
+            Services = services;
+
             Preferences = services.GetRequiredService<IPreferences>();
             Preferences = services.GetRequiredService<IPreferences>();
 
 
             Preferences.Init();
             Preferences.Init();
 
 
-            BitmapManager = new BitmapManager();
+            BitmapManager = services.GetRequiredService<BitmapManager>();
             BitmapManager.BitmapOperations.BitmapChanged += BitmapUtility_BitmapChanged;
             BitmapManager.BitmapOperations.BitmapChanged += BitmapUtility_BitmapChanged;
             BitmapManager.MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
             BitmapManager.MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
             BitmapManager.DocumentChanged += BitmapManager_DocumentChanged;
             BitmapManager.DocumentChanged += BitmapManager_DocumentChanged;
@@ -137,7 +152,9 @@ namespace PixiEditor.ViewModels
             CloseWindowCommand = new RelayCommand(CloseWindow);
             CloseWindowCommand = new RelayCommand(CloseWindow);
 
 
             FileSubViewModel = new FileViewModel(this);
             FileSubViewModel = new FileViewModel(this);
-            ToolsSubViewModel = new ToolsViewModel(this);
+            ToolsSubViewModel = GetSubViewModel<ToolsViewModel>(services);
+            ToolsSubViewModel.SetupTools(services);
+
             IoSubViewModel = new IoViewModel(this);
             IoSubViewModel = new IoViewModel(this);
             LayersSubViewModel = new LayersViewModel(this);
             LayersSubViewModel = new LayersViewModel(this);
             ClipboardSubViewModel = new ClipboardViewModel(this);
             ClipboardSubViewModel = new ClipboardViewModel(this);
@@ -148,11 +165,8 @@ namespace PixiEditor.ViewModels
             DiscordViewModel = new DiscordViewModel(this, "764168193685979138");
             DiscordViewModel = new DiscordViewModel(this, "764168193685979138");
             UpdateSubViewModel = new UpdateViewModel(this);
             UpdateSubViewModel = new UpdateViewModel(this);
 
 
-            WindowSubViewModel = services.GetService<WindowViewModel>();
-            WindowSubViewModel?.SetOwner(this);
-
-            StylusSubViewModel = services.GetService<StylusViewModel>();
-            StylusSubViewModel?.SetOwner(this);
+            WindowSubViewModel = GetSubViewModel<WindowViewModel>(services, false);
+            StylusSubViewModel = GetSubViewModel<StylusViewModel>(services);
 
 
             AddDebugOnlyViewModels();
             AddDebugOnlyViewModels();
             AddReleaseOnlyViewModels();
             AddReleaseOnlyViewModels();
@@ -353,5 +367,22 @@ namespace PixiEditor.ViewModels
                 ColorsSubViewModel.AddSwatch(ColorsSubViewModel.PrimaryColor);
                 ColorsSubViewModel.AddSwatch(ColorsSubViewModel.PrimaryColor);
             }
             }
         }
         }
+
+        private T GetSubViewModel<T>(IServiceProvider services, bool isRequired = true)
+        {
+            T subViewModel = services.GetService<T>();
+
+            if (subViewModel is null && isRequired)
+            {
+                throw new InvalidOperationException($"No required view model for type '{typeof(T)}' has been registered.");
+            }
+
+            if (subViewModel is ISettableOwner<ViewModelMain> settable)
+            {
+                settable.SetOwner(this);
+            }
+
+            return subViewModel;
+        }
     }
     }
 }
 }

+ 16 - 6
PixiEditor/Views/MainWindow.xaml.cs

@@ -12,6 +12,7 @@ using PixiEditor.Views.Dialogs;
 using System.Windows.Media.Imaging;
 using System.Windows.Media.Imaging;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using System.Windows.Interop;
 using System.Windows.Interop;
+using PixiEditor.Models.Controllers;
 
 
 namespace PixiEditor
 namespace PixiEditor
 {
 {
@@ -22,7 +23,7 @@ namespace PixiEditor
     {
     {
         private static WriteableBitmap pixiEditorLogo;
         private static WriteableBitmap pixiEditorLogo;
 
 
-        private PreferencesSettings preferences;
+        private readonly PreferencesSettings preferences;
 
 
         public new ViewModelMain DataContext { get => (ViewModelMain)base.DataContext; set => base.DataContext = value; }
         public new ViewModelMain DataContext { get => (ViewModelMain)base.DataContext; set => base.DataContext = value; }
 
 
@@ -30,12 +31,9 @@ namespace PixiEditor
         {
         {
             preferences = new PreferencesSettings();
             preferences = new PreferencesSettings();
 
 
-            IServiceCollection services = new ServiceCollection()
-                .AddSingleton<IPreferences>(preferences)
-                .AddSingleton<StylusViewModel>()
-                .AddSingleton<WindowViewModel>();
+            IServiceCollection services = ConfigureServices(new ServiceCollection());
 
 
-            DataContext = new ViewModelMain(services.BuildServiceProvider());
+            DataContext = new ViewModelMain(services);
 
 
             InitializeComponent();
             InitializeComponent();
 
 
@@ -80,6 +78,18 @@ namespace PixiEditor
             Application.Current.Windows.OfType<HelloTherePopup>().ToList().ForEach(x => { if (!x.IsClosing) x.Close(); });
             Application.Current.Windows.OfType<HelloTherePopup>().ToList().ForEach(x => { if (!x.IsClosing) x.Close(); });
         }
         }
 
 
+        private IServiceCollection ConfigureServices(IServiceCollection services)
+        {
+            services
+                .AddSingleton<IPreferences>(preferences)
+                .AddSingleton<StylusViewModel>()
+                .AddSingleton<WindowViewModel>()
+                .AddSingleton<BitmapManager>()
+                .AddSingleton<ToolsViewModel>();
+
+            return services;
+        }
+
         private void BitmapManager_DocumentChanged(object sender, Models.Events.DocumentChangedEventArgs e)
         private void BitmapManager_DocumentChanged(object sender, Models.Events.DocumentChangedEventArgs e)
         {
         {
             if (preferences.GetPreference("ImagePreviewInTaskbar", false))
             if (preferences.GetPreference("ImagePreviewInTaskbar", false))

+ 0 - 24
PixiEditorTests/Helpers.cs

@@ -1,24 +0,0 @@
-using System;
-using Microsoft.Extensions.DependencyInjection;
-using PixiEditor.Models.UserPreferences;
-using PixiEditor.ViewModels;
-
-namespace PixiEditorTests
-{
-    public static class Helpers
-    {
-        public static ViewModelMain MockedViewModelMain()
-        {
-            IServiceProvider provider = MockedServiceProvider();
-
-            return new ViewModelMain(provider);
-        }
-
-        public static IServiceProvider MockedServiceProvider()
-        {
-            return new ServiceCollection()
-                .AddSingleton<IPreferences>(new Mocks.PreferenceSettingsMock())
-                .BuildServiceProvider();
-        }
-    }
-}

+ 58 - 0
PixiEditorTests/Helpers/ViewModelHelper.cs

@@ -0,0 +1,58 @@
+using Microsoft.Extensions.DependencyInjection;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.Tools;
+using PixiEditor.Models.UserPreferences;
+using PixiEditor.ViewModels;
+using PixiEditor.ViewModels.SubViewModels.Main;
+using PixiEditorTests.Mocks;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditorTests.HelpersTests
+{
+    public static class ViewModelHelper
+    {
+        public static IServiceCollection GetViewModelMainCollection()
+        {
+            return new ServiceCollection()
+                .AddScoped<IPreferences, PreferenceSettingsMock>()
+                .AddSingleton<BitmapManager>();
+        }
+
+        public static ViewModelMain MockedViewModelMain()
+        {
+            IServiceCollection provider = MockedServiceCollection();
+
+            return new ViewModelMain(provider);
+        }
+
+        public static IServiceCollection MockedServiceCollection()
+        {
+            return new ServiceCollection()
+                .AddSingleton<IPreferences>(new Mocks.PreferenceSettingsMock())
+                .AddSingleton<StylusViewModel>()
+                .AddSingleton<BitmapManager>()
+                .AddSingleton<ToolsViewModel>();
+        }
+
+        public static T BuildMockedTool<T>(bool requireViewModelMain = false)
+            where T : Tool
+        {
+            IServiceProvider services;
+
+            if (requireViewModelMain)
+            {
+                services = MockedViewModelMain().Services;
+            }
+            else
+            {
+                services = MockedServiceCollection().BuildServiceProvider();
+            }
+
+            return ToolBuilder.BuildTool<T>(services);
+        }
+    }
+}

+ 1 - 1
PixiEditorTests/HelpersTests/ConvertersTests/DoubleToIntConverterTest.cs

@@ -2,7 +2,7 @@
 using PixiEditor.Helpers.Converters;
 using PixiEditor.Helpers.Converters;
 using Xunit;
 using Xunit;
 
 
-namespace PixiEditorTests.HelpersTests.ConvertersTests
+namespace PixiEditorTests.Helpers
 {
 {
     public class DoubleToIntConverterTest
     public class DoubleToIntConverterTest
     {
     {

+ 41 - 0
PixiEditorTests/HelpersTests/DependencyInjectionTests.cs

@@ -0,0 +1,41 @@
+using Microsoft.Extensions.DependencyInjection;
+using PixiEditor.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using Xunit;
+
+namespace PixiEditorTests.HelpersTests
+{
+    [Collection("Application collection")]
+    public class DependencyInjectionTests
+    {
+        private class TestService
+        {
+        }
+
+        private class TestInjectable
+        {
+            public TestService TestService { get; }
+
+            public TestInjectable(TestService service)
+            {
+                TestService = service;
+            }
+        }
+
+        [Fact]
+        public void TestThatInjectingWorks()
+        {
+            IServiceProvider provider = new ServiceCollection()
+                .AddSingleton<TestService>()
+                .BuildServiceProvider();
+
+            TestInjectable injectable = provider.Inject<TestInjectable>();
+
+            Assert.NotNull(injectable.TestService);
+        }
+    }
+}

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

@@ -6,6 +6,7 @@ using PixiEditor.Models.Enums;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
 using PixiEditor.ViewModels;
 using PixiEditor.ViewModels;
+using PixiEditorTests.HelpersTests;
 using Xunit;
 using Xunit;
 
 
 namespace PixiEditorTests.ModelsTests.DataHoldersTests
 namespace PixiEditorTests.ModelsTests.DataHoldersTests
@@ -301,7 +302,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
         [StaFact]
         [StaFact]
         public void TestThatDocumentGetsAddedToRecentlyOpenedList()
         public void TestThatDocumentGetsAddedToRecentlyOpenedList()
         {
         {
-            ViewModelMain viewModel = Helpers.MockedViewModelMain();
+            ViewModelMain viewModel = ViewModelHelper.MockedViewModelMain();
 
 
             Document document = new Document(1, 1)
             Document document = new Document(1, 1)
             {
             {

+ 2 - 1
PixiEditorTests/ModelsTests/ToolsTests/PenToolTests.cs

@@ -1,5 +1,6 @@
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.Tools.Tools;
+using PixiEditorTests.HelpersTests;
 using Xunit;
 using Xunit;
 
 
 namespace PixiEditorTests.ModelsTests.ToolsTests
 namespace PixiEditorTests.ModelsTests.ToolsTests
@@ -10,7 +11,7 @@ namespace PixiEditorTests.ModelsTests.ToolsTests
         [StaFact]
         [StaFact]
         public void TestThatPixelPerfectPenReturnsShapeWithoutLShapePixels()
         public void TestThatPixelPerfectPenReturnsShapeWithoutLShapePixels()
         {
         {
-            PenTool pen = new PenTool();
+            PenTool pen = ViewModelHelper.BuildMockedTool<PenTool>();
 
 
             Coordinates start = new Coordinates(0, 0);
             Coordinates start = new Coordinates(0, 0);
             Coordinates end = new Coordinates(0, 0);
             Coordinates end = new Coordinates(0, 0);

+ 4 - 4
PixiEditorTests/ModelsTests/ToolsTests/ZoomToolTests.cs

@@ -1,7 +1,7 @@
-using Microsoft.Extensions.DependencyInjection;
+using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.Tools.Tools;
-using PixiEditor.Models.UserPreferences;
 using PixiEditor.ViewModels;
 using PixiEditor.ViewModels;
+using PixiEditorTests.HelpersTests;
 using Xunit;
 using Xunit;
 
 
 namespace PixiEditorTests.ModelsTests.ToolsTests
 namespace PixiEditorTests.ModelsTests.ToolsTests
@@ -12,9 +12,9 @@ namespace PixiEditorTests.ModelsTests.ToolsTests
         [StaFact]
         [StaFact]
         public void TestThatZoomSetsActiveDocumentZoomPercentage()
         public void TestThatZoomSetsActiveDocumentZoomPercentage()
         {
         {
-            ViewModelMain vm = new ViewModelMain(new ServiceCollection().AddSingleton<IPreferences>(new Mocks.PreferenceSettingsMock()).BuildServiceProvider());
+            ViewModelMain vm = ViewModelHelper.MockedViewModelMain();
             vm.BitmapManager.ActiveDocument = new PixiEditor.Models.DataHolders.Document(10, 10);
             vm.BitmapManager.ActiveDocument = new PixiEditor.Models.DataHolders.Document(10, 10);
-            ZoomTool zoomTool = new ZoomTool();
+            ZoomTool zoomTool = ToolBuilder.BuildTool<ZoomTool>(vm.Services);
             double zoom = 110;
             double zoom = 110;
             zoomTool.Zoom(zoom);
             zoomTool.Zoom(zoom);
             Assert.Equal(zoom, vm.BitmapManager.ActiveDocument.ZoomPercentage);
             Assert.Equal(zoom, vm.BitmapManager.ActiveDocument.ZoomPercentage);

+ 19 - 33
PixiEditorTests/ViewModelsTests/ViewModelMainTests.cs

@@ -1,16 +1,11 @@
-using System;
-using System.IO;
-using System.Windows.Input;
-using System.Windows.Media;
-using Microsoft.Extensions.DependencyInjection;
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.IO;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
 using PixiEditor.Models.Tools.Tools;
-using PixiEditor.Models.UserPreferences;
 using PixiEditor.ViewModels;
 using PixiEditor.ViewModels;
+using PixiEditorTests.HelpersTests;
+using System.IO;
+using System.Windows.Input;
+using System.Windows.Media;
 using Xunit;
 using Xunit;
 
 
 namespace PixiEditorTests.ViewModelsTests
 namespace PixiEditorTests.ViewModelsTests
@@ -18,19 +13,10 @@ namespace PixiEditorTests.ViewModelsTests
     [Collection("Application collection")]
     [Collection("Application collection")]
     public class ViewModelMainTests
     public class ViewModelMainTests
     {
     {
-        public static IServiceProvider Services;
-
-        public ViewModelMainTests()
-        {
-            Services = new ServiceCollection()
-                .AddSingleton<IPreferences>(new Mocks.PreferenceSettingsMock())
-                .BuildServiceProvider();
-        }
-
         [StaFact]
         [StaFact]
         public void TestThatConstructorSetsUpControllersCorrectly()
         public void TestThatConstructorSetsUpControllersCorrectly()
         {
         {
-            ViewModelMain viewModel = new ViewModelMain(Services);
+            ViewModelMain viewModel = ViewModelHelper.MockedViewModelMain();
 
 
             Assert.NotNull(viewModel.ChangesController);
             Assert.NotNull(viewModel.ChangesController);
             Assert.NotNull(viewModel.ShortcutController);
             Assert.NotNull(viewModel.ShortcutController);
@@ -42,7 +28,7 @@ namespace PixiEditorTests.ViewModelsTests
         [StaFact]
         [StaFact]
         public void TestThatSwapColorsCommandSwapsColors()
         public void TestThatSwapColorsCommandSwapsColors()
         {
         {
-            ViewModelMain viewModel = new ViewModelMain(Services);
+            ViewModelMain viewModel = ViewModelHelper.MockedViewModelMain();
 
 
             viewModel.ColorsSubViewModel.PrimaryColor = Colors.Black;
             viewModel.ColorsSubViewModel.PrimaryColor = Colors.Black;
             viewModel.ColorsSubViewModel.SecondaryColor = Colors.White;
             viewModel.ColorsSubViewModel.SecondaryColor = Colors.White;
@@ -56,7 +42,7 @@ namespace PixiEditorTests.ViewModelsTests
         [StaFact]
         [StaFact]
         public void TestThatNewDocumentCreatesNewDocumentWithBaseLayer()
         public void TestThatNewDocumentCreatesNewDocumentWithBaseLayer()
         {
         {
-            ViewModelMain viewModel = new ViewModelMain(Services);
+            ViewModelMain viewModel = ViewModelHelper.MockedViewModelMain();
 
 
             viewModel.FileSubViewModel.NewDocument(5, 5);
             viewModel.FileSubViewModel.NewDocument(5, 5);
 
 
@@ -67,7 +53,7 @@ namespace PixiEditorTests.ViewModelsTests
         [StaFact]
         [StaFact]
         public void TestThatMouseMoveCommandUpdatesCurrentCoordinates()
         public void TestThatMouseMoveCommandUpdatesCurrentCoordinates()
         {
         {
-            ViewModelMain viewModel = new ViewModelMain(Services);
+            ViewModelMain viewModel = ViewModelHelper.MockedViewModelMain();
             viewModel.BitmapManager.ActiveDocument = new Document(10, 10);
             viewModel.BitmapManager.ActiveDocument = new Document(10, 10);
 
 
             Assert.Equal(new Coordinates(0, 0), MousePositionConverter.CurrentCoordinates);
             Assert.Equal(new Coordinates(0, 0), MousePositionConverter.CurrentCoordinates);
@@ -83,7 +69,7 @@ namespace PixiEditorTests.ViewModelsTests
         [StaFact]
         [StaFact]
         public void TestThatSelectToolCommandSelectsNewTool()
         public void TestThatSelectToolCommandSelectsNewTool()
         {
         {
-            ViewModelMain viewModel = new ViewModelMain(Services);
+            ViewModelMain viewModel = ViewModelHelper.MockedViewModelMain();
 
 
             Assert.Equal(typeof(MoveViewportTool), viewModel.BitmapManager.SelectedTool.GetType());
             Assert.Equal(typeof(MoveViewportTool), viewModel.BitmapManager.SelectedTool.GetType());
 
 
@@ -95,7 +81,7 @@ namespace PixiEditorTests.ViewModelsTests
         [StaFact]
         [StaFact]
         public void TestThatMouseUpCommandStopsRecordingMouseMovements()
         public void TestThatMouseUpCommandStopsRecordingMouseMovements()
         {
         {
-            ViewModelMain viewModel = new ViewModelMain(Services);
+            ViewModelMain viewModel = ViewModelHelper.MockedViewModelMain();
 
 
             viewModel.BitmapManager.MouseController.StartRecordingMouseMovementChanges(true);
             viewModel.BitmapManager.MouseController.StartRecordingMouseMovementChanges(true);
 
 
@@ -109,7 +95,7 @@ namespace PixiEditorTests.ViewModelsTests
         [StaFact]
         [StaFact]
         public void TestThatNewLayerCommandCreatesNewLayer()
         public void TestThatNewLayerCommandCreatesNewLayer()
         {
         {
-            ViewModelMain viewModel = new ViewModelMain(Services);
+            ViewModelMain viewModel = ViewModelHelper.MockedViewModelMain();
 
 
             viewModel.BitmapManager.ActiveDocument = new Document(1, 1);
             viewModel.BitmapManager.ActiveDocument = new Document(1, 1);
 
 
@@ -123,7 +109,7 @@ namespace PixiEditorTests.ViewModelsTests
         [StaFact]
         [StaFact]
         public void TestThatSaveDocumentCommandSavesFile()
         public void TestThatSaveDocumentCommandSavesFile()
         {
         {
-            ViewModelMain viewModel = new ViewModelMain(Services);
+            ViewModelMain viewModel = ViewModelHelper.MockedViewModelMain();
             string fileName = "testFile.pixi";
             string fileName = "testFile.pixi";
 
 
             viewModel.BitmapManager.ActiveDocument = new Document(1, 1)
             viewModel.BitmapManager.ActiveDocument = new Document(1, 1)
@@ -141,7 +127,7 @@ namespace PixiEditorTests.ViewModelsTests
         [StaFact]
         [StaFact]
         public void TestThatAddSwatchAddsNonDuplicateSwatch()
         public void TestThatAddSwatchAddsNonDuplicateSwatch()
         {
         {
-            ViewModelMain viewModel = new ViewModelMain(Services);
+            ViewModelMain viewModel = ViewModelHelper.MockedViewModelMain();
             viewModel.BitmapManager.ActiveDocument = new Document(1, 1);
             viewModel.BitmapManager.ActiveDocument = new Document(1, 1);
 
 
             viewModel.ColorsSubViewModel.AddSwatch(Colors.Green);
             viewModel.ColorsSubViewModel.AddSwatch(Colors.Green);
@@ -161,10 +147,10 @@ namespace PixiEditorTests.ViewModelsTests
         [InlineData(120, 150)]
         [InlineData(120, 150)]
         public void TestThatSelectAllCommandSelectsWholeDocument(int docWidth, int docHeight)
         public void TestThatSelectAllCommandSelectsWholeDocument(int docWidth, int docHeight)
         {
         {
-            ViewModelMain viewModel = new ViewModelMain(Services)
-            {
-                BitmapManager = { ActiveDocument = new Document(docWidth, docHeight) }
-            };
+            ViewModelMain viewModel = ViewModelHelper.MockedViewModelMain();
+
+            viewModel.BitmapManager.ActiveDocument = new Document(docWidth, docHeight);
+
             viewModel.BitmapManager.ActiveDocument.AddNewLayer("layer");
             viewModel.BitmapManager.ActiveDocument.AddNewLayer("layer");
 
 
             viewModel.SelectionSubViewModel.SelectAllCommand.Execute(null);
             viewModel.SelectionSubViewModel.SelectAllCommand.Execute(null);
@@ -177,7 +163,7 @@ namespace PixiEditorTests.ViewModelsTests
         [StaFact]
         [StaFact]
         public void TestThatDocumentIsNotNullReturnsTrue()
         public void TestThatDocumentIsNotNullReturnsTrue()
         {
         {
-            ViewModelMain viewModel = new ViewModelMain(Services);
+            ViewModelMain viewModel = ViewModelHelper.MockedViewModelMain();
 
 
             viewModel.BitmapManager.ActiveDocument = new Document(1, 1);
             viewModel.BitmapManager.ActiveDocument = new Document(1, 1);