Browse Source

Added tool dependency injection

CPKreuz 4 years ago
parent
commit
53ebebc3c6

+ 47 - 0
PixiEditor/Helpers/DependencyInjectionHelper.cs

@@ -0,0 +1,47 @@
+using System;
+using System.Reflection;
+
+namespace PixiEditor.Helpers
+{
+    public static class DependencyInjectionHelper
+    {
+        /// <summary>
+        /// Injects all services from <paramref name="services"/> into the public properties of <paramref name="obj"/>
+        /// </summary>
+        /// <typeparam name="T">The type of the object to inject</typeparam>
+        /// <param name="services">The <see cref="IServiceProvider"/></param>
+        /// <param name="obj">The object that should get injected</param>
+        public static void Inject<T>(this IServiceProvider services, T obj)
+            => Inject(services, obj, BindingFlags.Public | BindingFlags.Instance);
+
+        /// <summary>
+        /// Injects all services from <paramref name="services"/> into the properties of <paramref name="obj"/>
+        /// </summary>
+        /// <typeparam name="T">The type of the object to inject</typeparam>
+        /// <param name="services">The <see cref="IServiceProvider"/></param>
+        /// <param name="obj">The object that should get injected</param>
+        /// <param name="bindingFlags">The binding flags for the properties</param>
+        public static void Inject<T>(this IServiceProvider services, T obj, BindingFlags bindingFlags)
+            => Inject(services, obj, bindingFlags, typeof(T));
+
+        public static void Inject(this IServiceProvider services, object obj, BindingFlags bindingFlags, Type type)
+        {
+            foreach (PropertyInfo info in type.GetProperties(bindingFlags))
+            {
+                if (!info.CanWrite)
+                {
+                    continue;
+                }
+
+                object value = services.GetService(info.PropertyType);
+
+                if (value is null)
+                {
+                    continue;
+                }
+
+                info.SetValue(obj, value);
+            }
+        }
+    }
+}

+ 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)
         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>();
             foreach (Coordinates item in shape)

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

@@ -49,6 +49,8 @@ namespace PixiEditor.Models.Tools
 
         public Toolbar Toolbar { get; set; } = new EmptyToolbar();
 
+        public IServiceProvider Services { get; set; }
+
         public bool CanStartOutsideCanvas { get; set; } = false;
 
         public virtual void OnMouseDown(MouseEventArgs e)
@@ -86,5 +88,19 @@ namespace PixiEditor.Models.Tools
         public virtual void AfterAddedUndo(UndoManager undoManager)
         {
         }
+
+        public virtual void SetupSubTools()
+        {
+        }
+
+        /// <summary>
+        /// Creates a sub tool and injects the services in it. <para/>
+        /// Only use this inside <see cref="SetupSubTools"/>!
+        /// </summary>
+        protected T CreateSubTool<T>()
+            where T : Tool, new()
+        {
+            return ToolBuilder.BuildTool<T>(Services);
+        }
     }
 }

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

@@ -0,0 +1,57 @@
+using PixiEditor.Helpers;
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+
+namespace PixiEditor.Models.Tools
+{
+    public class ToolBuilder
+    {
+        private readonly IServiceProvider services;
+
+        private readonly List<Type> toBuild = new List<Type>();
+
+        public ToolBuilder(IServiceProvider services)
+        {
+            this.services = services;
+        }
+
+        public static T BuildTool<T>(IServiceProvider services)
+            where T : Tool, new()
+            => (T)BuildTool(typeof(T), services);
+
+        public static Tool BuildTool(Type type, IServiceProvider services)
+        {
+            Tool tool = (Tool)type.GetConstructor(Type.EmptyTypes).Invoke(null);
+
+            services.Inject(tool, BindingFlags.Public | BindingFlags.Instance, type);
+
+            tool.SetupSubTools();
+
+            return tool;
+        }
+
+        public ToolBuilder Add<T>()
+            where T : Tool, new()
+            => Add(typeof(T));
+
+        public ToolBuilder Add(Type type)
+        {
+            toBuild.Add(type);
+
+            return this;
+        }
+
+        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;
 
 namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
@@ -9,7 +8,7 @@ namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
         public BrightnessToolToolbar(float initialValue)
         {
             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"));
         }
     }
 }

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

@@ -10,7 +10,7 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class EraserTool : BitmapOperationTool
     {
-        private readonly PenTool pen = new PenTool();
+        private PenTool pen = new PenTool();
 
         public EraserTool()
         {
@@ -30,5 +30,10 @@ namespace PixiEditor.Models.Tools.Tools
             BitmapPixelChanges pixels = pen.Draw(startingCords, coordinates[0], System.Windows.Media.Colors.Transparent, toolSize);
             return Only(pixels, layer);
         }
+
+        public override void SetupSubTools()
+        {
+            pen = CreateSubTool<PenTool>();
+        }
     }
 }

+ 6 - 4
PixiEditor/Models/Tools/Tools/FloodFill.cs

@@ -1,14 +1,16 @@
 using System.Collections.Generic;
 using System.Windows.Media;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
-using PixiEditor.ViewModels;
 
 namespace PixiEditor.Models.Tools.Tools
 {
     public class FloodFill : BitmapOperationTool
     {
+        public BitmapManager BitmapManager { get; set; }
+
         public FloodFill()
         {
             ActionDisplay = "Press on a area to fill it.";
@@ -20,12 +22,12 @@ namespace PixiEditor.Models.Tools.Tools
             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>();
 
-            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];
 

+ 10 - 6
PixiEditor/Models/Tools/Tools/LineTool.cs

@@ -13,14 +13,13 @@ namespace PixiEditor.Models.Tools.Tools
 {
     public class LineTool : ShapeTool
     {
-        private readonly CircleTool circleTool;
+        private CircleTool circleTool;
 
         public LineTool()
         {
             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();
-            circleTool = new CircleTool();
         }
 
         public override void OnKeyDown(KeyEventArgs e)
@@ -58,13 +57,18 @@ namespace PixiEditor.Models.Tools.Tools
 
         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)
+        public override void SetupSubTools()
         {
-            Coordinates startingCoordinates = coordinates[^1];
-            Coordinates latestCoordinates = coordinates[0];
+            circleTool = CreateSubTool<CircleTool>();
+        }
+
+        private IEnumerable<Coordinates> CreateLine(IEnumerable<Coordinates> coordinates, int thickness, CapType startCap, CapType endCap)
+        {
+            Coordinates startingCoordinates = coordinates.Last();
+            Coordinates latestCoordinates = coordinates.First();
             if (thickness == 1)
             {
                 return BresenhamLine(startingCoordinates.X, startingCoordinates.Y, latestCoordinates.X, latestCoordinates.Y);

+ 28 - 24
PixiEditor/Models/Tools/Tools/MoveTool.cs

@@ -41,6 +41,8 @@ namespace PixiEditor.Models.Tools.Tools
 
         public bool MoveAll { get; set; } = false;
 
+        public BitmapManager BitmapManager { get; set; }
+
         public override void OnKeyDown(KeyEventArgs e)
         {
             if (e.Key == Key.LeftCtrl)
@@ -59,31 +61,33 @@ namespace PixiEditor.Models.Tools.Tools
 
         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 +97,7 @@ namespace PixiEditor.Models.Tools.Tools
         {
             if (currentSelection != null && currentSelection.Length == 0)
             {
-                ViewModelMain.Current.BitmapManager.ActiveDocument.UndoManager.AddUndoChange(new Change(
+                BitmapManager.ActiveDocument.UndoManager.AddUndoChange(new Change(
                     ApplyOffsets,
                     new object[] { startingOffsets },
                     ApplyOffsets,
@@ -107,7 +111,7 @@ namespace PixiEditor.Models.Tools.Tools
             ResetSelectionValues(startPos);
 
             // Move offset if no selection
-            var doc = ViewModelMain.Current.BitmapManager.ActiveDocument;
+            Document doc = ViewModelMain.Current.BitmapManager.ActiveDocument;
             Selection selection = doc.ActiveSelection;
             if (selection != null && selection.SelectedPoints.Count > 0)
             {
@@ -232,7 +236,7 @@ namespace PixiEditor.Models.Tools.Tools
             Guid layerGuid = layer.LayerGuid;
             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));
 
                 clearedPixels[layerGuid] = true;

+ 9 - 4
PixiEditor/Models/Tools/Tools/MoveViewportTool.cs

@@ -1,15 +1,20 @@
 using System.Collections.Generic;
 using System.Drawing;
 using System.Windows.Input;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Position;
-using PixiEditor.ViewModels;
-
+using PixiEditor.ViewModels.SubViewModels.Main;
+
 namespace PixiEditor.Models.Tools.Tools
 {
     public class MoveViewportTool : ReadonlyTool
     {
         private Point clickPoint;
 
+        public BitmapManager BitmapManager { get; set; }
+
+        public ToolsViewModel ToolsViewModel { get; set; }
+
         public MoveViewportTool()
         {
             HideHighlight = true;
@@ -31,7 +36,7 @@ namespace PixiEditor.Models.Tools.Tools
             if (e.LeftButton == MouseButtonState.Pressed || e.MiddleButton == MouseButtonState.Pressed)
             {
                 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.Y - clickPoint.Y);
             }
@@ -41,7 +46,7 @@ namespace PixiEditor.Models.Tools.Tools
         {
             if (e.MiddleButton == MouseButtonState.Pressed)
             {
-                ViewModelMain.Current.ToolsSubViewModel.SetActiveTool(ViewModelMain.Current.ToolsSubViewModel.LastActionTool);
+                ToolsViewModel.SetActiveTool(ToolsViewModel.LastActionTool);
             }
         }
 

+ 10 - 5
PixiEditor/Models/Tools/Tools/PenTool.cs

@@ -1,11 +1,11 @@
 using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Tools.ToolSettings;
 using PixiEditor.Models.Tools.ToolSettings.Settings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
-using PixiEditor.ViewModels;
 using System;
 using System.Collections.Generic;
 using System.Linq;
@@ -18,11 +18,13 @@ namespace PixiEditor.Models.Tools.Tools
     {
         private readonly SizeSetting toolSizeSetting;
         private readonly BoolSetting pixelPerfectSetting;
-        private readonly LineTool lineTool;
         private readonly List<Coordinates> confirmedPixels = new List<Coordinates>();
+        private LineTool lineTool;
         private Coordinates[] lastChangedPixels = new Coordinates[3];
         private byte changedPixelsindex = 0;
 
+        public BitmapManager BitmapManager { get; set; }
+
         public PenTool()
         {
             Cursor = Cursors.Pen;
@@ -32,8 +34,6 @@ namespace PixiEditor.Models.Tools.Tools
             toolSizeSetting = Toolbar.GetSetting<SizeSetting>("ToolSize");
             pixelPerfectSetting = Toolbar.GetSetting<BoolSetting>("PixelPerfectEnabled");
             pixelPerfectSetting.ValueChanged += PixelPerfectSettingValueChanged;
-            RequiresPreviewLayer = pixelPerfectSetting.Value;
-            lineTool = new LineTool();
             ClearPreviewLayerOnEachIteration = false;
         }
 
@@ -54,7 +54,7 @@ namespace PixiEditor.Models.Tools.Tools
                 color,
                 toolSizeSetting.Value,
                 pixelPerfectSetting.Value,
-                ViewModelMain.Current.BitmapManager.ActiveDocument.PreviewLayer);
+                BitmapManager.ActiveDocument.PreviewLayer);
             return Only(pixels, layer);
         }
 
@@ -98,6 +98,11 @@ namespace PixiEditor.Models.Tools.Tools
             return result;
         }
 
+        public override void SetupSubTools()
+        {
+            lineTool = CreateSubTool<LineTool>();
+        }
+
         private void MovePixelsToCheck(BitmapPixelChanges changes)
         {
             if (changes.ChangedPixels[lastChangedPixels[1]].A != 0)

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

@@ -23,6 +23,8 @@ namespace PixiEditor.Models.Tools.Tools
         private readonly CircleTool circleTool = new CircleTool();
         private IEnumerable<Coordinates> oldSelectedPoints;
 
+        public BitmapManager BitmapManager { get; set; }
+
         private static Selection ActiveSelection { get => ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection; }
 
         public SelectTool()
@@ -107,7 +109,7 @@ namespace PixiEditor.Models.Tools.Tools
                 throw new NotImplementedException($"Selection shape '{shape}' has not been implemented");
             }
 
-            ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(selection, SelectionType);
+            BitmapManager.ActiveDocument.ActiveSelection.SetSelection(selection, SelectionType);
         }
     }
 }

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

@@ -2,8 +2,8 @@
 using System.Collections.Generic;
 using System.Windows;
 using System.Windows.Input;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Position;
-using PixiEditor.ViewModels;
 
 namespace PixiEditor.Models.Tools.Tools
 {
@@ -16,6 +16,8 @@ namespace PixiEditor.Models.Tools.Tools
         private double workAreaWidth = SystemParameters.WorkArea.Width;
         private double pixelsPerZoomMultiplier;
 
+        public BitmapManager BitmapManager { get; set; }
+
         public ZoomTool()
         {
             HideHighlight = true;
@@ -44,7 +46,7 @@ namespace PixiEditor.Models.Tools.Tools
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
             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)
@@ -77,7 +79,7 @@ namespace PixiEditor.Models.Tools.Tools
 
         public void Zoom(double percentage)
         {
-            ViewModelMain.Current.BitmapManager.ActiveDocument.ZoomPercentage = percentage;
+            BitmapManager.ActiveDocument.ZoomPercentage = percentage;
         }
 
         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.Enums;
 using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools;
 using PixiEditor.Models.Tools.Tools;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
@@ -14,20 +15,22 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public RelayCommand SelectAllCommand { get; set; }
 
+        private SelectTool selectTool;
+
         public SelectionViewModel(ViewModelMain owner)
             : base(owner)
         {
             DeselectCommand = new RelayCommand(Deselect, SelectionIsNotEmpty);
             SelectAllCommand = new RelayCommand(SelectAll, CanSelectAll);
+
+            selectTool = ToolBuilder.BuildTool<SelectTool>(Owner.Services);
         }
 
         public void SelectAll(object parameter)
         {
-            SelectTool select = new SelectTool();
-
             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);
         }
 

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

@@ -19,14 +19,13 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         public StylusViewModel(ViewModelMain owner)
             : base(owner)
         {
-            SetOwner(owner);
         }
 
         public void SetOwner(ViewModelMain owner)
         {
             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;

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

@@ -1,6 +1,7 @@
 using System;
 using System.Collections.ObjectModel;
 using System.Linq;
+using System.Reflection;
 using System.Windows.Input;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Tools;
@@ -35,14 +36,20 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         {
             SelectToolCommand = new RelayCommand(SetTool, Owner.DocumentIsNotNull);
             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<MagicWandTool>().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>()
@@ -82,6 +89,25 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             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)
         {
             int increment = (int)parameter;

+ 34 - 8
PixiEditor/ViewModels/ViewModelMain.cs

@@ -29,6 +29,8 @@ namespace PixiEditor.ViewModels
 
         public static ViewModelMain Current { get; set; }
 
+        public IServiceProvider Services { get; private set; }
+
         public Action CloseAction { get; set; }
 
         public event EventHandler OnStartupEvent;
@@ -117,15 +119,28 @@ namespace PixiEditor.ViewModels
 #endif
         }
 
-        public ViewModelMain(IServiceProvider services)
+        public ViewModelMain(IServiceCollection services)
         {
             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.Init();
 
-            BitmapManager = new BitmapManager();
+            BitmapManager = services.GetRequiredService<BitmapManager>();
             BitmapManager.BitmapOperations.BitmapChanged += BitmapUtility_BitmapChanged;
             BitmapManager.MouseController.StoppedRecordingChanges += MouseController_StoppedRecordingChanges;
             BitmapManager.DocumentChanged += BitmapManager_DocumentChanged;
@@ -137,7 +152,9 @@ namespace PixiEditor.ViewModels
             CloseWindowCommand = new RelayCommand(CloseWindow);
 
             FileSubViewModel = new FileViewModel(this);
-            ToolsSubViewModel = new ToolsViewModel(this);
+            ToolsSubViewModel = GetSubViewModel<ToolsViewModel>(services);
+            ToolsSubViewModel.SetupTools(services);
+
             IoSubViewModel = new IoViewModel(this);
             LayersSubViewModel = new LayersViewModel(this);
             ClipboardSubViewModel = new ClipboardViewModel(this);
@@ -148,11 +165,8 @@ namespace PixiEditor.ViewModels
             DiscordViewModel = new DiscordViewModel(this, "764168193685979138");
             UpdateSubViewModel = new UpdateViewModel(this);
 
-            WindowSubViewModel = services.GetService<WindowViewModel>();
-            WindowSubViewModel?.SetOwner(this);
-
-            StylusSubViewModel = services.GetService<StylusViewModel>();
-            StylusSubViewModel?.SetOwner(this);
+            WindowSubViewModel = GetSubViewModel<WindowViewModel>(services);
+            StylusSubViewModel = GetSubViewModel<StylusViewModel>(services);
 
             AddDebugOnlyViewModels();
             AddReleaseOnlyViewModels();
@@ -353,5 +367,17 @@ namespace PixiEditor.ViewModels
                 ColorsSubViewModel.AddSwatch(ColorsSubViewModel.PrimaryColor);
             }
         }
+
+        private T GetSubViewModel<T>(IServiceProvider services)
+        {
+            T subViewModel = services.GetRequiredService<T>();
+
+            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 PixiEditor.Models.DataHolders;
 using System.Windows.Interop;
+using PixiEditor.Models.Controllers;
 
 namespace PixiEditor
 {
@@ -22,7 +23,7 @@ namespace PixiEditor
     {
         private static WriteableBitmap pixiEditorLogo;
 
-        private PreferencesSettings preferences;
+        private readonly PreferencesSettings preferences;
 
         public new ViewModelMain DataContext { get => (ViewModelMain)base.DataContext; set => base.DataContext = value; }
 
@@ -30,12 +31,9 @@ namespace PixiEditor
         {
             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();
 
@@ -80,6 +78,18 @@ namespace PixiEditor
             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)
         {
             if (preferences.GetPreference("ImagePreviewInTaskbar", false))

+ 22 - 0
PixiEditorTests/HelpersTests/ViewModelHelper.cs

@@ -0,0 +1,22 @@
+using Microsoft.Extensions.DependencyInjection;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.UserPreferences;
+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>();
+        }
+    }
+}