Browse Source

Merge branch 'master' into multiple-layers-selection

Krzysztof Krysiński 4 years ago
parent
commit
737ba71939
28 changed files with 1130 additions and 639 deletions
  1. 31 1
      PixiEditor/Models/DataHolders/Document/Document.IO.cs
  2. 2 2
      PixiEditor/Models/Dialogs/NewFileDialog.cs
  3. 17 0
      PixiEditor/Models/Dialogs/NoticeDialog.cs
  4. 32 0
      PixiEditor/Models/UserPreferences/IPreferences.cs
  5. 97 34
      PixiEditor/Models/UserPreferences/PreferencesSettings.cs
  6. 1 0
      PixiEditor/PixiEditor.csproj
  7. 8 8
      PixiEditor/ViewModels/SubViewModels/Main/DiscordViewModel.cs
  8. 50 1
      PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs
  9. 1 1
      PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs
  10. 89 0
      PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/DiscordSettings.cs
  11. 62 0
      PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/FileSettings.cs
  12. 18 0
      PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/UpdateSettings.cs
  13. 3 3
      PixiEditor/ViewModels/SubViewModels/UserPreferences/SettingsGroup.cs
  14. 3 143
      PixiEditor/ViewModels/SubViewModels/UserPreferences/SettingsViewModel.cs
  15. 9 3
      PixiEditor/ViewModels/ViewModelMain.cs
  16. 40 0
      PixiEditor/Views/Dialogs/NoticePopup.xaml
  17. 41 0
      PixiEditor/Views/Dialogs/NoticePopup.xaml.cs
  18. 8 4
      PixiEditor/Views/Dialogs/SettingsWindow.xaml
  19. 436 421
      PixiEditor/Views/MainWindow.xaml
  20. 8 0
      PixiEditor/Views/MainWindow.xaml.cs
  21. 24 0
      PixiEditorTests/Helpers.cs
  22. 56 0
      PixiEditorTests/Mocks/PreferenceSettingsMock.cs
  23. 18 0
      PixiEditorTests/ModelsTests/DataHoldersTests/DocumentTests.cs
  24. 4 2
      PixiEditorTests/ModelsTests/ToolsTests/ZoomToolTests.cs
  25. 43 1
      PixiEditorTests/ModelsTests/UserPreferencesTests/PreferencesSettingsTests.cs
  26. 2 0
      PixiEditorTests/PixiEditorTests.csproj
  27. 24 12
      PixiEditorTests/ViewModelsTests/ViewModelMainTests.cs
  28. 3 3
      README.md

+ 31 - 1
PixiEditor/Models/DataHolders/Document/Document.IO.cs

@@ -1,4 +1,6 @@
-using PixiEditor.Models.IO;
+using System.Collections.ObjectModel;
+using PixiEditor.Models.IO;
+using PixiEditor.Models.UserPreferences;
 
 namespace PixiEditor.Models.DataHolders
 {
@@ -14,6 +16,7 @@ namespace PixiEditor.Models.DataHolders
                 documentFilePath = value;
                 RaisePropertyChanged(nameof(DocumentFilePath));
                 RaisePropertyChanged(nameof(Name));
+                UpdateRecentlyOpened(value);
             }
         }
 
@@ -47,5 +50,32 @@ namespace PixiEditor.Models.DataHolders
             DocumentFilePath = Exporter.SaveAsEditableFile(this, path);
             ChangesSaved = true;
         }
+
+        private void UpdateRecentlyOpened(string newPath)
+        {
+            ObservableCollection<string> recentlyOpened = XamlAccesibleViewModel.FileSubViewModel.RecentlyOpened;
+
+            if (!recentlyOpened.Contains(newPath))
+            {
+                recentlyOpened.Insert(0, newPath);
+            }
+            else
+            {
+                int index = recentlyOpened.IndexOf(newPath);
+                recentlyOpened.Move(index, 0);
+            }
+
+            if (recentlyOpened.Count > IPreferences.Current.GetPreference("maxOpenedRecently", 10))
+            {
+                for (int i = 4; i < recentlyOpened.Count; i++)
+                {
+                    recentlyOpened.RemoveAt(i);
+                }
+            }
+
+            IPreferences.Current.UpdateLocalPreference("RecentlyOpened", recentlyOpened);
+
+            XamlAccesibleViewModel.FileSubViewModel.HasRecent = true;
+        }
     }
 }

+ 2 - 2
PixiEditor/Models/Dialogs/NewFileDialog.cs

@@ -7,9 +7,9 @@ namespace PixiEditor.Models.Dialogs
 {
     public class NewFileDialog : CustomDialog
     {
-        private int height = (int)PreferencesSettings.GetPreference("DefaultNewFileHeight", 16L);
+        private int height = (int)IPreferences.Current.GetPreference("DefaultNewFileHeight", 16L);
 
-        private int width = (int)PreferencesSettings.GetPreference("DefaultNewFileWidth", 16L);
+        private int width = (int)IPreferences.Current.GetPreference("DefaultNewFileWidth", 16L);
 
         public int Width
         {

+ 17 - 0
PixiEditor/Models/Dialogs/NoticeDialog.cs

@@ -0,0 +1,17 @@
+using PixiEditor.Views.Dialogs;
+
+namespace PixiEditor.Models.Dialogs
+{
+    public static class NoticeDialog
+    {
+        public static void Show(string message)
+        {
+            NoticePopup popup = new NoticePopup
+            {
+                Body = message
+            };
+
+            popup.ShowDialog();
+        }
+    }
+}

+ 32 - 0
PixiEditor/Models/UserPreferences/IPreferences.cs

@@ -0,0 +1,32 @@
+using System;
+using PixiEditor.ViewModels;
+
+namespace PixiEditor.Models.UserPreferences
+{
+    public interface IPreferences
+    {
+        public static IPreferences Current => ViewModelMain.Current.Preferences;
+
+        public void Save();
+
+        public void AddCallback(string setting, Action<object> action);
+
+        public void Init();
+
+        public void Init(string path, string localPath);
+
+        public void UpdatePreference<T>(string name, T value);
+
+        public void UpdateLocalPreference<T>(string name, T value);
+
+#nullable enable
+
+        public T? GetPreference<T>(string name);
+
+        public T? GetPreference<T>(string name, T? fallbackValue);
+
+        public T? GetLocalPreference<T>(string name);
+
+        public T? GetLocalPreference<T>(string name, T? fallbackValue);
+    }
+}

+ 97 - 34
PixiEditor/Models/UserPreferences/PreferencesSettings.cs

@@ -2,51 +2,44 @@
 using System.Collections.Generic;
 using System.IO;
 using Newtonsoft.Json;
+using PixiEditor.ViewModels;
 
 namespace PixiEditor.Models.UserPreferences
 {
-    public static class PreferencesSettings
+    public class PreferencesSettings : IPreferences
     {
-        public static bool IsLoaded { get; private set; } = false;
+        public static IPreferences Current => ViewModelMain.Current.Preferences;
 
-        public static string PathToUserPreferences { get; private set; } = Path.Join(
-            Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
-            "PixiEditor",
-            "user_preferences.json");
+        public bool IsLoaded { get; private set; } = false;
+
+        public string PathToRoamingUserPreferences { get; private set; } = GetPathToSettings(Environment.SpecialFolder.ApplicationData, "user_preferences.json");
+
+        public string PathToLocalPreferences { get; private set; } = GetPathToSettings(Environment.SpecialFolder.LocalApplicationData, "editor_data.json");
+
+        public Dictionary<string, object> Preferences { get; set; } = new Dictionary<string, object>();
 
-        public static Dictionary<string, object> Preferences { get; set; } = new Dictionary<string, object>();
+        public Dictionary<string, object> LocalPreferences { get; set; } = new Dictionary<string, object>();
 
-        public static void Init()
+        public void Init()
         {
-            Init(PathToUserPreferences);
+            Init(PathToRoamingUserPreferences, PathToLocalPreferences);
         }
 
-        public static void Init(string path)
+        public void Init(string path, string localPath)
         {
-            PathToUserPreferences = path;
+            PathToRoamingUserPreferences = path;
+            PathToLocalPreferences = localPath;
+
             if (IsLoaded == false)
             {
-                string dir = Path.GetDirectoryName(path);
-                if (!Directory.Exists(dir))
-                {
-                    Directory.CreateDirectory(dir);
-                }
-
-                if (!File.Exists(path))
-                {
-                    File.WriteAllText(path, "{\n}");
-                }
-                else
-                {
-                    string json = File.ReadAllText(path);
-                    Preferences = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
-                }
+                Preferences = InitPath(path);
+                LocalPreferences = InitPath(localPath);
 
                 IsLoaded = true;
             }
         }
 
-        public static void UpdatePreference<T>(string name, T value)
+        public void UpdatePreference<T>(string name, T value)
         {
             if (IsLoaded == false)
             {
@@ -66,21 +59,40 @@ namespace PixiEditor.Models.UserPreferences
             Save();
         }
 
-        public static void Save()
+        public void UpdateLocalPreference<T>(string name, T value)
         {
             if (IsLoaded == false)
             {
                 Init();
             }
 
-            File.WriteAllText(PathToUserPreferences, JsonConvert.SerializeObject(Preferences));
+            LocalPreferences[name] = value;
+
+            if (Callbacks.ContainsKey(name))
+            {
+                foreach (var action in Callbacks[name])
+                {
+                    action.Invoke(value);
+                }
+            }
+
+            Save();
         }
 
-#nullable enable
+        public void Save()
+        {
+            if (IsLoaded == false)
+            {
+                Init();
+            }
 
-        public static Dictionary<string, List<Action<object>>> Callbacks { get; set; } = new Dictionary<string, List<Action<object>>>();
+            File.WriteAllText(PathToRoamingUserPreferences, JsonConvert.SerializeObject(Preferences));
+            File.WriteAllText(PathToLocalPreferences, JsonConvert.SerializeObject(LocalPreferences));
+        }
+
+        public Dictionary<string, List<Action<object>>> Callbacks { get; set; } = new Dictionary<string, List<Action<object>>>();
 
-        public static void AddCallback(string setting, Action<object> action)
+        public void AddCallback(string setting, Action<object> action)
         {
             if (Callbacks.ContainsKey(setting))
             {
@@ -91,12 +103,14 @@ namespace PixiEditor.Models.UserPreferences
             Callbacks.Add(setting, new List<Action<object>>() { action });
         }
 
-        public static T? GetPreference<T>(string name)
+#nullable enable
+
+        public T? GetPreference<T>(string name)
         {
             return GetPreference(name, default(T));
         }
 
-        public static T? GetPreference<T>(string name, T? fallbackValue)
+        public T? GetPreference<T>(string name, T? fallbackValue)
         {
             if (IsLoaded == false)
             {
@@ -107,5 +121,54 @@ namespace PixiEditor.Models.UserPreferences
                 ? (T)Preferences[name]
                 : fallbackValue;
         }
+
+        public T? GetLocalPreference<T>(string name)
+        {
+            return GetPreference(name, default(T));
+        }
+
+        public T? GetLocalPreference<T>(string name, T? fallbackValue)
+        {
+            if (IsLoaded == false)
+            {
+                Init();
+            }
+
+            return LocalPreferences.ContainsKey(name)
+                ? (T)LocalPreferences[name]
+                : fallbackValue;
+        }
+
+#nullable disable
+
+        private static string GetPathToSettings(Environment.SpecialFolder folder, string fileName)
+        {
+            return Path.Join(
+            Environment.GetFolderPath(folder),
+            "PixiEditor",
+            fileName);
+        }
+
+        private static Dictionary<string, object> InitPath(string path)
+        {
+            string dir = Path.GetDirectoryName(path);
+
+            if (!Directory.Exists(dir))
+            {
+                Directory.CreateDirectory(dir);
+            }
+
+            if (!File.Exists(path))
+            {
+                File.WriteAllText(path, "{\n}");
+            }
+            else
+            {
+                string json = File.ReadAllText(path);
+                return JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
+            }
+
+            return new Dictionary<string, object>();
+        }
     }
 }

+ 1 - 0
PixiEditor/PixiEditor.csproj

@@ -59,6 +59,7 @@
       <Version>1.0.2</Version>
     </PackageReference>
     <PackageReference Include="Extended.Wpf.Toolkit" Version="3.8.2" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
     <PackageReference Include="MvvmLightLibs" Version="5.4.1.1" />
     <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
     <PackageReference Include="PixiEditor.ColorPicker" Version="2.0.0" />

+ 8 - 8
PixiEditor/ViewModels/SubViewModels/Main/DiscordViewModel.cs

@@ -30,7 +30,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
-        private bool showDocumentName = PreferencesSettings.GetPreference(nameof(ShowDocumentName), true);
+        private bool showDocumentName = IPreferences.Current.GetPreference(nameof(ShowDocumentName), true);
 
         public bool ShowDocumentName
         {
@@ -45,7 +45,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
-        private bool showDocumentSize = PreferencesSettings.GetPreference(nameof(ShowDocumentSize), true);
+        private bool showDocumentSize = IPreferences.Current.GetPreference(nameof(ShowDocumentSize), true);
 
         public bool ShowDocumentSize
         {
@@ -60,7 +60,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
         }
 
-        private bool showLayerCount = PreferencesSettings.GetPreference(nameof(ShowLayerCount), true);
+        private bool showLayerCount = IPreferences.Current.GetPreference(nameof(ShowLayerCount), true);
 
         public bool ShowLayerCount
         {
@@ -81,11 +81,11 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Owner.BitmapManager.DocumentChanged += DocumentChanged;
             this.clientId = clientId;
 
-            Enabled = PreferencesSettings.GetPreference<bool>("EnableRichPresence");
-            PreferencesSettings.AddCallback("EnableRichPresence", x => Enabled = (bool)x);
-            PreferencesSettings.AddCallback(nameof(ShowDocumentName), x => ShowDocumentName = (bool)x);
-            PreferencesSettings.AddCallback(nameof(ShowDocumentSize), x => ShowDocumentSize = (bool)x);
-            PreferencesSettings.AddCallback(nameof(ShowLayerCount), x => ShowLayerCount = (bool)x);
+            Enabled = IPreferences.Current.GetPreference<bool>("EnableRichPresence");
+            IPreferences.Current.AddCallback("EnableRichPresence", x => Enabled = (bool)x);
+            IPreferences.Current.AddCallback(nameof(ShowDocumentName), x => ShowDocumentName = (bool)x);
+            IPreferences.Current.AddCallback(nameof(ShowDocumentSize), x => ShowDocumentSize = (bool)x);
+            IPreferences.Current.AddCallback(nameof(ShowLayerCount), x => ShowLayerCount = (bool)x);
 
             AppDomain.CurrentDomain.ProcessExit += (_, _) => Enabled = false;
         }

+ 50 - 1
PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -1,9 +1,12 @@
 using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.IO;
 using System.Linq;
 using System.Windows;
 using System.Windows.Media.Imaging;
 using Microsoft.Win32;
+using Newtonsoft.Json.Linq;
 using PixiEditor.Exceptions;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Controllers;
@@ -18,6 +21,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 {
     public class FileViewModel : SubViewModel<ViewModelMain>
     {
+        private bool hasRecent;
+
         public RelayCommand OpenNewFilePopupCommand { get; set; }
 
         public RelayCommand SaveDocumentCommand { get; set; }
@@ -26,6 +31,20 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         public RelayCommand ExportFileCommand { get; set; } // Command that is used to save file
 
+        public RelayCommand OpenRecentCommand { get; set; }
+
+        public bool HasRecent
+        {
+            get => hasRecent;
+            set
+            {
+                hasRecent = value;
+                RaisePropertyChanged(nameof(HasRecent));
+            }
+        }
+
+        public ObservableCollection<string> RecentlyOpened { get; set; } = new ObservableCollection<string>();
+
         public FileViewModel(ViewModelMain owner)
             : base(owner)
         {
@@ -33,7 +52,37 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             SaveDocumentCommand = new RelayCommand(SaveDocument, Owner.DocumentIsNotNull);
             OpenFileCommand = new RelayCommand(Open);
             ExportFileCommand = new RelayCommand(ExportFile, CanSave);
+            OpenRecentCommand = new RelayCommand(OpenRecent);
             Owner.OnStartupEvent += Owner_OnStartupEvent;
+            RecentlyOpened = new ObservableCollection<string>(IPreferences.Current.GetLocalPreference<JArray>(nameof(RecentlyOpened), new JArray()).ToObject<string[]>());
+
+            if (RecentlyOpened.Count > 0)
+            {
+                HasRecent = true;
+            }
+        }
+
+        public void OpenRecent(object parameter)
+        {
+            string path = (string)parameter;
+
+            foreach (Document document in Owner.BitmapManager.Documents)
+            {
+                if (document.DocumentFilePath == path)
+                {
+                    Owner.BitmapManager.ActiveDocument = document;
+                    return;
+                }
+            }
+
+            if (!File.Exists(path))
+            {
+                NoticeDialog.Show("The file does no longer exist at that path");
+                RecentlyOpened.Remove(path);
+                return;
+            }
+
+            Open((string)parameter);
         }
 
         /// <summary>
@@ -98,7 +147,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
             else
             {
-                if (PreferencesSettings.GetPreference("ShowNewFilePopupOnStartup", true))
+                if (IPreferences.Current.GetPreference("ShowNewFilePopupOnStartup", true))
                 {
                     OpenNewFilePopup(null);
                 }

+ 1 - 1
PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs

@@ -86,7 +86,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
         private async void Owner_OnStartupEvent(object sender, EventArgs e)
         {
-            if (PreferencesSettings.GetPreference("CheckUpdatesOnStartup", true))
+            if (IPreferences.Current.GetPreference("CheckUpdatesOnStartup", true))
             {
                 await CheckForUpdate();
             }

+ 89 - 0
PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/DiscordSettings.cs

@@ -0,0 +1,89 @@
+namespace PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings
+{
+    public class DiscordSettings : SettingsGroup
+    {
+        private bool enableRichPresence = GetPreference(nameof(EnableRichPresence), true);
+
+        public bool EnableRichPresence
+        {
+            get => enableRichPresence;
+            set
+            {
+                enableRichPresence = value;
+                RaiseAndUpdatePreference(nameof(EnableRichPresence), value);
+            }
+        }
+
+        private bool showDocumentName = GetPreference(nameof(ShowDocumentName), true);
+
+        public bool ShowDocumentName
+        {
+            get => showDocumentName;
+            set
+            {
+                showDocumentName = value;
+                RaiseAndUpdatePreference(nameof(ShowDocumentName), value);
+                RaisePropertyChanged(nameof(DetailPreview));
+            }
+        }
+
+        private bool showDocumentSize = GetPreference(nameof(ShowDocumentSize), true);
+
+        public bool ShowDocumentSize
+        {
+            get => showDocumentSize;
+            set
+            {
+                showDocumentSize = value;
+                RaiseAndUpdatePreference(nameof(ShowDocumentSize), value);
+                RaisePropertyChanged(nameof(StatePreview));
+            }
+        }
+
+        private bool showLayerCount = GetPreference(nameof(ShowLayerCount), true);
+
+        public bool ShowLayerCount
+        {
+            get => showLayerCount;
+            set
+            {
+                showLayerCount = value;
+                RaiseAndUpdatePreference(nameof(ShowLayerCount), value);
+                RaisePropertyChanged(nameof(StatePreview));
+            }
+        }
+
+        public string DetailPreview
+        {
+            get
+            {
+                return ShowDocumentName ? $"Editing coolPixelArt.pixi" : "Editing something (incognito)";
+            }
+        }
+
+        public string StatePreview
+        {
+            get
+            {
+                string state = string.Empty;
+
+                if (ShowDocumentSize)
+                {
+                    state = "16x16";
+                }
+
+                if (ShowDocumentSize && ShowLayerCount)
+                {
+                    state += ", ";
+                }
+
+                if (ShowLayerCount)
+                {
+                    state += "2 Layers";
+                }
+
+                return state;
+            }
+        }
+    }
+}

+ 62 - 0
PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/FileSettings.cs

@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings
+{
+    public class FileSettings : SettingsGroup
+    {
+        private bool showNewFilePopupOnStartup = GetPreference("ShowNewFilePopupOnStartup", true);
+
+        public bool ShowNewFilePopupOnStartup
+        {
+            get => showNewFilePopupOnStartup;
+            set
+            {
+                showNewFilePopupOnStartup = value;
+                string name = nameof(ShowNewFilePopupOnStartup);
+                RaiseAndUpdatePreference(name, value);
+            }
+        }
+
+        private long defaultNewFileWidth = GetPreference("DefaultNewFileWidth", 16L);
+
+        public long DefaultNewFileWidth
+        {
+            get => defaultNewFileWidth;
+            set
+            {
+                defaultNewFileWidth = value;
+                string name = nameof(DefaultNewFileWidth);
+                RaiseAndUpdatePreference(name, value);
+            }
+        }
+
+        private long defaultNewFileHeight = GetPreference("DefaultNewFileHeight", 16L);
+
+        public long DefaultNewFileHeight
+        {
+            get => defaultNewFileHeight;
+            set
+            {
+                defaultNewFileHeight = value;
+                string name = nameof(DefaultNewFileHeight);
+                RaiseAndUpdatePreference(name, value);
+            }
+        }
+
+        private int maxOpenedRecently = GetPreference(nameof(MaxOpenedRecently), 10);
+
+        public int MaxOpenedRecently
+        {
+            get => maxOpenedRecently;
+            set
+            {
+                maxOpenedRecently = value;
+                RaiseAndUpdatePreference(nameof(MaxOpenedRecently), value);
+            }
+        }
+    }
+}

+ 18 - 0
PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/UpdateSettings.cs

@@ -0,0 +1,18 @@
+namespace PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings
+{
+    public class UpdateSettings : SettingsGroup
+    {
+        private bool checkUpdatesOnStartup = GetPreference("CheckUpdatesOnStartup", true);
+
+        public bool CheckUpdatesOnStartup
+        {
+            get => checkUpdatesOnStartup;
+            set
+            {
+                checkUpdatesOnStartup = value;
+                string name = nameof(CheckUpdatesOnStartup);
+                RaiseAndUpdatePreference(name, value);
+            }
+        }
+    }
+}

+ 3 - 3
PixiEditor/ViewModels/SubViewModels/UserPreferences/SettingsGroup.cs

@@ -8,14 +8,14 @@ namespace PixiEditor.ViewModels.SubViewModels.UserPreferences
     {
         protected static T GetPreference<T>(string name)
         {
-            return PreferencesSettings.GetPreference<T>(name);
+            return IPreferences.Current.GetPreference<T>(name);
         }
 
 #nullable enable
 
         protected static T? GetPreference<T>(string name, T? fallbackValue)
         {
-            return PreferencesSettings.GetPreference(name, fallbackValue);
+            return IPreferences.Current.GetPreference(name, fallbackValue);
         }
 
 #nullable disable
@@ -23,7 +23,7 @@ namespace PixiEditor.ViewModels.SubViewModels.UserPreferences
         protected void RaiseAndUpdatePreference<T>(string name, T value)
         {
             RaisePropertyChanged(name);
-            PreferencesSettings.UpdatePreference(name, value);
+            IPreferences.Current.UpdatePreference(name, value);
         }
     }
 }

+ 3 - 143
PixiEditor/ViewModels/SubViewModels/UserPreferences/SettingsViewModel.cs

@@ -1,158 +1,18 @@
 using System;
 using System.Configuration;
 using PixiEditor.Models.UserPreferences;
+using PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings;
 
 namespace PixiEditor.ViewModels.SubViewModels.UserPreferences
 {
     public class SettingsViewModel : SubViewModel<SettingsWindowViewModel>
     {
-        private bool showNewFilePopupOnStartup = PreferencesSettings.GetPreference("ShowNewFilePopupOnStartup", true);
+        public FileSettings File { get; set; } = new FileSettings();
 
-        public bool ShowNewFilePopupOnStartup
-        {
-            get => showNewFilePopupOnStartup;
-            set
-            {
-                showNewFilePopupOnStartup = value;
-                string name = nameof(ShowNewFilePopupOnStartup);
-                RaiseAndUpdatePreference(name, value);
-            }
-        }
-
-        private bool checkUpdatesOnStartup = PreferencesSettings.GetPreference("CheckUpdatesOnStartup", true);
-
-        public bool CheckUpdatesOnStartup
-        {
-            get => checkUpdatesOnStartup;
-            set
-            {
-                checkUpdatesOnStartup = value;
-                string name = nameof(CheckUpdatesOnStartup);
-                RaiseAndUpdatePreference(name, value);
-            }
-        }
-
-        private long defaultNewFileWidth = (int)PreferencesSettings.GetPreference("DefaultNewFileWidth", 16L);
-
-        public long DefaultNewFileWidth
-        {
-            get => defaultNewFileWidth;
-            set
-            {
-                defaultNewFileWidth = value;
-                string name = nameof(DefaultNewFileWidth);
-                RaiseAndUpdatePreference(name, value);
-            }
-        }
-
-        private long defaultNewFileHeight = (int)PreferencesSettings.GetPreference("DefaultNewFileHeight", 16L);
-
-        public long DefaultNewFileHeight
-        {
-            get => defaultNewFileHeight;
-            set
-            {
-                defaultNewFileHeight = value;
-                string name = nameof(DefaultNewFileHeight);
-                RaiseAndUpdatePreference(name, value);
-            }
-        }
-
-        public class DiscordSettings : SettingsGroup
-        {
-            private bool enableRichPresence = GetPreference(nameof(EnableRichPresence), true);
-
-            public bool EnableRichPresence
-            {
-                get => enableRichPresence;
-                set
-                {
-                    enableRichPresence = value;
-                    RaiseAndUpdatePreference(nameof(EnableRichPresence), value);
-                }
-            }
-
-            private bool showDocumentName = GetPreference(nameof(ShowDocumentName), true);
-
-            public bool ShowDocumentName
-            {
-                get => showDocumentName;
-                set
-                {
-                    showDocumentName = value;
-                    RaiseAndUpdatePreference(nameof(ShowDocumentName), value);
-                    RaisePropertyChanged(nameof(DetailPreview));
-                }
-            }
-
-            private bool showDocumentSize = GetPreference(nameof(ShowDocumentSize), true);
-
-            public bool ShowDocumentSize
-            {
-                get => showDocumentSize;
-                set
-                {
-                    showDocumentSize = value;
-                    RaiseAndUpdatePreference(nameof(ShowDocumentSize), value);
-                    RaisePropertyChanged(nameof(StatePreview));
-                }
-            }
-
-            private bool showLayerCount = GetPreference(nameof(ShowLayerCount), true);
-
-            public bool ShowLayerCount
-            {
-                get => showLayerCount;
-                set
-                {
-                    showLayerCount = value;
-                    RaiseAndUpdatePreference(nameof(ShowLayerCount), value);
-                    RaisePropertyChanged(nameof(StatePreview));
-                }
-            }
-
-            public string DetailPreview
-            {
-                get
-                {
-                    return ShowDocumentName ? $"Editing coolPixelArt.pixi" : "Editing something (incognito)";
-                }
-            }
-
-            public string StatePreview
-            {
-                get
-                {
-                    string state = string.Empty;
-
-                    if (ShowDocumentSize)
-                    {
-                        state = "16x16";
-                    }
-
-                    if (ShowDocumentSize && ShowLayerCount)
-                    {
-                        state += ", ";
-                    }
-
-                    if (ShowLayerCount)
-                    {
-                        state += "2 Layers";
-                    }
-
-                    return state;
-                }
-            }
-        }
+        public UpdateSettings Update { get; set; } = new UpdateSettings();
 
         public DiscordSettings Discord { get; set; } = new DiscordSettings();
 
-        public void RaiseAndUpdatePreference<T>(string name, T value)
-        {
-            RaisePropertyChanged(name);
-            PreferencesSettings.UpdatePreference(name, value);
-        }
-
         public SettingsViewModel(SettingsWindowViewModel owner)
             : base(owner)
         {

+ 9 - 3
PixiEditor/ViewModels/ViewModelMain.cs

@@ -4,6 +4,7 @@ using System.ComponentModel;
 using System.Linq;
 using System.Windows;
 using System.Windows.Input;
+using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Controllers.Shortcuts;
@@ -64,9 +65,15 @@ namespace PixiEditor.ViewModels
 
         public ShortcutController ShortcutController { get; set; }
 
-        public ViewModelMain()
+        public IPreferences Preferences { get; set; }
+
+        public ViewModelMain(IServiceProvider services)
         {
-            PreferencesSettings.Init();
+            Current = this;
+
+            Preferences = services.GetRequiredService<IPreferences>();
+
+            Preferences.Init();
 
             BitmapManager = new BitmapManager();
             BitmapManager.BitmapOperations.BitmapChanged += BitmapUtility_BitmapChanged;
@@ -144,7 +151,6 @@ namespace PixiEditor.ViewModels
                 }
             };
             BitmapManager.PrimaryColor = ColorsSubViewModel.PrimaryColor;
-            Current = this;
         }
 
         /// <summary>

+ 40 - 0
PixiEditor/Views/Dialogs/NoticePopup.xaml

@@ -0,0 +1,40 @@
+<Window x:Class="PixiEditor.Views.Dialogs.NoticePopup"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:system="clr-namespace:System;assembly=System.Runtime" xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours" xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
+        mc:Ignorable="d"
+        Title="NoticePopup" Height="200" Width="500"
+        x:Name="popup">
+
+    <WindowChrome.WindowChrome>
+        <WindowChrome CaptionHeight="32"
+                      ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
+    </WindowChrome.WindowChrome>
+
+    <Grid Background="{StaticResource AccentColor}" Focusable="True">
+        <Grid.RowDefinitions>
+            <RowDefinition Height="35" />
+            <RowDefinition Height="34*" />
+            <RowDefinition Height="21*" />
+        </Grid.RowDefinitions>
+        <i:Interaction.Behaviors>
+            <behaviours:ClearFocusOnClickBehavior/>
+        </i:Interaction.Behaviors>
+        <TextBlock Grid.Row="1" Text="{Binding Body, ElementName=popup}" HorizontalAlignment="Center"
+                   VerticalAlignment="Center" FontSize="18" Foreground="White" />
+        <DockPanel Grid.Row="0" Background="{StaticResource MainColor}">
+            <Button DockPanel.Dock="Right" HorizontalAlignment="Right" Style="{StaticResource CloseButtonStyle}"
+                    WindowChrome.IsHitTestVisibleInChrome="True" ToolTip="Close"
+                    Command="{Binding DataContext.CancelCommand, ElementName=popup}" />
+        </DockPanel>
+        <StackPanel Grid.Row="2" Orientation="Horizontal" VerticalAlignment="Bottom" HorizontalAlignment="Center"
+                    Margin="0,0,10,10">
+            <Button Height="30" Width="60"
+                    Click="OkButton_Close"
+                    Style="{StaticResource DarkRoundButton}" Content="Ok">
+            </Button>
+        </StackPanel>
+    </Grid>
+</Window>

+ 41 - 0
PixiEditor/Views/Dialogs/NoticePopup.xaml.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace PixiEditor.Views.Dialogs
+{
+    /// <summary>
+    /// Interaction logic for NoticePopup.xaml.
+    /// </summary>
+    public partial class NoticePopup : Window
+    {
+        public static readonly DependencyProperty BodyProperty =
+            DependencyProperty.Register(nameof(Body), typeof(string), typeof(NoticePopup));
+
+        public string Body
+        {
+            get => (string)GetValue(BodyProperty);
+            set => SetValue(BodyProperty, value);
+        }
+
+        public NoticePopup()
+        {
+            InitializeComponent();
+        }
+
+        private void OkButton_Close(object sender, RoutedEventArgs e)
+        {
+            Close();
+        }
+    }
+}

+ 8 - 4
PixiEditor/Views/Dialogs/SettingsWindow.xaml

@@ -58,13 +58,17 @@
                     <Label Content="File" Style="{StaticResource Header1}"/>
                     <StackPanel Orientation="Vertical" Margin="50 0 50 0">
                         <CheckBox Content="Show New File dialog on startup" 
-                                  IsChecked="{Binding SettingsSubViewModel.ShowNewFilePopupOnStartup}"/>
+                                  IsChecked="{Binding SettingsSubViewModel.File.ShowNewFilePopupOnStartup}"/>
+                        <StackPanel Orientation="Horizontal" Margin="0,10,0,0">
+                            <Label Content="Max Saved Opened Recently:" ToolTip="How many documents are shown under File > Recent. Default: 10" Style="{StaticResource BaseLabel}"/>
+                            <views:NumberInput FontSize="16" Value="{Binding SettingsSubViewModel.File.MaxOpenedRecently}" Width="40"/>
+                        </StackPanel>
                         <Label Content="Default new file size:" Style="{StaticResource Header2}" Margin="0 20 0 20"/>
                         <StackPanel Orientation="Horizontal" Margin="40,0,0,0">
                             <Label Content="Width:" Style="{StaticResource BaseLabel}"/>
-                            <views:SizeInput FontSize="16" Size="{Binding SettingsSubViewModel.DefaultNewFileWidth, Mode=TwoWay}" Width="60" Height="25"/>
+                            <views:SizeInput FontSize="16" Size="{Binding SettingsSubViewModel.File.DefaultNewFileWidth, Mode=TwoWay}" Width="60" Height="25"/>
                             <Label Content="Height:" Style="{StaticResource BaseLabel}"/>
-                            <views:SizeInput FontSize="16" Size="{Binding SettingsSubViewModel.DefaultNewFileHeight, Mode=TwoWay}" Width="60" Height="25"/>
+                            <views:SizeInput FontSize="16" Size="{Binding SettingsSubViewModel.File.DefaultNewFileHeight, Mode=TwoWay}" Width="60" Height="25"/>
                         </StackPanel>
                     </StackPanel>
                 </StackPanel>
@@ -74,7 +78,7 @@
                 <StackPanel Orientation="Vertical">
                     <Label Style="{StaticResource Header1}" Content="Auto-updates"/>
                     <StackPanel Orientation="Vertical" Margin="50 0 50 0">
-                        <CheckBox IsChecked="{Binding SettingsSubViewModel.CheckUpdatesOnStartup}" Content="Check updates on startup"/>
+                        <CheckBox IsChecked="{Binding SettingsSubViewModel.Update.CheckUpdatesOnStartup}" Content="Check updates on startup"/>
                     </StackPanel>
                 </StackPanel>
             </Grid>

+ 436 - 421
PixiEditor/Views/MainWindow.xaml

@@ -1,422 +1,437 @@
-<Window x:Class="PixiEditor.MainWindow" MinHeight="500" MinWidth="1100"
-        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
-        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-        xmlns:vm="clr-namespace:PixiEditor.ViewModels"
-        xmlns:vws="clr-namespace:PixiEditor.Views"
-        xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
-        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:usercontrols="clr-namespace:PixiEditor.Views.UserControls" xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours" 
-        xmlns:avalonDockTheme="clr-namespace:PixiEditor.Styles.AvalonDock"
-        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}">
-    <WindowChrome.WindowChrome>
-        <WindowChrome CaptionHeight="32"
-                      ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
-    </WindowChrome.WindowChrome>
-
-    <Window.Resources>
-        <ResourceDictionary>
-            <vm:ViewModelMain x:Key="ViewModelMain" />
-            <BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
-            <converters:BoolToIntConverter x:Key="BoolToIntConverter" />
-            <converters:NotNullToBoolConverter x:Key="NotNullToBoolConverter" />
-            <converters:FloatNormalizeConverter x:Key="FloatNormalizeConverter" />
-            <converters:DoubleToIntConverter x:Key="DoubleToIntConverter"/>
-            <ResourceDictionary.MergedDictionaries>
-                <ResourceDictionary Source="pack://application:,,,/ColorPicker;component/Styles/DefaultColorPickerStyle.xaml" />
-            </ResourceDictionary.MergedDictionaries>
-        </ResourceDictionary>
-    </Window.Resources>
-
-    <Window.CommandBindings>
-        <CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}" CanExecute="CommandBinding_CanExecute"
-                        Executed="CommandBinding_Executed_Close" />
-        <CommandBinding Command="{x:Static SystemCommands.MaximizeWindowCommand}"
-                        CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed_Maximize" />
-        <CommandBinding Command="{x:Static SystemCommands.MinimizeWindowCommand}"
-                        CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed_Minimize" />
-        <CommandBinding Command="{x:Static SystemCommands.RestoreWindowCommand}" CanExecute="CommandBinding_CanExecute"
-                        Executed="CommandBinding_Executed_Restore" />
-    </Window.CommandBindings>
-
-    <i:Interaction.Triggers>
-        <i:EventTrigger EventName="KeyDown">
-            <cmd:EventToCommand Command="{Binding IoSubViewModel.KeyDownCommand}" PassEventArgsToCommand="True" />
-        </i:EventTrigger>
-        <i:EventTrigger EventName="KeyUp">
-            <cmd:EventToCommand Command="{Binding IoSubViewModel.KeyUpCommand}" PassEventArgsToCommand="True"/>
-        </i:EventTrigger>
-        <i:EventTrigger EventName="ContentRendered">
-            <i:InvokeCommandAction Command="{Binding OnStartupCommand}" />
-        </i:EventTrigger>
-        <i:EventTrigger EventName="Closing">
-            <cmd:EventToCommand Command="{Binding CloseWindowCommand}" PassEventArgsToCommand="True" />
-        </i:EventTrigger>
-    </i:Interaction.Triggers>
-    <Grid Name="mainGrid" Margin="5" Focusable="True">
-        <Grid.ColumnDefinitions>
-            <ColumnDefinition Width="45" />
-            <ColumnDefinition Width="1*" />
-        </Grid.ColumnDefinitions>
-        <Grid.RowDefinitions>
-            <RowDefinition Height="30" />
-            <RowDefinition Height="40" />
-            <RowDefinition Height="1*" />
-            <RowDefinition Height="30" />
-        </Grid.RowDefinitions>
-        <i:Interaction.Behaviors>
-            <behaviours:ClearFocusOnClickBehavior/>
-        </i:Interaction.Behaviors>
-        <DockPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Background="{StaticResource MainColor}">
-            <Image DockPanel.Dock="Left" HorizontalAlignment="Left" VerticalAlignment="Top"
-                   Source="/Images/PixiEditorLogo.png" Width="20" Height="20" Margin="5,5,0,0" />
-            <Menu WindowChrome.IsHitTestVisibleInChrome="True" Margin="10, 4, 0, 0" DockPanel.Dock="Left"
-                  HorizontalAlignment="Left" VerticalAlignment="Top" Background="Transparent" IsMainMenu="True">
-                <Menu.Resources>
-                    <Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource menuItemStyle}" />
-                </Menu.Resources>
-                <MenuItem Header="_File">
-                    <MenuItem InputGestureText="CTRL+N" Header="_New" Command="{Binding FileSubViewModel.OpenNewFilePopupCommand}" />
-                    <MenuItem Header="_Open" InputGestureText="Ctrl+O" Command="{Binding FileSubViewModel.OpenFileCommand}" />
-                    <MenuItem Header="_Save" InputGestureText="Ctrl+S" Command="{Binding FileSubViewModel.SaveDocumentCommand}" />
-                    <MenuItem Header="_Save As..." InputGestureText="Ctrl+Shift+S"
-                              Command="{Binding FileSubViewModel.SaveDocumentCommand}" CommandParameter="AsNew" />
-                    <MenuItem Header="_Export" InputGestureText="Ctrl+Shift+Alt+S" Command="{Binding FileSubViewModel.ExportFileCommand}" />
-                    <Separator />
-                    <MenuItem Header="_Exit" Command="{x:Static SystemCommands.CloseWindowCommand}" />
-                </MenuItem>
-                <MenuItem Header="_Edit">
-                    <MenuItem Header="_Undo" InputGestureText="Ctrl+Z" Command="{Binding UndoSubViewModel.UndoCommand}" />
-                    <MenuItem Header="_Redo" InputGestureText="Ctrl+Y" Command="{Binding UndoSubViewModel.RedoCommand}" />
-                    <Separator />
-                    <MenuItem Header="_Cut" Command="{Binding ClipboardSubViewModel.CutCommand}" InputGestureText="Ctrl+X" />
-                    <MenuItem Header="_Copy" Command="{Binding ClipboardSubViewModel.CopyCommand}" InputGestureText="Ctrl+C" />
-                    <MenuItem Header="_Paste" Command="{Binding ClipboardSubViewModel.PasteCommand}" InputGestureText="Ctrl+V" />
-                    <MenuItem Header="_Duplicate" Command="{Binding ClipboardSubViewModel.DuplicateCommand}" InputGestureText="Ctrl+J" />
-                    <Separator />
-                    <MenuItem Header="_Delete Selected" Command="{Binding DocumentSubViewModel.DeletePixelsCommand}"
-                              InputGestureText="Delete" />
-                    <Separator />
-                    <MenuItem Header="_Settings" Command="{Binding MiscSubViewModel.OpenSettingsWindowCommand}" />
-                </MenuItem>
-                <MenuItem Header="_Select">
-                    <MenuItem Header="_Select All" Command="{Binding SelectionSubViewModel.SelectAllCommand}" InputGestureText="Ctrl+A" />
-                    <MenuItem Header="_Deselect" Command="{Binding SelectionSubViewModel.DeselectCommand}" InputGestureText="Ctrl+D" />
-                </MenuItem>
-                <MenuItem Header="_Document">
-                    <MenuItem Header="_Resize Document..." Command="{Binding DocumentSubViewModel.OpenResizePopupCommand}"
-                              InputGestureText="Ctrl+Shift+I" />
-                    <MenuItem Header="_Resize Canvas..." Command="{Binding DocumentSubViewModel.OpenResizePopupCommand}"
-                              CommandParameter="canvas" InputGestureText="Ctrl+Shift+C" />
-                    <MenuItem Header="_Clip Canvas" Command="{Binding DocumentSubViewModel.ClipCanvasCommand}" />
-                    <Separator/>
-                    <MenuItem Header="_Center Content" Command="{Binding DocumentSubViewModel.CenterContentCommand}" />
-                </MenuItem>
-                <MenuItem Header="_View">
-                    <MenuItem Header="_Show Grid Lines" IsChecked="{Binding ViewportSubViewModel.GridLinesEnabled, Mode=TwoWay}"
-                              IsCheckable="True" InputGestureText="Ctrl+`"/>
-                </MenuItem>
-                <MenuItem Header="_Help">
-                    <MenuItem Header="_Documentation" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
-                              CommandParameter="https://github.com/PixiEditor/PixiEditor/wiki"/>
-                    <MenuItem Header="_Repository" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
-                              CommandParameter="https://github.com/PixiEditor/PixiEditor"/>
-                    <MenuItem Header="_Shortcuts" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
-                              CommandParameter="https://github.com/PixiEditor/PixiEditor/wiki/Shortcuts"/>
-                    <Separator/>
-                    <MenuItem Header="_License" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
-                              CommandParameter="https://github.com/PixiEditor/PixiEditor/blob/master/LICENSE"/>
-                    <MenuItem Header="_Third Party Licenses" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
-                              CommandParameter="https://github.com/PixiEditor/PixiEditor/wiki/Third-party-licenses"/>
-                </MenuItem>
-            </Menu>
-            <StackPanel DockPanel.Dock="Right" VerticalAlignment="Top" Orientation="Horizontal"
-                        HorizontalAlignment="Right" WindowChrome.IsHitTestVisibleInChrome="True">
-                <Button Style="{StaticResource MinimizeButtonStyle}" WindowChrome.IsHitTestVisibleInChrome="True"
-                        ToolTip="Minimize"
-                        Command="{x:Static SystemCommands.MinimizeWindowCommand}" />
-                <Button x:Name="RestoreButton" Visibility="Visible" Style="{StaticResource RestoreButtonStyle}"
-                        Command="{x:Static SystemCommands.RestoreWindowCommand}"
-                        WindowChrome.IsHitTestVisibleInChrome="True" ToolTip="Restore" />
-                <Button x:Name="MaximizeButton" Visibility="Collapsed" Style="{StaticResource MaximizeButtonStyle}"
-                        Command="{x:Static SystemCommands.MaximizeWindowCommand}"
-                        WindowChrome.IsHitTestVisibleInChrome="True" ToolTip="Maximize" />
-                <Button Style="{StaticResource CloseButtonStyle}" WindowChrome.IsHitTestVisibleInChrome="True"
-                        ToolTip="Close"
-                        Command="{x:Static SystemCommands.CloseWindowCommand}" />
-            </StackPanel>
-        </DockPanel>
-        <StackPanel Background="{StaticResource MainColor}" Orientation="Horizontal" Grid.ColumnSpan="3" Grid.Column="0"
-                     Grid.Row="1">
-            <Label Style="{StaticResource BaseLabel}" Margin="10,0,0,0" FontSize="12" VerticalAlignment="Center" Content="{Binding BitmapManager.SelectedTool.ToolName}"/>
-            <Label Style="{StaticResource BaseLabel}" Padding="0" FontSize="12" VerticalAlignment="Center" Content="tool"/>
-            <ItemsControl ItemsSource="{Binding BitmapManager.SelectedTool.Toolbar.Settings}">
-                <ItemsControl.ItemsPanel>
-                    <ItemsPanelTemplate>
-                        <StackPanel Orientation="Horizontal" Margin="10, 0, 0, 0" />
-                    </ItemsPanelTemplate>
-                </ItemsControl.ItemsPanel>
-                <ItemsControl.ItemTemplate>
-                    <DataTemplate>
-                        <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="10,0,10,0">
-                            <Label
-                                Visibility="{Binding HasLabel, Converter={StaticResource BoolToVisibilityConverter}}"
-                                Foreground="White" Content="{Binding Label}" />
-                            <ContentControl Content="{Binding SettingControl}" />
-                        </StackPanel>
-                    </DataTemplate>
-                </ItemsControl.ItemTemplate>
-            </ItemsControl>
-        </StackPanel>
-        <Grid Grid.Column="1" Grid.Row="2" Background="#303030">
-            <Grid>
-                <DockingManager ActiveContent="{Binding BitmapManager.ActiveDocument, Mode=TwoWay}" 
-                                           DocumentsSource="{Binding BitmapManager.Documents}">
-                    <DockingManager.Theme>
-                        <avalonDockTheme:PixiEditorDockTheme />
-                    </DockingManager.Theme>
-                    <avalondock:DockingManager.LayoutItemContainerStyleSelector>
-                        <ui:PanelsStyleSelector>
-                            <ui:PanelsStyleSelector.DocumentTabStyle>
-                                <Style TargetType="{x:Type avalondock:LayoutItem}">
-                                    <Setter Property="Title" Value="{Binding Model.Name}" />
-                                    <Setter Property="CloseCommand" Value="{Binding Model.RequestCloseDocumentCommand}" />
-                                </Style>
-                            </ui:PanelsStyleSelector.DocumentTabStyle>
-                        </ui:PanelsStyleSelector>
-                    </avalondock:DockingManager.LayoutItemContainerStyleSelector>
-                    <DockingManager.LayoutItemTemplateSelector>
-                        <ui:DocumentsTemplateSelector>
-                            <ui:DocumentsTemplateSelector.DocumentsViewTemplate>
-                                <DataTemplate DataType="{x:Type vm:ViewModelMain}">
-                                    <usercontrols:DrawingViewPort
-                                        ZoomPercentage="{Binding ZoomPercentage}"
-                                        RecenterZoombox="{Binding RecenterZoombox}"
-                                        GridLinesVisible="{Binding XamlAccesibleViewModel.ViewportSubViewModel.GridLinesEnabled}"
-                                        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>
-                            </ui:DocumentsTemplateSelector.DocumentsViewTemplate>
-                        </ui:DocumentsTemplateSelector>
-                    </DockingManager.LayoutItemTemplateSelector>
-                    <avalondock:LayoutRoot x:Name="LayoutRoot">
-                        <LayoutPanel Orientation="Horizontal">
-                            <LayoutDocumentPane/>
-                            <LayoutAnchorablePaneGroup Orientation="Vertical" DockWidth="290">
-                                <LayoutAnchorablePane>
-                                <LayoutAnchorable ContentId="colorPicker" Title="Color Picker" CanHide="False"
-                                                             CanClose="False" CanAutoHide="False"
-                                                             CanDockAsTabbedDocument="False" CanFloat="True">
-                                    <colorpicker:StandardColorPicker Grid.Row="0" SelectedColor="{Binding ColorsSubViewModel.PrimaryColor, Mode=TwoWay}"
-                                     SecondaryColor="{Binding ColorsSubViewModel.SecondaryColor, Mode=TwoWay}" Style="{StaticResource DefaultColorPickerStyle}" >
-                                        <i:Interaction.Behaviors>
-                                            <behaviours:GlobalShortcutFocusBehavior/>
-                                        </i:Interaction.Behaviors>
-                                    </colorpicker:StandardColorPicker>
-                                </LayoutAnchorable>
-                                <avalondock:LayoutAnchorable ContentId="swatches" Title="Swatches" CanHide="False"
-                                                         CanClose="False" CanAutoHide="False"
-                                                         CanDockAsTabbedDocument="False" CanFloat="True">
-                                    <ScrollViewer HorizontalScrollBarVisibility="Disabled"
-                                              VerticalScrollBarVisibility="Auto">
-                                        <ItemsControl ItemsSource="{Binding BitmapManager.ActiveDocument.Swatches}">
-                                            <ItemsControl.ItemsPanel>
-                                                <ItemsPanelTemplate>
-                                                    <WrapPanel Margin="10,10,0,10" Orientation="Horizontal"
-                                                           VerticalAlignment="Top" HorizontalAlignment="Left" />
-                                                </ItemsPanelTemplate>
-                                            </ItemsControl.ItemsPanel>
-                                            <ItemsControl.ItemTemplate>
-                                                <DataTemplate>
-                                                    <Grid Width="45" Height="45" Margin="0 5 5 5">
-                                                        <Border CornerRadius="5.5" Width="44" Height="44">
-                                                            <Border.Background>
-                                                                <ImageBrush ImageSource="../Images/transparentbg.png"
-                                                                        Stretch="UniformToFill">
-                                                                    <ImageBrush.RelativeTransform>
-                                                                        <ScaleTransform ScaleX="6" ScaleY="6" CenterX="0.5"
-                                                                                    CenterY="0.5" />
-                                                                    </ImageBrush.RelativeTransform>
-                                                                </ImageBrush>
-                                                            </Border.Background>
-                                                        </Border>
-                                                        <Border CornerRadius="5.5" BorderThickness="0 0 0 0.1" BorderBrush="White" Cursor="Hand">
-                                                            <Border.Background>
-                                                                <SolidColorBrush Color="{Binding}" />
-                                                            </Border.Background>
-                                                        </Border>
-                                                        <i:Interaction.Triggers>
-                                                            <i:EventTrigger EventName="MouseDown">
-                                                                <i:InvokeCommandAction
-                                                                Command="{Binding
-                                                                    RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ColorsSubViewModel.SelectColorCommand}"
-                                                                CommandParameter="{Binding}" />
-                                                            </i:EventTrigger>
-                                                        </i:Interaction.Triggers>
-                                                        <Grid.ContextMenu>
-                                                            <ContextMenu>
-                                                                <MenuItem Header="Remove" Foreground="White"
-                                                                      Command="{Binding ColorsSubViewModel.RemoveSwatchCommand, Source={StaticResource ViewModelMain}}"
-                                                                      CommandParameter="{Binding}" />
-                                                            </ContextMenu>
-                                                        </Grid.ContextMenu>
-                                                    </Grid>
-                                                </DataTemplate>
-                                            </ItemsControl.ItemTemplate>
-                                        </ItemsControl>
-                                    </ScrollViewer>
-                                </avalondock:LayoutAnchorable>
-                            </LayoutAnchorablePane>
-                                <LayoutAnchorablePane>
-                                    <LayoutAnchorable ContentId="layers" Title="Layers" CanHide="False"
-                                                         CanClose="False" CanAutoHide="False"
-                                                         CanDockAsTabbedDocument="True" CanFloat="True">
-                                        <Grid>
-                                            <Grid.RowDefinitions>
-                                                <RowDefinition Height="40"/>
-                                                <RowDefinition Height="30"/>
-                                                <RowDefinition Height="15"/>
-                                                <RowDefinition Height="1*"/>
-                                            </Grid.RowDefinitions>
-                                            <Button Grid.Row="0" Command="{Binding LayersSubViewModel.NewLayerCommand}" Height="30" Content="New Layer"
-                                            HorizontalAlignment="Stretch" Margin="5"
-                                            Style="{StaticResource DarkRoundButton}" />
-                                            <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="10,0">
-                                                <Label Content="Opacity" Foreground="White" VerticalAlignment="Center"/>
-                                                <vws:NumberInput 
-                                                    Min="0" Max="100"
-                                                    IsEnabled="{Binding Path=BitmapManager.ActiveDocument, 
-                                                    Converter={StaticResource NotNullToBoolConverter}}" 
-                                                    Width="40" Height="20"
-                                                    VerticalAlignment="Center"
-                                                   Value="{Binding BitmapManager.ActiveDocument.ActiveLayer.OpacityUndoTriggerable, Mode=TwoWay, 
-                                            Converter={StaticResource FloatNormalizeConverter}}" />
-                                                <Label Content="%" Foreground="White" VerticalAlignment="Center"/>
-                                            </StackPanel>
-                                            <Separator Grid.Row="2" Background="{StaticResource BrighterAccentColor}"/>
-                                            <ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto">
-                                                <ItemsControl ItemsSource="{Binding BitmapManager.ActiveDocument.Layers}"
-                                                      x:Name="layersItemsControl" AlternationCount="9999">
-                                                    <ItemsControl.ItemsPanel>
-                                                        <ItemsPanelTemplate>
-                                                            <ui:ReversedOrderStackPanel Orientation="Vertical" />
-                                                        </ItemsPanelTemplate>
-                                                    </ItemsControl.ItemsPanel>
-                                                    <ItemsControl.ItemTemplate>
-                                                        <DataTemplate>
-                                                            <vws:LayerItem LayerIndex="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
-                                Path=(ItemsControl.AlternationIndex)}" SetActiveLayerCommand="{Binding Path=DataContext.LayersSubViewModel.SetActiveLayerCommand, ElementName=mainWindow}"
-                                                                   LayerName="{Binding Name, Mode=TwoWay}" IsActive="{Binding IsActive, Mode=TwoWay}"
-                                                                   IsRenaming="{Binding IsRenaming, Mode=TwoWay}"
-                                                                   PreviewImage="{Binding LayerBitmap}"
-                                                                   MoveToBackCommand="{Binding DataContext.LayersSubViewModel.MoveToBackCommand, ElementName=mainWindow}"
-                                                                   MoveToFrontCommand="{Binding DataContext.LayersSubViewModel.MoveToFrontCommand, ElementName=mainWindow}">
-                                                                <vws:LayerItem.ContextMenu>
-                                                                    <ContextMenu>
-                                                                        <MenuItem Header="Delete"
-                                                                                  Command="{Binding LayersSubViewModel.DeleteLayersCommand, Source={StaticResource ViewModelMain}}"
-                                                                                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
-                                Path=(ItemsControl.AlternationIndex)}" />
-                                                                        <MenuItem Header="Rename"
-                                                                                  Command="{Binding LayersSubViewModel.RenameLayerCommand, Source={StaticResource ViewModelMain}}"
-                                                                                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
-                                Path=(ItemsControl.AlternationIndex)}" />
-                                                                        <MenuItem Header="Move to front"
-                                                                                  Command="{Binding LayersSubViewModel.MoveToFrontCommand, Source={StaticResource ViewModelMain}}"
-                                                                                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
-                                Path=(ItemsControl.AlternationIndex)}" />
-                                                                        <MenuItem Header="Move to back"
-                                                                                  Command="{Binding LayersSubViewModel.MoveToBackCommand, Source={StaticResource ViewModelMain}}"
-                                                                                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
-                                Path=(ItemsControl.AlternationIndex)}" />
-                                                                        <Separator/>
-                                                                        <MenuItem Header="Merge selected"
-                                                                                  Command="{Binding LayersSubViewModel.MergeSelectedCommand, Source={StaticResource ViewModelMain}}"/>
-                                                                        <MenuItem Header="Merge with above"
-                                                                                  Command="{Binding LayersSubViewModel.MergeWithAboveCommand, Source={StaticResource ViewModelMain}}"
-                                                                                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
-                                Path=(ItemsControl.AlternationIndex)}" />
-                                                                        <MenuItem Header="Merge with below"
-                                                                                  Command="{Binding LayersSubViewModel.MergeWithBelowCommand, Source={StaticResource ViewModelMain}}"
-                                                                                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
-                                Path=(ItemsControl.AlternationIndex)}" />
-                                                                    </ContextMenu>
-                                                                </vws:LayerItem.ContextMenu>
-                                                            </vws:LayerItem>
-                                                        </DataTemplate>
-                                                    </ItemsControl.ItemTemplate>
-                                                </ItemsControl>
-                                            </ScrollViewer>
-                                        </Grid>
-                                    </LayoutAnchorable>
-                                </LayoutAnchorablePane>
-                            </LayoutAnchorablePaneGroup>
-                        </LayoutPanel>
-                    </avalondock:LayoutRoot>
-                </DockingManager>
-            </Grid>
-        </Grid>
-
-        <StackPanel Orientation="Vertical" Cursor="Arrow" Grid.Row="2" Grid.Column="0"
-                    Background="{StaticResource AccentColor}" Grid.RowSpan="2">
-
-            <ItemsControl ItemsSource="{Binding ToolsSubViewModel.ToolSet}">
-                <ItemsControl.ItemTemplate>
-                    <DataTemplate>
-                        <Button BorderBrush="White"
-                                BorderThickness="{Binding IsActive, Converter={StaticResource BoolToIntConverter}}"
-                                Style="{StaticResource ToolButtonStyle}"
-                                Command="{Binding Path=DataContext.ToolsSubViewModel.SelectToolCommand,
-                            RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
-                                CommandParameter="{Binding}" ToolTip="{Binding Tooltip}">
-                            <Button.Background>
-                                <ImageBrush ImageSource="{Binding ImagePath}" Stretch="Uniform" />
-                            </Button.Background>
-                        </Button>
-                    </DataTemplate>
-                </ItemsControl.ItemTemplate>
-            </ItemsControl>
-        </StackPanel>
-
-        <Grid Grid.Row="3" Grid.Column="1">
-            <Grid.ColumnDefinitions>
-                <ColumnDefinition Width="*"/>
-                <ColumnDefinition Width="290"/>
-            </Grid.ColumnDefinitions>
-            <DockPanel>
-            <TextBlock Text="{Binding BitmapManager.SelectedTool.ActionDisplay}" Foreground="White" FontSize="15" Margin="10,0,0,0" VerticalAlignment="Center"/>
-            <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
-                <TextBlock Text="X:" Foreground="White" FontSize="16"/>
-                <TextBlock Margin="4,0,10,0" Text="{Binding BitmapManager.ActiveDocument.MouseXOnCanvas, Converter={StaticResource DoubleToIntConverter}}" Foreground="White" FontSize="16"/>
-                <TextBlock Text="Y:" 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"
-                       Grid.Column="3" Orientation="Horizontal">
-            <Button Style="{StaticResource BaseDarkButton}" 
-                    Visibility="{Binding UpdateSubViewModel.UpdateReadyToInstall, Converter={StaticResource BoolToVisibilityConverter}}" FontSize="14" Height="20" 
-                    Command="{Binding UpdateSubViewModel.RestartApplicationCommand}">Restart</Button>
-            <TextBlock VerticalAlignment="Center" Padding="10" HorizontalAlignment="Right"
-                       Foreground="White" FontSize="14"  Text="{Binding UpdateSubViewModel.VersionText}" />
-        </StackPanel>
-        </Grid>
-    </Grid>
+<Window x:Class="PixiEditor.MainWindow" MinHeight="500" MinWidth="1100"
+        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+        xmlns:vm="clr-namespace:PixiEditor.ViewModels"
+        xmlns:vws="clr-namespace:PixiEditor.Views"
+        xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+        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:usercontrols="clr-namespace:PixiEditor.Views.UserControls" xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours" 
+        xmlns:avalonDockTheme="clr-namespace:PixiEditor.Styles.AvalonDock"
+        mc:Ignorable="d" WindowStyle="None" Initialized="MainWindow_Initialized"
+        Title="PixiEditor" Name="mainWindow" Height="1000" Width="1600" Background="{StaticResource MainColor}"
+        WindowStartupLocation="CenterScreen" WindowState="Maximized">
+    <WindowChrome.WindowChrome>
+        <WindowChrome CaptionHeight="32"
+                      ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
+    </WindowChrome.WindowChrome>
+
+    <Window.Resources>
+        <ResourceDictionary>
+            <!--<vm:ViewModelMain x:Key="ViewModelMain" />-->
+            <BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter" />
+            <converters:BoolToIntConverter x:Key="BoolToIntConverter" />
+            <converters:NotNullToBoolConverter x:Key="NotNullToBoolConverter" />
+            <converters:FloatNormalizeConverter x:Key="FloatNormalizeConverter" />
+            <converters:DoubleToIntConverter x:Key="DoubleToIntConverter"/>
+            <ResourceDictionary.MergedDictionaries>
+                <ResourceDictionary Source="pack://application:,,,/ColorPicker;component/Styles/DefaultColorPickerStyle.xaml" />
+            </ResourceDictionary.MergedDictionaries>
+        </ResourceDictionary>
+    </Window.Resources>
+
+    <Window.CommandBindings>
+        <CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}" CanExecute="CommandBinding_CanExecute"
+                        Executed="CommandBinding_Executed_Close" />
+        <CommandBinding Command="{x:Static SystemCommands.MaximizeWindowCommand}"
+                        CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed_Maximize" />
+        <CommandBinding Command="{x:Static SystemCommands.MinimizeWindowCommand}"
+                        CanExecute="CommandBinding_CanExecute" Executed="CommandBinding_Executed_Minimize" />
+        <CommandBinding Command="{x:Static SystemCommands.RestoreWindowCommand}" CanExecute="CommandBinding_CanExecute"
+                        Executed="CommandBinding_Executed_Restore" />
+    </Window.CommandBindings>
+
+    <i:Interaction.Triggers>
+        <i:EventTrigger EventName="KeyDown">
+            <cmd:EventToCommand Command="{Binding IoSubViewModel.KeyDownCommand}" PassEventArgsToCommand="True" />
+        </i:EventTrigger>
+        <i:EventTrigger EventName="KeyUp">
+            <cmd:EventToCommand Command="{Binding IoSubViewModel.KeyUpCommand}" PassEventArgsToCommand="True"/>
+        </i:EventTrigger>
+        <i:EventTrigger EventName="ContentRendered">
+            <i:InvokeCommandAction Command="{Binding OnStartupCommand}" />
+        </i:EventTrigger>
+        <i:EventTrigger EventName="Closing">
+            <cmd:EventToCommand Command="{Binding CloseWindowCommand}" PassEventArgsToCommand="True" />
+        </i:EventTrigger>
+    </i:Interaction.Triggers>
+    <Grid Name="mainGrid" Margin="5" Focusable="True">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition Width="45" />
+            <ColumnDefinition Width="1*" />
+        </Grid.ColumnDefinitions>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="30" />
+            <RowDefinition Height="40" />
+            <RowDefinition Height="1*" />
+            <RowDefinition Height="30" />
+        </Grid.RowDefinitions>
+        <i:Interaction.Behaviors>
+            <behaviours:ClearFocusOnClickBehavior/>
+        </i:Interaction.Behaviors>
+        <DockPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Background="{StaticResource MainColor}">
+            <Image DockPanel.Dock="Left" HorizontalAlignment="Left" VerticalAlignment="Top"
+                   Source="/Images/PixiEditorLogo.png" Width="20" Height="20" Margin="5,5,0,0" />
+            <Menu WindowChrome.IsHitTestVisibleInChrome="True" Margin="10, 4, 0, 0" DockPanel.Dock="Left"
+                  HorizontalAlignment="Left" VerticalAlignment="Top" Background="Transparent" IsMainMenu="True">
+                <Menu.Resources>
+                    <Style TargetType="{x:Type MenuItem}" BasedOn="{StaticResource menuItemStyle}" />
+                </Menu.Resources>
+                <MenuItem Header="_File">
+                    <MenuItem InputGestureText="CTRL+N" Header="_New" Command="{Binding FileSubViewModel.OpenNewFilePopupCommand}" />
+                    <MenuItem Header="_Open" InputGestureText="Ctrl+O" Command="{Binding FileSubViewModel.OpenFileCommand}" />
+                    <MenuItem Header="_Recent" ItemsSource="{Binding FileSubViewModel.RecentlyOpened}" x:Name="recentItemMenu" IsEnabled="{Binding FileSubViewModel.HasRecent}">
+                        <MenuItem.ItemContainerStyle>
+                            <Style TargetType="MenuItem" BasedOn="{StaticResource menuItemStyle}">
+                                <Setter Property="Command" Value="{Binding ElementName=recentItemMenu, Path=DataContext.FileSubViewModel.OpenRecentCommand}"/>
+                                <Setter Property="CommandParameter" Value="{Binding}"/>
+                            </Style>
+                        </MenuItem.ItemContainerStyle>
+                    </MenuItem>
+                    <MenuItem Header="_Save" InputGestureText="Ctrl+S" Command="{Binding FileSubViewModel.SaveDocumentCommand}" />
+                    <MenuItem Header="_Save As..." InputGestureText="Ctrl+Shift+S"
+                              Command="{Binding FileSubViewModel.SaveDocumentCommand}" CommandParameter="AsNew" />
+                    <MenuItem Header="_Export" InputGestureText="Ctrl+Shift+Alt+S" Command="{Binding FileSubViewModel.ExportFileCommand}" />
+                    <Separator />
+                    <MenuItem Header="_Exit" Command="{x:Static SystemCommands.CloseWindowCommand}" />
+                </MenuItem>
+                <MenuItem Header="_Edit">
+                    <MenuItem Header="_Undo" InputGestureText="Ctrl+Z" Command="{Binding UndoSubViewModel.UndoCommand}" />
+                    <MenuItem Header="_Redo" InputGestureText="Ctrl+Y" Command="{Binding UndoSubViewModel.RedoCommand}" />
+                    <Separator />
+                    <MenuItem Header="_Cut" Command="{Binding ClipboardSubViewModel.CutCommand}" InputGestureText="Ctrl+X" />
+                    <MenuItem Header="_Copy" Command="{Binding ClipboardSubViewModel.CopyCommand}" InputGestureText="Ctrl+C" />
+                    <MenuItem Header="_Paste" Command="{Binding ClipboardSubViewModel.PasteCommand}" InputGestureText="Ctrl+V" />
+                    <MenuItem Header="_Duplicate" Command="{Binding ClipboardSubViewModel.DuplicateCommand}" InputGestureText="Ctrl+J" />
+                    <Separator />
+                    <MenuItem Header="_Delete Selected" Command="{Binding DocumentSubViewModel.DeletePixelsCommand}"
+                              InputGestureText="Delete" />
+                    <Separator />
+                    <MenuItem Header="_Settings" Command="{Binding MiscSubViewModel.OpenSettingsWindowCommand}" />
+                </MenuItem>
+                <MenuItem Header="_Select">
+                    <MenuItem Header="_Select All" Command="{Binding SelectionSubViewModel.SelectAllCommand}" InputGestureText="Ctrl+A" />
+                    <MenuItem Header="_Deselect" Command="{Binding SelectionSubViewModel.DeselectCommand}" InputGestureText="Ctrl+D" />
+                </MenuItem>
+                <MenuItem Header="_Document">
+                    <MenuItem Header="_Resize Document..." Command="{Binding DocumentSubViewModel.OpenResizePopupCommand}"
+                              InputGestureText="Ctrl+Shift+I" />
+                    <MenuItem Header="_Resize Canvas..." Command="{Binding DocumentSubViewModel.OpenResizePopupCommand}"
+                              CommandParameter="canvas" InputGestureText="Ctrl+Shift+C" />
+                    <MenuItem Header="_Clip Canvas" Command="{Binding DocumentSubViewModel.ClipCanvasCommand}" />
+                    <Separator/>
+                    <MenuItem Header="_Center Content" Command="{Binding DocumentSubViewModel.CenterContentCommand}" />
+                </MenuItem>
+                <MenuItem Header="_View">
+                    <MenuItem Header="_Show Grid Lines" IsChecked="{Binding ViewportSubViewModel.GridLinesEnabled, Mode=TwoWay}"
+                              IsCheckable="True" InputGestureText="Ctrl+`"/>
+                </MenuItem>
+                <MenuItem Header="_Help">
+                    <MenuItem Header="_Documentation" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
+                              CommandParameter="https://github.com/PixiEditor/PixiEditor/wiki"/>
+                    <MenuItem Header="_Repository" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
+                              CommandParameter="https://github.com/PixiEditor/PixiEditor"/>
+                    <MenuItem Header="_Shortcuts" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
+                              CommandParameter="https://github.com/PixiEditor/PixiEditor/wiki/Shortcuts"/>
+                    <Separator/>
+                    <MenuItem Header="_License" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
+                              CommandParameter="https://github.com/PixiEditor/PixiEditor/blob/master/LICENSE"/>
+                    <MenuItem Header="_Third Party Licenses" Command="{Binding MiscSubViewModel.OpenHyperlinkCommand}"
+                              CommandParameter="https://github.com/PixiEditor/PixiEditor/wiki/Third-party-licenses"/>
+                </MenuItem>
+            </Menu>
+            <StackPanel DockPanel.Dock="Right" VerticalAlignment="Top" Orientation="Horizontal"
+                        HorizontalAlignment="Right" WindowChrome.IsHitTestVisibleInChrome="True">
+                <Button Style="{StaticResource MinimizeButtonStyle}" WindowChrome.IsHitTestVisibleInChrome="True"
+                        ToolTip="Minimize"
+                        Command="{x:Static SystemCommands.MinimizeWindowCommand}" />
+                <Button x:Name="RestoreButton" Visibility="Visible" Style="{StaticResource RestoreButtonStyle}"
+                        Command="{x:Static SystemCommands.RestoreWindowCommand}"
+                        WindowChrome.IsHitTestVisibleInChrome="True" ToolTip="Restore" />
+                <Button x:Name="MaximizeButton" Visibility="Collapsed" Style="{StaticResource MaximizeButtonStyle}"
+                        Command="{x:Static SystemCommands.MaximizeWindowCommand}"
+                        WindowChrome.IsHitTestVisibleInChrome="True" ToolTip="Maximize" />
+                <Button Style="{StaticResource CloseButtonStyle}" WindowChrome.IsHitTestVisibleInChrome="True"
+                        ToolTip="Close"
+                        Command="{x:Static SystemCommands.CloseWindowCommand}" />
+            </StackPanel>
+        </DockPanel>
+        <StackPanel Background="{StaticResource MainColor}" Orientation="Horizontal" Grid.ColumnSpan="3" Grid.Column="0"
+                     Grid.Row="1">
+            <Label Style="{StaticResource BaseLabel}" Margin="10,0,0,0" FontSize="12" VerticalAlignment="Center" Content="{Binding BitmapManager.SelectedTool.ToolName}"/>
+            <Label Style="{StaticResource BaseLabel}" Padding="0" FontSize="12" VerticalAlignment="Center" Content="tool"/>
+            <ItemsControl ItemsSource="{Binding BitmapManager.SelectedTool.Toolbar.Settings}">
+                <ItemsControl.ItemsPanel>
+                    <ItemsPanelTemplate>
+                        <StackPanel Orientation="Horizontal" Margin="10, 0, 0, 0" />
+                    </ItemsPanelTemplate>
+                </ItemsControl.ItemsPanel>
+                <ItemsControl.ItemTemplate>
+                    <DataTemplate>
+                        <StackPanel Orientation="Horizontal" VerticalAlignment="Center" Margin="10,0,10,0">
+                            <Label
+                                Visibility="{Binding HasLabel, Converter={StaticResource BoolToVisibilityConverter}}"
+                                Foreground="White" Content="{Binding Label}" />
+                            <ContentControl Content="{Binding SettingControl}" />
+                        </StackPanel>
+                    </DataTemplate>
+                </ItemsControl.ItemTemplate>
+            </ItemsControl>
+        </StackPanel>
+        <Grid Grid.Column="1" Grid.Row="2" Background="#303030">
+            <Grid>
+                <DockingManager ActiveContent="{Binding BitmapManager.ActiveDocument, Mode=TwoWay}" 
+                                           DocumentsSource="{Binding BitmapManager.Documents}">
+                    <DockingManager.Theme>
+                        <avalonDockTheme:PixiEditorDockTheme />
+                    </DockingManager.Theme>
+                    <avalondock:DockingManager.LayoutItemContainerStyleSelector>
+                        <ui:PanelsStyleSelector>
+                            <ui:PanelsStyleSelector.DocumentTabStyle>
+                                <Style TargetType="{x:Type avalondock:LayoutItem}">
+                                    <Setter Property="Title" Value="{Binding Model.Name}" />
+                                    <Setter Property="CloseCommand" Value="{Binding Model.RequestCloseDocumentCommand}" />
+                                </Style>
+                            </ui:PanelsStyleSelector.DocumentTabStyle>
+                        </ui:PanelsStyleSelector>
+                    </avalondock:DockingManager.LayoutItemContainerStyleSelector>
+                    <DockingManager.LayoutItemTemplateSelector>
+                        <ui:DocumentsTemplateSelector>
+                            <ui:DocumentsTemplateSelector.DocumentsViewTemplate>
+                                <DataTemplate DataType="{x:Type vm:ViewModelMain}">
+                                    <usercontrols:DrawingViewPort
+                                        ZoomPercentage="{Binding ZoomPercentage}"
+                                        RecenterZoombox="{Binding RecenterZoombox}"
+                                        GridLinesVisible="{Binding XamlAccesibleViewModel.ViewportSubViewModel.GridLinesEnabled}"
+                                        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>
+                            </ui:DocumentsTemplateSelector.DocumentsViewTemplate>
+                        </ui:DocumentsTemplateSelector>
+                    </DockingManager.LayoutItemTemplateSelector>
+                    <avalondock:LayoutRoot x:Name="LayoutRoot">
+                        <LayoutPanel Orientation="Horizontal">
+                            <LayoutDocumentPane/>
+                            <LayoutAnchorablePaneGroup Orientation="Vertical" DockWidth="290">
+                                <LayoutAnchorablePane>
+                                <LayoutAnchorable ContentId="colorPicker" Title="Color Picker" CanHide="False"
+                                                             CanClose="False" CanAutoHide="False"
+                                                             CanDockAsTabbedDocument="False" CanFloat="True">
+                                    <colorpicker:StandardColorPicker Grid.Row="0" SelectedColor="{Binding ColorsSubViewModel.PrimaryColor, Mode=TwoWay}"
+                                     SecondaryColor="{Binding ColorsSubViewModel.SecondaryColor, Mode=TwoWay}" Style="{StaticResource DefaultColorPickerStyle}" >
+                                        <i:Interaction.Behaviors>
+                                            <behaviours:GlobalShortcutFocusBehavior/>
+                                        </i:Interaction.Behaviors>
+                                    </colorpicker:StandardColorPicker>
+                                </LayoutAnchorable>
+                                <avalondock:LayoutAnchorable ContentId="swatches" Title="Swatches" CanHide="False"
+                                                         CanClose="False" CanAutoHide="False"
+                                                         CanDockAsTabbedDocument="False" CanFloat="True">
+                                    <ScrollViewer HorizontalScrollBarVisibility="Disabled"
+                                              VerticalScrollBarVisibility="Auto">
+                                        <ItemsControl ItemsSource="{Binding BitmapManager.ActiveDocument.Swatches}">
+                                            <ItemsControl.ItemsPanel>
+                                                <ItemsPanelTemplate>
+                                                    <WrapPanel Margin="10,10,0,10" Orientation="Horizontal"
+                                                           VerticalAlignment="Top" HorizontalAlignment="Left" />
+                                                </ItemsPanelTemplate>
+                                            </ItemsControl.ItemsPanel>
+                                            <ItemsControl.ItemTemplate>
+                                                <DataTemplate>
+                                                    <Grid Width="45" Height="45" Margin="0 5 5 5">
+                                                        <Border CornerRadius="5.5" Width="44" Height="44">
+                                                            <Border.Background>
+                                                                <ImageBrush ImageSource="../Images/transparentbg.png"
+                                                                        Stretch="UniformToFill">
+                                                                    <ImageBrush.RelativeTransform>
+                                                                        <ScaleTransform ScaleX="6" ScaleY="6" CenterX="0.5"
+                                                                                    CenterY="0.5" />
+                                                                    </ImageBrush.RelativeTransform>
+                                                                </ImageBrush>
+                                                            </Border.Background>
+                                                        </Border>
+                                                        <Border CornerRadius="5.5" BorderThickness="0 0 0 0.1" BorderBrush="White" Cursor="Hand">
+                                                            <Border.Background>
+                                                                <SolidColorBrush Color="{Binding}" />
+                                                            </Border.Background>
+                                                        </Border>
+                                                        <i:Interaction.Triggers>
+                                                            <i:EventTrigger EventName="MouseDown">
+                                                                <i:InvokeCommandAction
+                                                                Command="{Binding
+                                                                    RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ColorsSubViewModel.SelectColorCommand}"
+                                                                CommandParameter="{Binding}" />
+                                                            </i:EventTrigger>
+                                                        </i:Interaction.Triggers>
+                                                        <Grid.ContextMenu>
+                                                            <ContextMenu>
+                                                                <MenuItem Header="Remove" Foreground="White"
+                                                                      Command="{Binding ColorsSubViewModel.RemoveSwatchCommand}"
+                                                                      CommandParameter="{Binding}" />
+                                                            </ContextMenu>
+                                                        </Grid.ContextMenu>
+                                                    </Grid>
+                                                </DataTemplate>
+                                            </ItemsControl.ItemTemplate>
+                                        </ItemsControl>
+                                    </ScrollViewer>
+                                </avalondock:LayoutAnchorable>
+                            </LayoutAnchorablePane>
+                                <LayoutAnchorablePane>
+                                    <LayoutAnchorable ContentId="layers" Title="Layers" CanHide="False"
+                                                         CanClose="False" CanAutoHide="False"
+                                                         CanDockAsTabbedDocument="True" CanFloat="True">
+                                        <Grid>
+                                            <Grid.RowDefinitions>
+                                                <RowDefinition Height="40"/>
+                                                <RowDefinition Height="30"/>
+                                                <RowDefinition Height="15"/>
+                                                <RowDefinition Height="1*"/>
+                                            </Grid.RowDefinitions>
+                                            <Button Grid.Row="0" Command="{Binding LayersSubViewModel.NewLayerCommand}" Height="30" Content="New Layer"
+                                            HorizontalAlignment="Stretch" Margin="5"
+                                            Style="{StaticResource DarkRoundButton}" />
+                                            <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="10,0">
+                                                <Label Content="Opacity" Foreground="White" VerticalAlignment="Center"/>
+                                                <vws:NumberInput 
+                                                    Min="0" Max="100"
+                                                    IsEnabled="{Binding Path=BitmapManager.ActiveDocument, 
+                                                    Converter={StaticResource NotNullToBoolConverter}}" 
+                                                    Width="40" Height="20"
+                                                    VerticalAlignment="Center"
+                                                   Value="{Binding BitmapManager.ActiveDocument.ActiveLayer.OpacityUndoTriggerable, Mode=TwoWay, 
+                                            Converter={StaticResource FloatNormalizeConverter}}" />
+                                                <Label Content="%" Foreground="White" VerticalAlignment="Center"/>
+                                            </StackPanel>
+                                            <Separator Grid.Row="2" Background="{StaticResource BrighterAccentColor}"/>
+                                            <ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto">
+                                                <ItemsControl ItemsSource="{Binding BitmapManager.ActiveDocument.Layers}"
+                                                      x:Name="layersItemsControl" AlternationCount="9999">
+                                                    <ItemsControl.ItemsPanel>
+                                                        <ItemsPanelTemplate>
+                                                            <ui:ReversedOrderStackPanel Orientation="Vertical" />
+                                                        </ItemsPanelTemplate>
+                                                    </ItemsControl.ItemsPanel>
+                                                    <ItemsControl.ItemTemplate>
+                                                        <DataTemplate>
+                                                            <vws:LayerItem Tag="{Binding DataContext, ElementName=mainWindow}" LayerIndex="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
+                                Path=(ItemsControl.AlternationIndex)}" SetActiveLayerCommand="{Binding Path=DataContext.LayersSubViewModel.SetActiveLayerCommand, ElementName=mainWindow}"
+                                                                   LayerName="{Binding Name, Mode=TwoWay}" IsActive="{Binding IsActive, Mode=TwoWay}"
+                                                                   IsRenaming="{Binding IsRenaming, Mode=TwoWay}"
+                                                                   PreviewImage="{Binding LayerBitmap}"
+                                                                   MoveToBackCommand="{Binding DataContext.LayersSubViewModel.MoveToBackCommand, ElementName=mainWindow}"
+                                                                   MoveToFrontCommand="{Binding DataContext.LayersSubViewModel.MoveToFrontCommand, ElementName=mainWindow}">
+                                                                <vws:LayerItem.ContextMenu>
+                                                                    <ContextMenu>
+                                                                        <MenuItem Header="Delete"
+                                                                                  Command="{Binding PlacementTarget.Tag.LayersSubViewModel.DeleteLayerCommand,
+                                                                                    RelativeSource={RelativeSource AncestorType=ContextMenu}}"
+
+                                                                                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
+                                Path=(ItemsControl.AlternationIndex)}" />
+                                                                        <MenuItem Header="Rename"
+                                                                                  Command="{Binding PlacementTarget.Tag.LayersSubViewModel.RenameLayerCommand,
+                                                                            RelativeSource={RelativeSource AncestorType=ContextMenu}}"
+                                                                                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
+                                Path=(ItemsControl.AlternationIndex)}" />
+                                                                        <MenuItem Header="Move to front"
+                                                                                  Command="{Binding PlacementTarget.Tag.LayersSubViewModel.MoveToFrontCommand, 
+                                                                            RelativeSource={RelativeSource AncestorType=ContextMenu}}"
+                                                                                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
+                                Path=(ItemsControl.AlternationIndex)}" />
+                                                                        <MenuItem Header="Move to back"
+                                                                                  Command="{Binding PlacementTarget.Tag.LayersSubViewModel.MoveToBackCommand, 
+                                                                            RelativeSource={RelativeSource AncestorType=ContextMenu}}"
+                                                                                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
+                                Path=(ItemsControl.AlternationIndex)}" />
+                                                                        <Separator/>
+                                                                        <MenuItem Header="Merge selected"
+                                                                                  Command="{Binding LayersSubViewModel.MergeSelectedCommand, Source={StaticResource ViewModelMain}}"/>
+                                                                        <MenuItem Header="Merge with above"
+                                                                                  Command="{Binding PlacementTarget.Tag.LayersSubViewModel.MergeWithAboveCommand, 
+                                                                            RelativeSource={RelativeSource AncestorType=ContextMenu}}"
+                                                                                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
+                                Path=(ItemsControl.AlternationIndex)}" />
+                                                                        <MenuItem Header="Merge with below"
+                                                                                  Command="{Binding PlacementTarget.Tag.LayersSubViewModel.MergeWithBelowCommand, 
+                                                                            RelativeSource={RelativeSource AncestorType=ContextMenu}}"
+                                                                                  CommandParameter="{Binding RelativeSource={RelativeSource Mode=TemplatedParent},
+                                Path=(ItemsControl.AlternationIndex)}" />
+                                                                    </ContextMenu>
+                                                                </vws:LayerItem.ContextMenu>
+                                                            </vws:LayerItem>
+                                                        </DataTemplate>
+                                                    </ItemsControl.ItemTemplate>
+                                                </ItemsControl>
+                                            </ScrollViewer>
+                                        </Grid>
+                                    </LayoutAnchorable>
+                                </LayoutAnchorablePane>
+                            </LayoutAnchorablePaneGroup>
+                        </LayoutPanel>
+                    </avalondock:LayoutRoot>
+                </DockingManager>
+            </Grid>
+        </Grid>
+
+        <StackPanel Orientation="Vertical" Cursor="Arrow" Grid.Row="2" Grid.Column="0"
+                    Background="{StaticResource AccentColor}" Grid.RowSpan="2">
+
+            <ItemsControl ItemsSource="{Binding ToolsSubViewModel.ToolSet}">
+                <ItemsControl.ItemTemplate>
+                    <DataTemplate>
+                        <Button BorderBrush="White"
+                                BorderThickness="{Binding IsActive, Converter={StaticResource BoolToIntConverter}}"
+                                Style="{StaticResource ToolButtonStyle}"
+                                Command="{Binding Path=DataContext.ToolsSubViewModel.SelectToolCommand,
+                            RelativeSource={RelativeSource AncestorType={x:Type Window}}}"
+                                CommandParameter="{Binding}" ToolTip="{Binding Tooltip}">
+                            <Button.Background>
+                                <ImageBrush ImageSource="{Binding ImagePath}" Stretch="Uniform" />
+                            </Button.Background>
+                        </Button>
+                    </DataTemplate>
+                </ItemsControl.ItemTemplate>
+            </ItemsControl>
+        </StackPanel>
+
+        <Grid Grid.Row="3" Grid.Column="1">
+            <Grid.ColumnDefinitions>
+                <ColumnDefinition Width="*"/>
+                <ColumnDefinition Width="290"/>
+            </Grid.ColumnDefinitions>
+            <DockPanel>
+            <TextBlock Text="{Binding BitmapManager.SelectedTool.ActionDisplay}" Foreground="White" FontSize="15" Margin="10,0,0,0" VerticalAlignment="Center"/>
+            <StackPanel DockPanel.Dock="Right" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Center">
+                <TextBlock Text="X:" Foreground="White" FontSize="16"/>
+                <TextBlock Margin="4,0,10,0" Text="{Binding BitmapManager.ActiveDocument.MouseXOnCanvas, Converter={StaticResource DoubleToIntConverter}}" Foreground="White" FontSize="16"/>
+                <TextBlock Text="Y:" 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"
+                       Grid.Column="3" Orientation="Horizontal">
+            <Button Style="{StaticResource BaseDarkButton}" 
+                    Visibility="{Binding UpdateSubViewModel.UpdateReadyToInstall, Converter={StaticResource BoolToVisibilityConverter}}" FontSize="14" Height="20" 
+                    Command="{Binding UpdateSubViewModel.RestartApplicationCommand}">Restart</Button>
+            <TextBlock VerticalAlignment="Center" Padding="10" HorizontalAlignment="Right"
+                       Foreground="White" FontSize="14"  Text="{Binding UpdateSubViewModel.VersionText}" />
+        </StackPanel>
+        </Grid>
+    </Grid>
 </Window>

+ 8 - 0
PixiEditor/Views/MainWindow.xaml.cs

@@ -5,9 +5,11 @@ using System.IO;
 using System.Reflection;
 using System.Windows;
 using System.Windows.Input;
+using Microsoft.Extensions.DependencyInjection;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Processes;
+using PixiEditor.Models.UserPreferences;
 using PixiEditor.UpdateModule;
 using PixiEditor.ViewModels;
 
@@ -23,6 +25,12 @@ namespace PixiEditor
         public MainWindow()
         {
             InitializeComponent();
+
+            IServiceCollection services = new ServiceCollection()
+                .AddSingleton<IPreferences>(new PreferencesSettings());
+
+            DataContext = new ViewModelMain(services.BuildServiceProvider());
+
             StateChanged += MainWindowStateChangeRaised;
             MaxHeight = SystemParameters.MaximizedPrimaryScreenHeight;
             viewModel = (ViewModelMain)DataContext;

+ 24 - 0
PixiEditorTests/Helpers.cs

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

+ 56 - 0
PixiEditorTests/Mocks/PreferenceSettingsMock.cs

@@ -0,0 +1,56 @@
+using System;
+using PixiEditor.Models.UserPreferences;
+
+namespace PixiEditorTests.Mocks
+{
+    public class PreferenceSettingsMock : IPreferences
+    {
+        public void AddCallback(string setting, Action<object> action)
+        {
+        }
+
+#nullable enable
+
+        public T? GetLocalPreference<T>(string name)
+        {
+            return default;
+        }
+
+        public T? GetLocalPreference<T>(string name, T? fallbackValue)
+        {
+            return fallbackValue;
+        }
+
+        public T? GetPreference<T>(string name)
+        {
+            return default;
+        }
+
+        public T? GetPreference<T>(string name, T? fallbackValue)
+        {
+            return fallbackValue;
+        }
+
+#nullable disable
+
+        public void Init()
+        {
+        }
+
+        public void Init(string path, string localPath)
+        {
+        }
+
+        public void Save()
+        {
+        }
+
+        public void UpdateLocalPreference<T>(string name, T value)
+        {
+        }
+
+        public void UpdatePreference<T>(string name, T value)
+        {
+        }
+    }
+}

+ 18 - 0
PixiEditorTests/ModelsTests/DataHoldersTests/DocumentTests.cs

@@ -4,6 +4,7 @@ using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Position;
+using PixiEditor.ViewModels;
 using Xunit;
 
 namespace PixiEditorTests.ModelsTests.DataHoldersTests
@@ -283,5 +284,22 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
             Assert.Equal("Test", document.Layers[1].Name);
             Assert.Equal("Test2", document.Layers[0].Name);
         }
+
+        [StaFact]
+        public void TestThatDocumentGetsAddedToRecentlyOpenedList()
+        {
+            ViewModelMain viewModel = Helpers.MockedViewModelMain();
+
+            Document document = new Document(1, 1)
+            {
+                XamlAccesibleViewModel = viewModel
+            };
+
+            string testFilePath = @"C:\idk\somewhere\homework";
+
+            document.DocumentFilePath = testFilePath;
+
+            Assert.Contains(viewModel.FileSubViewModel.RecentlyOpened, x => x == testFilePath);
+        }
     }
 }

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

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

+ 43 - 1
PixiEditorTests/ModelsTests/UserPreferencesTests/PreferencesSettingsTests.cs

@@ -10,9 +10,13 @@ namespace PixiEditorTests.ModelsTests.UserPreferencesTests
     {
         public static string PathToPreferencesFile { get; } = Path.Join("PixiEditor", "test_preferences.json");
 
+        public static string PathToLocalPreferencesFile { get; } = Path.Join("PixiEditor", "local_test_preferences.json");
+
+        public static readonly PreferencesSettings PreferencesSettings = new PreferencesSettings();
+
         public PreferencesSettingsTests()
         {
-            PreferencesSettings.Init(PathToPreferencesFile);
+            PreferencesSettings.Init(PathToPreferencesFile, PathToLocalPreferencesFile);
         }
 
         [Fact]
@@ -25,6 +29,7 @@ namespace PixiEditorTests.ModelsTests.UserPreferencesTests
         public void TestThatInitCreatesUserPreferencesJson()
         {
             Assert.True(File.Exists(PathToPreferencesFile));
+            Assert.True(File.Exists(PathToLocalPreferencesFile));
         }
 
         [Theory]
@@ -63,5 +68,42 @@ namespace PixiEditorTests.ModelsTests.UserPreferencesTests
                 Assert.Equal(value, dict[name]);
             }
         }
+
+        [Theory]
+        [InlineData(-2)]
+        [InlineData(false)]
+        [InlineData("string")]
+        [InlineData(null)]
+        public void TestThatGetPreferenceOnNonExistingKeyReturnsFallbackValueLocal<T>(T value)
+        {
+            T fallbackValue = value;
+            T preferenceValue = PreferencesSettings.GetLocalPreference<T>("NonExistingPreference", fallbackValue);
+            Assert.Equal(fallbackValue, preferenceValue);
+        }
+
+        [Theory]
+        [InlineData("IntPreference", 1)]
+        [InlineData("BoolPreference", true)]
+        public void TestThatUpdatePreferenceUpdatesDictionaryLocal<T>(string name, T value)
+        {
+            PreferencesSettings.UpdateLocalPreference(name, value);
+            Assert.Equal(value, PreferencesSettings.GetLocalPreference<T>(name));
+        }
+
+        [Theory]
+        [InlineData("LongPreference", 1L)]
+        public void TestThatSaveUpdatesFileLocal<T>(string name, T value)
+        {
+            PreferencesSettings.LocalPreferences[name] = value;
+            PreferencesSettings.Save();
+            using (var fs = new FileStream(PathToPreferencesFile, FileMode.Open, FileAccess.Read, FileShare.Read))
+            {
+                using StreamReader sr = new StreamReader(fs);
+                string json = sr.ReadToEnd();
+                var dict = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
+                Assert.True(dict.ContainsKey(name));
+                Assert.Equal(value, dict[name]);
+            }
+        }
     }
 }

+ 2 - 0
PixiEditorTests/PixiEditorTests.csproj

@@ -23,6 +23,8 @@
       <PrivateAssets>all</PrivateAssets>
       <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
     </PackageReference>
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.1" />
+    <PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
     <PackageReference Include="Moq" Version="4.16.0" />
     <PackageReference Include="OpenCover" Version="4.7.922" />

+ 24 - 12
PixiEditorTests/ViewModelsTests/ViewModelMainTests.cs

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

+ 3 - 3
README.md

@@ -24,9 +24,9 @@ Have you ever used Photoshop or Gimp? Reinventing the wheel is unnecessary, we w
 
 
 
-### Light weighted
+### Lightweight
 
-Program weights only 3.3 MB! Already have .NET 5 installed? Download installer and enjoy saved space.
+Program weighs only 3.3 MB! Already have .NET 5 installed? Download installer and enjoy saved space.
 
 ### Active development
 
@@ -38,7 +38,7 @@ PixiEditor started in 2018 and it's been actively developed since. We continuous
 
 Follow these instructions to get PixiEditor working on your machine.
 
-1. Download .exe file from [here](https://github.com/flabbet/PixiEditor/releases)
+1. Download the .exe file from [here](https://github.com/flabbet/PixiEditor/releases)
 2. Open installer
 3. Follow installer instructions