Browse Source

Merge pull request #117 from PixiEditor/multiple-documents

Tabbed documents support
Krzysztof Krysiński 4 years ago
parent
commit
f0e9d49a82
32 changed files with 834 additions and 467 deletions
  1. 33 94
      PixiEditor/Models/Controllers/BitmapManager.cs
  2. 4 4
      PixiEditor/Models/Controllers/BitmapOperationsUtility.cs
  3. 3 3
      PixiEditor/Models/Controllers/ClipboardController.cs
  4. 22 17
      PixiEditor/Models/Controllers/UndoManager.cs
  5. 254 0
      PixiEditor/Models/DataHolders/Document.cs
  6. 12 14
      PixiEditor/Models/IO/Exporter.cs
  7. 3 1
      PixiEditor/Models/IO/Importer.cs
  8. 1 0
      PixiEditor/Models/Layers/Layer.cs
  9. 2 1
      PixiEditor/Models/Tools/Tool.cs
  10. 7 7
      PixiEditor/Models/Tools/Tools/MoveTool.cs
  11. 1 1
      PixiEditor/Models/Tools/Tools/MoveViewportTool.cs
  12. 11 8
      PixiEditor/Models/Tools/Tools/SelectTool.cs
  13. 2 2
      PixiEditor/Models/Tools/Tools/ZoomTool.cs
  14. 2 2
      PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs
  15. 22 4
      PixiEditor/ViewModels/SubViewModels/Main/DocumentViewModel.cs
  16. 24 27
      PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs
  17. 7 29
      PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs
  18. 6 5
      PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs
  19. 4 16
      PixiEditor/ViewModels/SubViewModels/Main/SelectionViewModel.cs
  20. 7 7
      PixiEditor/ViewModels/SubViewModels/Main/UndoViewModel.cs
  21. 2 43
      PixiEditor/ViewModels/SubViewModels/Main/ViewportViewModel.cs
  22. 54 16
      PixiEditor/ViewModels/ViewModelMain.cs
  23. 52 78
      PixiEditor/Views/MainWindow.xaml
  24. 75 0
      PixiEditor/Views/UserControls/DrawingViewPort.xaml
  25. 105 0
      PixiEditor/Views/UserControls/DrawingViewPort.xaml.cs
  26. 33 23
      PixiEditor/Views/UserControls/MainDrawingPanel.xaml.cs
  27. 17 12
      PixiEditorTests/ModelsTests/ControllersTests/BitmapManagerTests.cs
  28. 1 1
      PixiEditorTests/ModelsTests/ControllersTests/BitmapOperationsUtilityTests.cs
  29. 48 35
      PixiEditorTests/ModelsTests/ControllersTests/UndoManagerTests.cs
  30. 6 6
      PixiEditorTests/ModelsTests/DataHoldersTests/DocumentTests.cs
  31. 3 2
      PixiEditorTests/ModelsTests/ToolsTests/ZoomToolTests.cs
  32. 11 9
      PixiEditorTests/ViewModelsTests/ViewModelMainTests.cs

+ 33 - 94
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using System.Windows;
 using System.Windows.Input;
@@ -7,7 +8,6 @@ using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Enums;
 using PixiEditor.Models.Events;
 using PixiEditor.Models.ImageManipulation;
 using PixiEditor.Models.Layers;
@@ -21,7 +21,6 @@ namespace PixiEditor.Models.Controllers
     public class BitmapManager : NotifyableObject
     {
         private Document activeDocument;
-        private Layer previewLayer;
         private Tool selectedTool;
 
         public BitmapManager()
@@ -34,9 +33,7 @@ namespace PixiEditor.Models.Controllers
             MouseController.OnMouseUp += MouseController_OnMouseUp;
             BitmapOperations = new BitmapOperationsUtility(this);
             ReadonlyToolUtility = new ReadonlyToolUtility();
-        }
-
-        public event EventHandler<LayersChangedEventArgs> LayersChanged;
+        }
 
         public event EventHandler<DocumentChangedEventArgs> DocumentChanged;
 
@@ -52,16 +49,6 @@ namespace PixiEditor.Models.Controllers
             }
         }
 
-        public Layer PreviewLayer
-        {
-            get => previewLayer;
-            set
-            {
-                previewLayer = value;
-                RaisePropertyChanged("PreviewLayer");
-            }
-        }
-
         public Layer ActiveLayer => ActiveDocument.ActiveLayer;
 
         public Color PrimaryColor { get; set; }
@@ -96,6 +83,8 @@ namespace PixiEditor.Models.Controllers
             }
         }
 
+        public ObservableCollection<Document> Documents { get; set; } = new ObservableCollection<Document>();
+
         /// <summary>
         ///     Returns if tool is BitmapOperationTool.
         /// </summary>
@@ -104,69 +93,17 @@ namespace PixiEditor.Models.Controllers
             return tool is BitmapOperationTool;
         }
 
-        public void SetActiveTool(Tool tool)
-        {
-            PreviewLayer = null;
-            SelectedTool?.Toolbar.SaveToolbarSettings();
-            SelectedTool = tool;
-            SelectedTool.Toolbar.LoadSharedSettings();
-        }
-
-        public void SetActiveLayer(int index)
-        {
-            if (ActiveDocument.ActiveLayerIndex <= ActiveDocument.Layers.Count - 1)
-            {
-                ActiveDocument.ActiveLayer.IsActive = false;
-            }
-
-            ActiveDocument.ActiveLayerIndex = index;
-            ActiveDocument.ActiveLayer.IsActive = true;
-            LayersChanged?.Invoke(this, new LayersChangedEventArgs(index, LayerAction.SetActive));
-        }
-
-        public void AddNewLayer(string name, WriteableBitmap bitmap, bool setAsActive = true)
-        {
-            AddNewLayer(name, bitmap.PixelWidth, bitmap.PixelHeight, setAsActive);
-            ActiveDocument.Layers.Last().LayerBitmap = bitmap;
-        }
-
-        public void AddNewLayer(string name, bool setAsActive = true)
-        {
-            AddNewLayer(name, 0, 0, setAsActive);
-        }
-
-        public void AddNewLayer(string name, int width, int height, bool setAsActive = true)
-        {
-            ActiveDocument.Layers.Add(new Layer(name, width, height)
-            {
-                MaxHeight = ActiveDocument.Height,
-                MaxWidth = ActiveDocument.Width
-            });
-            if (setAsActive)
-            {
-                SetActiveLayer(ActiveDocument.Layers.Count - 1);
-            }
-
-            LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
-        }
-
-        public void RemoveLayer(int layerIndex)
-        {
-            if (ActiveDocument.Layers.Count == 0)
+        public void CloseDocument(Document document)
+        {
+            int nextIndex = 0;
+            if (document == ActiveDocument)
             {
-                return;
+                nextIndex = Documents.Count > 1 ? Documents.IndexOf(document) : -1;
+                nextIndex += nextIndex > 0 ? -1 : 0;
             }
 
-            bool wasActive = ActiveDocument.Layers[layerIndex].IsActive;
-            ActiveDocument.Layers.RemoveAt(layerIndex);
-            if (wasActive)
-            {
-                SetActiveLayer(0);
-            }
-            else if (ActiveDocument.ActiveLayerIndex > ActiveDocument.Layers.Count - 1)
-            {
-                SetActiveLayer(ActiveDocument.Layers.Count - 1);
-            }
+            Documents.Remove(document);
+            ActiveDocument = nextIndex >= 0 ? Documents[nextIndex] : null;
         }
 
         public void ExecuteTool(Coordinates newPosition, bool clickedOnCanvas)
@@ -184,18 +121,6 @@ namespace PixiEditor.Models.Controllers
             }
         }
 
-        public void GeneratePreviewLayer()
-        {
-            if (ActiveDocument != null)
-            {
-                PreviewLayer = new Layer("_previewLayer")
-                {
-                    MaxWidth = ActiveDocument.Width,
-                    MaxHeight = ActiveDocument.Height
-                };
-            }
-        }
-
         public WriteableBitmap GetCombinedLayersBitmap()
         {
             return BitmapUtils.CombineLayers(ActiveDocument.Layers.Where(x => x.IsVisible).ToArray(), ActiveDocument.Width, ActiveDocument.Height);
@@ -207,6 +132,17 @@ namespace PixiEditor.Models.Controllers
         public bool IsOperationTool()
         {
             return IsOperationTool(SelectedTool);
+        }
+
+        public void SetActiveTool(Tool tool)
+        {
+            if (ActiveDocument != null)
+            {
+                ActiveDocument.PreviewLayer = null;
+            }
+            SelectedTool?.Toolbar.SaveToolbarSettings();
+            SelectedTool = tool;
+            SelectedTool.Toolbar.LoadSharedSettings();
         }
 
         private void Controller_MousePositionChanged(object sender, MouseMovementEventArgs e)
@@ -240,7 +176,10 @@ namespace PixiEditor.Models.Controllers
         private void MouseController_StartedRecordingChanges(object sender, EventArgs e)
         {
             SelectedTool.OnRecordingLeftMouseDown(new MouseEventArgs(Mouse.PrimaryDevice, (int)DateTimeOffset.UtcNow.ToUnixTimeSeconds()));
-            PreviewLayer = null;
+            if (ActiveDocument != null)
+            {
+                ActiveDocument.PreviewLayer = null;
+            }
         }
 
         private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
@@ -264,16 +203,16 @@ namespace PixiEditor.Models.Controllers
             if (CanChangeHighlightOffset(highlightArea))
             {
                 Coordinates start = highlightArea.First();
-                PreviewLayer.Offset = new Thickness(start.X, start.Y, 0, 0);
+                ActiveDocument.PreviewLayer.Offset = new Thickness(start.X, start.Y, 0, 0);
             }
             else if (!IsInsideBounds(highlightArea))
             {
-                PreviewLayer = null;
+                ActiveDocument.PreviewLayer = null;
             }
             else
             {
-                GeneratePreviewLayer();
-                PreviewLayer.SetPixels(
+                ActiveDocument.GeneratePreviewLayer();
+                ActiveDocument.PreviewLayer.SetPixels(
                     BitmapPixelChanges.FromSingleColoredArray(highlightArea, Color.FromArgb(77, 0, 0, 0)));
             }
         }
@@ -281,8 +220,8 @@ namespace PixiEditor.Models.Controllers
         private bool CanChangeHighlightOffset(IEnumerable<Coordinates> highlightArea)
         {
             int count = highlightArea.Count();
-            return count > 0 && PreviewLayer != null &&
-                   IsInsideBounds(highlightArea) && count == PreviewLayer.Width * PreviewLayer.Height;
+            return count > 0 && ActiveDocument.PreviewLayer != null &&
+                   IsInsideBounds(highlightArea) && count == ActiveDocument.PreviewLayer.Width * ActiveDocument.PreviewLayer.Height;
         }
 
         private bool IsInsideBounds(IEnumerable<Coordinates> highlightArea)

+ 4 - 4
PixiEditor/Models/Controllers/BitmapOperationsUtility.cs

@@ -47,7 +47,7 @@ namespace PixiEditor.Models.Controllers
                 layers[i].SetPixels(changes);
             }
 
-            UndoManager.AddUndoChange(new Change("UndoChanges", old, newChange, "Deleted pixels"));
+            Manager.ActiveDocument.UndoManager.AddUndoChange(new Change("UndoChanges", old, newChange, "Deleted pixels"));
         }
 
         /// <summary>
@@ -92,7 +92,7 @@ namespace PixiEditor.Models.Controllers
                     lastModifiedLayers[i].PixelChanges,
                     oldValues,
                     lastModifiedLayers[i].LayerIndex));
-                Manager.PreviewLayer = null;
+                Manager.ActiveDocument.GeneratePreviewLayer();
             }
         }
 
@@ -184,13 +184,13 @@ namespace PixiEditor.Models.Controllers
             LayerChange[] modifiedLayers;
             if (mouseMove.Count > 0 && mouseMove[0] != lastMousePos)
             {
-                Manager.GeneratePreviewLayer();
+                Manager.ActiveDocument.GeneratePreviewLayer();
                 modifiedLayers = ((BitmapOperationTool)Manager.SelectedTool).Use(
                     Manager.ActiveDocument.ActiveLayer,
                     mouseMove.ToArray(),
                     Manager.PrimaryColor);
                 BitmapPixelChanges[] changes = modifiedLayers.Select(x => x.PixelChanges).ToArray();
-                Manager.PreviewLayer.SetPixels(BitmapPixelChanges.CombineOverride(changes));
+                Manager.ActiveDocument.PreviewLayer.SetPixels(BitmapPixelChanges.CombineOverride(changes));
                 lastModifiedLayers = modifiedLayers;
             }
         }

+ 3 - 3
PixiEditor/Models/Controllers/ClipboardController.cs

@@ -46,7 +46,7 @@ namespace PixiEditor.Models.Controllers
             {
                 AddImageToLayers(image);
                 int latestLayerIndex = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Count - 1;
-                UndoManager.AddUndoChange(
+                ViewModelMain.Current.BitmapManager.ActiveDocument.UndoManager.AddUndoChange(
                     new Change(RemoveLayerProcess, new object[] { latestLayerIndex }, AddLayerProcess, new object[] { image }));
             }
         }
@@ -58,7 +58,7 @@ namespace PixiEditor.Models.Controllers
                 return;
             }
 
-            ViewModelMain.Current.BitmapManager.RemoveLayer((int)parameters[0]);
+            ViewModelMain.Current.BitmapManager.ActiveDocument.RemoveLayer((int)parameters[0]);
         }
 
         private static void AddLayerProcess(object[] parameters)
@@ -125,7 +125,7 @@ namespace PixiEditor.Models.Controllers
 
         private static void AddImageToLayers(WriteableBitmap image)
         {
-            ViewModelMain.Current.BitmapManager.AddNewLayer("Image", image);
+            ViewModelMain.Current.BitmapManager.ActiveDocument.AddNewLayer("Image", image);
         }
     }
 }

+ 22 - 17
PixiEditor/Models/Controllers/UndoManager.cs

@@ -1,36 +1,41 @@
 using System.Collections.Generic;
 using System.Linq;
 using PixiEditor.Models.DataHolders;
+using PixiEditor.ViewModels;
 
 namespace PixiEditor.Models.Controllers
 {
-    public static class UndoManager
+    public class UndoManager
     {
-        private static bool lastChangeWasUndo;
+        private bool lastChangeWasUndo;
 
-        public static Stack<Change> UndoStack { get; set; } = new Stack<Change>();
+        public Stack<Change> UndoStack { get; set; } = new Stack<Change>();
 
-        public static Stack<Change> RedoStack { get; set; } = new Stack<Change>();
+        public Stack<Change> RedoStack { get; set; } = new Stack<Change>();
 
-        public static bool CanUndo => UndoStack.Count > 0;
+        public bool CanUndo => UndoStack.Count > 0;
 
-        public static bool CanRedo => RedoStack.Count > 0;
+        public bool CanRedo => RedoStack.Count > 0;
 
-        public static object MainRoot { get; set; }
+        public object MainRoot { get; set; }
 
-        /// <summary>
-        ///     Sets object(root) in which undo properties are stored.
-        /// </summary>
-        /// <param name="root">Parent object.</param>
-        public static void SetMainRoot(object root)
+        public UndoManager()
+        {
+            if (ViewModelMain.Current != null && ViewModelMain.Current.UndoSubViewModel != null)
+            {
+                MainRoot = ViewModelMain.Current.UndoSubViewModel;
+            }
+        }
+
+        public UndoManager(object mainRoot)
         {
-            MainRoot = root;
+            MainRoot = mainRoot;
         }
 
         /// <summary>
         ///     Adds property change to UndoStack.
         /// </summary>
-        public static void AddUndoChange(Change change)
+        public void AddUndoChange(Change change)
         {
             lastChangeWasUndo = false;
 
@@ -47,7 +52,7 @@ namespace PixiEditor.Models.Controllers
         /// <summary>
         ///     Sets top property in UndoStack to Old Value.
         /// </summary>
-        public static void Undo()
+        public void Undo()
         {
             lastChangeWasUndo = true;
             Change change = UndoStack.Pop();
@@ -66,7 +71,7 @@ namespace PixiEditor.Models.Controllers
         /// <summary>
         ///     Sets top property from RedoStack to old value.
         /// </summary>
-        public static void Redo()
+        public void Redo()
         {
             lastChangeWasUndo = true;
             Change change = RedoStack.Pop();
@@ -82,7 +87,7 @@ namespace PixiEditor.Models.Controllers
             UndoStack.Push(change);
         }
 
-        private static void SetPropertyValue(object target, string propName, object value)
+        private void SetPropertyValue(object target, string propName, object value)
         {
             string[] bits = propName.Split('.');
             for (int i = 0; i < bits.Length - 1; i++)

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

@@ -1,13 +1,18 @@
 using System;
+using System.Buffers;
 using System.Collections.ObjectModel;
+using System.IO;
 using System.Linq;
 using System.Windows;
 using System.Windows.Media;
+using System.Windows.Media.Imaging;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Enums;
+using PixiEditor.Models.IO;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Position;
+using PixiEditor.ViewModels;
 
 namespace PixiEditor.Models.DataHolders
 {
@@ -21,10 +26,66 @@ namespace PixiEditor.Models.DataHolders
         {
             Width = width;
             Height = height;
+            RequestCloseDocumentCommand = new RelayCommand(RequestCloseDocument);
+            SetAsActiveOnClickCommand = new RelayCommand(SetAsActiveOnClick);
+            UndoManager = new UndoManager();
+            XamlAccesibleViewModel = ViewModelMain.Current ?? null;
+            GeneratePreviewLayer();
+            DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(0, 0, width, height));
         }
 
         public event EventHandler<DocumentSizeChangedEventArgs> DocumentSizeChanged;
 
+        public event EventHandler<LayersChangedEventArgs> LayersChanged;
+
+        public RelayCommand RequestCloseDocumentCommand { get; set; }
+
+        public RelayCommand SetAsActiveOnClickCommand { get; set; }
+
+        private ViewModelMain xamlAccesibleViewModel = null;
+
+        public ViewModelMain XamlAccesibleViewModel // Used to access ViewModelMain, without changing DataContext in XAML
+        {
+            get => xamlAccesibleViewModel;
+            set
+            {
+                xamlAccesibleViewModel = value;
+                RaisePropertyChanged(nameof(XamlAccesibleViewModel));
+            }
+        }
+
+        private string documentFilePath = string.Empty;
+
+        public string DocumentFilePath
+        {
+            get => documentFilePath;
+            set
+            {
+                documentFilePath = value;
+                RaisePropertyChanged(nameof(DocumentFilePath));
+                RaisePropertyChanged(nameof(Name));
+            }
+        }
+
+        private bool changesSaved = true;
+
+        public bool ChangesSaved
+        {
+            get => changesSaved;
+            set
+            {
+                changesSaved = value;
+                RaisePropertyChanged(nameof(ChangesSaved));
+                RaisePropertyChanged(nameof(Name)); // This updates name so it shows asterisk if unsaved
+            }
+        }
+
+        public string Name
+        {
+            get => (string.IsNullOrEmpty(DocumentFilePath) ? "Untitled" : Path.GetFileName(DocumentFilePath))
+                + (!ChangesSaved ? " *" : string.Empty);
+        }
+
         public int Width
         {
             get => width;
@@ -45,6 +106,92 @@ namespace PixiEditor.Models.DataHolders
             }
         }
 
+        private Selection selection = new Selection(Array.Empty<Coordinates>());
+
+        public Selection ActiveSelection
+        {
+            get => selection;
+            set
+            {
+                selection = value;
+                RaisePropertyChanged("ActiveSelection");
+            }
+        }
+
+        private Layer previewLayer;
+
+        public Layer PreviewLayer
+        {
+            get => previewLayer;
+            set
+            {
+                previewLayer = value;
+                RaisePropertyChanged("PreviewLayer");
+            }
+        }
+
+        private double mouseXonCanvas;
+
+        private double mouseYonCanvas;
+
+        public double MouseXOnCanvas // Mouse X coordinate relative to canvas
+        {
+            get => mouseXonCanvas;
+            set
+            {
+                mouseXonCanvas = value;
+                RaisePropertyChanged(nameof(MouseXOnCanvas));
+            }
+        }
+
+        public double MouseYOnCanvas // Mouse Y coordinate relative to canvas
+        {
+            get => mouseYonCanvas;
+            set
+            {
+                mouseYonCanvas = value;
+                RaisePropertyChanged(nameof(MouseYOnCanvas));
+            }
+        }
+
+        private double zoomPercentage = 100;
+
+        public double ZoomPercentage
+        {
+            get => zoomPercentage;
+            set
+            {
+                zoomPercentage = value;
+                RaisePropertyChanged(nameof(ZoomPercentage));
+            }
+        }
+
+        private Point viewPortPosition;
+
+        public Point ViewportPosition
+        {
+            get => viewPortPosition;
+            set
+            {
+                viewPortPosition = value;
+                RaisePropertyChanged(nameof(ViewportPosition));
+            }
+        }
+
+        private bool recenterZoombox = true;
+
+        public bool RecenterZoombox
+        {
+            get => recenterZoombox;
+            set
+            {
+                recenterZoombox = value;
+                RaisePropertyChanged(nameof(RecenterZoombox));
+            }
+        }
+
+        public UndoManager UndoManager { get; set; }
+
         public ObservableCollection<Layer> Layers { get; set; } = new ObservableCollection<Layer>();
 
         public Layer ActiveLayer => Layers.Count > 0 ? Layers[ActiveLayerIndex] : null;
@@ -60,6 +207,41 @@ namespace PixiEditor.Models.DataHolders
             }
         }
 
+        public void GeneratePreviewLayer()
+        {
+            PreviewLayer = new Layer("_previewLayer")
+            {
+                MaxWidth = Width,
+                MaxHeight = Height
+            };
+        }
+
+        public void CenterViewport()
+        {
+            RecenterZoombox = false; // It's a trick to trigger change in UserControl
+            RecenterZoombox = true;
+            ViewportPosition = default;
+            ZoomPercentage = default;
+        }
+
+        public void SaveWithDialog()
+        {
+            bool savedSuccessfully = Exporter.SaveAsEditableFileWithDialog(this, out string path);
+            DocumentFilePath = path;
+            ChangesSaved = savedSuccessfully;
+        }
+
+        public void Save()
+        {
+            Save(DocumentFilePath);
+        }
+
+        public void Save(string path)
+        {
+            DocumentFilePath = Exporter.SaveAsEditableFile(this, path);
+            ChangesSaved = true;
+        }
+
         public ObservableCollection<Color> Swatches { get; set; } = new ObservableCollection<Color>();
 
         /// <summary>
@@ -96,6 +278,63 @@ namespace PixiEditor.Models.DataHolders
             DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
         }
 
+        public void SetActiveLayer(int index)
+        {
+            if (ActiveLayerIndex <= Layers.Count - 1)
+            {
+                ActiveLayer.IsActive = false;
+            }
+
+            ActiveLayerIndex = index;
+            ActiveLayer.IsActive = true;
+            LayersChanged?.Invoke(this, new LayersChangedEventArgs(index, LayerAction.SetActive));
+        }
+
+        public void AddNewLayer(string name, WriteableBitmap bitmap, bool setAsActive = true)
+        {
+            AddNewLayer(name, bitmap.PixelWidth, bitmap.PixelHeight, setAsActive);
+            Layers.Last().LayerBitmap = bitmap;
+        }
+
+        public void AddNewLayer(string name, bool setAsActive = true)
+        {
+            AddNewLayer(name, 0, 0, setAsActive);
+        }
+
+        public void AddNewLayer(string name, int width, int height, bool setAsActive = true)
+        {
+            Layers.Add(new Layer(name, width, height)
+            {
+                MaxHeight = Height,
+                MaxWidth = Width
+            });
+            if (setAsActive)
+            {
+                SetActiveLayer(Layers.Count - 1);
+            }
+
+            LayersChanged?.Invoke(this, new LayersChangedEventArgs(0, LayerAction.Add));
+        }
+
+        public void RemoveLayer(int layerIndex)
+        {
+            if (Layers.Count == 0)
+            {
+                return;
+            }
+
+            bool wasActive = Layers[layerIndex].IsActive;
+            Layers.RemoveAt(layerIndex);
+            if (wasActive)
+            {
+                SetActiveLayer(0);
+            }
+            else if (ActiveLayerIndex > Layers.Count - 1)
+            {
+                SetActiveLayer(Layers.Count - 1);
+            }
+        }
+
         /// <summary>
         ///     Resizes all document layers using NearestNeighbor interpolation.
         /// </summary>
@@ -153,6 +392,9 @@ namespace PixiEditor.Models.DataHolders
                 "Clip canvas"));
         }
 
+        /// <summary>
+        /// Centers content inside document.
+        /// </summary>
         public void CenterContent()
         {
             DoubleCords points = GetEdgePoints();
@@ -183,6 +425,18 @@ namespace PixiEditor.Models.DataHolders
                     "Center content"));
         }
 
+        private void SetAsActiveOnClick(object obj)
+        {
+            XamlAccesibleViewModel.BitmapManager.MouseController.StopRecordingMouseMovementChanges();
+            XamlAccesibleViewModel.BitmapManager.MouseController.StartRecordingMouseMovementChanges(true);
+            XamlAccesibleViewModel.BitmapManager.ActiveDocument = this;
+        }
+
+        private void RequestCloseDocument(object parameter)
+        {
+            ViewModelMain.Current.DocumentSubViewModel.RequestCloseDocument(this);
+        }
+
         private int GetOffsetXForAnchor(int srcWidth, int destWidth, AnchorPoint anchor)
         {
             if (anchor.HasFlag(AnchorPoint.Center))

+ 12 - 14
PixiEditor/Models/IO/Exporter.cs

@@ -10,16 +10,12 @@ namespace PixiEditor.Models.IO
 {
     public class Exporter
     {
-        public static Size FileDimensions { get; set; }
-
-        public static string SaveDocumentPath { get; set; }
-
         /// <summary>
         ///     Saves document as .pixi file that contains all document data.
         /// </summary>
         /// <param name="document">Document to save.</param>
-        /// <param name="updateWorkspacePath">Should editor remember dialog path for further saves.</param>
-        public static bool SaveAsEditableFileWithDialog(Document document, bool updateWorkspacePath = false)
+        /// <param name="path">Path where file was saved.</param>
+        public static bool SaveAsEditableFileWithDialog(Document document, out string path)
         {
             SaveFileDialog dialog = new SaveFileDialog
             {
@@ -28,21 +24,24 @@ namespace PixiEditor.Models.IO
             };
             if ((bool)dialog.ShowDialog())
             {
-                SaveAsEditableFile(document, dialog.FileName, updateWorkspacePath);
+                path = SaveAsEditableFile(document, dialog.FileName);
                 return true;
             }
 
+            path = string.Empty;
             return false;
         }
 
-        public static void SaveAsEditableFile(Document document, string path, bool updateWorkspacePath = false)
+        /// <summary>
+        /// Saves editable file to chosen path and returns it.
+        /// </summary>
+        /// <param name="document">Document to be saved.</param>
+        /// <param name="path">Path where to save file.</param>
+        /// <returns>Path.</returns>
+        public static string SaveAsEditableFile(Document document, string path)
         {
             BinarySerialization.WriteToBinaryFile(path, new SerializableDocument(document));
-
-            if (updateWorkspacePath)
-            {
-                SaveDocumentPath = path;
-            }
+            return path;
         }
 
         /// <summary>
@@ -64,7 +63,6 @@ namespace PixiEditor.Models.IO
                     return;
                 }
 
-                FileDimensions = new Size(info.FileWidth, info.FileHeight);
                 SaveAsPng(info.FilePath, info.FileWidth, info.FileHeight, bitmap);
             }
         }

+ 3 - 1
PixiEditor/Models/IO/Importer.cs

@@ -58,7 +58,9 @@ namespace PixiEditor.Models.IO
         {
             try
             {
-                return BinarySerialization.ReadFromBinaryFile<SerializableDocument>(path).ToDocument();
+                Document doc = BinarySerialization.ReadFromBinaryFile<SerializableDocument>(path).ToDocument();
+                doc.DocumentFilePath = path;
+                return doc;
             }
             catch (SerializationException)
             {

+ 1 - 0
PixiEditor/Models/Layers/Layer.cs

@@ -333,6 +333,7 @@ namespace PixiEditor.Models.Layers
         public void Clear()
         {
             LayerBitmap.Clear();
+            ClipCanvas();
         }
 
         /// <summary>

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

@@ -1,5 +1,6 @@
 using System.Windows.Input;
 using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Tools.ToolSettings;
 using PixiEditor.Models.Tools.ToolSettings.Toolbars;
 
@@ -72,7 +73,7 @@ namespace PixiEditor.Models.Tools
         {
         }
 
-        public virtual void AfterAddedUndo()
+        public virtual void AfterAddedUndo(UndoManager undoManager)
         {
         }
     }

+ 7 - 7
PixiEditor/Models/Tools/Tools/MoveTool.cs

@@ -58,7 +58,7 @@ namespace PixiEditor.Models.Tools.Tools
             }
         }
 
-        public override void AfterAddedUndo()
+        public override void AfterAddedUndo(UndoManager undoManager)
         {
             if (currentSelection != null && currentSelection.Length != 0)
             {
@@ -66,7 +66,7 @@ namespace PixiEditor.Models.Tools.Tools
                 foreach (var item in startPixelColors)
                 {
                     BitmapPixelChanges beforeMovePixels = BitmapPixelChanges.FromArrays(startSelection, item.Value);
-                    Change changes = UndoManager.UndoStack.Peek();
+                    Change changes = undoManager.UndoStack.Peek();
                     int layerIndex = ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.IndexOf(item.Key);
 
                     ((LayerChange[])changes.OldValue).First(x => x.LayerIndex == layerIndex).PixelChanges.ChangedPixels
@@ -86,7 +86,7 @@ namespace PixiEditor.Models.Tools.Tools
         {
             if (currentSelection != null && currentSelection.Length == 0)
             {
-                UndoManager.AddUndoChange(new Change(
+                ViewModelMain.Current.BitmapManager.ActiveDocument.UndoManager.AddUndoChange(new Change(
                     ApplyOffsets,
                     new object[] { startingOffsets },
                     ApplyOffsets,
@@ -105,10 +105,10 @@ namespace PixiEditor.Models.Tools.Tools
                 ResetSelectionValues(start);
 
                 // Move offset if no selection
-                if (ViewModelMain.Current.SelectionSubViewModel.ActiveSelection != null && 
-                    ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.SelectedPoints.Count > 0)
+                Selection selection = ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection;
+                if (selection != null && selection.SelectedPoints.Count > 0)
                 {
-                    currentSelection = ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.SelectedPoints.ToArray();
+                    currentSelection = selection.SelectedPoints.ToArray();
                 }
                 else
                 {
@@ -161,7 +161,7 @@ namespace PixiEditor.Models.Tools.Tools
             currentSelection = TranslateSelection(end, out Coordinates[] previousSelection);
             if (updateViewModelSelection)
             {
-                ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.SetSelection(currentSelection, SelectionType.New);
+                ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(currentSelection, SelectionType.New);
             }
 
             ClearSelectedPixels(layer, previousSelection);

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

@@ -32,7 +32,7 @@ namespace PixiEditor.Models.Tools.Tools
             if (e.LeftButton == MouseButtonState.Pressed || e.MiddleButton == MouseButtonState.Pressed)
             {
                 var point = MousePositionConverter.GetCursorPosition();
-                ViewModelMain.Current.ViewportSubViewModel.ViewportPosition = new System.Windows.Point(
+                ViewModelMain.Current.BitmapManager.ActiveDocument.ViewportPosition = new System.Windows.Point(
                     point.X - clickPoint.X,
                     point.Y - clickPoint.Y);
             }

+ 11 - 8
PixiEditor/Models/Tools/Tools/SelectTool.cs

@@ -34,23 +34,26 @@ namespace PixiEditor.Models.Tools.Tools
             SelectionType = selectionType;
 
             oldSelection = null;
-            if (ViewModelMain.Current.SelectionSubViewModel.ActiveSelection != null &&
-                ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.SelectedPoints != null)
+            Selection selection = ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection;
+            if (selection != null && selection.SelectedPoints != null)
             {
-                oldSelection = ViewModelMain.Current.SelectionSubViewModel.ActiveSelection;
+                oldSelection = selection;
             }
         }
 
         public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
         {
-            if (ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.SelectedPoints.Count() <= 1)
+            if (ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints.Count() <= 1)
             {
                 // If we have not selected multiple points, clear the selection
-                ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.Clear();
+                ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.Clear();
             }
 
-            UndoManager.AddUndoChange(
-                new Change("ActiveSelection", oldSelection, ViewModelMain.Current.SelectionSubViewModel.ActiveSelection, "Select pixels", ViewModelMain.Current.SelectionSubViewModel));
+            ViewModelMain.Current.BitmapManager.ActiveDocument.UndoManager.AddUndoChange(
+                new Change("ActiveSelection", 
+                oldSelection,
+                ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection,
+                "Select pixels", ViewModelMain.Current.SelectionSubViewModel));
         }
 
         public override void Use(Coordinates[] pixels)
@@ -87,7 +90,7 @@ namespace PixiEditor.Models.Tools.Tools
         private void Select(Coordinates[] pixels)
         {
             IEnumerable<Coordinates> selection = GetRectangleSelectionForPoints(pixels[^1], pixels[0]);
-            ViewModelMain.Current.SelectionSubViewModel.ActiveSelection.SetSelection(selection, SelectionType);
+            ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(selection, SelectionType);
         }
     }
 }

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

@@ -45,7 +45,7 @@ namespace PixiEditor.Models.Tools.Tools
         public override void OnRecordingLeftMouseDown(MouseEventArgs e)
         {
             startingX = MousePositionConverter.GetCursorPosition().X;
-            ViewModelMain.Current.ViewportSubViewModel.ZoomPercentage = 100; // This resest the value, so callback in MainDrawingPanel can fire again later
+            ViewModelMain.Current.BitmapManager.ActiveDocument.ZoomPercentage = 100; // This resest the value, so callback in MainDrawingPanel can fire again later
         }
 
         public override void OnMouseMove(MouseEventArgs e)
@@ -78,7 +78,7 @@ namespace PixiEditor.Models.Tools.Tools
 
         public void Zoom(double percentage)
         {
-            ViewModelMain.Current.ViewportSubViewModel.ZoomPercentage = percentage;
+            ViewModelMain.Current.BitmapManager.ActiveDocument.ZoomPercentage = percentage;
         }
 
         public override void Use(Coordinates[] pixels)

+ 2 - 2
PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs

@@ -40,7 +40,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Copy(null);
             Owner.BitmapManager.BitmapOperations.DeletePixels(
                 new[] { Owner.BitmapManager.ActiveDocument.ActiveLayer },
-                Owner.SelectionSubViewModel.ActiveSelection.SelectedPoints.ToArray());
+                Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints.ToArray());
         }
 
         public void Paste(object parameter)
@@ -57,7 +57,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         {
             ClipboardController.CopyToClipboard(
                 new[] { Owner.BitmapManager.ActiveDocument.ActiveLayer },
-                Owner.SelectionSubViewModel.ActiveSelection.SelectedPoints.ToArray(),
+                Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints.ToArray(),
                 Owner.BitmapManager.ActiveDocument.Width,
                 Owner.BitmapManager.ActiveDocument.Height);
         }

+ 22 - 4
PixiEditor/ViewModels/SubViewModels/Main/DocumentViewModel.cs

@@ -1,6 +1,9 @@
-using System.Linq;
+using System;
+using System.Linq;
 using PixiEditor.Helpers;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Dialogs;
+using PixiEditor.Models.Enums;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
@@ -8,8 +11,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
     {
         public const string ConfirmationDialogMessage = "Document was modified. Do you want to save changes?";
 
-        public bool UnsavedDocumentModified { get; set; }
-
         public RelayCommand CenterContentCommand { get; set; }
 
         public RelayCommand ClipCanvasCommand { get; set; }
@@ -32,11 +33,28 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Owner.BitmapManager.ActiveDocument?.ClipCanvas();
         }
 
+        public void RequestCloseDocument(Document document)
+        {
+            if (!document.ChangesSaved)
+            {
+                ConfirmationType result = ConfirmationDialog.Show(ConfirmationDialogMessage);
+                if (result == ConfirmationType.Yes)
+                {
+                    Owner.FileSubViewModel.SaveDocument(false);
+                }
+                else if (result == ConfirmationType.Canceled)
+                {
+                    return;
+                }
+            }
+            Owner.BitmapManager.CloseDocument(document);
+        }
+
         private void DeletePixels(object parameter)
         {
             Owner.BitmapManager.BitmapOperations.DeletePixels(
                 new[] { Owner.BitmapManager.ActiveLayer },
-                Owner.SelectionSubViewModel.ActiveSelection.SelectedPoints.ToArray());
+                Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints.ToArray());
         }
 
         private void OpenResizePopup(object parameter)

+ 24 - 27
PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -6,6 +6,7 @@ using System.Windows.Media.Imaging;
 using Microsoft.Win32;
 using PixiEditor.Exceptions;
 using PixiEditor.Helpers;
+using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Enums;
@@ -49,10 +50,11 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public void NewDocument(int width, int height, bool addBaseLayer = true)
         {
-            Owner.BitmapManager.ActiveDocument = new Document(width, height);
+            Owner.BitmapManager.Documents.Add(new Document(width, height));
+            Owner.BitmapManager.ActiveDocument = Owner.BitmapManager.Documents[^1];
             if (addBaseLayer)
             {
-                Owner.BitmapManager.AddNewLayer("Base Layer");
+                Owner.BitmapManager.ActiveDocument.AddNewLayer("Base Layer");
             }
 
             Owner.ResetProgramStateValues();
@@ -74,7 +76,10 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             if (dialog.ShowDialog())
             {
                 NewDocument(dialog.FileWidth, dialog.FileHeight, false);
-                Owner.BitmapManager.AddNewLayer("Image", Importer.ImportImage(dialog.FilePath, dialog.FileWidth, dialog.FileHeight));
+                Owner.BitmapManager.ActiveDocument.DocumentFilePath = path;
+                Owner.BitmapManager.ActiveDocument.AddNewLayer(
+                    "Image",
+                    Importer.ImportImage(dialog.FilePath, dialog.FileWidth, dialog.FileHeight));
             }
         }
 
@@ -101,22 +106,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         private void Open(string path)
         {
-            if (Owner.DocumentSubViewModel.UnsavedDocumentModified)
-            {
-                var result = ConfirmationDialog.Show(DocumentViewModel.ConfirmationDialogMessage);
-                if (result == ConfirmationType.Yes)
-                {
-                    SaveDocument(null);
-                }
-                else if (result == ConfirmationType.Canceled)
-                {
-                    return;
-                }
-            }
-
             try
             {
-                Owner.ResetProgramStateValues();
                 if (path.EndsWith(".pixi"))
                 {
                     OpenDocument(path);
@@ -125,6 +116,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 {
                     OpenFile(path);
                 }
+
+                Owner.ResetProgramStateValues();
             }
             catch (CorruptedFileException ex)
             {
@@ -144,31 +137,35 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 if (Importer.IsSupportedFile(dialog.FileName))
                 {
                     Open(dialog.FileName);
+                    Owner.BitmapManager.ActiveDocument = Owner.BitmapManager.Documents.Last();
                 }
-
-                Owner.ViewportSubViewModel.CenterViewport();
             }
         }
 
         private void OpenDocument(string path)
         {
-            Owner.BitmapManager.ActiveDocument = Importer.ImportDocument(path);
-            Exporter.SaveDocumentPath = path;
-            Owner.DocumentSubViewModel.UnsavedDocumentModified = false;
+            if (Owner.BitmapManager.Documents.Select(x => x.DocumentFilePath).All(y => y != path))
+            {
+                Owner.BitmapManager.Documents.Add(Importer.ImportDocument(path));
+            }
+            else
+            {
+                Owner.BitmapManager.ActiveDocument = Owner.BitmapManager.Documents.First(y => y.DocumentFilePath == path);
+            }
         }
 
         private void SaveDocument(object parameter)
         {
             bool paramIsAsNew = parameter != null && parameter.ToString()?.ToLower() == "asnew";
-            if (paramIsAsNew || Exporter.SaveDocumentPath == null)
+            if (paramIsAsNew ||
+                string.IsNullOrEmpty(Owner.BitmapManager.ActiveDocument.DocumentFilePath) ||
+                !Owner.BitmapManager.ActiveDocument.DocumentFilePath.EndsWith(".pixi"))
             {
-                var saved = Exporter.SaveAsEditableFileWithDialog(Owner.BitmapManager.ActiveDocument, !paramIsAsNew);
-                Owner.DocumentSubViewModel.UnsavedDocumentModified = Owner.DocumentSubViewModel.UnsavedDocumentModified && !saved;
+                Owner.BitmapManager.ActiveDocument.SaveWithDialog();
             }
             else
             {
-                Exporter.SaveAsEditableFile(Owner.BitmapManager.ActiveDocument, Exporter.SaveDocumentPath);
-                Owner.DocumentSubViewModel.UnsavedDocumentModified = false;
+                Owner.BitmapManager.ActiveDocument.Save();
             }
         }
 

+ 7 - 29
PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs

@@ -17,30 +17,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public RelayCommand KeyUpCommand { get; set; }
 
-        private double mouseXonCanvas;
-
-        private double mouseYonCanvas;
-
-        public double MouseXOnCanvas // Mouse X coordinate relative to canvas
-        {
-            get => mouseXonCanvas;
-            set
-            {
-                mouseXonCanvas = value;
-                RaisePropertyChanged(nameof(MouseXOnCanvas));
-            }
-        }
-
-        public double MouseYOnCanvas // Mouse Y coordinate relative to canvas
-        {
-            get => mouseYonCanvas;
-            set
-            {
-                mouseYonCanvas = value;
-                RaisePropertyChanged(nameof(MouseYOnCanvas));
-            }
-        }
-
         private bool restoreToolOnKeyUp = false;
 
         public IoViewModel(ViewModelMain owner)
@@ -90,10 +66,10 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             {
                 if (!Owner.BitmapManager.MouseController.IsRecordingChanges)
                 {
-                    bool clickedOnCanvas = MouseXOnCanvas >= 0 &&
-                        MouseXOnCanvas <= Owner.BitmapManager.ActiveDocument.Width &&
-                        MouseYOnCanvas >= 0 &&
-                        MouseYOnCanvas <= Owner.BitmapManager.ActiveDocument.Height;
+                    bool clickedOnCanvas = Owner.BitmapManager.ActiveDocument.MouseXOnCanvas >= 0 &&
+                        Owner.BitmapManager.ActiveDocument.MouseXOnCanvas <= Owner.BitmapManager.ActiveDocument.Width &&
+                        Owner.BitmapManager.ActiveDocument.MouseYOnCanvas >= 0 &&
+                        Owner.BitmapManager.ActiveDocument.MouseYOnCanvas <= Owner.BitmapManager.ActiveDocument.Height;
                     Owner.BitmapManager.MouseController.StartRecordingMouseMovementChanges(clickedOnCanvas);
                     Owner.BitmapManager.MouseController.RecordMouseMovementChange(MousePositionConverter.CurrentCoordinates);
                 }
@@ -115,7 +91,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         /// <param name="parameter">CommandParameter.</param>
         private void MouseMove(object parameter)
         {
-            Coordinates cords = new Coordinates((int)MouseXOnCanvas, (int)MouseYOnCanvas);
+            Coordinates cords = new Coordinates(
+                (int)Owner.BitmapManager.ActiveDocument.MouseXOnCanvas,
+                (int)Owner.BitmapManager.ActiveDocument.MouseYOnCanvas);
             MousePositionConverter.CurrentCoordinates = cords;
 
             if (Owner.BitmapManager.MouseController.IsRecordingChanges && Mouse.LeftButton == MouseButtonState.Pressed)

+ 6 - 5
PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs

@@ -29,7 +29,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public void NewLayer(object parameter)
         {
-            Owner.BitmapManager.AddNewLayer($"New Layer {Owner.BitmapManager.ActiveDocument.Layers.Count}");
+            //TODO: Implement AddNewLayer to Document, not BitmapManager
+            Owner.BitmapManager.ActiveDocument.AddNewLayer($"New Layer {Owner.BitmapManager.ActiveDocument.Layers.Count}");
         }
 
         public bool CanCreateNewLayer(object parameter)
@@ -39,12 +40,12 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public void SetActiveLayer(object parameter)
         {
-            Owner.BitmapManager.SetActiveLayer((int)parameter);
+            Owner.BitmapManager.ActiveDocument.SetActiveLayer((int)parameter);
         }
 
         public void DeleteLayer(object parameter)
         {
-            Owner.BitmapManager.RemoveLayer((int)parameter);
+            Owner.BitmapManager.ActiveDocument.RemoveLayer((int)parameter);
         }
 
         public bool CanDeleteLayer(object property)
@@ -63,7 +64,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Owner.BitmapManager.ActiveDocument.Layers.Move(oldIndex, oldIndex + 1);
             if (Owner.BitmapManager.ActiveDocument.ActiveLayerIndex == oldIndex)
             {
-                Owner.BitmapManager.SetActiveLayer(oldIndex + 1);
+                Owner.BitmapManager.ActiveDocument.SetActiveLayer(oldIndex + 1);
             }
         }
 
@@ -73,7 +74,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Owner.BitmapManager.ActiveDocument.Layers.Move(oldIndex, oldIndex - 1);
             if (Owner.BitmapManager.ActiveDocument.ActiveLayerIndex == oldIndex)
             {
-                Owner.BitmapManager.SetActiveLayer(oldIndex - 1);
+                Owner.BitmapManager.ActiveDocument.SetActiveLayer(oldIndex - 1);
             }
         }
 

+ 4 - 16
PixiEditor/ViewModels/SubViewModels/Main/SelectionViewModel.cs

@@ -13,40 +13,28 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public RelayCommand SelectAllCommand { get; set; }
 
-        private Selection selection;
-
-        public Selection ActiveSelection
-        {
-            get => selection;
-            set
-            {
-                selection = value;
-                RaisePropertyChanged("ActiveSelection");
-            }
-        }
-
         public SelectionViewModel(ViewModelMain owner)
             : base(owner)
         {
             DeselectCommand = new RelayCommand(Deselect, SelectionIsNotEmpty);
             SelectAllCommand = new RelayCommand(SelectAll, CanSelectAll);
-            ActiveSelection = new Selection(Array.Empty<Coordinates>());
         }
 
         public void SelectAll(object parameter)
         {
             SelectTool select = new SelectTool();
-            ActiveSelection.SetSelection(select.GetAllSelection(), SelectionType.New);
+            Owner.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(select.GetAllSelection(), SelectionType.New);
         }
 
         public void Deselect(object parameter)
         {
-            ActiveSelection?.Clear();
+            Owner.BitmapManager.ActiveDocument.ActiveSelection?.Clear();
         }
 
         public bool SelectionIsNotEmpty(object property)
         {
-            return ActiveSelection?.SelectedPoints != null && ActiveSelection.SelectedPoints.Count > 0;
+            var selectedPoints = Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints;
+            return selectedPoints != null && selectedPoints.Count > 0;
         }
 
         private bool CanSelectAll(object property)

+ 7 - 7
PixiEditor/ViewModels/SubViewModels/Main/UndoViewModel.cs

@@ -33,7 +33,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         {
             UndoCommand = new RelayCommand(Undo, CanUndo);
             RedoCommand = new RelayCommand(Redo, CanRedo);
-            UndoManager.SetMainRoot(this);
         }
 
         public void TriggerNewUndoChange(Tool toolUsed)
@@ -46,8 +45,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 {
                     LayerChange[] newValues = changes.Select(x => x.Item1).ToArray();
                     LayerChange[] oldValues = changes.Select(x => x.Item2).ToArray();
-                    UndoManager.AddUndoChange(new Change("UndoChanges", oldValues, newValues, root: this));
-                    toolUsed.AfterAddedUndo();
+                    Owner.BitmapManager.ActiveDocument.UndoManager.AddUndoChange(
+                        new Change("UndoChanges", oldValues, newValues, root: this));
+                    toolUsed.AfterAddedUndo(Owner.BitmapManager.ActiveDocument.UndoManager);
                 }
             }
         }
@@ -58,7 +58,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         /// <param name="parameter">CommandProperty.</param>
         public void Redo(object parameter)
         {
-            UndoManager.Redo();
+            Owner.BitmapManager.ActiveDocument.UndoManager.Redo();
         }
 
         /// <summary>
@@ -68,7 +68,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         public void Undo(object parameter)
         {
             Owner.SelectionSubViewModel.Deselect(null);
-            UndoManager.Undo();
+            Owner.BitmapManager.ActiveDocument.UndoManager.Undo();
         }
 
         /// <summary>
@@ -78,7 +78,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         /// <returns>True if can undo.</returns>
         private bool CanUndo(object property)
         {
-            return UndoManager.CanUndo;
+            return Owner.BitmapManager.ActiveDocument.UndoManager.CanUndo;
         }
 
         /// <summary>
@@ -88,7 +88,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         /// <returns>True if can redo.</returns>
         private bool CanRedo(object property)
         {
-            return UndoManager.CanRedo;
+            return Owner.BitmapManager.ActiveDocument.UndoManager.CanRedo;
         }
     }
 }

+ 2 - 43
PixiEditor/ViewModels/SubViewModels/Main/ViewportViewModel.cs

@@ -7,58 +7,17 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
     {
         public RelayCommand ZoomCommand { get; set; }
 
-        private double zoomPercentage = 100;
-
-        public double ZoomPercentage
-        {
-            get => zoomPercentage;
-            set
-            {
-                zoomPercentage = value;
-                RaisePropertyChanged(nameof(ZoomPercentage));
-            }
-        }
-
-        private Point viewPortPosition;
-
-        public Point ViewportPosition
-        {
-            get => viewPortPosition;
-            set
-            {
-                viewPortPosition = value;
-                RaisePropertyChanged(nameof(ViewportPosition));
-            }
-        }
-
-        private bool recenterZoombox;
-
-        public bool RecenterZoombox
-        {
-            get => recenterZoombox;
-            set
-            {
-                recenterZoombox = value;
-                RaisePropertyChanged(nameof(RecenterZoombox));
-            }
-        }
-
         public ViewportViewModel(ViewModelMain owner)
             : base(owner)
         {
             ZoomCommand = new RelayCommand(ZoomViewport);
         }
 
-        public void CenterViewport()
-        {
-            RecenterZoombox = !RecenterZoombox; // It's a trick to trigger change in UserControl
-        }
-
         private void ZoomViewport(object parameter)
         {
             double zoom = (int)parameter;
-            ZoomPercentage = zoom;
-            ZoomPercentage = 100;
+            Owner.BitmapManager.ActiveDocument.ZoomPercentage = zoom;
+            Owner.BitmapManager.ActiveDocument.ZoomPercentage = 100;
         }
     }
 }

+ 54 - 16
PixiEditor/ViewModels/ViewModelMain.cs

@@ -2,6 +2,7 @@
 using System.Collections.Generic;
 using System.ComponentModel;
 using System.Diagnostics;
+using System.Linq;
 using System.Windows;
 using System.Windows.Input;
 using PixiEditor.Helpers;
@@ -142,13 +143,12 @@ namespace PixiEditor.ViewModels
         /// </summary>
         public void ResetProgramStateValues()
         {
-            BitmapManager.PreviewLayer = null;
-            UndoManager.UndoStack.Clear();
-            UndoManager.RedoStack.Clear();
-            SelectionSubViewModel.ActiveSelection = new Selection(Array.Empty<Coordinates>());
-            ViewportSubViewModel.CenterViewport();
-            Exporter.SaveDocumentPath = null;
-            DocumentSubViewModel.UnsavedDocumentModified = false;
+            foreach (var document in BitmapManager.Documents)
+            {
+                document.PreviewLayer = null;
+            }
+
+            BitmapManager.ActiveDocument.CenterViewport();
         }
 
         public bool DocumentIsNotNull(object property)
@@ -163,10 +163,38 @@ namespace PixiEditor.ViewModels
                 throw new ArgumentException();
             }
 
-            ((CancelEventArgs)property).Cancel = true;
+            ((CancelEventArgs)property).Cancel = !RemoveDocumentsWithSaveConfirmation();
+        }
+
+        /// <summary>
+        /// Removes documents with unsaved changes confirmation dialog.
+        /// </summary>
+        /// <returns>If documents was removed successfully.</returns>
+        private bool RemoveDocumentsWithSaveConfirmation()
+        {
+            int docCount = BitmapManager.Documents.Count;
+            for (int i = 0; i < docCount; i++)
+            {
+                BitmapManager.ActiveDocument = BitmapManager.Documents.First();
+                bool canceled = !RemoveDocumentWithSaveConfirmation();
+                if (canceled)
+                {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /// <summary>
+        /// Removes document with unsaved changes confirmation dialog.
+        /// </summary>
+        /// <returns>If document was removed successfully.</returns>
+        private bool RemoveDocumentWithSaveConfirmation()
+        {
+            ConfirmationType result = ConfirmationType.No;
 
-            var result = ConfirmationType.No;
-            if (DocumentSubViewModel.UnsavedDocumentModified)
+            if (!BitmapManager.ActiveDocument.ChangesSaved)
             {
                 result = ConfirmationDialog.Show(DocumentViewModel.ConfirmationDialogMessage);
                 if (result == ConfirmationType.Yes)
@@ -177,8 +205,15 @@ namespace PixiEditor.ViewModels
 
             if (result != ConfirmationType.Canceled)
             {
-                ((CancelEventArgs)property).Cancel = false;
+                BitmapManager.Documents.Remove(BitmapManager.ActiveDocument);
+
+                return true;
+            }
+            else
+            {
+                return false;
             }
+
         }
 
         private void OnStartup(object parameter)
@@ -188,14 +223,17 @@ namespace PixiEditor.ViewModels
 
         private void BitmapManager_DocumentChanged(object sender, DocumentChangedEventArgs e)
         {
-            e.NewDocument.DocumentSizeChanged += ActiveDocument_DocumentSizeChanged;
+            if (e.NewDocument != null)
+            {
+                e.NewDocument.DocumentSizeChanged += ActiveDocument_DocumentSizeChanged;
+            }
         }
 
         private void ActiveDocument_DocumentSizeChanged(object sender, DocumentSizeChangedEventArgs e)
         {
-            SelectionSubViewModel.ActiveSelection = new Selection(Array.Empty<Coordinates>());
-            ViewportSubViewModel.CenterViewport();
-            DocumentSubViewModel.UnsavedDocumentModified = true;
+            BitmapManager.ActiveDocument.ActiveSelection = new Selection(Array.Empty<Coordinates>());
+            BitmapManager.ActiveDocument.CenterViewport();
+            BitmapManager.ActiveDocument.ChangesSaved = false;
         }
 
         private void MouseController_StoppedRecordingChanges(object sender, EventArgs e)
@@ -208,7 +246,7 @@ namespace PixiEditor.ViewModels
             ChangesController.AddChanges(
                 new LayerChange(e.PixelsChanged, e.ChangedLayerIndex),
                 new LayerChange(e.OldPixelsValues, e.ChangedLayerIndex));
-            DocumentSubViewModel.UnsavedDocumentModified = true;
+            BitmapManager.ActiveDocument.ChangesSaved = false;
             if (BitmapManager.IsOperationTool())
             {
                 ColorsSubViewModel.AddSwatch(ColorsSubViewModel.PrimaryColor);

+ 52 - 78
PixiEditor/Views/MainWindow.xaml

@@ -5,14 +5,12 @@
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:vm="clr-namespace:PixiEditor.ViewModels"
         xmlns:vws="clr-namespace:PixiEditor.Views"
-        xmlns:tools="clr-namespace:PixiEditor.Models.Tools"
         xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
-        xmlns:behaviors="clr-namespace:PixiEditor.Helpers.Behaviours"
         xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
         xmlns:ui="clr-namespace:PixiEditor.Helpers.UI"
         xmlns:cmd="http://www.galasoft.ch/mvvmlight" 
         xmlns:avalondock="https://github.com/Dirkster99/AvalonDock"
-        xmlns:colorpicker="clr-namespace:ColorPicker;assembly=ColorPicker"
+        xmlns:colorpicker="clr-namespace:ColorPicker;assembly=ColorPicker" xmlns:usercontrols="clr-namespace:PixiEditor.Views.UserControls"
         mc:Ignorable="d" WindowStyle="None" Initialized="MainWindow_Initialized"
         Title="PixiEditor" Name="mainWindow" Height="1000" Width="1600" Background="{StaticResource MainColor}"
         WindowStartupLocation="CenterScreen" WindowState="Maximized" DataContext="{DynamicResource ViewModelMain}">
@@ -143,7 +141,7 @@
             </StackPanel>
         </DockPanel>
         <StackPanel Background="{StaticResource AccentColor}" Orientation="Horizontal" Grid.ColumnSpan="3" Grid.Column="0"
-                    Margin="0,30,146,0" Grid.Row="0" Grid.RowSpan="2">
+                     Grid.Row="1">
             <ItemsControl ItemsSource="{Binding BitmapManager.SelectedTool.Toolbar.Settings}">
                 <ItemsControl.ItemsPanel>
                     <ItemsPanelTemplate>
@@ -164,65 +162,42 @@
         </StackPanel>
         <Grid Grid.Column="1" Grid.Row="2" Background="#303030" Margin="0,7,5,0">
             <Grid>
-                <vws:MainDrawingPanel ZoomPercentage="{Binding ViewportSubViewModel.ZoomPercentage, Mode=TwoWay}" Center="{Binding ViewportSubViewModel.RecenterZoombox, Mode=TwoWay}" x:Name="DrawingPanel"
-                                      CenterOnStart="True" Cursor="{Binding ToolsSubViewModel.ToolCursor}" 
-                                      MiddleMouseClickedCommand="{Binding ToolsSubViewModel.SelectToolCommand}" 
-                                      MiddleMouseClickedCommandParameter="{x:Static tools:ToolType.MoveViewport}"
-                                      ViewportPosition="{Binding ViewportSubViewModel.ViewportPosition, Mode=TwoWay}">
-                    <i:Interaction.Triggers>
-                        <i:EventTrigger EventName="MouseMove">
-                            <i:InvokeCommandAction Command="{Binding IoSubViewModel.MouseMoveCommand}" />
-                        </i:EventTrigger>
-                        <i:EventTrigger EventName="MouseDown">
-                            <i:InvokeCommandAction Command="{Binding IoSubViewModel.MouseDownCommand}"/>
-                        </i:EventTrigger>
-                    </i:Interaction.Triggers>
-                    <i:Interaction.Behaviors>
-                        <behaviors:MouseBehaviour RelativeTo="{Binding ElementName=DrawingPanel, Path=Item}"
-                                                  MouseX="{Binding IoSubViewModel.MouseXOnCanvas, Mode=OneWayToSource}"
-                                                  MouseY="{Binding IoSubViewModel.MouseYOnCanvas, Mode=OneWayToSource}" />
-                    </i:Interaction.Behaviors>
-                    <vws:MainDrawingPanel.Item>
-                        <Canvas Width="{Binding BitmapManager.ActiveDocument.Width}"
-                                Height="{Binding BitmapManager.ActiveDocument.Height}" VerticalAlignment="Center"
-                                HorizontalAlignment="Center">
-                            <Image Source="/Images/transparentbg.png"
-                                   Height="{Binding BitmapManager.ActiveDocument.Height}"
-                                   Width="{Binding BitmapManager.ActiveDocument.Width}" Opacity="0.9"
-                                   Stretch="UniformToFill" />
-                            <Image Source="{Binding BitmapManager.PreviewLayer.LayerBitmap}" Panel.ZIndex="2"
-                                   RenderOptions.BitmapScalingMode="NearestNeighbor" Stretch="Uniform"
-                                   Width="{Binding BitmapManager.PreviewLayer.Width}"
-                                   Height="{Binding BitmapManager.PreviewLayer.Height}" 
-                                   Margin="{Binding BitmapManager.PreviewLayer.Offset}"/>
-                            <ItemsControl ItemsSource="{Binding BitmapManager.ActiveDocument.Layers}">
-                                <ItemsControl.ItemsPanel>
-                                    <ItemsPanelTemplate>
-                                        <Grid />
-                                    </ItemsPanelTemplate>
-                                </ItemsControl.ItemsPanel>
-                                <ItemsControl.ItemTemplate>
-                                    <DataTemplate>
-                                        <Image VerticalAlignment="Top" HorizontalAlignment="Left" Source="{Binding LayerBitmap}"
-                                               Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}"
-                                               RenderOptions.BitmapScalingMode="NearestNeighbor" Stretch="Uniform"
-                                               Opacity="{Binding Opacity}"
-                                               Width="{Binding Width}" Height="{Binding Height}" Margin="{Binding Offset}" />
-                                    </DataTemplate>
-                                </ItemsControl.ItemTemplate>
-                            </ItemsControl>
-                            <Image VerticalAlignment="Top" HorizontalAlignment="Left" Source="{Binding SelectionSubViewModel.ActiveSelection.SelectionLayer.LayerBitmap}"
-                                   RenderOptions.BitmapScalingMode="NearestNeighbor" Stretch="Uniform"
-                                   Width="{Binding SelectionSubViewModel.ActiveSelection.SelectionLayer.Width}"
-                                   Height="{Binding SelectionSubViewModel.ActiveSelection.SelectionLayer.Height}" 
-                                   Margin="{Binding SelectionSubViewModel.ActiveSelection.SelectionLayer.Offset}" />
-                        </Canvas>
-                    </vws:MainDrawingPanel.Item>
-                </vws:MainDrawingPanel>
+                <avalondock:DockingManager ActiveContent="{Binding BitmapManager.ActiveDocument, Mode=TwoWay}" 
+                                           DocumentsSource="{Binding BitmapManager.Documents}">
+                    <avalondock:DockingManager.Theme>
+                        <avalondock:Vs2013DarkTheme/>
+                    </avalondock:DockingManager.Theme>
+                    <avalondock:DockingManager.LayoutItemContainerStyle>
+                        <Style TargetType="{x:Type avalondock:LayoutItem}">
+                            <Setter Property="Title" Value="{Binding Model.Name}" />
+                            <Setter Property="CloseCommand" Value="{Binding Model.RequestCloseDocumentCommand}" />
+                        </Style>
+                    </avalondock:DockingManager.LayoutItemContainerStyle>
+                    <avalondock:DockingManager.LayoutItemTemplate>
+                        <DataTemplate DataType="{x:Type vm:ViewModelMain}">
+                            <usercontrols:DrawingViewPort
+                                        ZoomPercentage="{Binding ZoomPercentage}"
+                                        RecenterZoombox="{Binding RecenterZoombox}"
+                                        Cursor="{Binding XamlAccesibleViewModel.ToolsSubViewModel.ToolCursor}"
+                                        MiddleMouseClickedCommand="{Binding XamlAccesibleViewModel.ToolsSubViewModel.SelectToolCommand}"
+                                        ViewportPosition="{Binding ViewportPosition}"
+                                        MouseMoveCommand="{Binding XamlAccesibleViewModel.IoSubViewModel.MouseMoveCommand}"
+                                        MouseDownCommand="{Binding XamlAccesibleViewModel.IoSubViewModel.MouseDownCommand}"
+                                        MouseXOnCanvas="{Binding MouseXOnCanvas, Mode=TwoWay}"
+                                        MouseYOnCanvas="{Binding MouseYOnCanvas, Mode=TwoWay}">
+                                <i:Interaction.Triggers>
+                                    <i:EventTrigger EventName="PreviewMouseDown">
+                                        <i:InvokeCommandAction Command="{Binding SetAsActiveOnClickCommand}"/>
+                                    </i:EventTrigger>
+                                </i:Interaction.Triggers>
+                            </usercontrols:DrawingViewPort>
+                        </DataTemplate>
+                    </avalondock:DockingManager.LayoutItemTemplate>
+                </avalondock:DockingManager>
             </Grid>
         </Grid>
 
-        <StackPanel Orientation="Vertical" Cursor="Arrow" Grid.Row="2" Grid.Column="0" Margin="0,7,5,0"
+        <StackPanel Orientation="Vertical" Cursor="Arrow" Grid.Row="2" Grid.Column="0"
                     Background="{StaticResource AccentColor}" Grid.RowSpan="2">
 
             <ItemsControl ItemsSource="{Binding ToolsSubViewModel.ToolSet}">
@@ -243,24 +218,32 @@
             </ItemsControl>
         </StackPanel>
 
-        <Grid Grid.Column="2" Background="{StaticResource AccentColor}" Margin="0,30,0,0" Grid.RowSpan="3">
+        <Grid Grid.Column="2" Background="{StaticResource AccentColor}" Grid.Row="2" Grid.RowSpan="1">
             <Grid.RowDefinitions>
-                <RowDefinition Height="340" />
+                <RowDefinition Height="330" />
                 <RowDefinition Height="250*" />
                 <RowDefinition Height="209*" />
             </Grid.RowDefinitions>
             <StackPanel Grid.Row="2" Orientation="Vertical" ZIndex="15">
             </StackPanel>
-            <colorpicker:StandardColorPicker Grid.Row="0" SelectedColor="{Binding ColorsSubViewModel.PrimaryColor, Mode=TwoWay}"
-                             SecondaryColor="{Binding ColorsSubViewModel.SecondaryColor, Mode=TwoWay}" />
             <avalondock:DockingManager Foreground="White" Background="{StaticResource AccentColor}" BorderThickness="0"
-                                       Grid.Row="1">
+                                       Grid.Row="0" Grid.RowSpan="3">
                 <avalondock:LayoutRoot x:Name="LayoutRoot">
                     <avalondock:LayoutPanel Orientation="Vertical">
+                        <LayoutAnchorablePane>
+                            <LayoutAnchorable ContentId="colorPicker" Title="Color Picker" CanHide="False"
+                                                         CanClose="False" CanAutoHide="False"
+                                                         CanDockAsTabbedDocument="True" CanFloat="True">
+                                <Grid Grid.Row="0">
+                                    <colorpicker:StandardColorPicker Grid.Row="0" SelectedColor="{Binding ColorsSubViewModel.PrimaryColor, Mode=TwoWay}"
+                                 SecondaryColor="{Binding ColorsSubViewModel.SecondaryColor, Mode=TwoWay}" />
+                                </Grid>
+                            </LayoutAnchorable>
+                        </LayoutAnchorablePane>
                         <avalondock:LayoutAnchorablePane>
                             <avalondock:LayoutAnchorable ContentId="layers" Title="Layers" CanHide="False"
                                                          CanClose="False" CanAutoHide="False"
-                                                         CanDockAsTabbedDocument="False" CanFloat="True">
+                                                         CanDockAsTabbedDocument="True" CanFloat="True">
                                 <StackPanel Orientation="Vertical">
                                     <Button Command="{Binding LayersSubViewModel.NewLayerCommand}" Height="30" Content="New Layer"
                                             HorizontalAlignment="Stretch" Margin="5"
@@ -315,15 +298,6 @@
                                 </StackPanel>
                             </avalondock:LayoutAnchorable>
                         </avalondock:LayoutAnchorablePane>
-                    </avalondock:LayoutPanel>
-                </avalondock:LayoutRoot>
-                <avalondock:DockingManager.Theme>
-                    <avalondock:Vs2013DarkTheme />
-                </avalondock:DockingManager.Theme>
-            </avalondock:DockingManager>
-            <avalondock:DockingManager Grid.Row="2" Background="{StaticResource AccentColor}">
-                <avalondock:LayoutRoot>
-                    <avalondock:LayoutPanel>
                         <avalondock:LayoutAnchorablePane>
                             <avalondock:LayoutAnchorable ContentId="swatches" Title="Swatches" CanHide="False"
                                                          CanClose="False" CanAutoHide="False"
@@ -386,12 +360,12 @@
             </avalondock:DockingManager>
         </Grid>
         <DockPanel Grid.Row="3" Grid.Column="1">
-            <TextBlock Text="{Binding BitmapManager.SelectedTool.ActionDisplay}" Foreground="White" FontSize="16"  VerticalAlignment="Center"/>
+            <TextBlock Text="{Binding BitmapManager.SelectedTool.ActionDisplay}" Foreground="White" FontSize="15"  VerticalAlignment="Center"/>
             <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
                 <TextBlock Text="X:" Foreground="White" FontSize="16"/>
-                <TextBlock Margin="4,0,10,0" Text="{Binding IoSubViewModel.MouseXOnCanvas, Converter={StaticResource DoubleToIntConverter}}" Foreground="White" FontSize="16"/>
+                <TextBlock Margin="4,0,10,0" Text="{Binding BitmapManager.ActiveDocument.MouseXOnCanvas, Converter={StaticResource DoubleToIntConverter}}" Foreground="White" FontSize="16"/>
                 <TextBlock Text="Y:" Foreground="White" FontSize="16"/>
-                <TextBlock Margin="4,0,10,0" Text="{Binding IoSubViewModel.MouseYOnCanvas, Converter={StaticResource DoubleToIntConverter}}" Foreground="White" FontSize="16"/>
+                <TextBlock Margin="4,0,10,0" Text="{Binding BitmapManager.ActiveDocument.MouseYOnCanvas, Converter={StaticResource DoubleToIntConverter}}" Foreground="White" FontSize="16"/>
             </StackPanel>
         </DockPanel>
         <StackPanel Margin="10,0,0,0" VerticalAlignment="Center" Grid.Row="3"

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

@@ -0,0 +1,75 @@
+<UserControl x:Class="PixiEditor.Views.UserControls.DrawingViewPort"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:PixiEditor.Views.UserControls" 
+             xmlns:tools="clr-namespace:PixiEditor.Models.Tools"
+             xmlns:vws="clr-namespace:PixiEditor.Views" 
+             xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" 
+             xmlns:behaviors="clr-namespace:PixiEditor.Helpers.Behaviours" xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+             mc:Ignorable="d" 
+             d:DesignHeight="450" d:DesignWidth="800" Name="uc">
+    <UserControl.Resources>
+        <converters:BoolToIntConverter x:Key="BoolToIntConverter" />
+        <BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
+    </UserControl.Resources>
+    <vws:MainDrawingPanel ZoomPercentage="{Binding ZoomPercentage, Mode=TwoWay, ElementName=uc}"
+                          Center="{Binding RecenterZoombox, Mode=TwoWay, ElementName=uc}" 
+                          x:Name="DrawingPanel"
+                          CenterOnStart="True" Cursor="{Binding Cursor, ElementName=uc}" 
+                          MiddleMouseClickedCommand="{Binding MiddleMouseClickedCommand, ElementName=uc}" 
+                          MiddleMouseClickedCommandParameter="{x:Static tools:ToolType.MoveViewport}"
+                          ViewportPosition="{Binding ViewportPosition, ElementName=uc, Mode=TwoWay}">
+            <i:Interaction.Triggers>
+                <i:EventTrigger EventName="MouseMove">
+                    <i:InvokeCommandAction Command="{Binding MouseMoveCommand, ElementName=uc}" />
+                </i:EventTrigger>
+                <i:EventTrigger EventName="MouseDown">
+                    <i:InvokeCommandAction Command="{Binding MouseDownCommand, ElementName=uc}"/>
+                </i:EventTrigger>
+            </i:Interaction.Triggers>
+            <i:Interaction.Behaviors>
+                <behaviors:MouseBehaviour RelativeTo="{Binding ElementName=DrawingPanel, Path=Item}"
+                                                  MouseX="{Binding MouseXOnCanvas, Mode=TwoWay, ElementName=uc}"
+                                                  MouseY="{Binding MouseYOnCanvas, Mode=TwoWay, ElementName=uc}" />
+            </i:Interaction.Behaviors>
+            <vws:MainDrawingPanel.Item>
+                <Canvas Width="{Binding Width}"
+                                Height="{Binding Height}" VerticalAlignment="Center"
+                                HorizontalAlignment="Center">
+                    <Image Source="/Images/transparentbg.png"
+                                   Height="{Binding Height}"
+                                   Width="{Binding Width}" Opacity="0.9"
+                                   Stretch="UniformToFill" />
+                <Image Source="{Binding PreviewLayer.LayerBitmap}" Panel.ZIndex="2"
+                                   RenderOptions.BitmapScalingMode="NearestNeighbor" Stretch="Uniform"
+                                   Width="{Binding PreviewLayer.Width}"
+                                   Height="{Binding PreviewLayer.Height}" 
+                                   Margin="{Binding PreviewLayer.Offset}"/>
+                <ItemsControl ItemsSource="{Binding Layers}">
+                        <ItemsControl.ItemsPanel>
+                            <ItemsPanelTemplate>
+                                <Grid />
+                            </ItemsPanelTemplate>
+                        </ItemsControl.ItemsPanel>
+                        <ItemsControl.ItemTemplate>
+                            <DataTemplate>
+                                <Image VerticalAlignment="Top" HorizontalAlignment="Left" Source="{Binding LayerBitmap}"
+                                               Visibility="{Binding IsVisible, Converter={StaticResource BoolToVisibilityConverter}}"
+                                               RenderOptions.BitmapScalingMode="NearestNeighbor" Stretch="Uniform"
+                                               Opacity="{Binding Opacity}"
+                                               Width="{Binding Width}" Height="{Binding Height}" Margin="{Binding Offset}" />
+                            </DataTemplate>
+                        </ItemsControl.ItemTemplate>
+                    </ItemsControl>
+                    <Image VerticalAlignment="Top" HorizontalAlignment="Left" 
+                           Source="{Binding ActiveSelection.SelectionLayer.LayerBitmap}"
+                                   RenderOptions.BitmapScalingMode="NearestNeighbor" Stretch="Uniform"
+                                   Width="{Binding ActiveSelection.SelectionLayer.Width}"
+                                   Height="{Binding ActiveSelection.SelectionLayer.Height}" 
+                                   Margin="{Binding ActiveSelection.SelectionLayer.Offset}" />
+                </Canvas>
+            </vws:MainDrawingPanel.Item>
+        </vws:MainDrawingPanel>
+</UserControl>

+ 105 - 0
PixiEditor/Views/UserControls/DrawingViewPort.xaml.cs

@@ -0,0 +1,105 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Position;
+
+namespace PixiEditor.Views.UserControls
+{
+    /// <summary>
+    /// Interaction logic for DrawingViewPort.xaml.
+    /// </summary>
+    public partial class DrawingViewPort : UserControl
+    {
+        public DrawingViewPort()
+        {
+            InitializeComponent();
+        }
+
+        public float ZoomPercentage
+        {
+            get { return (float)GetValue(ZoomPercentageProperty); }
+            set { SetValue(ZoomPercentageProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for ZoomPercentage.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty ZoomPercentageProperty =
+            DependencyProperty.Register("ZoomPercentage", typeof(float), typeof(DrawingViewPort), new PropertyMetadata(100f));
+
+        public bool RecenterZoombox
+        {
+            get { return (bool)GetValue(RecenterZoomboxProperty); }
+            set { SetValue(RecenterZoomboxProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for RecenterZoombox.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty RecenterZoomboxProperty =
+            DependencyProperty.Register("RecenterZoombox", typeof(bool), typeof(DrawingViewPort), new PropertyMetadata(false));
+
+        public ICommand MiddleMouseClickedCommand
+        {
+            get { return (ICommand)GetValue(MiddleMouseClickedCommandProperty); }
+            set { SetValue(MiddleMouseClickedCommandProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for MiddleMouseClickedCommand.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MiddleMouseClickedCommandProperty =
+            DependencyProperty.Register("MiddleMouseClickedCommand", typeof(ICommand), typeof(DrawingViewPort), new PropertyMetadata(default(ICommand)));
+
+        public Point ViewportPosition
+        {
+            get { return (Point)GetValue(ViewportPositionProperty); }
+            set { SetValue(ViewportPositionProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for ViewportPosition.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty ViewportPositionProperty =
+            DependencyProperty.Register("ViewportPosition", typeof(Point), typeof(DrawingViewPort), new PropertyMetadata(default(Point)));
+
+        public ICommand MouseMoveCommand
+        {
+            get { return (ICommand)GetValue(MouseMoveCommandProperty); }
+            set { SetValue(MouseMoveCommandProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for MouseMoveCommand.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MouseMoveCommandProperty =
+            DependencyProperty.Register("MouseMoveCommand", typeof(ICommand), typeof(DrawingViewPort), new PropertyMetadata(default(ICommand)));
+
+        public ICommand MouseDownCommand
+        {
+            get { return (ICommand)GetValue(MouseDownCommandProperty); }
+            set { SetValue(MouseDownCommandProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for MouseDownCommand.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MouseDownCommandProperty =
+            DependencyProperty.Register("MouseDownCommand", typeof(ICommand), typeof(DrawingViewPort), new PropertyMetadata(default(ICommand)));
+
+        public double MouseXOnCanvas
+        {
+            get { return (double)GetValue(MouseXOnCanvasProperty); }
+            set { SetValue(MouseXOnCanvasProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for MouseXOnCanvas.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MouseXOnCanvasProperty =
+            DependencyProperty.Register("MouseXOnCanvas", typeof(double), typeof(DrawingViewPort), new PropertyMetadata(0.0));
+
+        public double MouseYOnCanvas
+        {
+            get { return (double)GetValue(MouseYOnCanvasProperty); }
+            set { SetValue(MouseYOnCanvasProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for MouseXOnCanvas.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty MouseYOnCanvasProperty =
+            DependencyProperty.Register("MouseYOnCanvas", typeof(double), typeof(DrawingViewPort), new PropertyMetadata(0.0));
+    }
+}

+ 33 - 23
PixiEditor/Views/UserControls/MainDrawingPanel.xaml.cs

@@ -68,42 +68,44 @@ namespace PixiEditor.Views
 
         // Using a DependencyProperty as the backing store for ViewportPosition.  This enables animation, styling, binding, etc...
         public static readonly DependencyProperty ViewportPositionProperty =
-            DependencyProperty.Register("ViewportPosition", typeof(Point),
-                typeof(MainDrawingPanel), new PropertyMetadata(default(Point), ViewportPosCallback));
+            DependencyProperty.Register(
+                "ViewportPosition",
+                typeof(Point),
+                typeof(MainDrawingPanel),
+                new PropertyMetadata(
+                    default(Point),
+                    ViewportPosCallback));
 
         public bool Center
         {
-            get => (bool) GetValue(CenterProperty);
+            get => (bool)GetValue(CenterProperty);
             set => SetValue(CenterProperty, value);
         }
 
         public double MouseX
         {
-            get => (double) GetValue(MouseXProperty);
+            get => (double)GetValue(MouseXProperty);
             set => SetValue(MouseXProperty, value);
         }
 
         public double MouseY
         {
-            get => (double) GetValue(MouseYProperty);
+            get => (double)GetValue(MouseYProperty);
             set => SetValue(MouseYProperty, value);
         }
 
-
         public ICommand MouseMoveCommand
         {
-            get => (ICommand) GetValue(MouseMoveCommandProperty);
+            get => (ICommand)GetValue(MouseMoveCommandProperty);
             set => SetValue(MouseMoveCommandProperty, value);
         }
 
-
         public bool CenterOnStart
         {
-            get => (bool) GetValue(CenterOnStartProperty);
+            get => (bool)GetValue(CenterOnStartProperty);
             set => SetValue(CenterOnStartProperty, value);
         }
 
-
         public object Item
         {
             get => GetValue(ItemProperty);
@@ -126,8 +128,6 @@ namespace PixiEditor.Views
         public static readonly DependencyProperty MiddleMouseClickedCommandProperty =
             DependencyProperty.Register("MiddleMouseClickedCommand", typeof(ICommand), typeof(MainDrawingPanel), new PropertyMetadata(default(ICommand)));
 
-
-
         public object MiddleMouseClickedCommandParameter
         {
             get { return (object)GetValue(MiddleMouseClickedCommandParameterProperty); }
@@ -136,10 +136,12 @@ namespace PixiEditor.Views
 
         // Using a DependencyProperty as the backing store for MiddleMouseClickedCommandParameter.  This enables animation, styling, binding, etc...
         public static readonly DependencyProperty MiddleMouseClickedCommandParameterProperty =
-            DependencyProperty.Register("MiddleMouseClickedCommandParameter", typeof(object), typeof(MainDrawingPanel), 
-                new PropertyMetadata(default(object)));
-
-
+            DependencyProperty.Register(
+                "MiddleMouseClickedCommandParameter",
+                typeof(object),
+                typeof(MainDrawingPanel),
+                new PropertyMetadata(
+                    default(object)));
 
         private static void ZoomPercentegeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
         {
@@ -168,13 +170,14 @@ namespace PixiEditor.Views
             {
                 panel.Zoombox.Position = default;
                 return;
-            }           
+            }
             TranslateZoombox(panel, (Point)e.NewValue);
         }
 
         private static void TranslateZoombox(MainDrawingPanel panel, Point vector)
         {
-            var newPos = new Point(panel.ClickPosition.X + vector.X,
+            var newPos = new Point(
+                panel.ClickPosition.X + vector.X,
                 panel.ClickPosition.Y + vector.Y);
             panel.Zoombox.Position = newPos;
         }
@@ -195,7 +198,11 @@ namespace PixiEditor.Views
 
         private void SetClickValues()
         {
-            if (!IsUsingZoomTool) return;
+            if (!IsUsingZoomTool)
+            {
+                return;
+            }
+
             ClickScale = Zoombox.Scale;
             SetZoomOrigin();
         }
@@ -211,13 +218,16 @@ namespace PixiEditor.Views
         private static void OnCenterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
         {
             MainDrawingPanel panel = (MainDrawingPanel) d;
-            panel.Zoombox.CenterContent();
+            panel.Zoombox.FitToBounds();
         }
 
-
         private void Zoombox_Loaded(object sender, RoutedEventArgs e)
         {
-            if (CenterOnStart) ((Zoombox) sender).CenterContent();
+            if (CenterOnStart)
+            {
+                ((Zoombox)sender).FitToBounds();
+            }
+
             ClickScale = Zoombox.Scale;
         }
 
@@ -241,7 +251,7 @@ namespace PixiEditor.Views
 
         private void Zoombox_MouseDown(object sender, MouseButtonEventArgs e)
         {
-            if (e.MiddleButton == MouseButtonState.Pressed && 
+            if (e.MiddleButton == MouseButtonState.Pressed &&
                 MiddleMouseClickedCommand.CanExecute(MiddleMouseClickedCommandParameter))
             {
                 MiddleMouseClickedCommand.Execute(MiddleMouseClickedCommandParameter);

+ 17 - 12
PixiEditorTests/ModelsTests/ControllersTests/BitmapManagerTests.cs

@@ -25,7 +25,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             {
                 ActiveDocument = new Document(10, 10)
             };
-            bitmapManager.AddNewLayer(layerName);
+            bitmapManager.ActiveDocument.AddNewLayer(layerName);
             Assert.Single(bitmapManager.ActiveDocument.Layers);
             Assert.Equal(layerName, bitmapManager.ActiveDocument.ActiveLayer.Name);
             Assert.Equal(0, bitmapManager.ActiveDocument.ActiveLayer.Width + bitmapManager.ActiveDocument.ActiveLayer.Height);
@@ -38,10 +38,10 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             {
                 ActiveDocument = new Document(10, 10)
             };
-            bitmapManager.AddNewLayer("_");
-            bitmapManager.AddNewLayer("_1");
+            bitmapManager.ActiveDocument.AddNewLayer("_");
+            bitmapManager.ActiveDocument.AddNewLayer("_1");
             Assert.Equal(2, bitmapManager.ActiveDocument.Layers.Count);
-            bitmapManager.RemoveLayer(0);
+            bitmapManager.ActiveDocument.RemoveLayer(0);
             Assert.Single(bitmapManager.ActiveDocument.Layers);
         }
 
@@ -52,12 +52,12 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
             {
                 ActiveDocument = new Document(10, 10)
             };
-            bitmapManager.GeneratePreviewLayer();
-            Assert.NotNull(bitmapManager.PreviewLayer);
-            Assert.Equal(0, bitmapManager.PreviewLayer.Width + bitmapManager.PreviewLayer.Height); // Size is zero
-            Assert.Equal(0, bitmapManager.PreviewLayer.OffsetX + bitmapManager.PreviewLayer.OffsetY); // Offset is zero
-            Assert.Equal(bitmapManager.ActiveDocument.Width, bitmapManager.PreviewLayer.MaxWidth);
-            Assert.Equal(bitmapManager.ActiveDocument.Height, bitmapManager.PreviewLayer.MaxHeight);
+            bitmapManager.ActiveDocument.GeneratePreviewLayer();
+            Assert.NotNull(bitmapManager.ActiveDocument.PreviewLayer);
+            Assert.Equal(0, bitmapManager.ActiveDocument.PreviewLayer.Width + bitmapManager.ActiveDocument.PreviewLayer.Height); // Size is zero
+            Assert.Equal(0, bitmapManager.ActiveDocument.PreviewLayer.OffsetX + bitmapManager.ActiveDocument.PreviewLayer.OffsetY); // Offset is zero
+            Assert.Equal(bitmapManager.ActiveDocument.Width, bitmapManager.ActiveDocument.PreviewLayer.MaxWidth);
+            Assert.Equal(bitmapManager.ActiveDocument.Height, bitmapManager.ActiveDocument.PreviewLayer.MaxHeight);
         }
 
         [Fact]
@@ -72,10 +72,15 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         {
             BitmapManager bitmapManager = new BitmapManager
             {
-                ActiveDocument = new Document(5, 5)
+                Documents = new System.Collections.ObjectModel.ObservableCollection<Document>()
+                {
+                    new Document(5, 5)
+                }
             };
 
-            bitmapManager.AddNewLayer("Layer");
+            bitmapManager.ActiveDocument = bitmapManager.Documents[0];
+
+            bitmapManager.ActiveDocument.AddNewLayer("Layer");
             bitmapManager.SetActiveTool(new MockedSinglePixelPen());
             bitmapManager.PrimaryColor = Colors.Green;
 

+ 1 - 1
PixiEditorTests/ModelsTests/ControllersTests/BitmapOperationsUtilityTests.cs

@@ -35,7 +35,7 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
                 ActiveDocument = new Document(10, 10),
                 PrimaryColor = Colors.Black
             };
-            manager.AddNewLayer("Test layer", 10, 10);
+            manager.ActiveDocument.AddNewLayer("Test layer", 10, 10);
 
             BitmapOperationsUtility util = new BitmapOperationsUtility(manager);
 

+ 48 - 35
PixiEditorTests/ModelsTests/ControllersTests/UndoManagerTests.cs

@@ -19,75 +19,86 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestSetRoot()
         {
             PrepareUndoManagerForTest();
-            UndoManager.SetMainRoot(null);
-            UndoManager.SetMainRoot(this);
-            Assert.Equal(this, UndoManager.MainRoot);
+            UndoManager undoManager = new UndoManager(this);
+            Assert.Equal(this, undoManager.MainRoot);
         }
 
         [Fact]
         public void TestAddToUndoStack()
         {
             PrepareUndoManagerForTest();
-            UndoManager.AddUndoChange(new Change("ExampleProperty", ExampleProperty, ExampleProperty));
-            Assert.True(UndoManager.UndoStack.Count == 1);
-            Assert.True((int)UndoManager.UndoStack.Peek().OldValue == ExampleProperty);
+            UndoManager undoManager = new UndoManager(this);
+
+            undoManager.AddUndoChange(new Change("ExampleProperty", ExampleProperty, ExampleProperty));
+            Assert.True(undoManager.UndoStack.Count == 1);
+            Assert.True((int)undoManager.UndoStack.Peek().OldValue == ExampleProperty);
         }
 
         [Fact]
         public void TestThatUndoAddsToRedoStack()
         {
             PrepareUndoManagerForTest();
-            UndoManager.AddUndoChange(new Change("ExampleProperty", ExampleProperty, ExampleProperty));
-            UndoManager.Undo();
-            Assert.True(UndoManager.RedoStack.Count == 1);
+            UndoManager undoManager = new UndoManager(this);
+
+            undoManager.AddUndoChange(new Change("ExampleProperty", ExampleProperty, ExampleProperty));
+            undoManager.Undo();
+            Assert.True(undoManager.RedoStack.Count == 1);
         }
 
         [Fact]
         public void TestUndo()
         {
             PrepareUndoManagerForTest();
-            UndoManager.AddUndoChange(new Change("ExampleProperty", ExampleProperty, 55));
+            UndoManager undoManager = new UndoManager(this);
+
+            undoManager.AddUndoChange(new Change("ExampleProperty", ExampleProperty, 55));
             ExampleProperty = 55;
-            UndoManager.Undo();
-            Assert.True((int)UndoManager.RedoStack.Peek().OldValue == ExampleProperty);
+            undoManager.Undo();
+            Assert.True((int)undoManager.RedoStack.Peek().OldValue == ExampleProperty);
         }
 
         [Fact]
         public void TestThatRedoAddsToUndoStack()
         {
             PrepareUndoManagerForTest();
-            UndoManager.AddUndoChange(new Change("ExampleProperty", ExampleProperty, ExampleProperty));
-            UndoManager.Undo();
-            UndoManager.Redo();
-            Assert.True(UndoManager.UndoStack.Count == 1);
+            UndoManager undoManager = new UndoManager(this);
+
+            undoManager.AddUndoChange(new Change("ExampleProperty", ExampleProperty, ExampleProperty));
+            undoManager.Undo();
+            undoManager.Redo();
+            Assert.True(undoManager.UndoStack.Count == 1);
         }
 
         [Fact]
         public void TestRedo()
         {
             PrepareUndoManagerForTest();
+            UndoManager undoManager = new UndoManager(this);
+
             ExampleProperty = 55;
-            UndoManager.AddUndoChange(new Change("ExampleProperty", 1, ExampleProperty));
-            UndoManager.Undo();
-            UndoManager.Redo();
-            Assert.True((int)UndoManager.UndoStack.Peek().NewValue == ExampleProperty);
+            undoManager.AddUndoChange(new Change("ExampleProperty", 1, ExampleProperty));
+            undoManager.Undo();
+            undoManager.Redo();
+            Assert.True((int)undoManager.UndoStack.Peek().NewValue == ExampleProperty);
         }
 
         [Fact]
         public void TestThatUndoManagerUndoAndRedoWithCustomRootCorrectly()
         {
             PrepareUndoManagerForTest();
+            UndoManager undoManager = new UndoManager(this);
+
             TestPropertyClass testProp = new TestPropertyClass();
             int newVal = 5;
             testProp.IntProperty = newVal;
-            UndoManager.AddUndoChange(new Change("IntProperty", 0, newVal, root: testProp));
+            undoManager.AddUndoChange(new Change("IntProperty", 0, newVal, root: testProp));
             Assert.Equal(newVal, testProp.IntProperty);
 
-            UndoManager.Undo();
+            undoManager.Undo();
 
             Assert.Equal(0, testProp.IntProperty);
 
-            UndoManager.Redo();
+            undoManager.Redo();
 
             Assert.Equal(newVal, testProp.IntProperty);
         }
@@ -96,10 +107,11 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestThatMixedProcessOfUndoAndRedoWorks()
         {
             PrepareUndoManagerForTest();
+            UndoManager undoManager = new UndoManager(this);
 
             int newVal = 5;
 
-            UndoManager.AddUndoChange(
+            undoManager.AddUndoChange(
                 new Change(
                     "ExampleProperty",
                     ReverseProcess,
@@ -110,11 +122,11 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
 
             Assert.Equal(newVal, ExampleProperty);
 
-            UndoManager.Undo();
+            undoManager.Undo();
 
             Assert.Equal(1, ExampleProperty);
 
-            UndoManager.Redo();
+            undoManager.Redo();
 
             Assert.Equal(newVal, ExampleProperty);
         }
@@ -123,8 +135,10 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestThatProcessBasedUndoAndRedoWorks()
         {
             PrepareUndoManagerForTest();
+            UndoManager undoManager = new UndoManager(this);
+
             int newVal = 5;
-            UndoManager.AddUndoChange(new Change(
+            undoManager.AddUndoChange(new Change(
                 ReverseProcess,
                 new object[] { ExampleProperty },
                 ReverseProcess,
@@ -134,11 +148,11 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
 
             Assert.Equal(newVal, ExampleProperty);
 
-            UndoManager.Undo();
+            undoManager.Undo();
 
             Assert.Equal(1, ExampleProperty);
 
-            UndoManager.Redo();
+            undoManager.Redo();
 
             Assert.Equal(newVal, ExampleProperty);
         }
@@ -147,19 +161,21 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
         public void TestThatNestedPropertyUndoWorks()
         {
             PrepareUndoManagerForTest();
+            UndoManager undoManager = new UndoManager(this);
+
             int newVal = 5;
 
-            UndoManager.AddUndoChange(new Change("TestPropClass.IntProperty", TestPropClass.IntProperty, newVal));
+            undoManager.AddUndoChange(new Change("TestPropClass.IntProperty", TestPropClass.IntProperty, newVal));
 
             TestPropClass.IntProperty = newVal;
 
             Assert.Equal(newVal, TestPropClass.IntProperty);
 
-            UndoManager.Undo();
+            undoManager.Undo();
 
             Assert.Equal(0, TestPropClass.IntProperty);
 
-            UndoManager.Redo();
+            undoManager.Redo();
 
             Assert.Equal(newVal, TestPropClass.IntProperty);
         }
@@ -171,9 +187,6 @@ namespace PixiEditorTests.ModelsTests.ControllersTests
 
         private void PrepareUndoManagerForTest()
         {
-            UndoManager.SetMainRoot(this);
-            UndoManager.UndoStack.Clear();
-            UndoManager.RedoStack.Clear();
             ExampleProperty = 1;
             TestPropClass = new TestPropertyClass { IntProperty = 0 };
         }

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

@@ -46,7 +46,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
             {
                 ActiveDocument = document
             };
-            manager.AddNewLayer("test");
+            manager.ActiveDocument.AddNewLayer("test");
             manager.ActiveLayer.SetPixel(
                 new Coordinates(
                 (int)Math.Ceiling(initialWidth / 2f),
@@ -72,13 +72,13 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
             {
                 ActiveDocument = document
             };
-            manager.AddNewLayer("test");
+            manager.ActiveDocument.AddNewLayer("test");
             manager.ActiveLayer.SetPixel(
                 new Coordinates(
                 (int)Math.Ceiling(initialWidth / 2f),
                 (int)Math.Ceiling(initialHeight / 2f)), Colors.Black); // Set pixel in center
 
-            manager.AddNewLayer("test2");
+            manager.ActiveDocument.AddNewLayer("test2");
 
             manager.ActiveLayer.SetPixel(new Coordinates(secondLayerPixelX, secondLayerPixelY), Colors.Black);
 
@@ -107,7 +107,7 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
             {
                 ActiveDocument = doc
             };
-            manager.AddNewLayer("test");
+            manager.ActiveDocument.AddNewLayer("test");
 
             manager.ActiveLayer.SetPixel(new Coordinates(0, 0), Colors.Green);
 
@@ -128,10 +128,10 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
             {
                 ActiveDocument = doc
             };
-            manager.AddNewLayer("test");
+            manager.ActiveDocument.AddNewLayer("test");
             manager.ActiveLayer.SetPixel(new Coordinates(0, 0), Colors.Green);
 
-            manager.AddNewLayer("test2");
+            manager.ActiveDocument.AddNewLayer("test2");
             manager.ActiveLayer.SetPixel(new Coordinates(1, 1), Colors.Green);
 
             doc.CenterContent();

+ 3 - 2
PixiEditorTests/ModelsTests/ToolsTests/ZoomToolTests.cs

@@ -8,13 +8,14 @@ namespace PixiEditorTests.ModelsTests.ToolsTests
     public class ZoomToolTests
     {
         [StaFact]
-        public void TestThatZoomSetsViewModelsZoomPercentage()
+        public void TestThatZoomSetsActiveDocumentZoomPercentage()
         {
             ViewModelMain vm = new ViewModelMain();
+            vm.BitmapManager.ActiveDocument = new PixiEditor.Models.DataHolders.Document(10, 10);
             ZoomTool zoomTool = new ZoomTool();
             double zoom = 110;
             zoomTool.Zoom(zoom);
-            Assert.Equal(zoom, vm.ViewportSubViewModel.ZoomPercentage);
+            Assert.Equal(zoom, vm.BitmapManager.ActiveDocument.ZoomPercentage);
         }
     }
 }

+ 11 - 9
PixiEditorTests/ViewModelsTests/ViewModelMainTests.cs

@@ -1,4 +1,5 @@
 using System.IO;
+using System.Windows.Input;
 using System.Windows.Media;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
@@ -18,7 +19,6 @@ namespace PixiEditorTests.ViewModelsTests
         {
             ViewModelMain viewModel = new ViewModelMain();
 
-            Assert.Equal(viewModel.UndoSubViewModel, UndoManager.MainRoot);
             Assert.NotNull(viewModel.ChangesController);
             Assert.NotNull(viewModel.ShortcutController);
             Assert.NotEmpty(viewModel.ShortcutController.Shortcuts);
@@ -55,11 +55,12 @@ namespace PixiEditorTests.ViewModelsTests
         public void TestThatMouseMoveCommandUpdatesCurrentCoordinates()
         {
             ViewModelMain viewModel = new ViewModelMain();
+            viewModel.BitmapManager.ActiveDocument = new Document(10, 10);
 
             Assert.Equal(new Coordinates(0, 0), MousePositionConverter.CurrentCoordinates);
 
-            viewModel.IoSubViewModel.MouseXOnCanvas = 5;
-            viewModel.IoSubViewModel.MouseYOnCanvas = 5;
+            viewModel.BitmapManager.ActiveDocument.MouseXOnCanvas = 5;
+            viewModel.BitmapManager.ActiveDocument.MouseYOnCanvas = 5;
 
             viewModel.IoSubViewModel.MouseMoveCommand.Execute(null);
 
@@ -87,7 +88,7 @@ namespace PixiEditorTests.ViewModelsTests
 
             Assert.True(viewModel.BitmapManager.MouseController.IsRecordingChanges);
 
-            viewModel.IoSubViewModel.MouseHook_OnMouseUp(default, default, default);
+            viewModel.IoSubViewModel.MouseHook_OnMouseUp(default, default, MouseButton.Left);
 
             Assert.False(viewModel.BitmapManager.MouseController.IsRecordingChanges);
         }
@@ -112,9 +113,10 @@ namespace PixiEditorTests.ViewModelsTests
             ViewModelMain viewModel = new ViewModelMain();
             string fileName = "testFile.pixi";
 
-            viewModel.BitmapManager.ActiveDocument = new Document(1, 1);
-
-            Exporter.SaveDocumentPath = fileName;
+            viewModel.BitmapManager.ActiveDocument = new Document(1, 1)
+            {
+                DocumentFilePath = fileName
+            };
 
             viewModel.FileSubViewModel.SaveDocumentCommand.Execute(null);
 
@@ -150,13 +152,13 @@ namespace PixiEditorTests.ViewModelsTests
             {
                 BitmapManager = { ActiveDocument = new Document(docWidth, docHeight) }
             };
-            viewModel.BitmapManager.AddNewLayer("layer");
+            viewModel.BitmapManager.ActiveDocument.AddNewLayer("layer");
 
             viewModel.SelectionSubViewModel.SelectAllCommand.Execute(null);
 
             Assert.Equal(
                 viewModel.BitmapManager.ActiveDocument.Width * viewModel.BitmapManager.ActiveDocument.Height,
-                viewModel.SelectionSubViewModel.ActiveSelection.SelectedPoints.Count);
+                viewModel.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints.Count);
         }
 
         [StaFact]