浏览代码

Merge remote-tracking branch 'origin/master' into palettes

flabbet 3 年之前
父节点
当前提交
5afd05fc83
共有 100 个文件被更改,包括 1919 次插入794 次删除
  1. 23 4
      .github/ISSUE_TEMPLATE/report_a_bug.md
  2. 7 7
      CONTRIBUTING.md
  3. 5 2
      PixiEditor/App.xaml
  4. 36 1
      PixiEditor/App.xaml.cs
  5. 1 1
      PixiEditor/Exceptions/CorruptedFileException.cs
  6. 1 1
      PixiEditor/Helpers/Behaviours/ClearFocusOnClickBehavior.cs
  7. 2 2
      PixiEditor/Helpers/Behaviours/GlobalShortcutFocusBehavior.cs
  8. 43 26
      PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs
  9. 37 0
      PixiEditor/Helpers/Converters/EnumBooleanConverter.cs
  10. 29 0
      PixiEditor/Helpers/Converters/EnumToStringConverter.cs
  11. 3 1
      PixiEditor/Helpers/Converters/EqualityBoolToVisibilityConverter.cs
  12. 24 22
      PixiEditor/Helpers/Converters/FileExtensionToColorConverter.cs
  13. 6 2
      PixiEditor/Helpers/Converters/KeyToStringConverter.cs
  14. 1 1
      PixiEditor/Helpers/Converters/ToolSizeToIntConverter.cs
  15. 27 0
      PixiEditor/Helpers/Converters/WidthToBitmapScalingModeConverter.cs
  16. 22 0
      PixiEditor/Helpers/Converters/ZoomLevelToBitmapScalingModeConverter.cs
  17. 63 20
      PixiEditor/Helpers/CrashHelper.cs
  18. 8 2
      PixiEditor/Helpers/Extensions/Int32RectEx.cs
  19. 4 4
      PixiEditor/Helpers/Extensions/ParserHelpers.cs
  20. 13 0
      PixiEditor/Helpers/Extensions/SKRectIEx.cs
  21. 19 0
      PixiEditor/Helpers/ProcessHelpers.cs
  22. 20 0
      PixiEditor/Helpers/SizeCalculator.cs
  23. 88 0
      PixiEditor/Helpers/SupportedFilesHelper.cs
  24. 14 0
      PixiEditor/Models/Constants.cs
  25. 1 0
      PixiEditor/Models/Controllers/BitmapManager.cs
  26. 60 56
      PixiEditor/Models/Controllers/ClipboardController.cs
  27. 48 3
      PixiEditor/Models/Controllers/Shortcuts/ShortcutController.cs
  28. 9 2
      PixiEditor/Models/Controllers/SurfaceRenderer.cs
  29. 1 1
      PixiEditor/Models/Controllers/UndoManager.cs
  30. 192 0
      PixiEditor/Models/DataHolders/CrashReport.cs
  31. 2 0
      PixiEditor/Models/DataHolders/Document/Document.Constructors.cs
  32. 30 13
      PixiEditor/Models/DataHolders/Document/Document.Layers.cs
  33. 47 12
      PixiEditor/Models/DataHolders/Document/Document.Operations.cs
  34. 1 5
      PixiEditor/Models/DataHolders/Document/Document.Preview.cs
  35. 80 24
      PixiEditor/Models/DataHolders/Document/Document.cs
  36. 20 0
      PixiEditor/Models/DataHolders/PixelSize.cs
  37. 18 16
      PixiEditor/Models/DataHolders/RecentlyOpenedDocument.cs
  38. 3 3
      PixiEditor/Models/DataHolders/Selection.cs
  39. 5 4
      PixiEditor/Models/DataHolders/Surface.cs
  40. 2 3
      PixiEditor/Models/Dialogs/ConfirmationDialog.cs
  41. 21 3
      PixiEditor/Models/Dialogs/ExportFileDialog.cs
  42. 2 4
      PixiEditor/Models/Dialogs/NewFileDialog.cs
  43. 3 16
      PixiEditor/Models/Dialogs/NoticeDialog.cs
  44. 21 23
      PixiEditor/Models/Dialogs/ResizeDocumentDialog.cs
  45. 1 1
      PixiEditor/Models/Enums/FileType.cs
  46. 4 0
      PixiEditor/Models/Enums/SizeUnit.cs
  47. 49 21
      PixiEditor/Models/IO/Exporter.cs
  48. 55 0
      PixiEditor/Models/IO/FileTypeDialogData.cs
  49. 52 0
      PixiEditor/Models/IO/FileTypeDialogDataSet.cs
  50. 0 20
      PixiEditor/Models/IO/ImageFileMaxSizeChecker.cs
  51. 2 2
      PixiEditor/Models/IO/Importer.cs
  52. 0 22
      PixiEditor/Models/IO/PixiFileMaxSizeChecker.cs
  53. 5 9
      PixiEditor/Models/ImageManipulation/BitmapUtils.cs
  54. 18 7
      PixiEditor/Models/Layers/Layer.cs
  55. 4 4
      PixiEditor/Models/Layers/LayerHelper.cs
  56. 24 8
      PixiEditor/Models/Tools/BitmapOperationTool.cs
  57. 1 0
      PixiEditor/Models/Tools/Tool.cs
  58. 1 1
      PixiEditor/Models/Tools/Tools/BrightnessTool.cs
  59. 5 3
      PixiEditor/Models/Tools/Tools/CircleTool.cs
  60. 1 1
      PixiEditor/Models/Tools/Tools/ColorPickerTool.cs
  61. 1 1
      PixiEditor/Models/Tools/Tools/EraserTool.cs
  62. 1 1
      PixiEditor/Models/Tools/Tools/FloodFillTool.cs
  63. 5 3
      PixiEditor/Models/Tools/Tools/LineTool.cs
  64. 2 2
      PixiEditor/Models/Tools/Tools/MagicWandTool.cs
  65. 2 2
      PixiEditor/Models/Tools/Tools/MoveTool.cs
  66. 2 7
      PixiEditor/Models/Tools/Tools/MoveViewportTool.cs
  67. 1 1
      PixiEditor/Models/Tools/Tools/PenTool.cs
  68. 6 3
      PixiEditor/Models/Tools/Tools/RectangleTool.cs
  69. 1 1
      PixiEditor/Models/Tools/Tools/SelectTool.cs
  70. 1 1
      PixiEditor/Models/Tools/Tools/ZoomTool.cs
  71. 107 81
      PixiEditor/Models/Undo/StorageBasedChange.cs
  72. 5 3
      PixiEditor/PixiEditor.csproj
  73. 2 2
      PixiEditor/Properties/AssemblyInfo.cs
  74. 4 4
      PixiEditor/Styles/DarkCheckboxStyle.xaml
  75. 15 2
      PixiEditor/Styles/LabelStyles.xaml
  76. 26 0
      PixiEditor/Styles/PixiListBoxItemStyle.xaml
  77. 32 0
      PixiEditor/Styles/RadioButtonStyle.xaml
  78. 4 1
      PixiEditor/Styles/ThemeColors.xaml
  79. 13 2
      PixiEditor/Styles/ThemeStyle.xaml
  80. 62 0
      PixiEditor/ViewModels/CrashReportViewModel.cs
  81. 9 76
      PixiEditor/ViewModels/ImportFilePopupViewModel.cs
  82. 28 50
      PixiEditor/ViewModels/SaveFilePopupViewModel.cs
  83. 1 24
      PixiEditor/ViewModels/SettingsWindowViewModel.cs
  84. 2 1
      PixiEditor/ViewModels/SubViewModels/Main/ClipboardViewModel.cs
  85. 5 12
      PixiEditor/ViewModels/SubViewModels/Main/DebugViewModel.cs
  86. 4 4
      PixiEditor/ViewModels/SubViewModels/Main/DiscordViewModel.cs
  87. 3 2
      PixiEditor/ViewModels/SubViewModels/Main/DocumentViewModel.cs
  88. 16 14
      PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs
  89. 32 6
      PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs
  90. 64 19
      PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs
  91. 2 8
      PixiEditor/ViewModels/SubViewModels/Main/MiscViewModel.cs
  92. 14 0
      PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs
  93. 4 6
      PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs
  94. 6 11
      PixiEditor/ViewModels/SubViewModels/Main/WindowViewModel.cs
  95. 4 3
      PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/FileSettings.cs
  96. 31 29
      PixiEditor/ViewModels/ViewModelMain.cs
  97. 27 23
      PixiEditor/Views/Dialogs/ConfirmationPopup.xaml
  98. 56 0
      PixiEditor/Views/Dialogs/CrashReportDialog.xaml
  99. 9 6
      PixiEditor/Views/Dialogs/CrashReportDialog.xaml.cs
  100. 26 0
      PixiEditor/Views/Dialogs/DialogTitleBar.xaml

+ 23 - 4
.github/ISSUE_TEMPLATE/report_a_bug.md

@@ -4,13 +4,32 @@ about: Report a bug, not working feature or anything related
 ---
 ---
 
 
 **Describe as detailed as possible when it happens**
 **Describe as detailed as possible when it happens**
+
 A clear and concise description of what the problem is. Ex. Holding CTRL+Z and P at the same time, causes program to crash  
 A clear and concise description of what the problem is. Ex. Holding CTRL+Z and P at the same time, causes program to crash  
 
 
-**Describe what you tried to do in order to fix it**
-A clear and concise description of what you tried to do to fix the problem (if possible).
+**Add reproduction steps**
+
+If you are able to, include steps to reproduce bug
+
+Example:
+1. Create new file with size 64x64
+2. Draw line anywhere
+3. Center content
+4. PixiEditor crashes
+
+**Expected behaviour**
+
+What should happen?
+
+**Include related files,**
+
+If bug makes PixiEditor crash, include crash report. If it is possible, include screenshots and videos. 
+
+**System information**
+
+Windows version: 11/10/8/7
 
 
-**Include screenshots of error**
-If it is possible, include screenshots, videos etc.
 
 
 **Additional context**
 **Additional context**
+
 Add any other context here.
 Add any other context here.

+ 7 - 7
CONTRIBUTING.md

@@ -1,21 +1,21 @@
 # Contributing
 # Contributing
 
 
-Hey! Thanks for being interested in project! It means a lot. But, before contributing please read this guide :) 
+Hey! Thanks for being interested in the project! It means a lot. But, before contributing please read this guide :) 
 
 
 When contributing to this repository, please first discuss the change you wish to make via issue,
 When contributing to this repository, please first discuss the change you wish to make via issue,
 email, or any other method with the owners of this repository before making a change. 
 email, or any other method with the owners of this repository before making a change. 
 
 
 ## Issues
 ## Issues
 
 
-If you want to report a bug, follow steps below, if you want to request a feature, check [this](https://github.com/flabbet/PixiEditor/blob/master/.github/ISSUE_TEMPLATE/feature_request.md)
+If you want to report a bug, follow the steps below, if you want to request a feature, check [this](https://github.com/flabbet/PixiEditor/blob/master/.github/ISSUE_TEMPLATE/feature_request.md)
 
 
 * First of all, check if the issue is on the [list](https://github.com/flabbet/PixiEditor/issues) and/or [board](https://github.com/flabbet/PixiEditor/projects), if yes, upvote it.
 * First of all, check if the issue is on the [list](https://github.com/flabbet/PixiEditor/issues) and/or [board](https://github.com/flabbet/PixiEditor/projects), if yes, upvote it.
 
 
-* If not, report an issue [here](https://github.com/flabbet/PixiEditor/issues) like that:
- 1. Clear as short as possible title
- 2. Describe issue as detailed as possible
- 3. Include screenshots if possible.
+* If not, report an issue [here](https://github.com/flabbet/PixiEditor/issues) while following these guidelines:
+ 1. Keep the title short and straightforward.
+ 2. Describe the issue as detailed as possible
+ 3. Include screenshots if you can.
 
 
  ## Pull Requests
  ## Pull Requests
 
 
- Before pull request, read [this](https://github.com/flabbet/PixiEditor/blob/master/PULL_REQUEST_TEMPLATE.md)
+ Before submitting a pull request, read [this](https://github.com/flabbet/PixiEditor/blob/master/PULL_REQUEST_TEMPLATE.md)

+ 5 - 2
PixiEditor/App.xaml

@@ -1,7 +1,8 @@
 <Application x:Class="PixiEditor.App"
 <Application x:Class="PixiEditor.App"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-             StartupUri="Views/MainWindow.xaml">
+             >
+    <!--StartupUri="Views/MainWindow.xaml"-->
     <Application.Resources>
     <Application.Resources>
         <ResourceDictionary>
         <ResourceDictionary>
             <ResourceDictionary.MergedDictionaries>
             <ResourceDictionary.MergedDictionaries>
@@ -17,6 +18,7 @@
                 <ResourceDictionary Source="Styles/DarkCheckboxStyle.xaml" />
                 <ResourceDictionary Source="Styles/DarkCheckboxStyle.xaml" />
                 <ResourceDictionary Source="Styles/ListSwitchButtonStyle.xaml" />
                 <ResourceDictionary Source="Styles/ListSwitchButtonStyle.xaml" />
                 <ResourceDictionary Source="Styles/LabelStyles.xaml" />
                 <ResourceDictionary Source="Styles/LabelStyles.xaml" />
+                <ResourceDictionary Source="Styles/PixiListBoxItemStyle.xaml" />
                 <ResourceDictionary Source="Styles/AvalonDock/DarkBrushes.xaml" />
                 <ResourceDictionary Source="Styles/AvalonDock/DarkBrushes.xaml" />
                 <ResourceDictionary Source="Styles/AvalonDock/Themes/Menu/DarkBrushes.xaml" />
                 <ResourceDictionary Source="Styles/AvalonDock/Themes/Menu/DarkBrushes.xaml" />
                 <ResourceDictionary Source="Styles/AvalonDock/Themes/OverlayButtons.xaml" />
                 <ResourceDictionary Source="Styles/AvalonDock/Themes/OverlayButtons.xaml" />
@@ -25,8 +27,9 @@
                 <ResourceDictionary Source="Styles/AvalonDock/Themes/Generic.xaml" />
                 <ResourceDictionary Source="Styles/AvalonDock/Themes/Generic.xaml" />
                 <ResourceDictionary Source="Styles/AvalonDock/PixiEditorDockTheme.xaml" />
                 <ResourceDictionary Source="Styles/AvalonDock/PixiEditorDockTheme.xaml" />
                 <ResourceDictionary Source="Styles/TreeViewStyle.xaml" />
                 <ResourceDictionary Source="Styles/TreeViewStyle.xaml" />
+                <ResourceDictionary Source="Styles/RadioButtonStyle.xaml" />
                 <ResourceDictionary Source="pack://application:,,,/ColorPicker;component/Styles/DefaultColorPickerStyle.xaml" />
                 <ResourceDictionary Source="pack://application:,,,/ColorPicker;component/Styles/DefaultColorPickerStyle.xaml" />
             </ResourceDictionary.MergedDictionaries>
             </ResourceDictionary.MergedDictionaries>
         </ResourceDictionary>
         </ResourceDictionary>
     </Application.Resources>
     </Application.Resources>
-</Application>
+</Application>

+ 36 - 1
PixiEditor/App.xaml.cs

@@ -1,7 +1,12 @@
-using PixiEditor.Models.Dialogs;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Enums;
 using PixiEditor.ViewModels;
 using PixiEditor.ViewModels;
+using PixiEditor.Views.Dialogs;
+using System;
+using System.Diagnostics;
 using System.Linq;
 using System.Linq;
+using System.Text.RegularExpressions;
 using System.Windows;
 using System.Windows;
 
 
 namespace PixiEditor
 namespace PixiEditor
@@ -11,6 +16,23 @@ namespace PixiEditor
     /// </summary>
     /// </summary>
     public partial class App : Application
     public partial class App : Application
     {
     {
+        protected override void OnStartup(StartupEventArgs e)
+        {
+            string arguments = string.Join(' ', e.Args);
+
+            if (ParseArgument("--crash (\"?)([A-z0-9:\\/\\ -_.]+)\\1", arguments, out Group[] groups))
+            {
+                CrashReport report = CrashReport.Parse(groups[2].Value);
+                MainWindow = new CrashReportDialog(report);
+            }
+            else
+            {
+                MainWindow = new MainWindow();
+            }
+
+            MainWindow.Show();
+        }
+
         protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
         protected override void OnSessionEnding(SessionEndingCancelEventArgs e)
         {
         {
             base.OnSessionEnding(e);
             base.OnSessionEnding(e);
@@ -21,5 +43,18 @@ namespace PixiEditor
                 e.Cancel = confirmation != ConfirmationType.Yes;
                 e.Cancel = confirmation != ConfirmationType.Yes;
             }
             }
         }
         }
+
+        private bool ParseArgument(string pattern, string args, out Group[] groups)
+        {
+            Match match = Regex.Match(args, pattern, RegexOptions.IgnoreCase);
+            groups = null;
+
+            if (match.Success)
+            {
+                groups = match.Groups.Values.ToArray();
+            }
+
+            return match.Success;
+        }
     }
     }
 }
 }

+ 1 - 1
PixiEditor/Exceptions/CorruptedFileException.cs

@@ -6,7 +6,7 @@ namespace PixiEditor.Exceptions
     public class CorruptedFileException : Exception
     public class CorruptedFileException : Exception
     {
     {
         public CorruptedFileException()
         public CorruptedFileException()
-            : base("Selected file is invalid or corrupted.")
+            : base("The file you've chosen might be corrupted.")
         {
         {
         }
         }
 
 

+ 1 - 1
PixiEditor/Helpers/Behaviours/ClearFocusOnClickBehavior.cs

@@ -20,7 +20,7 @@ namespace PixiEditor.Helpers.Behaviours
         private void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
         private void AssociatedObject_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
         {
         {
             AssociatedObject.Focus();
             AssociatedObject.Focus();
-            ShortcutController.BlockShortcutExecution = false;
+            ShortcutController.UnblockShortcutExecutionAll();
         }
         }
     }
     }
 }
 }

+ 2 - 2
PixiEditor/Helpers/Behaviours/GlobalShortcutFocusBehavior.cs

@@ -27,12 +27,12 @@ namespace PixiEditor.Helpers.Behaviours
 
 
         private void AssociatedObject_LostKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
         private void AssociatedObject_LostKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
         {
         {
-            ShortcutController.BlockShortcutExecution = false;
+            ShortcutController.UnblockShortcutExecution("GlobalShortcutFocusBehavior");
         }
         }
 
 
         private void AssociatedObject_GotKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
         private void AssociatedObject_GotKeyboardFocus(object sender, System.Windows.Input.KeyboardFocusChangedEventArgs e)
         {
         {
-            ShortcutController.BlockShortcutExecution = true;
+            ShortcutController.BlockShortcutExection("GlobalShortcutFocusBehavior");
         }
         }
     }
     }
 }
 }

+ 43 - 26
PixiEditor/Helpers/Behaviours/TextBoxFocusBehavior.cs

@@ -1,5 +1,4 @@
-using System.Text.RegularExpressions;
-using System.Windows;
+using System.Windows;
 using System.Windows.Controls;
 using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Input;
 using System.Windows.Interactivity;
 using System.Windows.Interactivity;
@@ -8,27 +7,42 @@ namespace PixiEditor.Helpers.Behaviours
 {
 {
     internal class TextBoxFocusBehavior : Behavior<TextBox>
     internal class TextBoxFocusBehavior : Behavior<TextBox>
     {
     {
-        // Using a DependencyProperty as the backing store for FillSize.  This enables animation, styling, binding, etc...
-        public static readonly DependencyProperty SelectOnFocusProperty =
+        public static readonly DependencyProperty SelectOnMouseClickProperty =
             DependencyProperty.Register(
             DependencyProperty.Register(
-                nameof(SelectOnFocus),
+                nameof(SelectOnMouseClick),
                 typeof(bool),
                 typeof(bool),
                 typeof(TextBoxFocusBehavior),
                 typeof(TextBoxFocusBehavior),
-                new PropertyMetadata(true));
+                new PropertyMetadata(false));
 
 
-        public static readonly DependencyProperty NextControlProperty =
-            DependencyProperty.Register(nameof(NextControl), typeof(FrameworkElement), typeof(TextBoxFocusBehavior));
+        public static readonly DependencyProperty ConfirmOnEnterProperty =
+            DependencyProperty.Register(
+                nameof(ConfirmOnEnter),
+                typeof(bool),
+                typeof(TextBoxFocusBehavior),
+                new PropertyMetadata(false));
 
 
-        public FrameworkElement NextControl
+        public static readonly DependencyProperty DeselectOnFocusLossProperty =
+            DependencyProperty.Register(
+                nameof(DeselectOnFocusLoss),
+                typeof(bool),
+                typeof(TextBoxFocusBehavior),
+                new PropertyMetadata(false));
+
+        public bool SelectOnMouseClick
         {
         {
-            get => (FrameworkElement)GetValue(NextControlProperty);
-            set => SetValue(NextControlProperty, value);
+            get => (bool)GetValue(SelectOnMouseClickProperty);
+            set => SetValue(SelectOnMouseClickProperty, value);
         }
         }
 
 
-        public bool SelectOnFocus
+        public bool ConfirmOnEnter
+        {
+            get => (bool)GetValue(ConfirmOnEnterProperty);
+            set => SetValue(ConfirmOnEnterProperty, value);
+        }
+        public bool DeselectOnFocusLoss
         {
         {
-            get => (bool)GetValue(SelectOnFocusProperty);
-            set => SetValue(SelectOnFocusProperty, value);
+            get => (bool)GetValue(DeselectOnFocusLossProperty);
+            set => SetValue(DeselectOnFocusLossProperty, value);
         }
         }
 
 
         protected override void OnAttached()
         protected override void OnAttached()
@@ -36,6 +50,7 @@ namespace PixiEditor.Helpers.Behaviours
             base.OnAttached();
             base.OnAttached();
             AssociatedObject.GotKeyboardFocus += AssociatedObjectGotKeyboardFocus;
             AssociatedObject.GotKeyboardFocus += AssociatedObjectGotKeyboardFocus;
             AssociatedObject.GotMouseCapture += AssociatedObjectGotMouseCapture;
             AssociatedObject.GotMouseCapture += AssociatedObjectGotMouseCapture;
+            AssociatedObject.LostFocus += AssociatedObject_LostFocus;
             AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObjectPreviewMouseLeftButtonDown;
             AssociatedObject.PreviewMouseLeftButtonDown += AssociatedObjectPreviewMouseLeftButtonDown;
             AssociatedObject.KeyUp += AssociatedObject_KeyUp;
             AssociatedObject.KeyUp += AssociatedObject_KeyUp;
         }
         }
@@ -45,6 +60,7 @@ namespace PixiEditor.Helpers.Behaviours
             base.OnDetaching();
             base.OnDetaching();
             AssociatedObject.GotKeyboardFocus -= AssociatedObjectGotKeyboardFocus;
             AssociatedObject.GotKeyboardFocus -= AssociatedObjectGotKeyboardFocus;
             AssociatedObject.GotMouseCapture -= AssociatedObjectGotMouseCapture;
             AssociatedObject.GotMouseCapture -= AssociatedObjectGotMouseCapture;
+            AssociatedObject.LostFocus -= AssociatedObject_LostFocus;
             AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObjectPreviewMouseLeftButtonDown;
             AssociatedObject.PreviewMouseLeftButtonDown -= AssociatedObjectPreviewMouseLeftButtonDown;
             AssociatedObject.KeyUp -= AssociatedObject_KeyUp;
             AssociatedObject.KeyUp -= AssociatedObject_KeyUp;
         }
         }
@@ -52,10 +68,8 @@ namespace PixiEditor.Helpers.Behaviours
         // Converts number to proper format if enter is clicked and moves focus to next object
         // Converts number to proper format if enter is clicked and moves focus to next object
         private void AssociatedObject_KeyUp(object sender, KeyEventArgs e)
         private void AssociatedObject_KeyUp(object sender, KeyEventArgs e)
         {
         {
-            if (e.Key != Key.Enter)
-            {
+            if (e.Key != Key.Enter || !ConfirmOnEnter)
                 return;
                 return;
-            }
 
 
             RemoveFocus();
             RemoveFocus();
         }
         }
@@ -63,13 +77,6 @@ namespace PixiEditor.Helpers.Behaviours
         private void RemoveFocus()
         private void RemoveFocus()
         {
         {
             DependencyObject scope = FocusManager.GetFocusScope(AssociatedObject);
             DependencyObject scope = FocusManager.GetFocusScope(AssociatedObject);
-
-            if (NextControl != null)
-            {
-                FocusManager.SetFocusedElement(scope, NextControl);
-                return;
-            }
-
             FrameworkElement parent = (FrameworkElement)AssociatedObject.Parent;
             FrameworkElement parent = (FrameworkElement)AssociatedObject.Parent;
 
 
             while (parent != null && parent is IInputElement element && !element.Focusable)
             while (parent != null && parent is IInputElement element && !element.Focusable)
@@ -84,7 +91,7 @@ namespace PixiEditor.Helpers.Behaviours
             object sender,
             object sender,
             KeyboardFocusChangedEventArgs e)
             KeyboardFocusChangedEventArgs e)
         {
         {
-            if (SelectOnFocus)
+            if (SelectOnMouseClick || e.KeyboardDevice.IsKeyDown(Key.Tab))
                 AssociatedObject.SelectAll();
                 AssociatedObject.SelectAll();
         }
         }
 
 
@@ -92,12 +99,22 @@ namespace PixiEditor.Helpers.Behaviours
             object sender,
             object sender,
             MouseEventArgs e)
             MouseEventArgs e)
         {
         {
-            if (SelectOnFocus)
+            if (SelectOnMouseClick)
                 AssociatedObject.SelectAll();
                 AssociatedObject.SelectAll();
         }
         }
 
 
+        private void AssociatedObject_LostFocus(object sender, RoutedEventArgs e)
+        {
+            if (DeselectOnFocusLoss)
+                AssociatedObject.Select(0, 0);
+            RemoveFocus();
+        }
+
         private void AssociatedObjectPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
         private void AssociatedObjectPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
         {
         {
+            if (!SelectOnMouseClick)
+                return;
+
             if (!AssociatedObject.IsKeyboardFocusWithin)
             if (!AssociatedObject.IsKeyboardFocusWithin)
             {
             {
                 AssociatedObject.Focus();
                 AssociatedObject.Focus();

+ 37 - 0
PixiEditor/Helpers/Converters/EnumBooleanConverter.cs

@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+
+namespace PixiEditor.Helpers.Converters
+{
+    public class EnumBooleanConverter : SingleInstanceConverter<EnumBooleanConverter>
+    {
+        #region IValueConverter Members
+        public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+        {
+            string parameterString = parameter as string;
+            if (parameterString == null)
+                return DependencyProperty.UnsetValue;
+
+            if (Enum.IsDefined(value.GetType(), value) == false)
+                return DependencyProperty.UnsetValue;
+
+            object parameterValue = Enum.Parse(value.GetType(), parameterString);
+
+            return parameterValue.Equals(value);
+        }
+
+        public override object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+        {
+            string parameterString = parameter as string;
+            if (parameterString == null)
+                return DependencyProperty.UnsetValue;
+
+            return Enum.Parse(targetType, parameterString);
+        }
+        #endregion
+    }
+}

+ 29 - 0
PixiEditor/Helpers/Converters/EnumToStringConverter.cs

@@ -0,0 +1,29 @@
+using PixiEditor.Models.Enums;
+using System;
+
+namespace PixiEditor.Helpers.Converters
+{
+  internal class EnumToStringConverter : SingleInstanceConverter<EnumToStringConverter>
+  {
+    public override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
+    {
+      try
+      {
+        var type = value.GetType();
+        if (type == typeof(SizeUnit))
+        {
+          var valueCasted = (SizeUnit)value;
+          if (valueCasted == SizeUnit.Percentage)
+            return "%";
+          
+          return "px";
+        }
+        return Enum.GetName((value.GetType()), value);
+      }
+      catch
+      {
+        return string.Empty;
+      }
+    }
+  }
+}

+ 3 - 1
PixiEditor/Helpers/Converters/EqualityBoolToVisibilityConverter.cs

@@ -9,7 +9,9 @@ namespace PixiEditor.Helpers.Converters
     {
     {
         public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
         {
+            if (value == null)
+                return false;
             return value.Equals(parameter) ? Visibility.Visible : Visibility.Collapsed;
             return value.Equals(parameter) ? Visibility.Visible : Visibility.Collapsed;
         }
         }
     }
     }
-}
+}

+ 24 - 22
PixiEditor/Helpers/Converters/FileExtensionToColorConverter.cs

@@ -1,38 +1,40 @@
-using System;
+using PixiEditor.Models;
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Drawing.Imaging;
 using System.Globalization;
 using System.Globalization;
 using System.Windows.Media;
 using System.Windows.Media;
+using PixiEditor.Models.Enums;
 
 
 namespace PixiEditor.Helpers.Converters
 namespace PixiEditor.Helpers.Converters
 {
 {
     public class FileExtensionToColorConverter :
     public class FileExtensionToColorConverter :
         SingleInstanceConverter<FileExtensionToColorConverter>
         SingleInstanceConverter<FileExtensionToColorConverter>
     {
     {
-        private static readonly SolidColorBrush PixiBrush = ColorBrush(226, 1, 45);
-
-        private static readonly SolidColorBrush PngBrush = ColorBrush(56, 108, 254);
-
-        private static readonly SolidColorBrush JpgBrush = ColorBrush(36, 179, 66);
-
-        private static readonly SolidColorBrush UnknownBrush = ColorBrush(100, 100, 100);
+        private static readonly Dictionary<string, SolidColorBrush> extensionsToBrushes;
+        public static readonly SolidColorBrush UnknownBrush = ColorBrush(100, 100, 100);
 
 
+        static FileExtensionToColorConverter()
+        {
+            extensionsToBrushes = new Dictionary<string, SolidColorBrush>();
+            AssignFormatToBrush(FileType.Unset, UnknownBrush);
+            AssignFormatToBrush(FileType.Pixi, ColorBrush(226, 1, 45));
+            AssignFormatToBrush(FileType.Png, ColorBrush(56, 108, 254));
+            AssignFormatToBrush(FileType.Jpeg, ColorBrush(36, 179, 66));
+            AssignFormatToBrush(FileType.Bmp, ColorBrush(255, 140, 0));
+            AssignFormatToBrush(FileType.Gif, ColorBrush(180, 0, 255));
+        }
+        static void AssignFormatToBrush(FileType format, SolidColorBrush brush)
+        {
+            SupportedFilesHelper.GetFileTypeDialogData(format).Extensions.ForEach(i => extensionsToBrushes[i] = brush);
+        }
+        
         public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
         {
         {
             string extension = (string)value;
             string extension = (string)value;
 
 
-            if (extension == ".pixi")
-            {
-                return PixiBrush;
-            }
-            else if (extension == ".png")
-            {
-                return PngBrush;
-            }
-            else if (extension is ".jpg" or ".jpeg")
-            {
-                return JpgBrush;
-            }
-
-            return UnknownBrush;
+            return extensionsToBrushes.ContainsKey(extension) ? extensionsToBrushes[extension] : UnknownBrush;
         }
         }
 
 
         private static SolidColorBrush ColorBrush(byte r, byte g, byte b)
         private static SolidColorBrush ColorBrush(byte r, byte g, byte b)

+ 6 - 2
PixiEditor/Helpers/Converters/KeyToStringConverter.cs

@@ -11,7 +11,11 @@ namespace PixiEditor.Helpers.Converters
         {
         {
             if (value is Key key)
             if (value is Key key)
             {
             {
-                return InputKeyHelpers.GetCharFromKey(key);
+                return key switch
+                {
+                    Key.Space => "Space",
+                    _ => InputKeyHelpers.GetCharFromKey(key),
+                };
             }
             }
             else if (value is ModifierKeys)
             else if (value is ModifierKeys)
             {
             {
@@ -23,4 +27,4 @@ namespace PixiEditor.Helpers.Converters
             }
             }
         }
         }
     }
     }
-}
+}

+ 1 - 1
PixiEditor/Helpers/Converters/ToolSizeToIntConverter.cs

@@ -31,4 +31,4 @@ namespace PixiEditor.Helpers.Converters
             return int.Parse(match.Groups[0].ValueSpan);
             return int.Parse(match.Groups[0].ValueSpan);
         }
         }
     }
     }
-}
+}

+ 27 - 0
PixiEditor/Helpers/Converters/WidthToBitmapScalingModeConverter.cs

@@ -0,0 +1,27 @@
+using System;
+using System.Globalization;
+using System.Windows;
+using System.Windows.Media;
+
+namespace PixiEditor.Helpers.Converters
+{
+    internal class WidthToBitmapScalingModeConverter : SingleInstanceMultiValueConverter<WidthToBitmapScalingModeConverter>
+    {
+        public override object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
+        {
+            int? pixelWidth = values[0] as int?;
+            double? actualWidth = values[1] as double?;
+            if (pixelWidth == null || actualWidth == null)
+                return DependencyProperty.UnsetValue;
+            double zoomLevel = actualWidth.Value / pixelWidth.Value;
+            if (zoomLevel < 1)
+                return BitmapScalingMode.HighQuality;
+            return BitmapScalingMode.NearestNeighbor;
+        }
+
+        public override object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 22 - 0
PixiEditor/Helpers/Converters/ZoomLevelToBitmapScalingModeConverter.cs

@@ -0,0 +1,22 @@
+using System;
+using System.Globalization;
+using System.Windows.Media;
+
+namespace PixiEditor.Helpers.Converters
+{
+    internal class ZoomLevelToBitmapScalingModeConverter : SingleInstanceConverter<ZoomLevelToBitmapScalingModeConverter>
+    {
+        public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            double zoomLevel = (double)value;
+            if (zoomLevel < 1)
+                return BitmapScalingMode.HighQuality;
+            return BitmapScalingMode.NearestNeighbor;
+        }
+
+        public override object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 63 - 20
PixiEditor/Helpers/CrashHelper.cs

@@ -1,25 +1,76 @@
-using System;
-using System.Collections.Generic;
-using System.IO;
-using System.Linq;
+using ByteSizeLib;
+using Hardware.Info;
+using PixiEditor.Models.DataHolders;
+using System;
+using System.Globalization;
 using System.Text;
 using System.Text;
-using System.Threading.Tasks;
 
 
 namespace PixiEditor.Helpers
 namespace PixiEditor.Helpers
 {
 {
-    public static class CrashHelper
+    public class CrashHelper
     {
     {
-        public static void SaveCrashInfo(Exception e)
+        private readonly IHardwareInfo hwInfo;
+
+        public static void SaveCrashInfo(Exception exception)
+        {
+            CrashReport report = CrashReport.Generate(exception);
+            report.TrySave();
+            report.RestartToCrashReport();
+        }
+
+        public CrashHelper()
+        {
+            hwInfo = new HardwareInfo();
+        }
+
+        public void GetCPUInformation(StringBuilder builder)
+        {
+            builder.AppendLine("CPU:");
+            hwInfo.RefreshCPUList(false);
+
+            foreach (var processor in hwInfo.CpuList)
+            {
+                builder
+                    .AppendLine($"  Name: {processor.Name}")
+                    .AppendLine($"  Speed: {(processor.CurrentClockSpeed / 1000f).ToString("F2", CultureInfo.InvariantCulture)} GHz")
+                    .AppendLine($"  Max Speed: {(processor.MaxClockSpeed / 1000f).ToString("F2", CultureInfo.InvariantCulture)} GHz")
+                    .AppendLine();
+            }
+        }
+
+        public void GetGPUInformation(StringBuilder builder)
         {
         {
-            StringBuilder builder = new System.Text.StringBuilder();
-            DateTime currentTime = DateTime.Now;
+            builder.AppendLine("GPU:");
+            hwInfo.RefreshVideoControllerList();
 
 
+            foreach (var gpu in hwInfo.VideoControllerList)
+            {
+                builder
+                    .AppendLine($"  Name: {gpu.Name}")
+                    .AppendLine($"  Driver: {gpu.DriverVersion}")
+                    .AppendLine();
+            }
+        }
+
+        public void GetMemoryInformation(StringBuilder builder)
+        {
+            builder.AppendLine("Memory:");
+            hwInfo.RefreshMemoryStatus();
+
+            var memInfo = hwInfo.MemoryStatus;
+
+            builder
+                .AppendLine($"  Available: {new ByteSize(memInfo.AvailablePhysical).ToString("", CultureInfo.InvariantCulture)}")
+                .AppendLine($"  Total: {new ByteSize(memInfo.TotalPhysical).ToString("", CultureInfo.InvariantCulture)}");
+        }
+
+        public static void AddExceptionMessage(StringBuilder builder, Exception e)
+        {
             builder
             builder
-                .Append($"PixiEditor crashed on {currentTime:yyyy.MM.dd} at {currentTime:HH:mm:ss}\n\n")
-                .Append("-------Crash message-------\n")
+                .AppendLine("\n-------Crash message-------")
                 .Append(e.GetType().ToString())
                 .Append(e.GetType().ToString())
                 .Append(": ")
                 .Append(": ")
-                .Append(e.Message);
+                .AppendLine(e.Message);
             {
             {
                 var innerException = e.InnerException;
                 var innerException = e.InnerException;
                 while (innerException != null)
                 while (innerException != null)
@@ -46,14 +97,6 @@ namespace PixiEditor.Helpers
                     innerException = innerException.InnerException;
                     innerException = innerException.InnerException;
                 }
                 }
             }
             }
-
-            string filename = $"crash-{currentTime:yyyy-MM-dd_HH-mm-ss_fff}.txt";
-            string path = Path.Combine(
-                Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
-                "PixiEditor",
-                "crash_logs");
-            Directory.CreateDirectory(path);
-            File.WriteAllText(Path.Combine(path, filename), builder.ToString());
         }
         }
     }
     }
 }
 }

+ 8 - 2
PixiEditor/Helpers/Extensions/Int32RectEx.cs

@@ -1,9 +1,10 @@
-using System;
+using SkiaSharp;
+using System;
 using System.Windows;
 using System.Windows;
 
 
 namespace PixiEditor.Helpers.Extensions
 namespace PixiEditor.Helpers.Extensions
 {
 {
-    static class Int32RectEx
+    public static class Int32RectEx
     {
     {
         public static Int32Rect Intersect(this Int32Rect rect, Int32Rect other)
         public static Int32Rect Intersect(this Int32Rect rect, Int32Rect other)
         {
         {
@@ -50,5 +51,10 @@ namespace PixiEditor.Helpers.Extensions
 
 
             return new Int32Rect(minX1, minY1, width, height);
             return new Int32Rect(minX1, minY1, width, height);
         }
         }
+
+        public static SKRectI ToSKRectI(this Int32Rect rect)
+        {
+            return new SKRectI(rect.X, rect.Y, rect.X + rect.Width, rect.Y + rect.Height);
+        }
     }
     }
 }
 }

+ 4 - 4
PixiEditor/Helpers/Extensions/ParserHelpers.cs

@@ -35,19 +35,19 @@ namespace PixiEditor.Helpers.Extensions
             Models.DataHolders.ObservableCollection<Layer> layers = new();
             Models.DataHolders.ObservableCollection<Layer> layers = new();
             foreach (SerializableLayer slayer in document)
             foreach (SerializableLayer slayer in document)
             {
             {
-                layers.Add(slayer.ToLayer());
+                layers.Add(slayer.ToLayer(document.Width, document.Height));
             }
             }
 
 
             return layers;
             return layers;
         }
         }
 
 
-        public static Layer ToLayer(this SerializableLayer layer)
+        public static Layer ToLayer(this SerializableLayer layer, int maxWidth, int maxHeight)
         {
         {
-            return new Layer(layer.Name, new Surface(layer.ToSKImage()))
+            return new Layer(layer.Name, new Surface(layer.ToSKImage()), maxWidth, maxHeight)
             {
             {
                 Opacity = layer.Opacity,
                 Opacity = layer.Opacity,
                 IsVisible = layer.IsVisible,
                 IsVisible = layer.IsVisible,
-                Offset = new(layer.OffsetX, layer.OffsetY, 0, 0)
+                Offset = new(layer.OffsetX, layer.OffsetY, 0, 0),
             };
             };
         }
         }
 
 

+ 13 - 0
PixiEditor/Helpers/Extensions/SKRectIEx.cs

@@ -0,0 +1,13 @@
+using SkiaSharp;
+using System.Windows;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class SKRectIEx
+    {
+        public static Int32Rect ToInt32Rect(this SKRectI rect)
+        {
+            return new Int32Rect(rect.Left, rect.Top, rect.Width, rect.Height);
+        }
+    }
+}

+ 19 - 0
PixiEditor/Helpers/ProcessHelpers.cs

@@ -0,0 +1,19 @@
+using System;
+using System.Diagnostics;
+
+namespace PixiEditor.Helpers
+{
+    public static class ProcessHelpers
+    {
+        public static void ShellExecute(string url)
+        {
+            Process.Start(new ProcessStartInfo
+            {
+                FileName = url,
+                UseShellExecute = true
+            });
+        }
+
+        public static void ShellExecuteEV(string path) => ShellExecute(Environment.ExpandEnvironmentVariables(path));
+    }
+}

+ 20 - 0
PixiEditor/Helpers/SizeCalculator.cs

@@ -0,0 +1,20 @@
+using System;
+
+namespace PixiEditor.Helpers
+{
+    public static class SizeCalculator
+    {
+        public static System.Drawing.Size CalcAbsoluteFromPercentage(float percentage, System.Drawing.Size currentSize)
+        {
+            float percFactor = percentage / 100f;
+            float newWidth = currentSize.Width * percFactor;
+            float newHeight = currentSize.Height * percFactor;
+            return new System.Drawing.Size((int)MathF.Round(newWidth), (int)MathF.Round(newHeight));
+        }
+
+        public static int CalcPercentageFromAbsolute(int initAbsoluteSize, int currentAbsoluteSize)
+        {
+            return (int)((float)currentAbsoluteSize * 100) / initAbsoluteSize;
+        }
+    }
+}

+ 88 - 0
PixiEditor/Helpers/SupportedFilesHelper.cs

@@ -0,0 +1,88 @@
+using PixiEditor.Models;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.IO;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace PixiEditor.Helpers
+{
+    public class SupportedFilesHelper
+    {
+        static Dictionary<FileType, FileTypeDialogData> fileTypeDialogsData;
+        static List<FileTypeDialogData> allFileTypeDialogsData;
+        public static string[] AllSupportedExtensions { get; private set; }
+        public static string[] PrimaryExtensions { get; private set; }
+
+        static SupportedFilesHelper()
+        {
+            fileTypeDialogsData = new Dictionary<FileType, FileTypeDialogData>();
+            allFileTypeDialogsData = new List<FileTypeDialogData>();
+
+            var allFormats = Enum.GetValues(typeof(FileType)).Cast<FileType>().ToList();
+            
+            foreach (var format in allFormats)
+            {
+                var fileTypeDialogData = new FileTypeDialogData(format);
+                if (format != FileType.Unset)
+                    fileTypeDialogsData[format] = fileTypeDialogData;
+
+                allFileTypeDialogsData.Add(fileTypeDialogData);
+            }
+
+            AllSupportedExtensions = fileTypeDialogsData.SelectMany(i => i.Value.Extensions).ToArray();
+            PrimaryExtensions = fileTypeDialogsData.Select(i => i.Value.PrimaryExtension).ToArray();
+        }
+
+        public static FileTypeDialogData GetFileTypeDialogData(FileType type)
+        {
+            return allFileTypeDialogsData.Where(i => i.FileType == type).Single();
+        }
+
+        public static bool IsSupportedFile(string path)
+        {
+            var ext = Path.GetExtension(path.ToLower());
+            return IsExtensionSupported(ext);
+        }
+
+        public static bool IsExtensionSupported(string fileExtension)
+        {
+            return AllSupportedExtensions.Contains(fileExtension);
+        }
+        public static FileType ParseImageFormat(string extension)
+        {
+            var allExts = fileTypeDialogsData.Values.ToList();
+            var fileData = allExts.Where(i => i.Extensions.Contains(extension)).SingleOrDefault();
+            if (fileData != null)
+                return fileData.FileType;
+            return FileType.Unset;
+        }
+
+        public static List<FileTypeDialogData> GetAllSupportedFileTypes(bool includePixi)
+        {
+            var allExts = fileTypeDialogsData.Values.ToList();
+            if (!includePixi)
+                allExts.RemoveAll(item => item.FileType == FileType.Pixi);
+            return allExts;
+        }
+
+        public static string BuildSaveFilter(bool includePixi)
+        {
+            var allSupportedExtensions = GetAllSupportedFileTypes(includePixi);
+            var filter = string.Join("|", allSupportedExtensions.Select(i => i.SaveFilter));
+
+            return filter;
+        }
+
+        public static string BuildOpenFilter()
+        {
+            var any = new FileTypeDialogDataSet(FileTypeDialogDataSet.SetKind.Any).GetFormattedTypes();
+            var pixi = new FileTypeDialogDataSet(FileTypeDialogDataSet.SetKind.Pixi).GetFormattedTypes();
+            var images = new FileTypeDialogDataSet(FileTypeDialogDataSet.SetKind.Images).GetFormattedTypes();
+
+            var filter = any + "|" + pixi + "|" + images;
+            return filter;
+        }
+    }
+}

+ 14 - 0
PixiEditor/Models/Constants.cs

@@ -0,0 +1,14 @@
+namespace PixiEditor.Models
+{
+    internal class Constants
+    {
+        public const int DefaultCanvasSize = 64;
+        public const int MaxPreviewWidth = 128;
+        public const int MaxPreviewHeight = 128;
+
+        public const int MaxCanvasSize = 9999;
+
+        public const string NativeExtensionNoDot = "pixi";
+        public const string NativeExtension = "." + NativeExtensionNoDot;
+    }
+}

+ 1 - 0
PixiEditor/Models/Controllers/BitmapManager.cs

@@ -34,6 +34,7 @@ namespace PixiEditor.Models.Controllers
                 activeDocument?.UpdatePreviewImage();
                 activeDocument?.UpdatePreviewImage();
                 Document oldDoc = activeDocument;
                 Document oldDoc = activeDocument;
                 activeDocument = value;
                 activeDocument = value;
+                activeDocument?.UpdatePreviewImage();
                 RaisePropertyChanged(nameof(ActiveDocument));
                 RaisePropertyChanged(nameof(ActiveDocument));
                 ActiveWindow = value;
                 ActiveWindow = value;
                 DocumentChanged?.Invoke(this, new DocumentChangedEventArgs(value, oldDoc));
                 DocumentChanged?.Invoke(this, new DocumentChangedEventArgs(value, oldDoc));

+ 60 - 56
PixiEditor/Models/Controllers/ClipboardController.cs

@@ -15,6 +15,7 @@ using System.Collections.Generic;
 using System.Collections.Specialized;
 using System.Collections.Specialized;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
+using System.Runtime.InteropServices;
 using System.Windows;
 using System.Windows;
 using System.Windows.Media;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
 using System.Windows.Media.Imaging;
@@ -111,35 +112,66 @@ namespace PixiEditor.Models.Controllers
         /// <summary>
         /// <summary>
         ///     Pastes image from clipboard into new layer.
         ///     Pastes image from clipboard into new layer.
         /// </summary>
         /// </summary>
-        public static void PasteFromClipboard()
+        public static void PasteFromClipboard(Document document)
         {
         {
-            IEnumerable<Layer> layers;
+            Layer[] layers;
             try
             try
             {
             {
-                layers = GetLayersFromClipboard();
+                layers = GetLayersFromClipboard(document).ToArray();
             }
             }
             catch
             catch
             {
             {
                 return;
                 return;
             }
             }
 
 
-            Document activeDocument = ViewModelMain.Current.BitmapManager.ActiveDocument;
-            int startIndex = activeDocument.Layers.Count;
+            int resizedCount = 0;
+
+            Guid[] guids = layers.Select(x => x.GuidValue).ToArray();
+
+            var undoArgs = new object[] { guids, document, new PixelSize(document.Width, document.Height) };
 
 
             foreach (var layer in layers)
             foreach (var layer in layers)
             {
             {
-                activeDocument.Layers.Add(layer);
+                document.Layers.Add(layer);
+
+                if (layer.Width > document.Width || layer.Height > document.Height)
+                {
+                    ResizeToLayer(document, layer);
+                    resizedCount++;
+                }
+            }
+
+            StorageBasedChange change = new StorageBasedChange(document, layers, false);
+
+            document.UndoManager.AddUndoChange(change.ToChange(RemoveLayersProcess, undoArgs,
+                RestoreLayersProcess, new object[] { document }, "Paste from clipboard"));
+        }
+
+        private static void RemoveLayersProcess(object[] parameters)
+        {
+            if (parameters.Length > 2 && parameters[1] is Document document && parameters[2] is PixelSize size) 
+            {
+                document.RemoveLayersProcess(parameters);
+                document.ResizeCanvas(size.Width, size.Height, Enums.AnchorPoint.Left | Enums.AnchorPoint.Top, false);
             }
             }
+        }
 
 
-            activeDocument.UndoManager.AddUndoChange(
-                new Change(RemoveLayersProcess, new object[] { startIndex }, AddLayersProcess, new object[] { layers }) { DisposeProcess = DisposeProcess });
+        private static void RestoreLayersProcess(Layer[] layers, UndoLayer[] data, object[] parameters)
+        {
+            if (parameters.Length > 0 && parameters[0] is Document document)
+            {
+                document.RestoreLayersProcess(layers, data);
+                foreach (var layer in layers)
+                {
+                    ResizeToLayer(document, layer);
+                }
+            }
         }
         }
 
 
         /// <summary>
         /// <summary>
         ///     Gets image from clipboard, supported PNG, Dib and Bitmap.
         ///     Gets image from clipboard, supported PNG, Dib and Bitmap.
         /// </summary>
         /// </summary>
-        /// <returns>WriteableBitmap.</returns>
-        private static IEnumerable<Layer> GetLayersFromClipboard()
+        private static IEnumerable<Layer> GetLayersFromClipboard(Document document)
         {
         {
             DataObject data = ClipboardHelper.TryGetDataObject();
             DataObject data = ClipboardHelper.TryGetDataObject();
             if (data == null)
             if (data == null)
@@ -173,7 +205,7 @@ namespace PixiEditor.Models.Controllers
             else */
             else */
             if (TryFromSingleImage(data, out Surface singleImage))
             if (TryFromSingleImage(data, out Surface singleImage))
             {
             {
-                yield return new Layer("Image", singleImage);
+                yield return new Layer("Image", singleImage, document.Width, document.Height);
             }
             }
             else if (data.GetDataPresent(DataFormats.FileDrop))
             else if (data.GetDataPresent(DataFormats.FileDrop))
             {
             {
@@ -188,13 +220,13 @@ namespace PixiEditor.Models.Controllers
 
 
                     try
                     try
                     {
                     {
-                        layer = new(Path.GetFileName(path), Importer.ImportSurface(path));
+                        layer = new(Path.GetFileName(path), Importer.ImportSurface(path), document.Width, document.Height);
                     }
                     }
                     catch (CorruptedFileException)
                     catch (CorruptedFileException)
                     {
                     {
                     }
                     }
 
 
-                    yield return layer ?? new($"Corrupt {path}");
+                    yield return layer ?? new($"Corrupt {path}", document.Width, document.Height);
                 }
                 }
             }
             }
             else
             else
@@ -209,18 +241,24 @@ namespace PixiEditor.Models.Controllers
             if (dao == null)
             if (dao == null)
                 return false;
                 return false;
 
 
-            var files = dao.GetFileDropList();
-
-            if (files != null)
+            try
             {
             {
-                foreach (var file in files)
+                var files = dao.GetFileDropList();
+                if (files != null)
                 {
                 {
-                    if (Importer.IsSupportedFile(file))
+                    foreach (var file in files)
                     {
                     {
-                        return true;
+                        if (Importer.IsSupportedFile(file))
+                        {
+                            return true;
+                        }
                     }
                     }
                 }
                 }
             }
             }
+            catch(COMException)
+            {
+                return false;
+            }
 
 
             return dao.GetDataPresent("PNG") || dao.GetDataPresent(DataFormats.Dib) ||
             return dao.GetDataPresent("PNG") || dao.GetDataPresent(DataFormats.Dib) ||
                    dao.GetDataPresent(DataFormats.Bitmap) || dao.GetDataPresent(DataFormats.FileDrop) ||
                    dao.GetDataPresent(DataFormats.Bitmap) || dao.GetDataPresent(DataFormats.FileDrop) ||
@@ -290,7 +328,7 @@ namespace PixiEditor.Models.Controllers
                     FormatConvertedBitmap newFormat = new FormatConvertedBitmap();
                     FormatConvertedBitmap newFormat = new FormatConvertedBitmap();
                     newFormat.BeginInit();
                     newFormat.BeginInit();
                     newFormat.Source = source;
                     newFormat.Source = source;
-                    newFormat.DestinationFormat = PixelFormats.Rgba64;
+                    newFormat.DestinationFormat = PixelFormats.Bgra32;
                     newFormat.EndInit();
                     newFormat.EndInit();
 
 
                     result = new Surface(newFormat);
                     result = new Surface(newFormat);
@@ -304,43 +342,9 @@ namespace PixiEditor.Models.Controllers
             return false;
             return false;
         }
         }
 
 
-        private static void RemoveLayersProcess(object[] parameters)
+        private static void ResizeToLayer(Document document, Layer layer)
         {
         {
-            if (parameters.Length == 0 || parameters[0] is not int i)
-            {
-                return;
-            }
-
-            Document document = ViewModelMain.Current.BitmapManager.ActiveDocument;
-
-            while (i < document.Layers.Count)
-            {
-                document.RemoveLayer(i, true);
-            }
-        }
-
-        private static void AddLayersProcess(object[] parameters)
-        {
-            if (parameters.Length == 0 || parameters[0] is not IEnumerable<Layer> layers)
-            {
-                return;
-            }
-
-            foreach (var layer in layers)
-            {
-                ViewModelMain.Current.BitmapManager.ActiveDocument.Layers.Add(layer);
-            }
-        }
-
-        private static void DisposeProcess(object[] rev, object[] proc)
-        {
-            if (proc[0] is IEnumerable<Layer> layers)
-            {
-                foreach (var layer in layers)
-                {
-                    layer.LayerBitmap.Dispose();
-                }
-            }
+            document.ResizeCanvas(Math.Max(document.Width, layer.Width), Math.Max(document.Height, layer.Height), Enums.AnchorPoint.Left | Enums.AnchorPoint.Top, false);
         }
         }
     }
     }
 }
 }

+ 48 - 3
PixiEditor/Models/Controllers/Shortcuts/ShortcutController.cs

@@ -1,5 +1,8 @@
-using System.Collections.ObjectModel;
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
 using System.Linq;
 using System.Linq;
+using System.Windows.Documents;
 using System.Windows.Input;
 using System.Windows.Input;
 
 
 namespace PixiEditor.Models.Controllers.Shortcuts
 namespace PixiEditor.Models.Controllers.Shortcuts
@@ -11,15 +14,57 @@ namespace PixiEditor.Models.Controllers.Shortcuts
             ShortcutGroups = new ObservableCollection<ShortcutGroup>(shortcutGroups);
             ShortcutGroups = new ObservableCollection<ShortcutGroup>(shortcutGroups);
         }
         }
 
 
-        public static bool BlockShortcutExecution { get; set; }
+        public static bool ShortcutExecutionBlocked => _shortcutExecutionBlockers.Count > 0;
+
+        private static List<string> _shortcutExecutionBlockers = new List<string>();
 
 
         public ObservableCollection<ShortcutGroup> ShortcutGroups { get; init; }
         public ObservableCollection<ShortcutGroup> ShortcutGroups { get; init; }
 
 
         public Shortcut LastShortcut { get; private set; }
         public Shortcut LastShortcut { get; private set; }
 
 
+        public const Key MoveViewportToolTransientChangeKey = Key.Space;
+
+        public static void BlockShortcutExection(string blocker)
+        {
+            if (_shortcutExecutionBlockers.Contains(blocker)) return;
+            _shortcutExecutionBlockers.Add(blocker);
+        }
+
+        public static void UnblockShortcutExecution(string blocker)
+        {
+            if (!_shortcutExecutionBlockers.Contains(blocker)) return;
+            _shortcutExecutionBlockers.Remove(blocker);
+        }
+
+        public static void UnblockShortcutExecutionAll()
+        {
+            _shortcutExecutionBlockers.Clear();
+        }
+
+        public Shortcut GetToolShortcut<T>()
+        {
+            return GetToolShortcut(typeof(T));
+        }
+
+        public Shortcut GetToolShortcut(Type type)
+        {
+            return ShortcutGroups.SelectMany(x => x.Shortcuts).ToList().Where(i => i.CommandParameter is Type nextType && nextType == type).SingleOrDefault();
+        }
+
+        public Key GetToolShortcutKey<T>()
+        {
+            return GetToolShortcutKey(typeof(T));
+        }
+
+        public Key GetToolShortcutKey(Type type)
+        {
+            var sh = GetToolShortcut(type);
+            return sh != null ? sh.ShortcutKey : Key.None;
+        }
+
         public void KeyPressed(Key key, ModifierKeys modifiers)
         public void KeyPressed(Key key, ModifierKeys modifiers)
         {
         {
-            if (!BlockShortcutExecution)
+            if (!ShortcutExecutionBlocked)
             {
             {
                 Shortcut[] shortcuts = ShortcutGroups.SelectMany(x => x.Shortcuts).ToList().FindAll(x => x.ShortcutKey == key).ToArray();
                 Shortcut[] shortcuts = ShortcutGroups.SelectMany(x => x.Shortcuts).ToList().FindAll(x => x.ShortcutKey == key).ToArray();
                 if (shortcuts.Length < 1)
                 if (shortcuts.Length < 1)

+ 9 - 2
PixiEditor/Models/Controllers/SurfaceRenderer.cs

@@ -12,6 +12,7 @@ namespace PixiEditor.Models.Controllers
         public SKSurface BackingSurface { get; private set; }
         public SKSurface BackingSurface { get; private set; }
         public WriteableBitmap FinalBitmap { get; private set; }
         public WriteableBitmap FinalBitmap { get; private set; }
         private SKPaint BlendingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
         private SKPaint BlendingPaint { get; } = new SKPaint() { BlendMode = SKBlendMode.SrcOver };
+        private SKPaint HighQualityResizePaint { get; } = new SKPaint() { FilterQuality = SKFilterQuality.High };
         public SurfaceRenderer(int width, int height)
         public SurfaceRenderer(int width, int height)
         {
         {
             FinalBitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Pbgra32, null);
             FinalBitmap = new WriteableBitmap(width, height, 96, 96, PixelFormats.Pbgra32, null);
@@ -23,15 +24,21 @@ namespace PixiEditor.Models.Controllers
         {
         {
             BackingSurface.Dispose();
             BackingSurface.Dispose();
             BlendingPaint.Dispose();
             BlendingPaint.Dispose();
+            HighQualityResizePaint.Dispose();
         }
         }
 
 
         public void Draw(Surface otherSurface, byte opacity)
         public void Draw(Surface otherSurface, byte opacity)
+        {
+            Draw(otherSurface, opacity, new SKRectI(0, 0, otherSurface.Width, otherSurface.Height));
+        }
+
+        public void Draw(Surface otherSurface, byte opacity, SKRectI drawRect)
         {
         {
             BackingSurface.Canvas.Clear();
             BackingSurface.Canvas.Clear();
             FinalBitmap.Lock();
             FinalBitmap.Lock();
             BlendingPaint.Color = new SKColor(255, 255, 255, opacity);
             BlendingPaint.Color = new SKColor(255, 255, 255, opacity);
-            using (var snapshot = otherSurface.SkiaSurface.Snapshot())
-                BackingSurface.Canvas.DrawImage(snapshot, new SKRect(0, 0, FinalBitmap.PixelWidth, FinalBitmap.PixelHeight));
+            using (var snapshot = otherSurface.SkiaSurface.Snapshot(drawRect))
+                BackingSurface.Canvas.DrawImage(snapshot, new SKRect(0, 0, FinalBitmap.PixelWidth, FinalBitmap.PixelHeight), HighQualityResizePaint);
             FinalBitmap.AddDirtyRect(new Int32Rect(0, 0, FinalBitmap.PixelWidth, FinalBitmap.PixelHeight));
             FinalBitmap.AddDirtyRect(new Int32Rect(0, 0, FinalBitmap.PixelWidth, FinalBitmap.PixelHeight));
             FinalBitmap.Unlock();
             FinalBitmap.Unlock();
         }
         }

+ 1 - 1
PixiEditor/Models/Controllers/UndoManager.cs

@@ -56,7 +56,7 @@ namespace PixiEditor.Models.Controllers
             {
             {
                 foreach (var redo in RedoStack)
                 foreach (var redo in RedoStack)
                 {
                 {
-                    //redo.Dispose();
+                    redo.Dispose();
                 }
                 }
                 RedoStack.Clear();
                 RedoStack.Clear();
             }
             }

+ 192 - 0
PixiEditor/Models/DataHolders/CrashReport.cs

@@ -0,0 +1,192 @@
+using PixiEditor.Helpers;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Parser;
+using PixiEditor.ViewModels;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace PixiEditor.Models.DataHolders
+{
+    public class CrashReport : IDisposable
+    {
+        public static CrashReport Generate(Exception exception)
+        {
+            StringBuilder builder = new();
+            DateTime currentTime = DateTime.Now;
+
+            builder
+                .AppendLine($"PixiEditor {VersionHelpers.GetCurrentAssemblyVersionString()} crashed on {currentTime:yyyy.MM.dd} at {currentTime:HH:mm:ss}\n")
+                .AppendLine("-----System Information----")
+                .AppendLine("General:")
+                .AppendLine($"  OS: {Environment.OSVersion.VersionString}")
+                .AppendLine();
+
+            CrashHelper helper = new();
+
+            try
+            {
+                helper.GetCPUInformation(builder);
+            }
+            catch (Exception cpuE)
+            {
+                builder.AppendLine($"Error ({cpuE.GetType().FullName}: {cpuE.Message}) while gathering CPU information, skipping...");
+            }
+
+            try
+            {
+                helper.GetGPUInformation(builder);
+            }
+            catch (Exception gpuE)
+            {
+                builder.AppendLine($"Error ({gpuE.GetType().FullName}: {gpuE.Message}) while gathering GPU information, skipping...");
+            }
+
+            try
+            {
+                helper.GetMemoryInformation(builder);
+            }
+            catch (Exception memE)
+            {
+                builder.AppendLine($"Error ({memE.GetType().FullName}: {memE.Message}) while gathering memory information, skipping...");
+            }
+
+            CrashHelper.AddExceptionMessage(builder, exception);
+
+            string filename = $"crash-{currentTime:yyyy-MM-dd_HH-mm-ss_fff}.zip";
+            string path = Path.Combine(
+                Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData),
+                "PixiEditor",
+                "crash_logs");
+            Directory.CreateDirectory(path);
+
+            CrashReport report = new();
+            report.FilePath = Path.Combine(path, filename);
+            report.ReportText = builder.ToString();
+
+            return report;
+        }
+
+        public static CrashReport Parse(string path)
+        {
+            CrashReport report = new();
+            report.FilePath = path;
+
+            report.ZipFile = System.IO.Compression.ZipFile.Open(path, ZipArchiveMode.Read);
+            report.ExtractReport();
+
+            return report;
+        }
+
+        public string FilePath { get; set; }
+
+        public string ReportText { get; set; }
+
+        private ZipArchive ZipFile { get; set; }
+
+        public int GetDocumentCount() => ZipFile.Entries.Where(x => x.FullName.EndsWith(".pixi")).Count();
+
+        public IEnumerable<Document> RecoverDocuments()
+        {
+            foreach (ZipArchiveEntry entry in ZipFile.Entries.Where(x => x.FullName.EndsWith(".pixi")))
+            {
+                using Stream stream = entry.Open();
+
+                Document document;
+
+                try
+                {
+                    document = PixiParser.Deserialize(stream).ToDocument();
+                    document.ChangesSaved = false;
+                }
+                catch
+                {
+                    continue;
+                }
+
+                yield return document;
+            }
+        }
+
+        public void Dispose()
+        {
+            ZipFile.Dispose();
+        }
+
+        public void RestartToCrashReport()
+        {
+            Process process = new();
+
+            process.StartInfo = new()
+            {
+                FileName = Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, "exe"),
+                Arguments = $"--crash \"{Path.GetFullPath(FilePath)}\""
+            };
+
+            process.Start();
+        }
+
+        public bool TrySave()
+        {
+            try
+            {
+                Save();
+                return true;
+            }
+            catch
+            {
+                return false;
+            }
+        }
+
+        public void Save()
+        {
+            using FileStream zipStream = new(FilePath, FileMode.Create, FileAccess.Write);
+            using ZipArchive archive = new ZipArchive(zipStream, ZipArchiveMode.Create);
+
+            using (Stream reportStream = archive.CreateEntry("report.txt").Open())
+            {
+                reportStream.Write(Encoding.UTF8.GetBytes(ReportText));
+            }
+
+            foreach (Document document in ViewModelMain.Current.BitmapManager.Documents)
+            {
+                try
+                {
+                    string documentPath =
+                        $"{(string.IsNullOrWhiteSpace(document.DocumentFilePath) ? "Unsaved" : Path.GetFileNameWithoutExtension(document.DocumentFilePath))}-{document.OpenedUTC}.pixi";
+
+                    byte[] serialized = PixiParser.Serialize(document.ToSerializable());
+
+                    using Stream documentStream = archive.CreateEntry($"Documents/{documentPath}").Open();
+                    documentStream.Write(serialized);
+                }
+                catch
+                { }
+            }
+        }
+
+        private void ExtractReport()
+        {
+            ZipArchiveEntry entry = ZipFile.GetEntry("report.txt");
+            using Stream stream = entry.Open();
+
+            byte[] encodedReport = new byte[entry.Length];
+            stream.Read(encodedReport);
+
+            ReportText = Encoding.UTF8.GetString(encodedReport);
+        }
+
+        public class CrashReportUserMessage
+        {
+            public string Message { get; set; }
+
+            public string Mail { get; set; }
+        }
+    }
+}

+ 2 - 0
PixiEditor/Models/DataHolders/Document/Document.Constructors.cs

@@ -1,5 +1,6 @@
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
+using PixiEditor.Models.Position;
 using PixiEditor.ViewModels;
 using PixiEditor.ViewModels;
 using System;
 using System;
 using System.Linq;
 using System.Linq;
@@ -31,6 +32,7 @@ namespace PixiEditor.Models.DataHolders
             LayerStructure.LayerStructureChanged += LayerStructure_LayerStructureChanged;
             LayerStructure.LayerStructureChanged += LayerStructure_LayerStructureChanged;
             DocumentSizeChanged += (sender, args) =>
             DocumentSizeChanged += (sender, args) =>
             {
             {
+                ActiveSelection = new Selection(Array.Empty<Coordinates>(), new PixelSize(args.NewWidth, args.NewHeight));
                 Renderer.Resize(args.NewWidth, args.NewHeight);
                 Renderer.Resize(args.NewWidth, args.NewHeight);
                 GeneratePreviewLayer();
                 GeneratePreviewLayer();
             };
             };

+ 30 - 13
PixiEditor/Models/DataHolders/Document/Document.Layers.cs

@@ -12,6 +12,7 @@ using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using System.Text.RegularExpressions;
 using System.Text.RegularExpressions;
 using System.Windows;
 using System.Windows;
+using Windows.Graphics;
 
 
 namespace PixiEditor.Models.DataHolders
 namespace PixiEditor.Models.DataHolders
 {
 {
@@ -221,9 +222,7 @@ namespace PixiEditor.Models.DataHolders
             if (width <= 0 || height <= 0)
             if (width <= 0 || height <= 0)
                 throw new ArgumentException("Dimensions must be greater than 0");
                 throw new ArgumentException("Dimensions must be greater than 0");
 
 
-            layer = bitmap == null ? new Layer(name, width, height) : new Layer(name, bitmap);
-            layer.MaxHeight = Height;
-            layer.MaxWidth = Width;
+            layer = bitmap == null ? new Layer(name, width, height, Width, Height) : new Layer(name, bitmap, Width, Height);
 
 
             Layers.Add(layer);
             Layers.Add(layer);
 
 
@@ -441,7 +440,7 @@ namespace PixiEditor.Models.DataHolders
 
 
             var groupParent = LayerStructure.GetGroupByLayer(layersToMerge[^1].GuidValue);
             var groupParent = LayerStructure.GetGroupByLayer(layersToMerge[^1].GuidValue);
 
 
-            Layer placeholderLayer = new("_placeholder");
+            Layer placeholderLayer = new("_placeholder", Width, Height);
             Layers.Insert(index, placeholderLayer);
             Layers.Insert(index, placeholderLayer);
             LayerStructure.AssignParent(placeholderLayer.GuidValue, groupParent?.GroupGuid);
             LayerStructure.AssignParent(placeholderLayer.GuidValue, groupParent?.GroupGuid);
 
 
@@ -449,6 +448,8 @@ namespace PixiEditor.Models.DataHolders
             {
             {
                 Layer firstLayer = mergedLayer;
                 Layer firstLayer = mergedLayer;
                 Layer secondLayer = layersToMerge[i + 1];
                 Layer secondLayer = layersToMerge[i + 1];
+                firstLayer.ClipCanvas();
+                secondLayer.ClipCanvas();
                 mergedLayer = firstLayer.MergeWith(secondLayer, name, Width, Height);
                 mergedLayer = firstLayer.MergeWith(secondLayer, name, Width, Height);
                 RemoveLayer(layersToMerge[i], false);
                 RemoveLayer(layersToMerge[i], false);
             }
             }
@@ -471,7 +472,7 @@ namespace PixiEditor.Models.DataHolders
                 throw new ArgumentException("Not enough layers were provided to merge. Minimum amount is 2");
                 throw new ArgumentException("Not enough layers were provided to merge. Minimum amount is 2");
             }
             }
 
 
-            IEnumerable<Layer> undoArgs = layersToMerge;
+            Layer[] undoArgs = layersToMerge;
 
 
             var oldLayerStructure = LayerStructure.CloneGroups();
             var oldLayerStructure = LayerStructure.CloneGroups();
 
 
@@ -602,7 +603,7 @@ namespace PixiEditor.Models.DataHolders
                 for (int i = 0; i < layers.Length; i++)
                 for (int i = 0; i < layers.Length; i++)
                 {
                 {
                     Layer layer = layers[i];
                     Layer layer = layers[i];
-                    layer.IsActive = true;
+                    layer.IsActive = data[i].IsActive;
                     Layers.Insert(data[i].LayerIndex, layer);
                     Layers.Insert(data[i].LayerIndex, layer);
                 }
                 }
 
 
@@ -614,20 +615,36 @@ namespace PixiEditor.Models.DataHolders
         /// <summary>
         /// <summary>
         ///     Moves offsets of layers by specified vector.
         ///     Moves offsets of layers by specified vector.
         /// </summary>
         /// </summary>
-        private void MoveOffsets(IEnumerable<Layer> layers, Coordinates moveVector)
+        private void MoveOffsets(IList<Layer> layers, IList<Int32Rect> bounds, Coordinates moveVector)
         {
         {
-            foreach (Layer layer in layers)
+            for (int i = 0; i < layers.Count; i++)
             {
             {
+                Layer layer = layers[i];
+                Int32Rect bound = bounds[i];
                 Thickness offset = layer.Offset;
                 Thickness offset = layer.Offset;
                 layer.Offset = new Thickness(offset.Left + moveVector.X, offset.Top + moveVector.Y, 0, 0);
                 layer.Offset = new Thickness(offset.Left + moveVector.X, offset.Top + moveVector.Y, 0, 0);
+                if (!bound.IsEmpty && layer.Bounds != bound)
+                {
+                    layer.DynamicResizeAbsolute(bound);
+                }
+                else
+                {
+                    layer.ClipCanvas();
+                }
             }
             }
         }
         }
 
 
         private void MoveOffsetsProcess(object[] arguments)
         private void MoveOffsetsProcess(object[] arguments)
         {
         {
-            if (arguments.Length > 0 && arguments[0] is IEnumerable<Layer> layers && arguments[1] is Coordinates vector)
+            if (arguments.Length > 0 && arguments[0] is List<Guid> guids && arguments[1] is List<Int32Rect> bounds && arguments[2] is Coordinates vector)
             {
             {
-                MoveOffsets(layers, vector);
+                List<Layer> layers = new List<Layer>(guids.Count);
+                foreach (Guid guid in guids)
+                {
+                    layers.Add(Layers.First(x => x.GuidValue == guid));
+                }
+
+                MoveOffsets(layers, bounds, vector);
             }
             }
             else
             else
             {
             {
@@ -712,7 +729,7 @@ namespace PixiEditor.Models.DataHolders
             Renderer.ForceRerender();
             Renderer.ForceRerender();
         }
         }
 
 
-        private void RestoreLayersProcess(Layer[] layers, UndoLayer[] layersData)
+        public void RestoreLayersProcess(Layer[] layers, UndoLayer[] layersData)
         {
         {
             for (int i = 0; i < layers.Length; i++)
             for (int i = 0; i < layers.Length; i++)
             {
             {
@@ -726,7 +743,7 @@ namespace PixiEditor.Models.DataHolders
             }
             }
         }
         }
 
 
-        private void RemoveLayerProcess(object[] parameters)
+        public void RemoveLayerProcess(object[] parameters)
         {
         {
             if (parameters is { Length: > 0 } && parameters[0] is Guid layerGuid)
             if (parameters is { Length: > 0 } && parameters[0] is Guid layerGuid)
             {
             {
@@ -837,7 +854,7 @@ namespace PixiEditor.Models.DataHolders
             return sucess;
             return sucess;
         }
         }
 
 
-        private void RemoveLayersProcess(object[] parameters)
+        public void RemoveLayersProcess(object[] parameters)
         {
         {
             if (parameters != null && parameters.Length > 0 && parameters[0] is IEnumerable<Guid> layerGuids)
             if (parameters != null && parameters.Length > 0 && parameters[0] is IEnumerable<Guid> layerGuids)
             {
             {

+ 47 - 12
PixiEditor/Models/DataHolders/Document/Document.Operations.cs

@@ -23,7 +23,7 @@ namespace PixiEditor.Models.DataHolders
         ///     Point that will act as "starting position" of resizing. Use pipe to connect horizontal and
         ///     Point that will act as "starting position" of resizing. Use pipe to connect horizontal and
         ///     vertical.
         ///     vertical.
         /// </param>
         /// </param>
-        public void ResizeCanvas(int width, int height, AnchorPoint anchor)
+        public void ResizeCanvas(int width, int height, AnchorPoint anchor, bool addToUndo = true)
         {
         {
             int oldWidth = Width;
             int oldWidth = Width;
             int oldHeight = Height;
             int oldHeight = Height;
@@ -31,20 +31,29 @@ namespace PixiEditor.Models.DataHolders
             int offsetX = GetOffsetXForAnchor(Width, width, anchor);
             int offsetX = GetOffsetXForAnchor(Width, width, anchor);
             int offsetY = GetOffsetYForAnchor(Height, height, anchor);
             int offsetY = GetOffsetYForAnchor(Height, height, anchor);
 
 
-            Thickness[] oldOffsets = Layers.Select(x => x.Offset).ToArray();
             Thickness[] newOffsets = Layers.Select(x => new Thickness(offsetX + x.OffsetX, offsetY + x.OffsetY, 0, 0))
             Thickness[] newOffsets = Layers.Select(x => new Thickness(offsetX + x.OffsetX, offsetY + x.OffsetY, 0, 0))
                 .ToArray();
                 .ToArray();
 
 
             object[] processArgs = { newOffsets, width, height };
             object[] processArgs = { newOffsets, width, height };
-            object[] reverseProcessArgs = { oldOffsets, Width, Height };
+            object[] reverseProcessArgs = { Width, Height };
+
+            if (addToUndo) 
+            { 
+                StorageBasedChange change = new(this, Layers);
+                ResizeCanvas(newOffsets, width, height);
+
+                UndoManager.AddUndoChange(change.ToChange(
+                    RestoreDocumentLayersProcess,
+                    reverseProcessArgs,
+                    ResizeCanvasProcess,
+                    processArgs,
+                    "Resize canvas"));
+            }
+            else
+            {
+                ResizeCanvas(newOffsets, width, height);
+            }
 
 
-            ResizeCanvas(newOffsets, width, height);
-            UndoManager.AddUndoChange(new Change(
-                ResizeCanvasProcess,
-                reverseProcessArgs,
-                ResizeCanvasProcess,
-                processArgs,
-                "Resize canvas"));
             DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
             DocumentSizeChanged?.Invoke(this, new DocumentSizeChangedEventArgs(oldWidth, oldHeight, width, height));
         }
         }
 
 
@@ -137,7 +146,7 @@ namespace PixiEditor.Models.DataHolders
                         int diff = documentCenter.X - newOffsetX;
                         int diff = documentCenter.X - newOffsetX;
                         newOffsetX = layer.OffsetX + (diff * 2);
                         newOffsetX = layer.OffsetX + (diff * 2);
                     }
                     }
-                    else if(flip == FlipType.Vertical)
+                    else if (flip == FlipType.Vertical)
                     {
                     {
                         newOffsetY += layerCenter.Y;
                         newOffsetY += layerCenter.Y;
                         int diff = documentCenter.Y - newOffsetY;
                         int diff = documentCenter.Y - newOffsetY;
@@ -205,8 +214,13 @@ namespace PixiEditor.Models.DataHolders
 
 
         private void RestoreDocumentLayersProcess(Layer[] layers, UndoLayer[] data, object[] args)
         private void RestoreDocumentLayersProcess(Layer[] layers, UndoLayer[] data, object[] args)
         {
         {
+            int oldWidth = Width;
+            int oldHeight = Height;
             Width = (int)args[0];
             Width = (int)args[0];
             Height = (int)args[1];
             Height = (int)args[1];
+            DocumentSizeChanged?.Invoke(
+                this,
+                new DocumentSizeChangedEventArgs(oldWidth, oldHeight, Width, Height));
             Layers.Clear();
             Layers.Clear();
             Layers.AddRange(layers);
             Layers.AddRange(layers);
         }
         }
@@ -219,11 +233,32 @@ namespace PixiEditor.Models.DataHolders
         /// <param name="newHeight">New canvas height.</param>
         /// <param name="newHeight">New canvas height.</param>
         private void ResizeCanvas(Thickness[] offset, int newWidth, int newHeight)
         private void ResizeCanvas(Thickness[] offset, int newWidth, int newHeight)
         {
         {
+            Int32Rect newCanvasRect = new(0, 0, newWidth, newHeight);
             for (int i = 0; i < Layers.Count; i++)
             for (int i = 0; i < Layers.Count; i++)
             {
             {
-                Layers[i].Offset = offset[i];
+                Layer layer = Layers[i];
                 Layers[i].MaxWidth = newWidth;
                 Layers[i].MaxWidth = newWidth;
                 Layers[i].MaxHeight = newHeight;
                 Layers[i].MaxHeight = newHeight;
+                if (layer.IsReset)
+                    continue;
+
+                Thickness newOffset = offset[i];
+                Int32Rect newRect = new((int)newOffset.Left, (int)newOffset.Top, layer.Width, layer.Height);
+                Int32Rect newLayerRect = newRect.Intersect(newCanvasRect);
+                if (!newLayerRect.HasArea)
+                {
+                    layer.Reset();
+                    continue;
+                }
+                Surface newBitmap = new(newLayerRect.Width, newLayerRect.Height);
+                var oldBitmap = layer.LayerBitmap;
+                using var snapshot = oldBitmap.SkiaSurface.Snapshot();
+                newBitmap.SkiaSurface.Canvas.DrawImage(snapshot, newRect.X - newLayerRect.X, newRect.Y - newLayerRect.Y, Surface.ReplacingPaint);
+
+                layer.LayerBitmap = newBitmap;
+                oldBitmap.Dispose();
+
+                Layers[i].Offset = new Thickness(newLayerRect.X, newLayerRect.Y, 0, 0);
             }
             }
 
 
             Width = newWidth;
             Width = newWidth;

+ 1 - 5
PixiEditor/Models/DataHolders/Document/Document.Preview.cs

@@ -43,11 +43,7 @@ namespace PixiEditor.Models.DataHolders
 
 
         public void GeneratePreviewLayer()
         public void GeneratePreviewLayer()
         {
         {
-            PreviewLayer = new Layer("_previewLayer")
-            {
-                MaxWidth = Width,
-                MaxHeight = Height
-            };
+            PreviewLayer = new Layer("_previewLayer", Width, Height);
         }
         }
     }
     }
 }
 }

+ 80 - 24
PixiEditor/Models/DataHolders/Document/Document.cs

@@ -61,7 +61,7 @@ namespace PixiEditor.Models.DataHolders
             }
             }
         }
         }
 
 
-        private Selection selection = new Selection(Array.Empty<Coordinates>());
+        private Selection selection;
 
 
         public Selection ActiveSelection
         public Selection ActiveSelection
         {
         {
@@ -115,16 +115,19 @@ namespace PixiEditor.Models.DataHolders
         /// </summary>
         /// </summary>
         public void ClipCanvas()
         public void ClipCanvas()
         {
         {
-            DoubleCoords points = GetEdgePoints(Layers);
-            int smallestX = points.Coords1.X;
-            int smallestY = points.Coords1.Y;
-            int biggestX = points.Coords2.X;
-            int biggestY = points.Coords2.Y;
+            DoubleCoords? maybePoints = GetEdgePoints(Layers);
 
 
-            if (smallestX == 0 && smallestY == 0 && biggestX == 0 && biggestY == 0)
+            if (maybePoints == null)
             {
             {
+                //all layers are empty
                 return;
                 return;
             }
             }
+            DoubleCoords points = maybePoints.Value;
+
+            int smallestX = points.Coords1.X;
+            int smallestY = points.Coords1.Y;
+            int biggestX = points.Coords2.X;
+            int biggestY = points.Coords2.Y;
 
 
             int width = biggestX - smallestX;
             int width = biggestX - smallestX;
             int height = biggestY - smallestY;
             int height = biggestY - smallestY;
@@ -134,15 +137,15 @@ namespace PixiEditor.Models.DataHolders
             int oldWidth = Width;
             int oldWidth = Width;
             int oldHeight = Height;
             int oldHeight = Height;
 
 
-            MoveOffsets(Layers, moveVector);
+            StorageBasedChange change = new StorageBasedChange(this, Layers);
 
 
-            object[] reverseArguments = { oldOffsets, oldWidth, oldHeight };
-            object[] processArguments = { Layers.Select(x => x.Offset).ToArray(), width, height };
+            object[] reverseArguments = { oldWidth, oldHeight };
+            object[] processArguments = { Layers.Select(x => new Thickness(x.OffsetX - smallestX, x.OffsetY - smallestY, 0, 0)).ToArray(), width, height };
 
 
             ResizeCanvasProcess(processArguments);
             ResizeCanvasProcess(processArguments);
 
 
-            UndoManager.AddUndoChange(new Change(
-                ResizeCanvasProcess,
+            UndoManager.AddUndoChange(change.ToChange(
+                RestoreDocumentLayersProcess,
                 reverseArguments,
                 reverseArguments,
                 ResizeCanvasProcess,
                 ResizeCanvasProcess,
                 processArguments,
                 processArguments,
@@ -154,37 +157,41 @@ namespace PixiEditor.Models.DataHolders
         /// </summary>
         /// </summary>
         public void CenterContent()
         public void CenterContent()
         {
         {
-            var layersToCenter = Layers.Where(x => x.IsActive && LayerStructureUtils.GetFinalLayerIsVisible(x, LayerStructure));
-            if (!layersToCenter.Any())
+            var layersToCenter = Layers.Where(x => x.IsActive && LayerStructureUtils.GetFinalLayerIsVisible(x, LayerStructure)).ToList();
+            if (layersToCenter.Count == 0)
             {
             {
                 return;
                 return;
             }
             }
 
 
-            DoubleCoords points = GetEdgePoints(layersToCenter);
+            List<Int32Rect> oldBounds = layersToCenter.Select(x => x.Bounds).ToList();
+
+            DoubleCoords? maybePoints = ClipLayersAndGetEdgePoints(layersToCenter);
+            if (maybePoints == null)
+                return;
+            DoubleCoords points = maybePoints.Value;
 
 
             int smallestX = points.Coords1.X;
             int smallestX = points.Coords1.X;
             int smallestY = points.Coords1.Y;
             int smallestY = points.Coords1.Y;
             int biggestX = points.Coords2.X;
             int biggestX = points.Coords2.X;
             int biggestY = points.Coords2.Y;
             int biggestY = points.Coords2.Y;
 
 
-            if (smallestX == 0 && smallestY == 0 && biggestX == 0 && biggestY == 0)
-            {
-                return;
-            }
-
             Coordinates contentCenter = CoordinatesCalculator.GetCenterPoint(points.Coords1, points.Coords2);
             Coordinates contentCenter = CoordinatesCalculator.GetCenterPoint(points.Coords1, points.Coords2);
             Coordinates documentCenter = CoordinatesCalculator.GetCenterPoint(
             Coordinates documentCenter = CoordinatesCalculator.GetCenterPoint(
                 new Coordinates(0, 0),
                 new Coordinates(0, 0),
                 new Coordinates(Width, Height));
                 new Coordinates(Width, Height));
             Coordinates moveVector = new Coordinates(documentCenter.X - contentCenter.X, documentCenter.Y - contentCenter.Y);
             Coordinates moveVector = new Coordinates(documentCenter.X - contentCenter.X, documentCenter.Y - contentCenter.Y);
 
 
-            MoveOffsets(layersToCenter, moveVector);
+            List<Int32Rect> emptyBounds = Enumerable.Repeat(Int32Rect.Empty, layersToCenter.Count).ToList();
+
+            MoveOffsets(layersToCenter, emptyBounds, moveVector);
+
+            List <Guid> guids = layersToCenter.Select(x => x.GuidValue).ToList();
             UndoManager.AddUndoChange(
             UndoManager.AddUndoChange(
                 new Change(
                 new Change(
                     MoveOffsetsProcess,
                     MoveOffsetsProcess,
-                    new object[] { layersToCenter, new Coordinates(-moveVector.X, -moveVector.Y) },
+                    new object[] { guids, oldBounds, new Coordinates(-moveVector.X, -moveVector.Y) },
                     MoveOffsetsProcess,
                     MoveOffsetsProcess,
-                    new object[] { layersToCenter, moveVector },
+                    new object[] { guids, emptyBounds, moveVector },
                     "Center content"));
                     "Center content"));
         }
         }
 
 
@@ -242,7 +249,47 @@ namespace PixiEditor.Models.DataHolders
             return 0;
             return 0;
         }
         }
 
 
-        private DoubleCoords GetEdgePoints(IEnumerable<Layer> layers)
+        private DoubleCoords? GetEdgePoints(IEnumerable<Layer> layers)
+        {
+            if (Layers.Count == 0)
+                throw new ArgumentException("Not enough layers");
+
+            int smallestX = int.MaxValue;
+            int smallestY = int.MaxValue;
+            int biggestX = int.MinValue;
+            int biggestY = int.MinValue;
+
+            bool allLayersSkipped = true;
+
+            foreach (Layer layer in layers)
+            {
+                Int32Rect bounds = layer.TightBounds;
+                if (layer.IsReset || !bounds.HasArea)
+                    continue;
+                allLayersSkipped = false;
+
+                if (layer.OffsetX + bounds.X < smallestX)
+                    smallestX = layer.OffsetX + bounds.X;
+
+                if (layer.OffsetX + bounds.X + bounds.Width > biggestX)
+                    biggestX = layer.OffsetX + bounds.X + bounds.Width;
+
+                if (layer.OffsetY + bounds.Y < smallestY)
+                    smallestY = layer.OffsetY + bounds.Y;
+
+                if (layer.OffsetY + bounds.Y + bounds.Height > biggestY)
+                    biggestY = layer.OffsetY + bounds.Y + bounds.Height;
+            }
+
+            if (allLayersSkipped)
+                return null;
+
+            return new DoubleCoords(
+                new Coordinates(smallestX, smallestY),
+                new Coordinates(biggestX, biggestY));
+        }
+
+        private DoubleCoords? ClipLayersAndGetEdgePoints(IEnumerable<Layer> layers)
         {
         {
             if (Layers.Count == 0)
             if (Layers.Count == 0)
             {
             {
@@ -254,9 +301,15 @@ namespace PixiEditor.Models.DataHolders
             int biggestX = int.MinValue;
             int biggestX = int.MinValue;
             int biggestY = int.MinValue;
             int biggestY = int.MinValue;
 
 
+            bool allLayersSkipped = true;
+
             foreach (Layer layer in layers)
             foreach (Layer layer in layers)
             {
             {
                 layer.ClipCanvas();
                 layer.ClipCanvas();
+                if (layer.IsReset)
+                    continue;
+                allLayersSkipped = false;
+
                 if (layer.OffsetX < smallestX)
                 if (layer.OffsetX < smallestX)
                 {
                 {
                     smallestX = layer.OffsetX;
                     smallestX = layer.OffsetX;
@@ -278,6 +331,9 @@ namespace PixiEditor.Models.DataHolders
                 }
                 }
             }
             }
 
 
+            if (allLayersSkipped)
+                return null;
+
             return new DoubleCoords(
             return new DoubleCoords(
                 new Coordinates(smallestX, smallestY),
                 new Coordinates(smallestX, smallestY),
                 new Coordinates(biggestX, biggestY));
                 new Coordinates(biggestX, biggestY));

+ 20 - 0
PixiEditor/Models/DataHolders/PixelSize.cs

@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace PixiEditor.Models.DataHolders
+{
+    public struct PixelSize
+    {
+        public int Width { get; set; }
+        public int Height { get; set; }
+
+        public PixelSize(int width, int height)
+        {
+            Width = width;
+            Height = height;
+        }
+    }
+}

+ 18 - 16
PixiEditor/Models/DataHolders/RecentlyOpenedDocument.cs

@@ -3,6 +3,7 @@ using PixiEditor.Models.IO;
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
 using PixiEditor.Parser;
 using PixiEditor.Parser;
 using PixiEditor.Parser.Skia;
 using PixiEditor.Parser.Skia;
+using System;
 using System.Diagnostics;
 using System.Diagnostics;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
@@ -43,11 +44,8 @@ namespace PixiEditor.Models.DataHolders
                 {
                 {
                     return "? (Corrupt)";
                     return "? (Corrupt)";
                 }
                 }
-
                 string extension = Path.GetExtension(filePath).ToLower();
                 string extension = Path.GetExtension(filePath).ToLower();
-                return extension is not (".pixi" or ".png" or ".jpg" or ".jpeg")
-                    ? $"? ({extension})"
-                    : extension;
+                return SupportedFilesHelper.IsExtensionSupported(extension) ? extension : $"? ({extension})";
             }
             }
         }
         }
 
 
@@ -91,9 +89,9 @@ namespace PixiEditor.Models.DataHolders
                               .Where(x => x.Opacity > 0.8)
                               .Where(x => x.Opacity > 0.8)
                               .Select(x => (x.ToSKImage(), new Coordinates(x.OffsetX, x.OffsetY))));
                               .Select(x => (x.ToSKImage(), new Coordinates(x.OffsetX, x.OffsetY))));
 
 
-                return surface.ToWriteableBitmap();
+                return DownscaleToMaxSize(surface.ToWriteableBitmap());
             }
             }
-            else if (FileExtension is ".png" or ".jpg" or ".jpeg")
+            else if (SupportedFilesHelper.IsExtensionSupported(FileExtension))
             {
             {
                 WriteableBitmap bitmap = null;
                 WriteableBitmap bitmap = null;
 
 
@@ -104,22 +102,26 @@ namespace PixiEditor.Models.DataHolders
                 catch
                 catch
                 {
                 {
                     corrupt = true;
                     corrupt = true;
+                    return null;
                 }
                 }
 
 
-                const int MaxWidthInPixels = 2048;
-                const int MaxHeightInPixels = 2048;
-                ImageFileMaxSizeChecker imageFileMaxSizeChecker = new ImageFileMaxSizeChecker()
-                {
-                    MaxAllowedWidthInPixels = MaxWidthInPixels,
-                    MaxAllowedHeightInPixels = MaxHeightInPixels,
-                };
+                if (bitmap == null) //prevent crash
+                    return null;
 
 
-                return imageFileMaxSizeChecker.IsFileUnderMaxSize(bitmap) ?
-                    bitmap
-                    : bitmap.Resize(width: MaxWidthInPixels, height: MaxHeightInPixels, WriteableBitmapExtensions.Interpolation.Bilinear);
+                return DownscaleToMaxSize(bitmap);
             }
             }
 
 
             return null;
             return null;
         }
         }
+
+        private WriteableBitmap DownscaleToMaxSize(WriteableBitmap bitmap)
+        {
+            if (bitmap.PixelWidth > Constants.MaxPreviewWidth || bitmap.PixelHeight > Constants.MaxPreviewHeight)
+            {
+                double factor = Math.Min(Constants.MaxPreviewWidth / (double)bitmap.PixelWidth, Constants.MaxPreviewHeight / (double)bitmap.PixelHeight);
+                return bitmap.Resize((int)(bitmap.PixelWidth * factor), (int)(bitmap.PixelHeight * factor), WriteableBitmapExtensions.Interpolation.Bilinear);
+            }
+            return bitmap;
+        }
     }
     }
 }
 }

+ 3 - 3
PixiEditor/Models/DataHolders/Selection.cs

@@ -17,10 +17,10 @@ namespace PixiEditor.Models.DataHolders
         private readonly SKColor selectionBlue;
         private readonly SKColor selectionBlue;
         private Layer selectionLayer;
         private Layer selectionLayer;
 
 
-        public Selection(Coordinates[] selectedPoints)
+        public Selection(Coordinates[] selectedPoints, PixelSize maxSize)
         {
         {
-            SelectedPoints = new System.Collections.ObjectModel.ObservableCollection<Coordinates>(selectedPoints);
-            SelectionLayer = new Layer("_selectionLayer");
+            SelectedPoints = new ObservableCollection<Coordinates>(selectedPoints);
+            SelectionLayer = new Layer("_selectionLayer", maxSize.Width, maxSize.Height);
             selectionBlue = new SKColor(142, 202, 255, 255);
             selectionBlue = new SKColor(142, 202, 255, 255);
         }
         }
 
 

+ 5 - 4
PixiEditor/Models/DataHolders/Surface.cs

@@ -64,8 +64,9 @@ namespace PixiEditor.Models.DataHolders
             if (original.PixelWidth <= 0 || original.PixelHeight <= 0)
             if (original.PixelWidth <= 0 || original.PixelHeight <= 0)
                 throw new ArgumentException("Surface dimensions must be non-zero");
                 throw new ArgumentException("Surface dimensions must be non-zero");
 
 
-            byte[] pixels = new byte[original.PixelWidth * original.PixelHeight * 4];
-            original.CopyPixels(pixels, original.PixelWidth * 4, 0);
+            int stride = (original.PixelWidth * original.Format.BitsPerPixel + 7) / 8;
+            byte[] pixels = new byte[stride * original.PixelHeight];
+            original.CopyPixels(pixels, stride, 0);
 
 
             Width = original.PixelWidth;
             Width = original.PixelWidth;
             Height = original.PixelHeight;
             Height = original.PixelHeight;
@@ -117,8 +118,8 @@ namespace PixiEditor.Models.DataHolders
         public unsafe SKColor GetSRGBPixel(int x, int y)
         public unsafe SKColor GetSRGBPixel(int x, int y)
         {
         {
             Half* ptr = (Half*)(surfaceBuffer + (x + y * Width) * 8);
             Half* ptr = (Half*)(surfaceBuffer + (x + y * Width) * 8);
-            SKColor color = (SKColor)new SKColorF((float)ptr[0], (float)ptr[1], (float)ptr[2], (float)ptr[3]);
-            return SKPMColor.UnPreMultiply(new SKPMColor((uint)color));
+            float a = (float)ptr[3];
+            return (SKColor)new SKColorF((float)ptr[0] / a, (float)ptr[1] / a, (float)ptr[2] / a, (float)ptr[3]);
         }
         }
 
 
         public void SetSRGBPixel(int x, int y, SKColor color)
         public void SetSRGBPixel(int x, int y, SKColor color)

+ 2 - 3
PixiEditor/Models/Dialogs/ConfirmationDialog.cs

@@ -11,8 +11,7 @@ namespace PixiEditor.Models.Dialogs
         {
         {
             ConfirmationPopup popup = new ConfirmationPopup
             ConfirmationPopup popup = new ConfirmationPopup
             {
             {
-                Body = message,
-                Topmost = true
+                Body = message
             };
             };
             if (popup.ShowDialog().GetValueOrDefault())
             if (popup.ShowDialog().GetValueOrDefault())
             {
             {
@@ -38,4 +37,4 @@ namespace PixiEditor.Models.Dialogs
             return ConfirmationType.Canceled;
             return ConfirmationType.Canceled;
         }
         }
     }
     }
-}
+}

+ 21 - 3
PixiEditor/Models/Dialogs/ExportFileDialog.cs

@@ -1,10 +1,14 @@
-using System.Windows;
+using PixiEditor.Models.Enums;
 using PixiEditor.Views;
 using PixiEditor.Views;
+using System.Drawing.Imaging;
+using System.Windows;
 
 
 namespace PixiEditor.Models.Dialogs
 namespace PixiEditor.Models.Dialogs
 {
 {
     public class ExportFileDialog : CustomDialog
     public class ExportFileDialog : CustomDialog
     {
     {
+        FileType _chosenFormat;
+
         private int fileHeight;
         private int fileHeight;
 
 
         private string filePath;
         private string filePath;
@@ -56,9 +60,22 @@ namespace PixiEditor.Models.Dialogs
             }
             }
         }
         }
 
 
+        public FileType ChosenFormat
+        {
+            get => _chosenFormat;
+            set
+            {
+                if (_chosenFormat != value)
+                {
+                    _chosenFormat = value;
+                    RaisePropertyChanged(nameof(ChosenFormat));
+                }
+            }
+        }
+
         public override bool ShowDialog()
         public override bool ShowDialog()
         {
         {
-            SaveFilePopup popup = new SaveFilePopup
+            ExportFilePopup popup = new ExportFilePopup
             {
             {
                 SaveWidth = FileWidth,
                 SaveWidth = FileWidth,
                 SaveHeight = FileHeight
                 SaveHeight = FileHeight
@@ -69,9 +86,10 @@ namespace PixiEditor.Models.Dialogs
                 FileWidth = popup.SaveWidth;
                 FileWidth = popup.SaveWidth;
                 FileHeight = popup.SaveHeight;
                 FileHeight = popup.SaveHeight;
                 FilePath = popup.SavePath;
                 FilePath = popup.SavePath;
+                ChosenFormat = popup.SaveFormat;
             }
             }
 
 
             return (bool)popup.DialogResult;
             return (bool)popup.DialogResult;
         }
         }
     }
     }
-}
+}

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

@@ -5,11 +5,9 @@ namespace PixiEditor.Models.Dialogs
 {
 {
     public class NewFileDialog : CustomDialog
     public class NewFileDialog : CustomDialog
     {
     {
-        public const int defaultSize = 64;
+        private int height = IPreferences.Current.GetPreference("DefaultNewFileHeight", Constants.DefaultCanvasSize);
 
 
-        private int height = IPreferences.Current.GetPreference("DefaultNewFileHeight", defaultSize);
-
-        private int width = IPreferences.Current.GetPreference("DefaultNewFileWidth", defaultSize);
+        private int width = IPreferences.Current.GetPreference("DefaultNewFileWidth", Constants.DefaultCanvasSize);
 
 
         public int Width
         public int Width
         {
         {

+ 3 - 16
PixiEditor/Models/Dialogs/NoticeDialog.cs

@@ -4,28 +4,15 @@ namespace PixiEditor.Models.Dialogs
 {
 {
     public static class NoticeDialog
     public static class NoticeDialog
     {
     {
-        public static void Show(string message)
-        {
-            NoticePopup popup = new ()
-            {
-                Body = message,
-                Title = string.Empty,
-                Topmost = true
-            };
-
-            popup.ShowDialog();
-        }
-
         public static void Show(string message, string title)
         public static void Show(string message, string title)
         {
         {
-            NoticePopup popup = new ()
+            NoticePopup popup = new()
             {
             {
                 Body = message,
                 Body = message,
-                Title = title,
-                Topmost = true
+                Title = title
             };
             };
 
 
             popup.ShowDialog();
             popup.ShowDialog();
         }
         }
     }
     }
-}
+}

+ 21 - 23
PixiEditor/Models/Dialogs/ResizeDocumentDialog.cs

@@ -50,41 +50,39 @@ namespace PixiEditor.Models.Dialogs
             return OpenResizeCanvas ? ShowResizeCanvasDialog() : ShowResizeDocumentCanvas();
             return OpenResizeCanvas ? ShowResizeCanvasDialog() : ShowResizeDocumentCanvas();
         }
         }
 
 
-        private bool ShowResizeDocumentCanvas()
-        {
-            ResizeDocumentPopup popup = new ResizeDocumentPopup
+        bool ShowDialog<T>()
+            where T : ResizeablePopup, new()
+        {
+            var popup = new T()
             {
             {
-                NewHeight = Height,
-                NewWidth = Width
+                NewAbsoluteHeight = Height,
+                NewAbsoluteWidth = Width,
+                NewPercentageSize = 100,
+                NewSelectedUnit = SizeUnit.Pixel
             };
             };
 
 
             popup.ShowDialog();
             popup.ShowDialog();
             if (popup.DialogResult == true)
             if (popup.DialogResult == true)
             {
             {
-                Width = popup.NewWidth;
-                Height = popup.NewHeight;
+                Width = popup.NewAbsoluteWidth;
+                Height = popup.NewAbsoluteHeight;
+                if (popup is ResizeCanvasPopup resizeCanvas)
+                {
+                    ResizeAnchor = resizeCanvas.SelectedAnchorPoint;
+                }
             }
             }
 
 
             return (bool)popup.DialogResult;
             return (bool)popup.DialogResult;
         }
         }
 
 
-        private bool ShowResizeCanvasDialog()
+        private bool ShowResizeDocumentCanvas()
         {
         {
-            ResizeCanvasPopup popup = new ResizeCanvasPopup
-            {
-                NewHeight = Height,
-                NewWidth = Width
-            };
-
-            popup.ShowDialog();
-            if (popup.DialogResult == true)
-            {
-                Width = popup.NewWidth;
-                Height = popup.NewHeight;
-                ResizeAnchor = popup.SelectedAnchorPoint;
-            }
+            return ShowDialog<ResizeDocumentPopup>();
+        }
 
 
-            return (bool)popup.DialogResult;
+        private bool ShowResizeCanvasDialog()
+        {
+            return ShowDialog<ResizeCanvasPopup>();
         }
         }
     }
     }
-}
+}

+ 1 - 1
PixiEditor/Models/Enums/FileType.cs

@@ -2,6 +2,6 @@
 {
 {
     public enum FileType
     public enum FileType
     {
     {
-        Png = 0
+        Unset, Pixi, Png, Jpeg, Bmp, Gif
     }
     }
 }
 }

+ 4 - 0
PixiEditor/Models/Enums/SizeUnit.cs

@@ -0,0 +1,4 @@
+namespace PixiEditor.Models.Enums
+{
+  public enum SizeUnit { Pixel, Percentage }
+}

+ 49 - 21
PixiEditor/Models/IO/Exporter.cs

@@ -1,11 +1,17 @@
 using Microsoft.Win32;
 using Microsoft.Win32;
+using PixiEditor.Helpers;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Dialogs;
+using PixiEditor.Models.Enums;
 using SkiaSharp;
 using SkiaSharp;
 using System;
 using System;
+using System.Collections.Generic;
+using System.Drawing.Imaging;
 using System.IO;
 using System.IO;
 using System.IO.Compression;
 using System.IO.Compression;
+using System.Linq;
+using System.Reflection;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using System.Windows;
 using System.Windows;
 using System.Windows.Media.Imaging;
 using System.Windows.Media.Imaging;
@@ -23,8 +29,8 @@ namespace PixiEditor.Models.IO
         {
         {
             SaveFileDialog dialog = new SaveFileDialog
             SaveFileDialog dialog = new SaveFileDialog
             {
             {
-                Filter = "PixiEditor Files | *.pixi",
-                DefaultExt = "pixi"
+                Filter = SupportedFilesHelper.BuildSaveFilter(true),
+                FilterIndex = 0
             };
             };
             if ((bool)dialog.ShowDialog())
             if ((bool)dialog.ShowDialog())
             {
             {
@@ -44,10 +50,39 @@ namespace PixiEditor.Models.IO
         /// <returns>Path.</returns>
         /// <returns>Path.</returns>
         public static string SaveAsEditableFile(Document document, string path)
         public static string SaveAsEditableFile(Document document, string path)
         {
         {
-            Parser.PixiParser.Serialize(ParserHelpers.ToSerializable(document), path);
+            if (Path.GetExtension(path) != Constants.NativeExtension)
+            {
+                var chosenFormat = ParseImageFormat(Path.GetExtension(path));
+                var bitmap = document.Renderer.FinalBitmap;
+                SaveAs(encodersFactory[chosenFormat](), path, bitmap.PixelWidth, bitmap.PixelHeight, bitmap);
+            }
+            else if(Directory.Exists(Path.GetDirectoryName(path)))
+            {
+                Parser.PixiParser.Serialize(ParserHelpers.ToSerializable(document), path);
+            }
+            else
+            {
+                SaveAsEditableFileWithDialog(document, out path);
+            }
+
             return path;
             return path;
         }
         }
 
 
+        public static FileType ParseImageFormat(string extension)
+        {
+            return SupportedFilesHelper.ParseImageFormat(extension);
+        }
+
+        static Dictionary<FileType, Func<BitmapEncoder>> encodersFactory = new Dictionary<FileType, Func<BitmapEncoder>>();
+
+        static Exporter()
+        {
+            encodersFactory[FileType.Png] = () => new PngBitmapEncoder();
+            encodersFactory[FileType.Jpeg] = () => new JpegBitmapEncoder();
+            encodersFactory[FileType.Bmp] = () => new BmpBitmapEncoder(); 
+            encodersFactory[FileType.Gif] = () => new GifBitmapEncoder();
+        }
+
         /// <summary>
         /// <summary>
         ///     Creates ExportFileDialog to get width, height and path of file.
         ///     Creates ExportFileDialog to get width, height and path of file.
         /// </summary>
         /// </summary>
@@ -55,22 +90,15 @@ namespace PixiEditor.Models.IO
         /// <param name="fileDimensions">Size of file.</param>
         /// <param name="fileDimensions">Size of file.</param>
         public static void Export(WriteableBitmap bitmap, Size fileDimensions)
         public static void Export(WriteableBitmap bitmap, Size fileDimensions)
         {
         {
-            ExportFileDialog info = new ExportFileDialog(fileDimensions);
-
-            // If OK on dialog has been clicked
-            if (info.ShowDialog())
-            {
-                // If sizes are incorrect
-                if (info.FileWidth < bitmap.Width || info.FileHeight < bitmap.Height)
-                {
-                    MessageBox.Show("Incorrect height or width value", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
-                    return;
-                }
+          ExportFileDialog info = new ExportFileDialog(fileDimensions);
 
 
-                SaveAsPng(info.FilePath, info.FileWidth, info.FileHeight, bitmap);
-            }
+          // If OK on dialog has been clicked
+          if (info.ShowDialog())
+          {
+            if(encodersFactory.ContainsKey(info.ChosenFormat))
+              SaveAs(encodersFactory[info.ChosenFormat](), info.FilePath, info.FileWidth, info.FileHeight, bitmap);
+          }
         }
         }
-
         public static void SaveAsGZippedBytes(string path, Surface surface)
         public static void SaveAsGZippedBytes(string path, Surface surface)
         {
         {
             SaveAsGZippedBytes(path, surface, SKRectI.Create(0, 0, surface.Width, surface.Height));
             SaveAsGZippedBytes(path, surface, SKRectI.Create(0, 0, surface.Width, surface.Height));
@@ -101,25 +129,25 @@ namespace PixiEditor.Models.IO
         /// <summary>
         /// <summary>
         ///     Saves image to PNG file.
         ///     Saves image to PNG file.
         /// </summary>
         /// </summary>
+        /// <param name="encoder">encoder to do the job.</param>
         /// <param name="savePath">Save file path.</param>
         /// <param name="savePath">Save file path.</param>
         /// <param name="exportWidth">File width.</param>
         /// <param name="exportWidth">File width.</param>
         /// <param name="exportHeight">File height.</param>
         /// <param name="exportHeight">File height.</param>
         /// <param name="bitmap">Bitmap to save.</param>
         /// <param name="bitmap">Bitmap to save.</param>
-        public static void SaveAsPng(string savePath, int exportWidth, int exportHeight, WriteableBitmap bitmap)
+        private static void SaveAs(BitmapEncoder encoder, string savePath, int exportWidth, int exportHeight, WriteableBitmap bitmap)
         {
         {
             try
             try
             {
             {
                 bitmap = bitmap.Resize(exportWidth, exportHeight, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
                 bitmap = bitmap.Resize(exportWidth, exportHeight, WriteableBitmapExtensions.Interpolation.NearestNeighbor);
-                using (FileStream stream = new FileStream(savePath, FileMode.Create))
+                using (var stream = new FileStream(savePath, FileMode.Create))
                 {
                 {
-                    PngBitmapEncoder encoder = new PngBitmapEncoder();
                     encoder.Frames.Add(BitmapFrame.Create(bitmap));
                     encoder.Frames.Add(BitmapFrame.Create(bitmap));
                     encoder.Save(stream);
                     encoder.Save(stream);
                 }
                 }
             }
             }
             catch (Exception err)
             catch (Exception err)
             {
             {
-                MessageBox.Show(err.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
+                NoticeDialog.Show(err.ToString(), "Error");
             }
             }
         }
         }
     }
     }

+ 55 - 0
PixiEditor/Models/IO/FileTypeDialogData.cs

@@ -0,0 +1,55 @@
+using PixiEditor.Models.Enums;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace PixiEditor.Models.IO
+{
+    public class FileTypeDialogData
+    {
+        public FileType FileType { get; set; }
+
+        /// <summary>
+        /// Gets or sets file type extensions e.g. {jpg,jpeg}
+        /// </summary>
+        public List<string> Extensions { get; set; }
+
+        /// <summary>
+        /// Gets file type's main extensions e.g. jpeg
+        /// </summary>
+        public string PrimaryExtension { get => Extensions.FirstOrDefault(); }
+
+        /// <summary>
+        /// Gets or sets name displayed before extension e.g. JPEG Files
+        /// </summary>
+        public string DisplayName { get; set; }
+
+        public FileTypeDialogData(FileType fileType)
+        {
+            FileType = fileType;
+            Extensions = new List<string>();
+            Extensions.Add("." + FileType.ToString().ToLower());
+            if (FileType == FileType.Jpeg)
+                Extensions.Add(".jpg");
+
+            if (fileType == FileType.Pixi)
+                DisplayName = "PixiEditor Files";
+            else
+                DisplayName = FileType.ToString() + " Images";
+        }
+
+        public string SaveFilter
+        {
+            get { return DisplayName + "|" + GetExtensionFormattedForDialog(PrimaryExtension); }
+        }
+
+        public string ExtensionsFormattedForDialog
+        {
+            get { return string.Join(";", Extensions.Select(i => GetExtensionFormattedForDialog(i))); }
+        }
+
+        string GetExtensionFormattedForDialog(string extension)
+        {
+            return "*" + extension;
+        }
+    }
+}

+ 52 - 0
PixiEditor/Models/IO/FileTypeDialogDataSet.cs

@@ -0,0 +1,52 @@
+using PixiEditor.Helpers;
+using PixiEditor.Models.Enums;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace PixiEditor.Models.IO
+{
+    public class FileTypeDialogDataSet
+    {
+        public enum SetKind { Any, Pixi, Images }
+        IEnumerable<FileTypeDialogData> fileTypes;
+        string displayName;
+
+        public FileTypeDialogDataSet(SetKind kind, IEnumerable<FileTypeDialogData> fileTypes = null)
+        {
+            if (fileTypes == null)
+                fileTypes = SupportedFilesHelper.GetAllSupportedFileTypes(true);
+            var allSupportedExtensions = fileTypes;
+            if (kind == SetKind.Any)
+            {
+                Init("Any", allSupportedExtensions);
+            }
+            else if (kind == SetKind.Pixi)
+            {
+                Init("PixiEditor Files", new[] { new FileTypeDialogData(FileType.Pixi) });
+            }
+            else if (kind == SetKind.Images)
+            {
+                Init("Image Files", allSupportedExtensions, FileType.Pixi);
+            }
+        }
+        public FileTypeDialogDataSet(string displayName, IEnumerable<FileTypeDialogData> fileTypes, FileType? fileTypeToSkip = null)
+        {
+            Init(displayName, fileTypes, fileTypeToSkip);
+        }
+
+        private void Init(string displayName, IEnumerable<FileTypeDialogData> fileTypes, FileType? fileTypeToSkip = null)
+        {
+            var copy = fileTypes.ToList();
+            if (fileTypeToSkip.HasValue)
+                copy.RemoveAll(i => i.FileType == fileTypeToSkip.Value);
+            this.fileTypes = copy;
+
+            this.displayName = displayName;
+        }
+
+        public string GetFormattedTypes()
+        {
+            return displayName + " |" + string.Join(";", this.fileTypes.Select(i => i.ExtensionsFormattedForDialog));
+        }
+    }
+}

+ 0 - 20
PixiEditor/Models/IO/ImageFileMaxSizeChecker.cs

@@ -1,20 +0,0 @@
-using System.Windows.Media.Imaging;
-
-namespace PixiEditor.Models.IO
-{
-    internal class ImageFileMaxSizeChecker
-    {
-        public int MaxAllowedWidthInPixels { get; init; } = 2048;
-        public int MaxAllowedHeightInPixels { get; init; } = 2048;
-
-        public ImageFileMaxSizeChecker()
-        {
-        }
-
-        public bool IsFileUnderMaxSize(WriteableBitmap fileToCheck)
-        {
-            return fileToCheck.PixelWidth <= MaxAllowedWidthInPixels
-                && fileToCheck.PixelHeight <= MaxAllowedHeightInPixels;
-        }
-    }
-}

+ 2 - 2
PixiEditor/Models/IO/Importer.cs

@@ -7,6 +7,7 @@ using SkiaSharp;
 using System;
 using System;
 using System.IO;
 using System.IO;
 using System.IO.Compression;
 using System.IO.Compression;
+using System.Linq;
 using System.Runtime.InteropServices;
 using System.Runtime.InteropServices;
 using System.Windows.Media.Imaging;
 using System.Windows.Media.Imaging;
 
 
@@ -87,8 +88,7 @@ namespace PixiEditor.Models.IO
 
 
         public static bool IsSupportedFile(string path)
         public static bool IsSupportedFile(string path)
         {
         {
-            path = path.ToLower();
-            return path.EndsWith(".pixi") || path.EndsWith(".png") || path.EndsWith(".jpg") || path.EndsWith(".jpeg");
+            return SupportedFilesHelper.IsSupportedFile(path);
         }
         }
 
 
         public static Surface LoadFromGZippedBytes(string path)
         public static Surface LoadFromGZippedBytes(string path)

+ 0 - 22
PixiEditor/Models/IO/PixiFileMaxSizeChecker.cs

@@ -1,22 +0,0 @@
-using PixiEditor.Parser;
-
-namespace PixiEditor.Models.IO
-{
-    internal class PixiFileMaxSizeChecker
-    {
-        public int MaxAllowedWidthInPixels { get; init; } = 1080;
-        public int MaxAllowedHeightInPixels { get; init; } = 1080;
-        public int MaxAllowedLayerCount { get; init; } = 5;
-
-        public PixiFileMaxSizeChecker()
-        {
-        }
-
-        public bool IsFileUnderMaxSize(SerializableDocument fileToCheck)
-        {
-            return fileToCheck.Width <= MaxAllowedWidthInPixels
-                && fileToCheck.Height <= MaxAllowedHeightInPixels
-                && fileToCheck.Layers.Count <= MaxAllowedLayerCount;
-        }
-    }
-}

+ 5 - 9
PixiEditor/Models/ImageManipulation/BitmapUtils.cs

@@ -185,10 +185,7 @@ namespace PixiEditor.Models.ImageManipulation
                 throw new ArgumentException("There were not the same amount of bitmaps and offsets", nameof(layerBitmaps));
                 throw new ArgumentException("There were not the same amount of bitmaps and offsets", nameof(layerBitmaps));
             }
             }
 
 
-            using Surface previewSurface = new Surface(maxPreviewWidth, maxPreviewHeight);
-            return previewSurface.ToWriteableBitmap();
-            /*
-            WriteableBitmap previewBitmap = BitmapFactory.New(width, height);
+            using Surface previewSurface = new Surface(width, height);
 
 
             var layerBitmapsEnumerator = layerBitmaps.GetEnumerator();
             var layerBitmapsEnumerator = layerBitmaps.GetEnumerator();
             var offsetsXEnumerator = offsetsX.GetEnumerator();
             var offsetsXEnumerator = offsetsX.GetEnumerator();
@@ -199,19 +196,18 @@ namespace PixiEditor.Models.ImageManipulation
                 offsetsXEnumerator.MoveNext();
                 offsetsXEnumerator.MoveNext();
                 offsetsYEnumerator.MoveNext();
                 offsetsYEnumerator.MoveNext();
 
 
-                var bitmap = layerBitmapsEnumerator.Current;
+                var bitmap = layerBitmapsEnumerator.Current.SkiaSurface.Snapshot();
                 var offsetX = offsetsXEnumerator.Current;
                 var offsetX = offsetsXEnumerator.Current;
                 var offsetY = offsetsYEnumerator.Current;
                 var offsetY = offsetsYEnumerator.Current;
 
 
-                previewBitmap.Blit(
-                    new Rect(offsetX, offsetY, bitmap.Width, bitmap.Height),
+                previewSurface.SkiaSurface.Canvas.DrawImage(
                     bitmap,
                     bitmap,
-                    new Rect(0, 0, bitmap.Width, bitmap.Height));
+                    offsetX, offsetY, Surface.BlendingPaint);
             }
             }
 
 
             int newWidth = width >= height ? maxPreviewWidth : (int)Math.Ceiling(width / ((float)height / maxPreviewHeight));
             int newWidth = width >= height ? maxPreviewWidth : (int)Math.Ceiling(width / ((float)height / maxPreviewHeight));
             int newHeight = height > width ? maxPreviewHeight : (int)Math.Ceiling(height / ((float)width / maxPreviewWidth));
             int newHeight = height > width ? maxPreviewHeight : (int)Math.Ceiling(height / ((float)width / maxPreviewWidth));
-            return previewBitmap.Redesize(newWidth, newHeight, WriteableBitmapExtensions.Interpolation.NearestNeighbor);*/
+            return previewSurface.ResizeNearestNeighbor(newWidth, newHeight).ToWriteableBitmap();
         }
         }
     }
     }
 }
 }

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

@@ -31,32 +31,38 @@ namespace PixiEditor.Models.Layers
 
 
         private string layerHighlightColor = "#666666";
         private string layerHighlightColor = "#666666";
 
 
-        public Layer(string name)
+        public Layer(string name, int maxWidth, int maxHeight)
         {
         {
             Name = name;
             Name = name;
             LayerBitmap = new Surface(1, 1);
             LayerBitmap = new Surface(1, 1);
             IsReset = true;
             IsReset = true;
             Width = 1;
             Width = 1;
             Height = 1;
             Height = 1;
+            MaxWidth = maxWidth;
+            MaxHeight = maxHeight;
             GuidValue = Guid.NewGuid();
             GuidValue = Guid.NewGuid();
         }
         }
 
 
-        public Layer(string name, int width, int height)
+        public Layer(string name, int width, int height, int maxWidth, int maxHeight)
         {
         {
             Name = name;
             Name = name;
             LayerBitmap = new Surface(width, height);
             LayerBitmap = new Surface(width, height);
             IsReset = true;
             IsReset = true;
             Width = width;
             Width = width;
             Height = height;
             Height = height;
+            MaxWidth = maxWidth;
+            MaxHeight = maxHeight;
             GuidValue = Guid.NewGuid();
             GuidValue = Guid.NewGuid();
         }
         }
 
 
-        public Layer(string name, Surface layerBitmap)
+        public Layer(string name, Surface layerBitmap, int maxWidth, int maxHeight)
         {
         {
             Name = name;
             Name = name;
             LayerBitmap = layerBitmap;
             LayerBitmap = layerBitmap;
             Width = layerBitmap.Width;
             Width = layerBitmap.Width;
             Height = layerBitmap.Height;
             Height = layerBitmap.Height;
+            MaxWidth = maxWidth;
+            MaxHeight = maxHeight;
             GuidValue = Guid.NewGuid();
             GuidValue = Guid.NewGuid();
         }
         }
 
 
@@ -208,6 +214,9 @@ namespace PixiEditor.Models.Layers
 
 
         public bool IsReset { get; private set; }
         public bool IsReset { get; private set; }
 
 
+        public Int32Rect TightBounds => GetContentDimensions();
+        public Int32Rect Bounds => new Int32Rect(OffsetX, OffsetY, Width, Height);
+
         public event EventHandler<Int32Rect> LayerBitmapChanged;
         public event EventHandler<Int32Rect> LayerBitmapChanged;
 
 
         public void InvokeLayerBitmapChange()
         public void InvokeLayerBitmapChange()
@@ -243,12 +252,10 @@ namespace PixiEditor.Models.Layers
         /// </summary>
         /// </summary>
         public Layer Clone(bool generateNewGuid = false)
         public Layer Clone(bool generateNewGuid = false)
         {
         {
-            return new Layer(Name, new Surface(LayerBitmap))
+            return new Layer(Name, new Surface(LayerBitmap), MaxWidth, MaxHeight)
             {
             {
                 IsVisible = IsVisible,
                 IsVisible = IsVisible,
                 Offset = Offset,
                 Offset = Offset,
-                MaxHeight = MaxHeight,
-                MaxWidth = MaxWidth,
                 Opacity = Opacity,
                 Opacity = Opacity,
                 IsActive = IsActive,
                 IsActive = IsActive,
                 IsRenaming = IsRenaming,
                 IsRenaming = IsRenaming,
@@ -488,7 +495,11 @@ namespace PixiEditor.Models.Layers
         public void ClipCanvas()
         public void ClipCanvas()
         {
         {
             var dimensions = GetContentDimensions();
             var dimensions = GetContentDimensions();
-            if (dimensions == Int32Rect.Empty) return;
+            if (dimensions == Int32Rect.Empty)
+            {
+                Reset();
+                return;
+            }
 
 
             ResizeCanvas(0, 0, dimensions.X, dimensions.Y, dimensions.Width, dimensions.Height);
             ResizeCanvas(0, 0, dimensions.X, dimensions.Y, dimensions.Width, dimensions.Height);
             Offset = new Thickness(OffsetX + dimensions.X, OffsetY + dimensions.Y, 0, 0);
             Offset = new Thickness(OffsetX + dimensions.X, OffsetY + dimensions.Y, 0, 0);

+ 4 - 4
PixiEditor/Models/Layers/LayerHelper.cs

@@ -57,7 +57,7 @@ namespace PixiEditor.Models.Layers
             }
             }
         }
         }
 
 
-        public static Layer MergeWith(this Layer thisLayer, Layer otherLayer, string newName, Vector documentsSize)
+        public static Layer MergeWith(this Layer thisLayer, Layer otherLayer, string newName, PixelSize documentSize)
         {
         {
             Int32Rect thisRect = new(thisLayer.OffsetX, thisLayer.OffsetY, thisLayer.Width, thisLayer.Height);
             Int32Rect thisRect = new(thisLayer.OffsetX, thisLayer.OffsetY, thisLayer.Width, thisLayer.Height);
             Int32Rect otherRect = new(otherLayer.OffsetX, otherLayer.OffsetY, otherLayer.Width, otherLayer.Height);
             Int32Rect otherRect = new(otherLayer.OffsetX, otherLayer.OffsetY, otherLayer.Width, otherLayer.Height);
@@ -66,9 +66,9 @@ namespace PixiEditor.Models.Layers
 
 
             Surface mergedBitmap = BitmapUtils.CombineLayers(combined, new Layer[] { thisLayer, otherLayer });
             Surface mergedBitmap = BitmapUtils.CombineLayers(combined, new Layer[] { thisLayer, otherLayer });
 
 
-            Layer mergedLayer = new Layer(newName, mergedBitmap)
+            Layer mergedLayer = new Layer(newName, mergedBitmap, documentSize.Width, documentSize.Height)
             {
             {
-                Offset = new Thickness(combined.X, combined.Y, 0, 0)
+                Offset = new Thickness(combined.X, combined.Y, 0, 0),
             };
             };
 
 
             return mergedLayer;
             return mergedLayer;
@@ -76,7 +76,7 @@ namespace PixiEditor.Models.Layers
 
 
         public static Layer MergeWith(this Layer thisLayer, Layer otherLayer, string newName, int documentWidth, int documentHeight)
         public static Layer MergeWith(this Layer thisLayer, Layer otherLayer, string newName, int documentWidth, int documentHeight)
         {
         {
-            return MergeWith(thisLayer, otherLayer, newName, new Vector(documentWidth, documentHeight));
+            return MergeWith(thisLayer, otherLayer, newName, new PixelSize(documentWidth, documentHeight));
         }
         }
     }
     }
 }
 }

+ 24 - 8
PixiEditor/Models/Tools/BitmapOperationTool.cs

@@ -17,6 +17,9 @@ namespace PixiEditor.Models.Tools
 
 
         public bool UseDocumentRectForUndo { get; set; } = false;
         public bool UseDocumentRectForUndo { get; set; } = false;
 
 
+        private SKRectI _rectReportedByTool;
+        private bool _customRectReported = false;
+
         private StorageBasedChange _change;
         private StorageBasedChange _change;
 
 
         public abstract void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color);
         public abstract void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color);
@@ -49,16 +52,22 @@ namespace PixiEditor.Models.Tools
             _change = null;
             _change = null;
         }
         }
 
 
+        protected void ReportCustomSessionRect(SKRectI rect)
+        {
+            _rectReportedByTool = rect;
+            _customRectReported = true;
+        }
+
         private void InitializeStorageBasedChange(SKRectI toolSessionRect)
         private void InitializeStorageBasedChange(SKRectI toolSessionRect)
         {
         {
             Document doc = ViewModels.ViewModelMain.Current.BitmapManager.ActiveDocument;
             Document doc = ViewModels.ViewModelMain.Current.BitmapManager.ActiveDocument;
-            //var toolSize = Toolbar.GetSetting<SizeSetting>("ToolSize");
-            //SKRectI finalRect = toolSessionRect;
-            //if (toolSize != null)
-            //{
-            //    int halfSize = (int)Math.Ceiling(toolSize.Value / 2f);
-            //    finalRect.Inflate(halfSize, halfSize);
-            //}
+            var toolSize = Toolbar.GetSetting<SizeSetting>("ToolSize");
+            SKRectI finalRect = toolSessionRect;
+            if (toolSize != null && toolSize.Value > 1)
+            {
+                int halfSize = (int)Math.Ceiling(toolSize.Value / 2f);
+                finalRect.Inflate(halfSize, halfSize);
+            }
 
 
             //if (toolSessionRect.IsEmpty)
             //if (toolSessionRect.IsEmpty)
             //{
             //{
@@ -71,7 +80,14 @@ namespace PixiEditor.Models.Tools
             //    finalRect = SKRectI.Create(0, 0, doc.Width, doc.Height);
             //    finalRect = SKRectI.Create(0, 0, doc.Width, doc.Height);
             //}
             //}
 
 
-            _change = new StorageBasedChange(doc, new[] { doc.ActiveLayer });
+            if (_customRectReported)
+            {
+                _customRectReported = false;
+                finalRect = _rectReportedByTool;
+                _rectReportedByTool = SKRectI.Empty;
+            }
+
+            _change = new StorageBasedChange(doc, new[] { new LayerChunk(doc.ActiveLayer, finalRect) });
         }
         }
     }
     }
 }
 }

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

@@ -10,6 +10,7 @@ namespace PixiEditor.Models.Tools
 {
 {
     public abstract class Tool : NotifyableObject
     public abstract class Tool : NotifyableObject
     {
     {
+        public Key ShortcutKey { get; set; }
         public virtual string ToolName => GetType().Name.Replace("Tool", string.Empty);
         public virtual string ToolName => GetType().Name.Replace("Tool", string.Empty);
 
 
         public virtual string DisplayName => ToolName.AddSpacesBeforeUppercaseLetters();
         public virtual string DisplayName => ToolName.AddSpacesBeforeUppercaseLetters();

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

@@ -28,7 +28,7 @@ namespace PixiEditor.Models.Tools.Tools
             Toolbar = new BrightnessToolToolbar(CorrectionFactor);
             Toolbar = new BrightnessToolToolbar(CorrectionFactor);
         }
         }
 
 
-        public override string Tooltip => "Makes pixels brighter or darker (U). Hold Ctrl to make pixels darker.";
+        public override string Tooltip => $"Makes pixels brighter or darker ({ShortcutKey}). Hold Ctrl to make pixels darker.";
 
 
         public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
         public BrightnessMode Mode { get; set; } = BrightnessMode.Default;
 
 

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

@@ -20,7 +20,7 @@ namespace PixiEditor.Models.Tools.Tools
             ActionDisplay = defaultActionDisplay;
             ActionDisplay = defaultActionDisplay;
         }
         }
 
 
-        public override string Tooltip => "Draws circle on canvas (C). Hold Shift to draw even circle.";
+        public override string Tooltip => $"Draws circle on canvas ({ShortcutKey}). Hold Shift to draw even circle.";
 
 
         public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
         public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
         {
         {
@@ -41,10 +41,11 @@ namespace PixiEditor.Models.Tools.Tools
                 CoordinatesHelper.GetSquareCoordiantes(recordedMouseMovement) :
                 CoordinatesHelper.GetSquareCoordiantes(recordedMouseMovement) :
                 (recordedMouseMovement[0], recordedMouseMovement[^1]);
                 (recordedMouseMovement[0], recordedMouseMovement[^1]);
 
 
-            DrawEllipseFromCoordinates(previewLayer, start, end, color, fill, thickness, hasFillColor);
+            var dirtyRect = DrawEllipseFromCoordinates(previewLayer, start, end, color, fill, thickness, hasFillColor);
+            ReportCustomSessionRect(SKRectI.Create(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height));
         }
         }
 
 
-        public static void DrawEllipseFromCoordinates(Layer layer, Coordinates first, Coordinates second,
+        public static Int32Rect DrawEllipseFromCoordinates(Layer layer, Coordinates first, Coordinates second,
             SKColor color, SKColor fillColor, int thickness, bool hasFillColor)
             SKColor color, SKColor fillColor, int thickness, bool hasFillColor)
         {
         {
             DoubleCoords corners = CalculateCoordinatesForShapeRotation(first, second);
             DoubleCoords corners = CalculateCoordinatesForShapeRotation(first, second);
@@ -71,6 +72,7 @@ namespace PixiEditor.Models.Tools.Tools
             }
             }
 
 
             layer.InvokeLayerBitmapChange(dirtyRect);
             layer.InvokeLayerBitmapChange(dirtyRect);
+            return dirtyRect;
         }
         }
 
 
         public static void DrawEllipseFill(Layer layer, SKColor color, List<Coordinates> outlineCoordinates)
         public static void DrawEllipseFill(Layer layer, SKColor color, List<Coordinates> outlineCoordinates)

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

@@ -28,7 +28,7 @@ namespace PixiEditor.Models.Tools.Tools
 
 
         public override bool RequiresPreciseMouseData => true;
         public override bool RequiresPreciseMouseData => true;
 
 
-        public override string Tooltip => "Picks the primary color from the canvas. (O)";
+        public override string Tooltip => $"Picks the primary color from the canvas. ({ShortcutKey})";
 
 
         public override void Use(IReadOnlyList<Coordinates> recordedMouseMovement)
         public override void Use(IReadOnlyList<Coordinates> recordedMouseMovement)
         {
         {

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

@@ -18,7 +18,7 @@ namespace PixiEditor.Models.Tools.Tools
             Toolbar = new BasicToolbar();
             Toolbar = new BasicToolbar();
             pen = new PenTool(bitmapManager);
             pen = new PenTool(bitmapManager);
         }

         }

-        public override string Tooltip => "Erasers color from pixel. (E)";
+        public override string Tooltip => $"Erasers color from pixel. ({ShortcutKey})";
 
 
         public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color)
         public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color)
         {
         {

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

@@ -20,7 +20,7 @@ namespace PixiEditor.Models.Tools.Tools
             UseDocumentRectForUndo = true;
             UseDocumentRectForUndo = true;
         }
         }
 
 
-        public override string Tooltip => "Fills area with color. (G)";
+        public override string Tooltip => $"Fills area with color. ({ShortcutKey})";
 
 
         public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color)
         public override void Use(Layer activeLayer, Layer previewLayer, IEnumerable<Layer> allLayers, IReadOnlyList<Coordinates> recordedMouseMovement, SKColor color)
         {
         {

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

@@ -25,7 +25,7 @@ namespace PixiEditor.Models.Tools.Tools
             Toolbar = new BasicToolbar();
             Toolbar = new BasicToolbar();
         }
         }
 
 
-        public override string Tooltip => "Draws line on canvas (L). Hold Shift to draw even line.";
+        public override string Tooltip => $"Draws line on canvas ({ShortcutKey}). Hold Shift to draw even line.";
 
 
         public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
         public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
         {
         {
@@ -45,10 +45,11 @@ namespace PixiEditor.Models.Tools.Tools
             if (Session.IsShiftDown)
             if (Session.IsShiftDown)
                 (start, end) = CoordinatesHelper.GetSquareOrLineCoordinates(recordedMouseMovement);
                 (start, end) = CoordinatesHelper.GetSquareOrLineCoordinates(recordedMouseMovement);
 
 
-            DrawLine(previewLayer, start, end, color, thickness, SKBlendMode.Src);
+            var dirtyRect = DrawLine(previewLayer, start, end, color, thickness, SKBlendMode.Src);
+            ReportCustomSessionRect(SKRectI.Create(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height));
         }
         }
 
 
-        public void DrawLine(
+        public Int32Rect DrawLine(
             Layer layer, Coordinates start, Coordinates end, SKColor color, int thickness, SKBlendMode blendMode,
             Layer layer, Coordinates start, Coordinates end, SKColor color, int thickness, SKBlendMode blendMode,
             SKStrokeCap strokeCap = SKStrokeCap.Butt)
             SKStrokeCap strokeCap = SKStrokeCap.Butt)
         {
         {
@@ -90,6 +91,7 @@ namespace PixiEditor.Models.Tools.Tools
             }
             }
 
 
             layer.InvokeLayerBitmapChange(dirtyRect);
             layer.InvokeLayerBitmapChange(dirtyRect);
+            return dirtyRect;
         }
         }
 
 
         private void DrawBresenhamLine(Layer layer, int x1, int y1, int x2, int y2, SKPaint paint)
         private void DrawBresenhamLine(Layer layer, int x1, int y1, int x2, int y2, SKPaint paint)

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

@@ -24,7 +24,7 @@ namespace PixiEditor.Models.Tools.Tools
         private IEnumerable<Coordinates> oldSelection;
         private IEnumerable<Coordinates> oldSelection;
         private List<Coordinates> newSelection = new List<Coordinates>();
         private List<Coordinates> newSelection = new List<Coordinates>();
 
 
-        public override string Tooltip => "Magic Wand (W). Flood's the selection";
+        public override string Tooltip => $"Magic Wand ({ShortcutKey}). Flood's the selection";
 
 
         private Layer cachedDocument;
         private Layer cachedDocument;
 
 
@@ -88,7 +88,7 @@ namespace PixiEditor.Models.Tools.Tools
             cachedDocument ??= new Layer("_CombinedLayers", BitmapUtils.CombineLayers(
             cachedDocument ??= new Layer("_CombinedLayers", BitmapUtils.CombineLayers(
                 new Int32Rect(0, 0, document.Width, document.Height),
                 new Int32Rect(0, 0, document.Width, document.Height),
                 document.Layers,
                 document.Layers,
-                document.LayerStructure));
+                document.LayerStructure), document.Width, document.Height);
         }
         }
     }
     }
 }
 }

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

@@ -40,7 +40,7 @@ namespace PixiEditor.Models.Tools.Tools
             BitmapManager = bitmapManager;
             BitmapManager = bitmapManager;
         }
         }
 
 
-        public override string Tooltip => "Moves selected pixels (V). Hold Ctrl to move all layers.";
+        public override string Tooltip => $"Moves selected pixels ({ShortcutKey}). Hold Ctrl to move all layers.";
 
 
         public override bool HideHighlight => true;
         public override bool HideHighlight => true;
 
 
@@ -69,7 +69,7 @@ namespace PixiEditor.Models.Tools.Tools
                 affectedLayers = doc.Layers.Where(x => x.IsActive && doc.GetFinalLayerIsVisible(x)).ToArray();
                 affectedLayers = doc.Layers.Where(x => x.IsActive && doc.GetFinalLayerIsVisible(x)).ToArray();
             }
             }
 
 
-            change = new StorageBasedChange(doc, affectedLayers, true);
+            change = new StorageBasedChange(doc, affectedLayers, true, true);
 
 
             Layer selLayer = selection.SelectionLayer;
             Layer selLayer = selection.SelectionLayer;
             moveStartRect = anySelection ?
             moveStartRect = anySelection ?

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

@@ -1,5 +1,4 @@
 using PixiEditor.Models.Position;
 using PixiEditor.Models.Position;
-using PixiEditor.ViewModels.SubViewModels.Main;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Windows.Input;
 using System.Windows.Input;
 
 
@@ -7,18 +6,14 @@ namespace PixiEditor.Models.Tools.Tools
 {
 {
     public class MoveViewportTool : ReadonlyTool
     public class MoveViewportTool : ReadonlyTool
     {
     {
-        private ToolsViewModel ToolsViewModel { get; }
-
-        public MoveViewportTool(ToolsViewModel toolsViewModel)
+        public MoveViewportTool()
         {
         {
             Cursor = Cursors.SizeAll;
             Cursor = Cursors.SizeAll;
             ActionDisplay = "Click and move to pan viewport.";
             ActionDisplay = "Click and move to pan viewport.";
-
-            ToolsViewModel = toolsViewModel;
         }
         }
 
 
         public override bool HideHighlight => true;
         public override bool HideHighlight => true;
-        public override string Tooltip => "Move viewport. (Space)";
+        public override string Tooltip => $"Move viewport. ({ShortcutKey})"; 
 
 
         public override void Use(IReadOnlyList<Coordinates> pixels)
         public override void Use(IReadOnlyList<Coordinates> pixels)
         {
         {

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

@@ -49,7 +49,7 @@ namespace PixiEditor.Models.Tools.Tools
             };
             };
         }
         }
 
 
-        public override string Tooltip => "Standard brush. (B)";
+        public override string Tooltip => $"Standard brush. ({ShortcutKey})";
 
 
         public bool AutomaticallyResizeCanvas { get; set; } = true;
         public bool AutomaticallyResizeCanvas { get; set; } = true;
 
 

+ 6 - 3
PixiEditor/Models/Tools/Tools/RectangleTool.cs

@@ -17,7 +17,7 @@ namespace PixiEditor.Models.Tools.Tools
             ActionDisplay = defaultActionDisplay;
             ActionDisplay = defaultActionDisplay;
         }
         }
 
 
-        public override string Tooltip => "Draws rectangle on canvas (R). Hold Shift to draw a square.";
+        public override string Tooltip => $"Draws rectangle on canvas ({ShortcutKey}). Hold Shift to draw a square.";
 
 
         public bool Filled { get; set; } = false;
         public bool Filled { get; set; } = false;
 
 
@@ -38,10 +38,11 @@ namespace PixiEditor.Models.Tools.Tools
                 var temp = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
                 var temp = Toolbar.GetSetting<ColorSetting>("FillColor").Value;
                 fillColor = new SKColor(temp.R, temp.G, temp.B, temp.A);
                 fillColor = new SKColor(temp.R, temp.G, temp.B, temp.A);
             }
             }
-            CreateRectangle(previewLayer, color, fillColor, recordedMouseMovement, thickness);
+            var dirtyRect = CreateRectangle(previewLayer, color, fillColor, recordedMouseMovement, thickness);
+            ReportCustomSessionRect(SKRectI.Create(dirtyRect.X, dirtyRect.Y, dirtyRect.Width, dirtyRect.Height));
         }
         }
 
 
-        private void CreateRectangle(Layer layer, SKColor color, SKColor? fillColor, IReadOnlyList<Coordinates> coordinates, int thickness)
+        private Int32Rect CreateRectangle(Layer layer, SKColor color, SKColor? fillColor, IReadOnlyList<Coordinates> coordinates, int thickness)
         {
         {
             var (start, end) = Session.IsShiftDown ? CoordinatesHelper.GetSquareCoordiantes(coordinates) : (coordinates[0], coordinates[^1]);
             var (start, end) = Session.IsShiftDown ? CoordinatesHelper.GetSquareCoordiantes(coordinates) : (coordinates[0], coordinates[^1]);
 
 
@@ -75,7 +76,9 @@ namespace PixiEditor.Models.Tools.Tools
                 paint.Color = color;
                 paint.Color = color;
                 layer.LayerBitmap.SkiaSurface.Canvas.DrawRect(x, y, w, h, paint);
                 layer.LayerBitmap.SkiaSurface.Canvas.DrawRect(x, y, w, h, paint);
             }
             }
+
             layer.InvokeLayerBitmapChange(dirtyRect);
             layer.InvokeLayerBitmapChange(dirtyRect);
+            return dirtyRect;
         }
         }
     }
     }
 }
 }

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

@@ -36,7 +36,7 @@ namespace PixiEditor.Models.Tools.Tools
 
 
         public SelectionType SelectionType { get; set; } = SelectionType.Add;
         public SelectionType SelectionType { get; set; } = SelectionType.Add;
 
 
-        public override string Tooltip => "Selects area. (M)";
+        public override string Tooltip => $"Selects area. ({ShortcutKey})";
 
 
         public override void BeforeUse()
         public override void BeforeUse()
         {
         {

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

@@ -18,7 +18,7 @@ namespace PixiEditor.Models.Tools.Tools
 
 
         public override bool HideHighlight => true;
         public override bool HideHighlight => true;
 
 
-        public override string Tooltip => "Zooms viewport (Z). Click to zoom in, hold alt and click to zoom out.";
+        public override string Tooltip => $"Zooms viewport ({ShortcutKey}). Click to zoom in, hold alt and click to zoom out.";
 
 
         public override void OnKeyDown(Key key)
         public override void OnKeyDown(Key key)
         {
         {

+ 107 - 81
PixiEditor/Models/Undo/StorageBasedChange.cs

@@ -1,8 +1,10 @@
-using PixiEditor.Models.DataHolders;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.IO;
 using PixiEditor.Models.IO;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Diagnostics;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Text;
 using System.Text;
@@ -26,7 +28,33 @@ namespace PixiEditor.Models.Undo
         public StorageBasedChange(Document doc, IEnumerable<Layer> layers, bool saveOnStartup = true)
         public StorageBasedChange(Document doc, IEnumerable<Layer> layers, bool saveOnStartup = true)
         {
         {
             Document = doc;
             Document = doc;
-            layersToStore = layers.Select(x => x.GuidValue).ToList();
+            Initialize(layers, DefaultUndoChangeLocation, saveOnStartup);
+        }
+
+        public StorageBasedChange(Document doc, IEnumerable<Layer> layers, bool useDocumentSize, bool saveOnStartup)
+        {
+            Document = doc;
+            Initialize(layers, DefaultUndoChangeLocation, saveOnStartup, useDocumentSize);
+        }
+
+        public StorageBasedChange(Document doc, IEnumerable<Layer> layers, string undoChangeLocation, bool saveOnStartup = true)
+        {
+            Document = doc;
+            Initialize(layers, undoChangeLocation, saveOnStartup);
+        }
+
+        public StorageBasedChange(Document doc, IEnumerable<LayerChunk> chunks, bool saveOnStartup = true)
+        {
+            Document = doc;
+            var chunkData = chunks as LayerChunk[] ?? chunks.ToArray();
+            LayerChunk[] layerChunks = new LayerChunk[chunkData.Length];
+            for (var i = 0; i < chunkData.Length; i++)
+            {
+                var chunk = chunkData[i];
+                layerChunks[i] = chunk;
+                layersToStore.Add(chunk.Layer.GuidValue);
+            }
+
             UndoChangeLocation = DefaultUndoChangeLocation;
             UndoChangeLocation = DefaultUndoChangeLocation;
             GenerateUndoLayers();
             GenerateUndoLayers();
             if (saveOnStartup)
             if (saveOnStartup)
@@ -35,12 +63,29 @@ namespace PixiEditor.Models.Undo
             }
             }
         }
         }
 
 
-        public StorageBasedChange(Document doc, IEnumerable<Layer> layers, string undoChangeLocation, bool saveOnStartup = true)
+        private void Initialize(IEnumerable<Layer> layers, string undoChangeLocation, bool saveOnStartup, bool useDocumentSize = false)
         {
         {
-            Document = doc;
-            layersToStore = layers.Select(x => x.GuidValue).ToList();
-            UndoChangeLocation = undoChangeLocation;
-            GenerateUndoLayers();
+            var layersArray = layers as Layer[] ?? layers.ToArray();
+            LayerChunk[] layerChunks = new LayerChunk[layersArray.Length];
+            for (var i = 0; i < layersArray.Length; i++)
+            {
+                var layer = layersArray[i];
+                int width = layer.Width;
+                int height = layer.Height;
+                int offsetX = layer.OffsetX;
+                int offsetY = layer.OffsetY;
+
+                if (useDocumentSize)
+                {
+                    width = layer.MaxWidth;
+                    height = layer.MaxHeight;
+                    offsetX = 0;
+                    offsetY = 0;
+                }
+
+                layerChunks[i] = new LayerChunk(layer, SKRectI.Create(offsetX, offsetY, width, height));
+                layersToStore.Add(layer.GuidValue);
+            }
 
 
             if (saveOnStartup)
             if (saveOnStartup)
             {
             {
@@ -64,7 +109,21 @@ namespace PixiEditor.Models.Undo
                 UndoLayer storedLayer = StoredLayers[i];
                 UndoLayer storedLayer = StoredLayers[i];
                 if (Directory.Exists(Path.GetDirectoryName(storedLayer.StoredPngLayerName)))
                 if (Directory.Exists(Path.GetDirectoryName(storedLayer.StoredPngLayerName)))
                 {
                 {
-                    Exporter.SaveAsGZippedBytes(storedLayer.StoredPngLayerName, layer.LayerBitmap);
+                    // Calculate absolute rect to relative rect
+                    SKRectI finalRect = SKRectI.Create(
+                        storedLayer.SerializedRect.Left - layer.OffsetX,
+                        storedLayer.SerializedRect.Top - layer.OffsetY,
+                        storedLayer.SerializedRect.Width,
+                        storedLayer.SerializedRect.Height);
+
+                    using var image = layer.LayerBitmap.SkiaSurface.Snapshot();
+                    using Surface targetSizeSurface = new Surface(finalRect.Width, finalRect.Height);
+
+                    targetSizeSurface.SkiaSurface.Canvas.DrawImage(image, finalRect, SKRect.Create(0, 0, finalRect.Width, finalRect.Height), Surface.ReplacingPaint);
+
+                    //DebugSavePng(targetSizeSurface, storedLayer);
+
+                    Exporter.SaveAsGZippedBytes(storedLayer.StoredPngLayerName, targetSizeSurface);
                 }
                 }
 
 
                 i++;
                 i++;
@@ -73,6 +132,19 @@ namespace PixiEditor.Models.Undo
             layersToStore = new List<Guid>();
             layersToStore = new List<Guid>();
         }
         }
 
 
+        [Conditional("DEBUG")]
+        private static void DebugSavePng(Surface surface, UndoLayer storedLayer)
+        {
+            //Debug png visualization
+            using var targetSizeImage = surface.SkiaSurface.Snapshot();
+            using (var data = targetSizeImage.Encode(SKEncodedImageFormat.Png, 100))
+            using (var stream = File.OpenWrite(storedLayer.StoredPngLayerName + ".png"))
+            {
+                // save the data to a stream
+                data.SaveTo(stream);
+            }
+        }
+
         /// <summary>
         /// <summary>
         /// Loads saved layers from disk.
         /// Loads saved layers from disk.
         /// </summary>
         /// </summary>
@@ -84,12 +156,10 @@ namespace PixiEditor.Models.Undo
             {
             {
                 UndoLayer storedLayer = StoredLayers[i];
                 UndoLayer storedLayer = StoredLayers[i];
                 var bitmap = Importer.LoadFromGZippedBytes(storedLayer.StoredPngLayerName);
                 var bitmap = Importer.LoadFromGZippedBytes(storedLayer.StoredPngLayerName);
-                layers[i] = new Layer(storedLayer.Name, bitmap)
+                layers[i] = new Layer(storedLayer.Name, bitmap, storedLayer.MaxWidth, storedLayer.MaxHeight)
                 {
                 {
                     Offset = new System.Windows.Thickness(storedLayer.OffsetX, storedLayer.OffsetY, 0, 0),
                     Offset = new System.Windows.Thickness(storedLayer.OffsetX, storedLayer.OffsetY, 0, 0),
                     Opacity = storedLayer.Opacity,
                     Opacity = storedLayer.Opacity,
-                    MaxWidth = storedLayer.MaxWidth,
-                    MaxHeight = storedLayer.MaxHeight,
                     IsVisible = storedLayer.IsVisible,
                     IsVisible = storedLayer.IsVisible,
                     IsActive = storedLayer.IsActive,
                     IsActive = storedLayer.IsActive,
                     Width = storedLayer.Width,
                     Width = storedLayer.Width,
@@ -279,8 +349,15 @@ namespace PixiEditor.Models.Undo
                 {
                 {
                     Layer layer = layers[i];
                     Layer layer = layers[i];
 
 
-                    document.RemoveLayer(data[i].LayerIndex, false);
-                    document.Layers.Insert(data[i].LayerIndex, layer);
+                    if (foundLayer != null)
+                    {
+                        ApplyChunkToLayer(foundLayer, layerData.SerializedRect, layer.LayerBitmap);
+                    }
+                    else
+                    {
+                        document.RemoveLayer(layerData.LayerIndex, false);
+                        document.Layers.Insert(layerData.LayerIndex, layer);
+                    }
 
 
                     if (data[i].IsActive)
                     if (data[i].IsActive)
                     {
                     {
@@ -294,75 +371,24 @@ namespace PixiEditor.Models.Undo
     }
     }
 }
 }
 
 
-//using PixiEditor.Models.DataHolders;
-//using PixiEditor.Models.IO;
-//using PixiEditor.Models.Layers;
-//using SkiaSharp;
-//using System;
-//using System.Collections.Generic;
-//using System.IO;
-//using System.Linq;
-//using System.Text;
-//using System.Windows;
-
-//namespace PixiEditor.Models.Undo
-//{
-//    /// <summary>
-//    ///     A class that allows to save layers on disk and load them on Undo/Redo.
-//    /// </summary>
-//    public class StorageBasedChange : IDisposable
-//    {
-//        public static string DefaultUndoChangeLocation { get; } = Path.Join(Path.GetTempPath(), "PixiEditor", Guid.NewGuid().ToString(), "UndoStack");
-
-//        public string UndoChangeLocation { get; set; }
-
-//        public UndoLayer[] StoredLayers { get; set; }
-
-//        private List<Guid> layersToStore = new List<Guid>();
-//        public Document Document { get; }
-
-//        public StorageBasedChange(Document doc, IEnumerable<Layer> layers, bool saveOnStartup = true)
-//        {
-//            Document = doc;
-//            Initialize(layers, DefaultUndoChangeLocation, saveOnStartup);
-//        }
-
-//        public StorageBasedChange(Document doc, IEnumerable<Layer> layers, string undoChangeLocation, bool saveOnStartup = true)
-//        {
-//            Document = doc;
-//            Initialize(layers, undoChangeLocation, saveOnStartup);
-//        }
-
-//        public StorageBasedChange(Document doc, IEnumerable<LayerChunk> chunks, bool saveOnStartup = true)
-//        {
-//            Document = doc;
-//            var chunkData = chunks as LayerChunk[] ?? chunks.ToArray();
-//            LayerChunk[] layerChunks = new LayerChunk[chunkData.Length];
-//            for (var i = 0; i < chunkData.Length; i++)
-//            {
-//                var chunk = chunkData[i];
-//                layerChunks[i] = chunk;
-//                layersToStore.Add(chunk.Layer.GuidValue);
-//            }
-
-//            UndoChangeLocation = DefaultUndoChangeLocation;
-//            GenerateUndoLayers(layerChunks);
-//            if (saveOnStartup)
-//            {
-//                SaveLayersOnDevice();
-//            }
-//        }
+        private static void ApplyChunkToLayer(Layer layer, SKRectI rect, Surface chunk)
+        {
+            layer.DynamicResizeAbsolute(rect.ToInt32Rect());
+            using var snapshot = chunk.SkiaSurface.Snapshot();
+            layer.LayerBitmap.SkiaSurface.Canvas.DrawImage(snapshot, new SKPoint(rect.Left - layer.OffsetX, rect.Top - layer.OffsetY), Surface.ReplacingPaint);
+            layer.InvokeLayerBitmapChange(rect.ToInt32Rect());
+        }
 
 
-//        private void Initialize(IEnumerable<Layer> layers, string undoChangeLocation, bool saveOnStartup)
-//        {
-//            var layersArray = layers as Layer[] ?? layers.ToArray();
-//            LayerChunk[] layerChunks = new LayerChunk[layersArray.Length];
-//            for (var i = 0; i < layersArray.Length; i++)
-//            {
-//                var layer = layersArray[i];
-//                layerChunks[i] = new LayerChunk(layer, SKRectI.Create(layer.OffsetX, layer.OffsetY, layer.Width, layer.Height));
-//                layersToStore.Add(layer.GuidValue);
-//            }
+        public void Dispose()
+        {
+            for (int i = 0; i < StoredLayers.Length; i++)
+            {
+                if (File.Exists(StoredLayers[i].StoredPngLayerName))
+                    File.Delete(StoredLayers[i].StoredPngLayerName);
+            }
+        }
+    }
+}
 
 
 //            UndoChangeLocation = undoChangeLocation;
 //            UndoChangeLocation = undoChangeLocation;
 //            GenerateUndoLayers(layerChunks);
 //            GenerateUndoLayers(layerChunks);

+ 5 - 3
PixiEditor/PixiEditor.csproj

@@ -186,21 +186,23 @@
 		</None>
 		</None>
 	</ItemGroup>
 	</ItemGroup>
 	<ItemGroup>
 	<ItemGroup>
-		<PackageReference Include="Dirkster.AvalonDock" Version="4.60.0" />
+		<PackageReference Include="Dirkster.AvalonDock" Version="4.60.1" />
+		<PackageReference Include="ByteSize" Version="2.1.1" />
 		<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
 		<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
 		<PackageReference Include="Expression.Blend.Sdk">
 		<PackageReference Include="Expression.Blend.Sdk">
 			<Version>1.0.2</Version>
 			<Version>1.0.2</Version>
 			<NoWarn>NU1701</NoWarn>
 			<NoWarn>NU1701</NoWarn>
 		</PackageReference>
 		</PackageReference>
+		<PackageReference Include="Hardware.Info" Version="1.1.1.1" />
 		<PackageReference Include="MessagePack" Version="2.3.85" />
 		<PackageReference Include="MessagePack" Version="2.3.85" />
 		<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
 		<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
 		<PackageReference Include="MvvmLightLibs" Version="5.4.1.1">
 		<PackageReference Include="MvvmLightLibs" Version="5.4.1.1">
 			<NoWarn>NU1701</NoWarn>
 			<NoWarn>NU1701</NoWarn>
 		</PackageReference>
 		</PackageReference>
 		<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
 		<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
-		<PackageReference Include="PixiEditor.ColorPicker" Version="3.1.0" />
+		<PackageReference Include="PixiEditor.ColorPicker" Version="3.2.0" />
 		<PackageReference Include="PixiEditor.Parser" Version="2.0.0" />
 		<PackageReference Include="PixiEditor.Parser" Version="2.0.0" />
-		<PackageReference Include="PixiEditor.Parser.Skia" Version="2.0.0" />
+		<PackageReference Include="PixiEditor.Parser.Skia" Version="2.0.0.1" />
 		<PackageReference Include="SkiaSharp" Version="2.80.3" />
 		<PackageReference Include="SkiaSharp" Version="2.80.3" />
 		<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
 		<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
 		<PackageReference Include="WpfAnimatedGif" Version="2.0.2" />
 		<PackageReference Include="WpfAnimatedGif" Version="2.0.2" />

+ 2 - 2
PixiEditor/Properties/AssemblyInfo.cs

@@ -50,5 +50,5 @@ using System.Windows;
 // You can specify all the values or you can default the Build and Revision Numbers
 // You can specify all the values or you can default the Build and Revision Numbers
 // by using the '*' as shown below:
 // by using the '*' as shown below:
 // [assembly: AssemblyVersion("1.0.*")]
 // [assembly: AssemblyVersion("1.0.*")]
-[assembly: AssemblyVersion("0.1.7.1")]
-[assembly: AssemblyFileVersion("0.1.7.1")]
+[assembly: AssemblyVersion("0.1.7.2")]
+[assembly: AssemblyFileVersion("0.1.7.2")]

+ 4 - 4
PixiEditor/Styles/DarkCheckboxStyle.xaml

@@ -11,9 +11,9 @@
                 <ControlTemplate TargetType="CheckBox">
                 <ControlTemplate TargetType="CheckBox">
                     <BulletDecorator Background="Transparent">
                     <BulletDecorator Background="Transparent">
                         <BulletDecorator.Bullet>
                         <BulletDecorator.Bullet>
-                            <Border x:Name="Border" Width="20" Height="20" CornerRadius="2" Background="#FF1B1B1B"
+                            <Border x:Name="Border" Width="18" Height="18" CornerRadius="2" Background="#FF1B1B1B"
                                     BorderThickness="1">
                                     BorderThickness="1">
-                                <Path Width="9" Height="9" x:Name="CheckMark" SnapsToDevicePixels="False" Stroke="#FF0077C9" StrokeThickness="2" Data="M 0 4 L 3 8 8 0" />
+                                <Path Width="9" Height="9" x:Name="CheckMark" SnapsToDevicePixels="False" Stroke="{StaticResource UIElementBlue}" StrokeThickness="1.5" Data="M 0 4 L 3 8 8 0" />
                             </Border>
                             </Border>
                         </BulletDecorator.Bullet>
                         </BulletDecorator.Bullet>
                         <ContentPresenter Margin="4,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Left" RecognizesAccessKey="True"/>
                         <ContentPresenter Margin="4,0,0,0" VerticalAlignment="Center" HorizontalAlignment="Left" RecognizesAccessKey="True"/>
@@ -21,7 +21,7 @@
                     <ControlTemplate.Triggers>
                     <ControlTemplate.Triggers>
                         <Trigger Property="IsMouseOver" Value="False">
                         <Trigger Property="IsMouseOver" Value="False">
                             <Setter TargetName="Border" Property="Background" Value="#252525" />
                             <Setter TargetName="Border" Property="Background" Value="#252525" />
-                            <Setter TargetName="Border" Property="BorderBrush" Value="#3F3F46"/>
+                            <Setter TargetName="Border" Property="BorderBrush" Value="{StaticResource UIElementBorder}"/>
                         </Trigger>
                         </Trigger>
                         <Trigger Property="IsChecked" Value="false">
                         <Trigger Property="IsChecked" Value="false">
                             <Setter TargetName="CheckMark" Property="Visibility" Value="Collapsed"/>
                             <Setter TargetName="CheckMark" Property="Visibility" Value="Collapsed"/>
@@ -43,4 +43,4 @@
             </Setter.Value>
             </Setter.Value>
         </Setter>
         </Setter>
     </Style>
     </Style>
-</ResourceDictionary>
+</ResourceDictionary>

+ 15 - 2
PixiEditor/Styles/LabelStyles.xaml

@@ -5,7 +5,20 @@
     <Style TargetType="Label" x:Key="BaseLabel">
     <Style TargetType="Label" x:Key="BaseLabel">
         <Setter Property="Foreground" Value="White"/>
         <Setter Property="Foreground" Value="White"/>
     </Style>
     </Style>
-    
+
+    <Style x:Key="SettingsHeader" TargetType="Label" BasedOn="{StaticResource BaseLabel}">
+        <Setter Property="FontSize" Value="15"/>
+        <Setter Property="Padding" Value="0"/>
+        <Setter Property="VerticalAlignment" Value="Center"/>
+        <Setter Property="FontWeight" Value="DemiBold"/>
+    </Style>
+
+    <Style x:Key="SettingsText" TargetType="Label" BasedOn="{StaticResource BaseLabel}">
+        <Setter Property="FontSize" Value="12"/>
+        <Setter Property="Padding" Value="0"/>
+        <Setter Property="VerticalAlignment" Value="Center"/>
+    </Style>
+
     <Style x:Key="Header1" TargetType="Label" BasedOn="{StaticResource BaseLabel}">
     <Style x:Key="Header1" TargetType="Label" BasedOn="{StaticResource BaseLabel}">
         <Setter Property="FontSize" Value="36"/>
         <Setter Property="FontSize" Value="36"/>
         <Setter Property="Margin" Value="20"/>
         <Setter Property="Margin" Value="20"/>
@@ -19,4 +32,4 @@
     <Style x:Key="Paragraph" TargetType="Label" BasedOn="{StaticResource BaseLabel}">
     <Style x:Key="Paragraph" TargetType="Label" BasedOn="{StaticResource BaseLabel}">
         <Setter Property="Margin" Value="0 10 0 10"/>
         <Setter Property="Margin" Value="0 10 0 10"/>
     </Style>
     </Style>
-</ResourceDictionary>
+</ResourceDictionary>

+ 26 - 0
PixiEditor/Styles/PixiListBoxItemStyle.xaml

@@ -0,0 +1,26 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+    <Style TargetType="ListBoxItem" x:Key="PixiListBoxItemStyle">
+        <Setter Property="OverridesDefaultStyle" Value="True"/>
+        <Setter Property="Foreground" Value="White"/>
+        <Setter Property="Background" Value="Transparent"/>
+        <Setter Property="FontSize" Value="15"/>
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="ListBoxItem">
+                    <Border x:Name="Border" Padding="15,7" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}">
+                        <ContentPresenter Content="{TemplateBinding Content}"/>
+                    </Border>
+                    <ControlTemplate.Triggers>
+                        <Trigger Property="IsMouseOver" Value="True">
+                            <Setter Property="Background" Value="{StaticResource AlmostLightModeAccentColor}"/>
+                        </Trigger>
+                        <Trigger Property="IsSelected" Value="True">
+                            <Setter Property="Background" Value="#B00022"/>
+                        </Trigger>
+                    </ControlTemplate.Triggers>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+</ResourceDictionary>

+ 32 - 0
PixiEditor/Styles/RadioButtonStyle.xaml

@@ -0,0 +1,32 @@
+<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+    <Style TargetType="{x:Type RadioButton}" >
+        <Setter Property="Template">
+            <Setter.Value>
+                <ControlTemplate TargetType="{x:Type RadioButton}">
+                    <BulletDecorator Background="Transparent" Cursor="Hand">
+                        <BulletDecorator.Bullet>
+                            <Grid Height="16" Width="16">
+                                <!--Define size of the Bullet-->
+                                <!--The two borders-->
+                                <Border Name="RadioOuter" Background="#202020" BorderBrush="{StaticResource UIElementBorder}" BorderThickness="1" CornerRadius="10" />
+                                <Border CornerRadius="10" Margin="4" Name="RadioMark" Background="{StaticResource UIElementBlue}" Visibility="Hidden" />
+                            </Grid>
+                        </BulletDecorator.Bullet>
+                        <!--Text element-->
+                        <TextBlock Margin="3,1,0,0" Foreground="White" FontSize="12">
+                        <ContentPresenter />
+                        </TextBlock>
+                    </BulletDecorator>
+                    <!--If item is checked, trigger the visibility of the mark-->
+                    <ControlTemplate.Triggers>
+                        <Trigger Property="IsChecked" Value="true">
+                            <!--If item is checked, trigger the visibility of the mark and change the color of the selected bullet into a darker gray for better highlighting-->
+                            <Setter TargetName="RadioMark" Property="Visibility" Value="Visible"/>
+                        </Trigger>
+                    </ControlTemplate.Triggers>
+                </ControlTemplate>
+            </Setter.Value>
+        </Setter>
+    </Style>
+</ResourceDictionary>

+ 4 - 1
PixiEditor/Styles/ThemeColors.xaml

@@ -8,4 +8,7 @@
     <SolidColorBrush x:Key="DarkerAccentColor" Color="#202020" />
     <SolidColorBrush x:Key="DarkerAccentColor" Color="#202020" />
     <SolidColorBrush x:Key="BrighterAccentColor" Color="#3F3F46" />
     <SolidColorBrush x:Key="BrighterAccentColor" Color="#3F3F46" />
     <SolidColorBrush x:Key="AlmostLightModeAccentColor" Color="#4F4F4F" />
     <SolidColorBrush x:Key="AlmostLightModeAccentColor" Color="#4F4F4F" />
-</ResourceDictionary>
+    <SolidColorBrush x:Key="SelectionColor" Color="#999" />
+    <SolidColorBrush x:Key="UIElementBlue" Color="#FF0077C9"/>
+    <SolidColorBrush x:Key="UIElementBorder" Color="#3F3F46" />
+</ResourceDictionary>

+ 13 - 2
PixiEditor/Styles/ThemeStyle.xaml

@@ -5,10 +5,18 @@
         <Setter Property="Foreground" Value="White" />
         <Setter Property="Foreground" Value="White" />
     </Style>
     </Style>
 
 
+    <Style TargetType="{x:Type Grid}">
+        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
+    </Style>
+
+    <Style TargetType="{x:Type Border}">
+        <Setter Property="FocusVisualStyle" Value="{x:Null}"/>
+    </Style>
+
     <Style TargetType="Button" x:Key="BaseDarkButton">
     <Style TargetType="Button" x:Key="BaseDarkButton">
         <Setter Property="Background" Value="#404040" />
         <Setter Property="Background" Value="#404040" />
         <Setter Property="Foreground" Value="White" />
         <Setter Property="Foreground" Value="White" />
-        <Setter Property="FontSize" Value="22" />
+        <Setter Property="FontSize" Value="15" />
         <Setter Property="SnapsToDevicePixels" Value="True" />
         <Setter Property="SnapsToDevicePixels" Value="True" />
         <Setter Property="Template">
         <Setter Property="Template">
             <Setter.Value>
             <Setter.Value>
@@ -52,6 +60,8 @@
     <Style TargetType="Button" x:Key="DarkRoundButton" BasedOn="{StaticResource BaseDarkButton}">
     <Style TargetType="Button" x:Key="DarkRoundButton" BasedOn="{StaticResource BaseDarkButton}">
         <Setter Property="OverridesDefaultStyle" Value="True" />
         <Setter Property="OverridesDefaultStyle" Value="True" />
         <Setter Property="Background" Value="#303030" />
         <Setter Property="Background" Value="#303030" />
+        <Setter Property="Height" Value="28"/>
+        <Setter Property="Width" Value="70"/>
         <Setter Property="Template">
         <Setter Property="Template">
             <Setter.Value>
             <Setter.Value>
                 <ControlTemplate TargetType="Button">
                 <ControlTemplate TargetType="Button">
@@ -154,6 +164,7 @@
     <Style TargetType="TextBox" x:Key="DarkTextBoxStyle">
     <Style TargetType="TextBox" x:Key="DarkTextBoxStyle">
         <Setter Property="BorderThickness" Value="1" />
         <Setter Property="BorderThickness" Value="1" />
         <Setter Property="Foreground" Value="Snow" />
         <Setter Property="Foreground" Value="Snow" />
+        <Setter Property="SelectionBrush" Value="{StaticResource SelectionColor}" />
 
 
         <Setter Property="Template">
         <Setter Property="Template">
             <Setter.Value>
             <Setter.Value>
@@ -272,4 +283,4 @@
             </Setter.Value>
             </Setter.Value>
         </Setter>
         </Setter>
     </Style>
     </Style>
-</ResourceDictionary>
+</ResourceDictionary>

+ 62 - 0
PixiEditor/ViewModels/CrashReportViewModel.cs

@@ -0,0 +1,62 @@
+using GalaSoft.MvvmLight.CommandWpf;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Views.Dialogs;
+using System.Diagnostics;
+using System.Windows;
+
+namespace PixiEditor.ViewModels
+{
+    public class CrashReportViewModel : ViewModelBase
+    {
+        private bool hasRecoveredDocuments = true;
+
+        public CrashReport CrashReport { get; }
+
+        public string ReportText { get; }
+
+        public int DocumentCount { get; }
+
+        public RelayCommand OpenSendCrashReportCommand { get; }
+
+        public RelayCommand RecoverDocumentsCommand { get; }
+
+        public RelayCommand AttachDebuggerCommand { get; }
+
+        public bool IsDebugBuild { get; set; }
+
+        public CrashReportViewModel(CrashReport report)
+        {
+            SetIsDebug();
+
+            CrashReport = report;
+            ReportText = report.ReportText;
+            DocumentCount = report.GetDocumentCount();
+            OpenSendCrashReportCommand = new(() => new SendCrashReportWindow(CrashReport).Show());
+            RecoverDocumentsCommand = new(RecoverDocuments, () => hasRecoveredDocuments, false);
+            AttachDebuggerCommand = new(AttachDebugger);
+        }
+
+        public void RecoverDocuments()
+        {
+            MainWindow window = MainWindow.CreateWithDocuments(CrashReport.RecoverDocuments());
+
+            Application.Current.MainWindow = window;
+            window.Show();
+            hasRecoveredDocuments = false;
+        }
+
+        [Conditional("DEBUG")]
+        private void SetIsDebug()
+        {
+            IsDebugBuild = true;
+        }
+
+        private void AttachDebugger()
+        {
+            if (!Debugger.Launch())
+            {
+                MessageBox.Show("Starting debugger failed", "Starting debugger failed", MessageBoxButton.OK, MessageBoxImage.Error);
+            }
+        }
+    }
+}

+ 9 - 76
PixiEditor/ViewModels/ImportFilePopupViewModel.cs

@@ -1,6 +1,6 @@
-using Microsoft.Win32;
-using PixiEditor.Exceptions;
+using PixiEditor.Exceptions;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
+using PixiEditor.Models.IO;
 using System;
 using System;
 using System.IO;
 using System.IO;
 using System.Windows;
 using System.Windows;
@@ -15,53 +15,19 @@ namespace PixiEditor.ViewModels
         private int importHeight = 16;
         private int importHeight = 16;
 
 
         private int importWidth = 16;
         private int importWidth = 16;
-
-        private string pathButtonBorder = "#f08080";
-
-        private bool pathIsCorrect;
-
         public ImportFilePopupViewModel()
         public ImportFilePopupViewModel()
         {
         {
             CloseButtonCommand = new RelayCommand(CloseWindow);
             CloseButtonCommand = new RelayCommand(CloseWindow);
             DragMoveCommand = new RelayCommand(MoveWindow);
             DragMoveCommand = new RelayCommand(MoveWindow);
-            ChoosePathCommand = new RelayCommand(ChoosePath);
-            OkCommand = new RelayCommand(OkButton, CanClickOk);
+            OkCommand = new RelayCommand(OkButton);
         }
         }
 
 
         public RelayCommand CloseButtonCommand { get; set; }
         public RelayCommand CloseButtonCommand { get; set; }
 
 
         public RelayCommand DragMoveCommand { get; set; }
         public RelayCommand DragMoveCommand { get; set; }
 
 
-        public RelayCommand ChoosePathCommand { get; set; }
-
         public RelayCommand OkCommand { get; set; }
         public RelayCommand OkCommand { get; set; }
 
 
-        public string PathButtonBorder
-        {
-            get => pathButtonBorder;
-            set
-            {
-                if (pathButtonBorder != value)
-                {
-                    pathButtonBorder = value;
-                    RaisePropertyChanged("PathButtonBorder");
-                }
-            }
-        }
-
-        public bool PathIsCorrect
-        {
-            get => pathIsCorrect;
-            set
-            {
-                if (pathIsCorrect != value)
-                {
-                    pathIsCorrect = value;
-                    RaisePropertyChanged("PathIsCorrect");
-                }
-            }
-        }
-
         public string FilePath
         public string FilePath
         {
         {
             get => filePath;
             get => filePath;
@@ -71,7 +37,7 @@ namespace PixiEditor.ViewModels
                 {
                 {
                     filePath = value;
                     filePath = value;
                     CheckForPath(value);
                     CheckForPath(value);
-                    RaisePropertyChanged("FilePath");
+                    RaisePropertyChanged(nameof(FilePath));
                 }
                 }
             }
             }
         }
         }
@@ -84,7 +50,7 @@ namespace PixiEditor.ViewModels
                 if (importWidth != value)
                 if (importWidth != value)
                 {
                 {
                     importWidth = value;
                     importWidth = value;
-                    RaisePropertyChanged("ImportWidth");
+                    RaisePropertyChanged(nameof(ImportWidth));
                 }
                 }
             }
             }
         }
         }
@@ -97,47 +63,19 @@ namespace PixiEditor.ViewModels
                 if (importHeight != value)
                 if (importHeight != value)
                 {
                 {
                     importHeight = value;
                     importHeight = value;
-                    RaisePropertyChanged("ImportHeight");
-                }
-            }
-        }
-
-        /// <summary>
-        ///     Command that handles Path choosing to save file.
-        /// </summary>
-        /// <param name="parameter">Binding parameter.</param>
-        private void ChoosePath(object parameter)
-        {
-            OpenFileDialog path = new OpenFileDialog
-            {
-                Title = "Import path",
-                CheckPathExists = true,
-                Filter = "Image Files|*.png;*.jpeg;*.jpg"
-            };
-            if (path.ShowDialog() == true)
-            {
-                if (string.IsNullOrEmpty(path.FileName) == false)
-                {
-                    CheckForPath(path.FileName);
-                }
-                else
-                {
-                    PathButtonBorder = "#f08080";
-                    PathIsCorrect = false;
+                    RaisePropertyChanged(nameof(ImportHeight));
                 }
                 }
             }
             }
         }
         }
 
 
         private void CheckForPath(string path)
         private void CheckForPath(string path)
         {
         {
-            if (File.Exists(path) && (path.EndsWith(".png") || path.EndsWith(".jpeg") || path.EndsWith(".jpg")))
+            if (SupportedFilesHelper.IsSupportedFile(path))
             {
             {
                 try
                 try
                 {
                 {
-                    PathButtonBorder = "#b8f080";
-                    PathIsCorrect = true;
                     filePath = path;
                     filePath = path;
-                    BitmapImage bitmap = new BitmapImage(new Uri(path));
+                    var bitmap = new BitmapImage(new Uri(path));
                     ImportHeight = bitmap.PixelHeight;
                     ImportHeight = bitmap.PixelHeight;
                     ImportWidth = bitmap.PixelWidth;
                     ImportWidth = bitmap.PixelWidth;
                 }
                 }
@@ -168,10 +106,5 @@ namespace PixiEditor.ViewModels
             ((Window)parameter).DialogResult = true;
             ((Window)parameter).DialogResult = true;
             CloseButton(parameter);
             CloseButton(parameter);
         }
         }
-
-        private bool CanClickOk(object property)
-        {
-            return PathIsCorrect;
-        }
     }
     }
-}
+}

+ 28 - 50
PixiEditor/ViewModels/SaveFilePopupViewModel.cs

@@ -1,5 +1,8 @@
 using Microsoft.Win32;
 using Microsoft.Win32;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.IO;
+using System.IO;
 using System.Windows;
 using System.Windows;
 
 
 namespace PixiEditor.ViewModels
 namespace PixiEditor.ViewModels
@@ -7,91 +10,66 @@ namespace PixiEditor.ViewModels
     internal class SaveFilePopupViewModel : ViewModelBase
     internal class SaveFilePopupViewModel : ViewModelBase
     {
     {
         private string _filePath;
         private string _filePath;
-
-
-        private string _pathButtonBorder = "#f08080";
-
-
-        private bool _pathIsCorrect;
+        private FileType _chosenFormat;
 
 
         public SaveFilePopupViewModel()
         public SaveFilePopupViewModel()
         {
         {
             CloseButtonCommand = new RelayCommand(CloseWindow);
             CloseButtonCommand = new RelayCommand(CloseWindow);
             DragMoveCommand = new RelayCommand(MoveWindow);
             DragMoveCommand = new RelayCommand(MoveWindow);
-            ChoosePathCommand = new RelayCommand(ChoosePath);
-            OkCommand = new RelayCommand(OkButton, CanClickOk);
+            OkCommand = new RelayCommand(OkButton);
         }
         }
 
 
         public RelayCommand CloseButtonCommand { get; set; }
         public RelayCommand CloseButtonCommand { get; set; }
         public RelayCommand DragMoveCommand { get; set; }
         public RelayCommand DragMoveCommand { get; set; }
-        public RelayCommand ChoosePathCommand { get; set; }
         public RelayCommand OkCommand { get; set; }
         public RelayCommand OkCommand { get; set; }
 
 
-        public string PathButtonBorder
-        {
-            get => _pathButtonBorder;
-            set
-            {
-                if (_pathButtonBorder != value)
-                {
-                    _pathButtonBorder = value;
-                    RaisePropertyChanged("PathButtonBorder");
-                }
-            }
-        }
-
-        public bool PathIsCorrect
+        public string FilePath
         {
         {
-            get => _pathIsCorrect;
+            get => _filePath;
             set
             set
             {
             {
-                if (_pathIsCorrect != value)
+                if (_filePath != value)
                 {
                 {
-                    _pathIsCorrect = value;
-                    RaisePropertyChanged("PathIsCorrect");
+                    _filePath = value;
+                    RaisePropertyChanged(nameof(FilePath));
                 }
                 }
             }
             }
         }
         }
 
 
-        public string FilePath
-        {
-            get => _filePath;
+        public FileType ChosenFormat 
+        { 
+            get => _chosenFormat;
             set
             set
             {
             {
-                if (_filePath != value)
+                if (_chosenFormat != value)
                 {
                 {
-                    _filePath = value;
-                    RaisePropertyChanged("FilePath");
+                    _chosenFormat = value;
+                    RaisePropertyChanged(nameof(ChosenFormat));
                 }
                 }
             }
             }
         }
         }
-
+                
         /// <summary>
         /// <summary>
         ///     Command that handles Path choosing to save file
         ///     Command that handles Path choosing to save file
         /// </summary>
         /// </summary>
-        private void ChoosePath(object parameter)
+        private string ChoosePath()
         {
         {
             SaveFileDialog path = new SaveFileDialog
             SaveFileDialog path = new SaveFileDialog
             {
             {
                 Title = "Export path",
                 Title = "Export path",
                 CheckPathExists = true,
                 CheckPathExists = true,
-                DefaultExt = "PNG Image (.png) | *.png",
-                Filter = "PNG Image (.png) | *.png"
+                Filter = SupportedFilesHelper.BuildSaveFilter(false),
+                FilterIndex = 0
             };
             };
             if (path.ShowDialog() == true)
             if (path.ShowDialog() == true)
             {
             {
                 if (string.IsNullOrEmpty(path.FileName) == false)
                 if (string.IsNullOrEmpty(path.FileName) == false)
                 {
                 {
-                    PathButtonBorder = "#b8f080";
-                    PathIsCorrect = true;
-                    FilePath = path.FileName;
-                }
-                else
-                {
-                    PathButtonBorder = "#f08080";
-                    PathIsCorrect = false;
+                    ChosenFormat = Exporter.ParseImageFormat(Path.GetExtension(path.SafeFileName));
+                    return path.FileName;
                 }
                 }
             }
             }
+            return null;
         }
         }
 
 
         private void CloseWindow(object parameter)
         private void CloseWindow(object parameter)
@@ -107,13 +85,13 @@ namespace PixiEditor.ViewModels
 
 
         private void OkButton(object parameter)
         private void OkButton(object parameter)
         {
         {
+            string path = ChoosePath();
+            if (path == null)
+                return;
+            FilePath = path;
+            
             ((Window)parameter).DialogResult = true;
             ((Window)parameter).DialogResult = true;
             CloseButton(parameter);
             CloseButton(parameter);
         }
         }
-
-        private bool CanClickOk(object property)
-        {
-            return PathIsCorrect;
-        }
     }
     }
 }
 }

+ 1 - 24
PixiEditor/ViewModels/SettingsWindowViewModel.cs

@@ -10,20 +10,6 @@ namespace PixiEditor.ViewModels
 {
 {
     public class SettingsWindowViewModel : ViewModelBase
     public class SettingsWindowViewModel : ViewModelBase
     {
     {
-        public RelayCommand SelectCategoryCommand { get; set; }
-
-        private string selectedCategory = "General";
-
-        public string SelectedCategory
-        {
-            get => selectedCategory;
-            set
-            {
-                selectedCategory = value;
-                RaisePropertyChanged(nameof(SelectedCategory));
-            }
-        }
-
         public bool ShowUpdateTab
         public bool ShowUpdateTab
         {
         {
             get
             get
@@ -41,15 +27,6 @@ namespace PixiEditor.ViewModels
         public SettingsWindowViewModel()
         public SettingsWindowViewModel()
         {
         {
             SettingsSubViewModel = new SettingsViewModel(this);
             SettingsSubViewModel = new SettingsViewModel(this);
-            SelectCategoryCommand = new RelayCommand(SelectCategory);
-        }
-
-        private void SelectCategory(object parameter)
-        {
-            if (parameter is not null && parameter is string value)
-            {
-                SelectedCategory = value;
-            }
         }
         }
     }
     }
-}
+}

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

@@ -45,7 +45,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         public void Paste(object parameter)
         public void Paste(object parameter)
         {
         {
-            ClipboardController.PasteFromClipboard();
+            if (Owner.BitmapManager.ActiveDocument == null) return;
+            ClipboardController.PasteFromClipboard(Owner.BitmapManager.ActiveDocument);
         }
         }
 
 
         private bool CanPaste(object property)
         private bool CanPaste(object property)

+ 5 - 12
PixiEditor/ViewModels/SubViewModels/Main/DebugViewModel.cs

@@ -1,6 +1,5 @@
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
 using System;
 using System;
-using System.Diagnostics;
 using System.IO;
 using System.IO;
 using System.Reflection;
 using System.Reflection;
 
 
@@ -12,30 +11,24 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         public RelayCommand OpenInstallLocationCommand { get; set; }
         public RelayCommand OpenInstallLocationCommand { get; set; }
 
 
+        public RelayCommand CrashCommand { get; set; }
+
         public DebugViewModel(ViewModelMain owner)
         public DebugViewModel(ViewModelMain owner)
             : base(owner)
             : base(owner)
         {
         {
             OpenFolderCommand = new RelayCommand(OpenFolder);
             OpenFolderCommand = new RelayCommand(OpenFolder);
             OpenInstallLocationCommand = new RelayCommand(OpenInstallLocation);
             OpenInstallLocationCommand = new RelayCommand(OpenInstallLocation);
+            CrashCommand = new RelayCommand(_ => throw new InvalidOperationException("Debug Crash"));
         }
         }
 
 
         public static void OpenFolder(object parameter)
         public static void OpenFolder(object parameter)
         {
         {
-            OpenShellExecute((string)parameter);
+            ProcessHelpers.ShellExecuteEV(parameter as string);
         }
         }
 
 
         public static void OpenInstallLocation(object parameter)
         public static void OpenInstallLocation(object parameter)
         {
         {
-            OpenShellExecute(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
-        }
-
-        private static void OpenShellExecute(string path)
-        {
-            ProcessStartInfo startInfo = new (Environment.ExpandEnvironmentVariables(path));
-
-            startInfo.UseShellExecute = true;
-
-            Process.Start(startInfo);
+            ProcessHelpers.ShellExecuteEV(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
         }
         }
     }
     }
 }
 }

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

@@ -1,8 +1,8 @@
-using System;
-using DiscordRPC;
+using DiscordRPC;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Helpers.Extensions;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.UserPreferences;
 using PixiEditor.Models.UserPreferences;
+using System;
 
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
 {
@@ -158,7 +158,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 Assets = new Assets
                 Assets = new Assets
                 {
                 {
                     LargeImageKey = "editorlogo",
                     LargeImageKey = "editorlogo",
-                    LargeImageText = "You discovered PixiEditor's logo",
+                    LargeImageText = "You've discovered PixiEditor's logo",
                     SmallImageKey = "github",
                     SmallImageKey = "github",
                     SmallImageText = "Download PixiEditor on GitHub (github.com/PixiEditor/PixiEditor)!"
                     SmallImageText = "Download PixiEditor on GitHub (github.com/PixiEditor/PixiEditor)!"
                 },
                 },
@@ -210,4 +210,4 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Enabled = false;
             Enabled = false;
         }
         }
     }
     }
-}
+}

+ 3 - 2
PixiEditor/ViewModels/SubViewModels/Main/DocumentViewModel.cs

@@ -10,7 +10,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 {
 {
     public class DocumentViewModel : SubViewModel<ViewModelMain>
     public class DocumentViewModel : SubViewModel<ViewModelMain>
     {
     {
-        public const string ConfirmationDialogMessage = "Document was modified. Do you want to save changes?";
+        public const string ConfirmationDialogTitle = "Unsaved changes";
+        public const string ConfirmationDialogMessage = "The document has been modified. Do you want to save changes?";
 
 
         public RelayCommand CenterContentCommand { get; set; }
         public RelayCommand CenterContentCommand { get; set; }
         public RelayCommand ClipCanvasCommand { get; set; }
         public RelayCommand ClipCanvasCommand { get; set; }
@@ -59,7 +60,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         {
         {
             if (!document.ChangesSaved)
             if (!document.ChangesSaved)
             {
             {
-                ConfirmationType result = ConfirmationDialog.Show(ConfirmationDialogMessage);
+                ConfirmationType result = ConfirmationDialog.Show(ConfirmationDialogMessage, ConfirmationDialogTitle);
                 if (result == ConfirmationType.Yes)
                 if (result == ConfirmationType.Yes)
                 {
                 {
                     Owner.FileSubViewModel.SaveDocument(false);
                     Owner.FileSubViewModel.SaveDocument(false);

+ 16 - 14
PixiEditor/ViewModels/SubViewModels/Main/FileViewModel.cs

@@ -2,6 +2,7 @@
 using Newtonsoft.Json.Linq;
 using Newtonsoft.Json.Linq;
 using PixiEditor.Exceptions;
 using PixiEditor.Exceptions;
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
+using PixiEditor.Models;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.Dialogs;
 using PixiEditor.Models.IO;
 using PixiEditor.Models.IO;
@@ -10,6 +11,7 @@ using PixiEditor.Parser;
 using PixiEditor.Views.Dialogs;
 using PixiEditor.Views.Dialogs;
 using System;
 using System;
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Drawing.Imaging;
 using System.IO;
 using System.IO;
 using System.Linq;
 using System.Linq;
 using System.Windows;
 using System.Windows;
@@ -80,7 +82,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
             if (!File.Exists(path))
             if (!File.Exists(path))
             {
             {
-                NoticeDialog.Show("The file does no longer exist at that path");
+                NoticeDialog.Show("The file does not exist", "Failed to open the file");
                 RecentlyOpened.Remove(path);
                 RecentlyOpened.Remove(path);
                 return;
                 return;
             }
             }
@@ -146,6 +148,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 Owner.BitmapManager.ActiveDocument.AddNewLayer(
                 Owner.BitmapManager.ActiveDocument.AddNewLayer(
                     "Image",
                     "Image",
                     Importer.ImportImage(dialog.FilePath, dialog.FileWidth, dialog.FileHeight));
                     Importer.ImportImage(dialog.FilePath, dialog.FileWidth, dialog.FileHeight));
+                Owner.BitmapManager.ActiveDocument.UpdatePreviewImage();
             }
             }
         }
         }
 
 
@@ -176,7 +179,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
             }
             catch (CorruptedFileException ex)
             catch (CorruptedFileException ex)
             {
             {
-                NoticeDialog.Show(ex.Message, "Failed to open file.");
+                NoticeDialog.Show(ex.Message, "Failed to open the file");
             }
             }
             catch (OldFileFormatException)
             catch (OldFileFormatException)
             {
             {
@@ -186,12 +189,13 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         private void Owner_OnStartupEvent(object sender, System.EventArgs e)
         private void Owner_OnStartupEvent(object sender, System.EventArgs e)
         {
         {
-            var lastArg = Environment.GetCommandLineArgs().Last();
-            if (Importer.IsSupportedFile(lastArg) && File.Exists(lastArg))
+            var args = Environment.GetCommandLineArgs();
+            var file = args.Last();
+            if (Importer.IsSupportedFile(file) && File.Exists(file))
             {
             {
-                Open(lastArg);
+                Open(file);
             }
             }
-            else
+            else if (Owner.BitmapManager.Documents.Count == 0 || !args.Contains("--crash"))
             {
             {
                 if (IPreferences.Current.GetPreference("ShowStartupWindow", true))
                 if (IPreferences.Current.GetPreference("ShowStartupWindow", true))
                 {
                 {
@@ -199,16 +203,15 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 }
                 }
             }
             }
         }
         }
-
+                
         private void Open(object property)
         private void Open(object property)
         {
         {
+            var filter = SupportedFilesHelper.BuildOpenFilter();
+
             OpenFileDialog dialog = new OpenFileDialog
             OpenFileDialog dialog = new OpenFileDialog
             {
             {
-                Filter =
-                "Any|*.pixi;*.png;*.jpg;*.jpeg;|" +
-                "PixiEditor Files | *.pixi|" +
-                "Image Files|*.png;*.jpg;*.jpeg;",
-                DefaultExt = "pixi"
+                Filter = filter,
+                FilterIndex = 0
             };
             };
 
 
             if ((bool)dialog.ShowDialog())
             if ((bool)dialog.ShowDialog())
@@ -244,8 +247,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         {
         {
             bool paramIsAsNew = parameter != null && parameter.ToString()?.ToLower() == "asnew";
             bool paramIsAsNew = parameter != null && parameter.ToString()?.ToLower() == "asnew";
             if (paramIsAsNew ||
             if (paramIsAsNew ||
-                string.IsNullOrEmpty(Owner.BitmapManager.ActiveDocument.DocumentFilePath) ||
-                !Owner.BitmapManager.ActiveDocument.DocumentFilePath.EndsWith(".pixi"))
+                string.IsNullOrEmpty(Owner.BitmapManager.ActiveDocument.DocumentFilePath)) 
             {
             {
                 Owner.BitmapManager.ActiveDocument.SaveWithDialog();
                 Owner.BitmapManager.ActiveDocument.SaveWithDialog();
             }
             }

+ 32 - 6
PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs

@@ -24,7 +24,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         private bool restoreToolOnKeyUp = false;
         private bool restoreToolOnKeyUp = false;
 
 
         private MouseInputFilter filter = new();
         private MouseInputFilter filter = new();
-
+    
         public IoViewModel(ViewModelMain owner)
         public IoViewModel(ViewModelMain owner)
             : base(owner)
             : base(owner)
         {
         {
@@ -54,6 +54,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             {
             {
                 Owner.BitmapManager.InputTarget.OnKeyDown(key);
                 Owner.BitmapManager.InputTarget.OnKeyDown(key);
             }
             }
+
+            if (args.Key == ShortcutController.MoveViewportToolTransientChangeKey)
+                ChangeMoveViewportToolState(true);
         }
         }
 
 
         private void ProcessShortcutDown(bool isRepeat, Key key)
         private void ProcessShortcutDown(bool isRepeat, Key key)
@@ -62,7 +65,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 Owner.ShortcutController.LastShortcut.Command == Owner.ToolsSubViewModel.SelectToolCommand)
                 Owner.ShortcutController.LastShortcut.Command == Owner.ToolsSubViewModel.SelectToolCommand)
             {
             {
                 restoreToolOnKeyUp = true;
                 restoreToolOnKeyUp = true;
-                ShortcutController.BlockShortcutExecution = true;
+                ShortcutController.BlockShortcutExection("ShortcutDown");
             }
             }
 
 
             Owner.ShortcutController.KeyPressed(key, Keyboard.Modifiers);
             Owner.ShortcutController.KeyPressed(key, Keyboard.Modifiers);
@@ -79,6 +82,11 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
             if (Owner.BitmapManager.ActiveDocument != null)
             if (Owner.BitmapManager.ActiveDocument != null)
                 Owner.BitmapManager.InputTarget.OnKeyUp(key);
                 Owner.BitmapManager.InputTarget.OnKeyUp(key);
+
+            if (args.Key == ShortcutController.MoveViewportToolTransientChangeKey)
+            {
+                ChangeMoveViewportToolState(false);     
+            }
         }
         }
 
 
         private void ProcessShortcutUp(Key key)
         private void ProcessShortcutUp(Key key)
@@ -88,7 +96,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             {
             {
                 restoreToolOnKeyUp = false;
                 restoreToolOnKeyUp = false;
                 Owner.ToolsSubViewModel.SetActiveTool(Owner.ToolsSubViewModel.LastActionTool);
                 Owner.ToolsSubViewModel.SetActiveTool(Owner.ToolsSubViewModel.LastActionTool);
-                ShortcutController.BlockShortcutExecution = false;
+                ShortcutController.UnblockShortcutExecution("ShortcutDown");
             }
             }
         }
         }
 
 
@@ -107,7 +115,26 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         private void OnPreviewMiddleMouseButton(object sender)
         private void OnPreviewMiddleMouseButton(object sender)
         {
         {
-            Owner.ToolsSubViewModel.SetActiveTool<MoveViewportTool>();
+            ChangeMoveViewportToolState(true);
+        }
+
+        void ChangeMoveViewportToolState(bool setOn)
+        {
+            if (setOn)
+            {
+                var moveViewportToolIsActive = Owner.ToolsSubViewModel.ActiveTool is MoveViewportTool;
+                if (!moveViewportToolIsActive)
+                {
+                    Owner.ToolsSubViewModel.SetActiveTool<MoveViewportTool>();
+                    Owner.ToolsSubViewModel.MoveToolIsTransient = true;
+                }
+            }
+            else if (Owner.ToolsSubViewModel.LastActionTool != null && Owner.ToolsSubViewModel.MoveToolIsTransient)
+            {
+                Owner.ToolsSubViewModel.SetActiveTool(Owner.ToolsSubViewModel.LastActionTool);
+                restoreToolOnKeyUp = false;
+                ShortcutController.UnblockShortcutExecution("ShortcutDown");
+            }
         }
         }
 
 
         private void OnMouseMove(object sender, EventArgs args)
         private void OnMouseMove(object sender, EventArgs args)
@@ -128,8 +155,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
             }
             else if (button == MouseButton.Middle)
             else if (button == MouseButton.Middle)
             {
             {
-                if (Owner.ToolsSubViewModel.LastActionTool != null)
-                    Owner.ToolsSubViewModel.SetActiveTool(Owner.ToolsSubViewModel.LastActionTool);
+                ChangeMoveViewportToolState(false);
             }
             }
         }
         }
     }
     }

+ 64 - 19
PixiEditor/ViewModels/SubViewModels/Main/LayersViewModel.cs

@@ -1,7 +1,6 @@
 using PixiEditor.Helpers;
 using PixiEditor.Helpers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Layers;
 using PixiEditor.Models.Layers;
-using PixiEditor.Models.Undo;
 using PixiEditor.Views.UserControls.Layers;
 using PixiEditor.Views.UserControls.Layers;
 using System;
 using System;
 using System.Linq;
 using System.Linq;
@@ -48,7 +47,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             NewLayerCommand = new RelayCommand(NewLayer, CanCreateNewLayer);
             NewLayerCommand = new RelayCommand(NewLayer, CanCreateNewLayer);
             NewGroupCommand = new RelayCommand(NewGroup, CanCreateNewLayer);
             NewGroupCommand = new RelayCommand(NewGroup, CanCreateNewLayer);
             CreateGroupFromActiveLayersCommand = new RelayCommand(CreateGroupFromActiveLayers, CanCreateGroupFromSelected);
             CreateGroupFromActiveLayersCommand = new RelayCommand(CreateGroupFromActiveLayers, CanCreateGroupFromSelected);
-            DeleteLayersCommand = new RelayCommand(DeleteLayer, CanDeleteLayer);
+            DeleteLayersCommand = new RelayCommand(DeleteActiveLayers, CanDeleteActiveLayers);
             DuplicateLayerCommand = new RelayCommand(DuplicateLayer, CanDuplicateLayer);
             DuplicateLayerCommand = new RelayCommand(DuplicateLayer, CanDuplicateLayer);
             MoveToBackCommand = new RelayCommand(MoveLayerToBack, CanMoveToBack);
             MoveToBackCommand = new RelayCommand(MoveLayerToBack, CanMoveToBack);
             MoveToFrontCommand = new RelayCommand(MoveLayerToFront, CanMoveToFront);
             MoveToFrontCommand = new RelayCommand(MoveLayerToFront, CanMoveToFront);
@@ -57,7 +56,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             MergeWithAboveCommand = new RelayCommand(MergeWithAbove, CanMergeWithAbove);
             MergeWithAboveCommand = new RelayCommand(MergeWithAbove, CanMergeWithAbove);
             MergeWithBelowCommand = new RelayCommand(MergeWithBelow, CanMergeWithBelow);
             MergeWithBelowCommand = new RelayCommand(MergeWithBelow, CanMergeWithBelow);
             RenameGroupCommand = new RelayCommand(RenameGroup);
             RenameGroupCommand = new RelayCommand(RenameGroup);
-            DeleteGroupCommand = new RelayCommand(DeleteGroup);
+            DeleteGroupCommand = new RelayCommand(DeleteGroup, CanDeleteGroup);
             DeleteSelectedCommand = new RelayCommand(DeleteSelected, CanDeleteSelected);
             DeleteSelectedCommand = new RelayCommand(DeleteSelected, CanDeleteSelected);
             Owner.BitmapManager.DocumentChanged += BitmapManager_DocumentChanged;
             Owner.BitmapManager.DocumentChanged += BitmapManager_DocumentChanged;
         }
         }
@@ -73,22 +72,37 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         public bool CanDeleteSelected(object parameter)
         public bool CanDeleteSelected(object parameter)
         {
         {
-            return (
-                (
-                    parameter is not null and (Layer or LayerGroup)) || (Owner.BitmapManager?.ActiveDocument?.ActiveLayer != null)
-                )
-                && Owner.BitmapManager.ActiveDocument != null;
+            bool paramIsLayerOrGroup = parameter is not null and (Layer or LayerGroup);
+            bool activeLayerExists = Owner.BitmapManager?.ActiveDocument?.ActiveLayer != null;
+            bool activeDocumentExists = Owner.BitmapManager.ActiveDocument != null;
+            bool allGood = (paramIsLayerOrGroup || activeLayerExists) && activeDocumentExists;
+            if (!allGood)
+                return false;
+
+            if (parameter is Layer or LayerStructureItemContainer)
+            {
+                return CanDeleteActiveLayers(null);
+            }
+            else if (parameter is LayerGroup group)
+            {
+                return CanDeleteGroup(group.GuidValue);
+            }
+            else if (parameter is LayerGroupControl groupControl)
+            {
+                return CanDeleteGroup(groupControl.GroupGuid);
+            }
+            else if (Owner.BitmapManager.ActiveDocument.ActiveLayer != null)
+            {
+                return CanDeleteActiveLayers(null);
+            }
+            return false;
         }
         }
 
 
         public void DeleteSelected(object parameter)
         public void DeleteSelected(object parameter)
         {
         {
-            if (parameter is Layer layer)
-            {
-                DeleteLayer(Owner.BitmapManager.ActiveDocument.Layers.IndexOf(layer));
-            }
-            else if (parameter is LayerStructureItemContainer container)
+            if (parameter is Layer or LayerStructureItemContainer)
             {
             {
-                DeleteLayer(Owner.BitmapManager.ActiveDocument.Layers.IndexOf(container.Layer));
+                DeleteActiveLayers(null);
             }
             }
             else if (parameter is LayerGroup group)
             else if (parameter is LayerGroup group)
             {
             {
@@ -100,14 +114,35 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
             }
             else if (Owner.BitmapManager.ActiveDocument.ActiveLayer != null)
             else if (Owner.BitmapManager.ActiveDocument.ActiveLayer != null)
             {
             {
-                DeleteLayer(Owner.BitmapManager.ActiveDocument.Layers.IndexOf(Owner.BitmapManager.ActiveDocument.ActiveLayer));
+                DeleteActiveLayers(null);
             }
             }
         }
         }
 
 
+        public bool CanDeleteGroup(object parameter)
+        {
+            if (parameter is not Guid guid)
+                return false;
+
+            var document = Owner.BitmapManager.ActiveDocument;
+            if (document == null)
+                return false;
+
+            var group = document.LayerStructure.GetGroupByGuid(guid);
+            if (group == null)
+                return false;
+
+            return document.LayerStructure.GetGroupLayers(group).Count < document.Layers.Count;
+        }
+
         public void DeleteGroup(object parameter)
         public void DeleteGroup(object parameter)
         {
         {
             if (parameter is Guid guid)
             if (parameter is Guid guid)
             {
             {
+                foreach (var layer in Owner.BitmapManager.ActiveDocument?.Layers)
+                {
+                    layer.IsActive = false;
+                }
+
                 var group = Owner.BitmapManager.ActiveDocument?.LayerStructure.GetGroupByGuid(guid);
                 var group = Owner.BitmapManager.ActiveDocument?.LayerStructure.GetGroupByGuid(guid);
                 var layers = Owner.BitmapManager.ActiveDocument?.LayerStructure.GetGroupLayers(group);
                 var layers = Owner.BitmapManager.ActiveDocument?.LayerStructure.GetGroupLayers(group);
                 foreach (var layer in layers)
                 foreach (var layer in layers)
@@ -182,7 +217,11 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             if (doc.Layers.Count > 1)
             if (doc.Layers.Count > 1)
             {
             {
                 doc.MoveLayerInStructure(doc.Layers[^1].GuidValue, lastActiveLayerGuid, true);
                 doc.MoveLayerInStructure(doc.Layers[^1].GuidValue, lastActiveLayerGuid, true);
-                Guid? parent = parameter is Layer or LayerStructureItemContainer ? activeLayerParent?.GroupGuid : activeLayerParent.Parent?.GroupGuid;
+                Guid? parent = null;
+                if (activeLayerParent != null)
+                {
+                    parent = parameter is Layer or LayerStructureItemContainer ? activeLayerParent?.GroupGuid : activeLayerParent.Parent?.GroupGuid;
+                }
                 doc.LayerStructure.AssignParent(doc.ActiveLayerGuid, parent);
                 doc.LayerStructure.AssignParent(doc.ActiveLayerGuid, parent);
                 doc.AddLayerStructureToUndo(oldGroups);
                 doc.AddLayerStructureToUndo(oldGroups);
                 doc.UndoManager.SquashUndoChanges(3, "Add New Layer");
                 doc.UndoManager.SquashUndoChanges(3, "Add New Layer");
@@ -224,15 +263,18 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
             }
         }
         }
 
 
-        public void DeleteLayer(object parameter)
+        public void DeleteActiveLayers(object unusedParameter)
         {
         {
             var doc = Owner.BitmapManager.ActiveDocument;
             var doc = Owner.BitmapManager.ActiveDocument;
             doc.RemoveActiveLayers();
             doc.RemoveActiveLayers();
         }
         }
 
 
-        public bool CanDeleteLayer(object property)
+        public bool CanDeleteActiveLayers(object unusedParam)
         {
         {
-            return Owner.BitmapManager.ActiveDocument != null && Owner.BitmapManager.ActiveDocument.Layers.Count > 1;
+            if (Owner.BitmapManager.ActiveDocument == null)
+                return false;
+            int activeLayerCount = Owner.BitmapManager.ActiveDocument.Layers.Where(layer => layer.IsActive).Count();
+            return Owner.BitmapManager.ActiveDocument.Layers.Count > activeLayerCount;
         }
         }
 
 
         public void DuplicateLayer(object parameter)
         public void DuplicateLayer(object parameter)
@@ -247,6 +289,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         public void RenameLayer(object parameter)
         public void RenameLayer(object parameter)
         {
         {
+            if (Owner.BitmapManager.ActiveDocument == null)
+                return;
+
             int? index = (int?)parameter;
             int? index = (int?)parameter;
 
 
             if (index == null)
             if (index == null)

+ 2 - 8
PixiEditor/ViewModels/SubViewModels/Main/MiscViewModel.cs

@@ -40,18 +40,12 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         private void OpenHyperlink(object parameter)
         private void OpenHyperlink(object parameter)
         {
         {
-            if (parameter == null)
+            if (parameter is not string s)
             {
             {
                 return;
                 return;
             }
             }
 
 
-            var url = (string)parameter;
-            var processInfo = new ProcessStartInfo()
-            {
-                FileName = url,
-                UseShellExecute = true
-            };
-            Process.Start(processInfo);
+            ProcessHelpers.ShellExecute(s);
         }
         }
 
 
         private void OpenShortcutWindow(object parameter)
         private void OpenShortcutWindow(object parameter)

+ 14 - 0
PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs

@@ -23,6 +23,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         public Tool LastActionTool { get; private set; }
         public Tool LastActionTool { get; private set; }
 
 
+        public bool MoveToolIsTransient { get; set; }
+
         public Cursor ToolCursor
         public Cursor ToolCursor
         {
         {
             get => toolCursor;
             get => toolCursor;
@@ -74,6 +76,14 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Owner.BitmapManager.DocumentChanged += BitmapManager_DocumentChanged;
             Owner.BitmapManager.DocumentChanged += BitmapManager_DocumentChanged;
         }
         }
 
 
+        public void SetupToolsTooltipShortcuts(IServiceProvider services)
+        {
+            foreach (var tool in ToolSet)
+            {
+                tool.ShortcutKey = Owner.ShortcutController.GetToolShortcutKey(tool.GetType());
+            }
+        }
+
         public void SetActiveTool<T>()
         public void SetActiveTool<T>()
             where T : Tool
             where T : Tool
         {
         {
@@ -82,9 +92,11 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
         public void SetActiveTool(Tool tool)
         public void SetActiveTool(Tool tool)
         {
         {
+            MoveToolIsTransient = false;
             if (ActiveTool != null)
             if (ActiveTool != null)
             {
             {
                 activeTool.IsActive = false;
                 activeTool.IsActive = false;
+                ActiveTool.Toolbar.SaveToolbarSettings();
             }
             }
 
 
             LastActionTool = ActiveTool;
             LastActionTool = ActiveTool;
@@ -92,6 +104,8 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 
 
             ActiveTool = tool;
             ActiveTool = tool;
 
 
+            ActiveTool.Toolbar.LoadSharedSettings();
+
             if (LastActionTool != ActiveTool)
             if (LastActionTool != ActiveTool)
                 SelectedToolChanged?.Invoke(this, new SelectedToolEventArgs(LastActionTool, ActiveTool));
                 SelectedToolChanged?.Invoke(this, new SelectedToolEventArgs(LastActionTool, ActiveTool));
 
 

+ 4 - 6
PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs

@@ -127,11 +127,9 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             }
             }
             catch (Win32Exception)
             catch (Win32Exception)
             {
             {
-                MessageBox.Show(
+                NoticeDialog.Show(
                     "Couldn't update without administrator rights.",
                     "Couldn't update without administrator rights.",
-                    "Insufficient permissions",
-                    MessageBoxButton.OK,
-                    MessageBoxImage.Error);
+                    "Insufficient permissions");
             }
             }
         }
         }
 
 
@@ -185,7 +183,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
                 }
                 }
                 catch (System.Net.Http.HttpRequestException)
                 catch (System.Net.Http.HttpRequestException)
                 {
                 {
-                    NoticeDialog.Show("Could not check if there's an update available");
+                    NoticeDialog.Show("Could not check if there is an update available", "Update check failed");
                 }
                 }
 
 
                 AskToInstall();
                 AskToInstall();
@@ -210,4 +208,4 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             return selectedChannel;
             return selectedChannel;
         }
         }
     }
     }
-}
+}

+ 6 - 11
PixiEditor/ViewModels/SubViewModels/Main/WindowViewModel.cs

@@ -1,5 +1,5 @@
 using AvalonDock.Layout;
 using AvalonDock.Layout;
-using PixiEditor.Helpers;
+using GalaSoft.MvvmLight.CommandWpf;
 using System.Collections.Generic;
 using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 
 
@@ -7,9 +7,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
 {
 {
     public class WindowViewModel : SubViewModel<ViewModelMain>, ISettableOwner<ViewModelMain>
     public class WindowViewModel : SubViewModel<ViewModelMain>, ISettableOwner<ViewModelMain>
     {
     {
-        public MainWindow MainWindow { get; private set; }
-
-        public RelayCommand ShowAvalonDockWindowCommand { get; set; }
+        public RelayCommand<string> ShowAvalonDockWindowCommand { get; set; }
 
 
         public WindowViewModel()
         public WindowViewModel()
             : this(null)
             : this(null)
@@ -19,9 +17,7 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         public WindowViewModel(ViewModelMain owner)
         public WindowViewModel(ViewModelMain owner)
             : base(owner)
             : base(owner)
         {
         {
-            ShowAvalonDockWindowCommand = new RelayCommand(ShowAvalonDockWindow);
-
-            MainWindow = (MainWindow)System.Windows.Application.Current?.MainWindow;
+            ShowAvalonDockWindowCommand = new(ShowAvalonDockWindow);
         }
         }
 
 
         public void SetOwner(ViewModelMain owner)
         public void SetOwner(ViewModelMain owner)
@@ -29,11 +25,10 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Owner = owner;
             Owner = owner;
         }
         }
 
 
-        private void ShowAvalonDockWindow(object parameter)
+        private void ShowAvalonDockWindow(string id)
         {
         {
-            string id = (string)parameter;
-
-            var anchorables = new List<LayoutAnchorable>(MainWindow.LayoutRoot.Manager.Layout
+            if (MainWindow.Current?.LayoutRoot?.Manager?.Layout == null) return;
+            var anchorables = new List<LayoutAnchorable>(MainWindow.Current.LayoutRoot.Manager.Layout
                     .Descendents()
                     .Descendents()
                     .OfType<LayoutAnchorable>());
                     .OfType<LayoutAnchorable>());
 
 

+ 4 - 3
PixiEditor/ViewModels/SubViewModels/UserPreferences/Settings/FileSettings.cs

@@ -1,4 +1,5 @@
-using PixiEditor.Models.Dialogs;
+using PixiEditor.Models;
+using PixiEditor.Models.Dialogs;
 
 
 namespace PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings
 namespace PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings
 {
 {
@@ -12,7 +13,7 @@ namespace PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings
             set => RaiseAndUpdatePreference(ref showStartupWindow, value);
             set => RaiseAndUpdatePreference(ref showStartupWindow, value);
         }
         }
 
 
-        private int defaultNewFileWidth = GetPreference("DefaultNewFileWidth", NewFileDialog.defaultSize);
+        private int defaultNewFileWidth = GetPreference("DefaultNewFileWidth", Constants.DefaultCanvasSize);
 
 
         public int DefaultNewFileWidth
         public int DefaultNewFileWidth
         {
         {
@@ -25,7 +26,7 @@ namespace PixiEditor.ViewModels.SubViewModels.UserPreferences.Settings
             }
             }
         }
         }
 
 
-        private int defaultNewFileHeight = GetPreference("DefaultNewFileHeight", NewFileDialog.defaultSize);
+        private int defaultNewFileHeight = GetPreference("DefaultNewFileHeight", Constants.DefaultCanvasSize);
 
 
         public int DefaultNewFileHeight
         public int DefaultNewFileHeight
         {
         {

+ 31 - 29
PixiEditor/ViewModels/ViewModelMain.cs

@@ -163,45 +163,45 @@ namespace PixiEditor.ViewModels
             ShortcutController = new ShortcutController(
             ShortcutController = new ShortcutController(
                     new ShortcutGroup(
                     new ShortcutGroup(
                         "Tools",
                         "Tools",
-                        CreateToolShortcut<PenTool>(Key.B, "Select Pen Tool"),
-                        CreateToolShortcut<EraserTool>(Key.E, "Select Eraser Tool"),
-                        CreateToolShortcut<ColorPickerTool>(Key.O, "Select Color Picker Tool"),
-                        CreateToolShortcut<RectangleTool>(Key.R, "Select Rectangle Tool"),
-                        CreateToolShortcut<CircleTool>(Key.C, "Select Circle Tool"),
-                        CreateToolShortcut<LineTool>(Key.L, "Select Line Tool"),
-                        CreateToolShortcut<FloodFillTool>(Key.G, "Select Flood Fill Tool"),
-                        CreateToolShortcut<BrightnessTool>(Key.U, "Select Brightness Tool"),
-                        CreateToolShortcut<MoveTool>(Key.V, "Select Move Tool"),
-                        CreateToolShortcut<SelectTool>(Key.M, "Select Select Tool"),
-                        CreateToolShortcut<ZoomTool>(Key.Z, "Select Zoom Tool"),
-                        CreateToolShortcut<MoveViewportTool>(Key.Space, "Select Viewport Move Tool"),
-                        CreateToolShortcut<MagicWandTool>(Key.W, "Select Magic Wand Tool"),
+                        CreateToolShortcut<PenTool>(Key.B, "Pen"),
+                        CreateToolShortcut<EraserTool>(Key.E, "Eraser"),
+                        CreateToolShortcut<ColorPickerTool>(Key.O, "Color picker"),
+                        CreateToolShortcut<RectangleTool>(Key.R, "Rectangle"),
+                        CreateToolShortcut<CircleTool>(Key.C, "Ellipse"),
+                        CreateToolShortcut<LineTool>(Key.L, "Line"),
+                        CreateToolShortcut<FloodFillTool>(Key.G, "Flood fill"),
+                        CreateToolShortcut<BrightnessTool>(Key.U, "Brightness"),
+                        CreateToolShortcut<MoveTool>(Key.V, "Move selection"),
+                        CreateToolShortcut<SelectTool>(Key.M, "Select"),
+                        CreateToolShortcut<ZoomTool>(Key.Z, "Zoom"),
+                        CreateToolShortcut<MoveViewportTool>(Key.H, "Move viewport"),
+                        CreateToolShortcut<MagicWandTool>(Key.W, "Magic wand"),
                         new Shortcut(Key.OemPlus, ViewportSubViewModel.ZoomCommand, "Zoom in", 1),
                         new Shortcut(Key.OemPlus, ViewportSubViewModel.ZoomCommand, "Zoom in", 1),
                         new Shortcut(Key.OemMinus, ViewportSubViewModel.ZoomCommand, "Zoom out", -1),
                         new Shortcut(Key.OemMinus, ViewportSubViewModel.ZoomCommand, "Zoom out", -1),
-                        new Shortcut(Key.OemOpenBrackets, ToolsSubViewModel.ChangeToolSizeCommand, "Decrease Tool Size", -1),
-                        new Shortcut(Key.OemCloseBrackets, ToolsSubViewModel.ChangeToolSizeCommand, "Increase Tool Size", 1)),
+                        new Shortcut(Key.OemOpenBrackets, ToolsSubViewModel.ChangeToolSizeCommand, "Decrease tool size", -1),
+                        new Shortcut(Key.OemCloseBrackets, ToolsSubViewModel.ChangeToolSizeCommand, "Increase tool size", 1)),
                     new ShortcutGroup(
                     new ShortcutGroup(
                         "Editor",
                         "Editor",
-                        new Shortcut(Key.X, ColorsSubViewModel.SwapColorsCommand, "Swap primary and secondary color"),
+                        new Shortcut(Key.X, ColorsSubViewModel.SwapColorsCommand, "Swap primary and secondary colors"),
                         new Shortcut(Key.Y, UndoSubViewModel.RedoCommand, "Redo", modifier: ModifierKeys.Control),
                         new Shortcut(Key.Y, UndoSubViewModel.RedoCommand, "Redo", modifier: ModifierKeys.Control),
                         new Shortcut(Key.Z, UndoSubViewModel.UndoCommand, "Undo", modifier: ModifierKeys.Control),
                         new Shortcut(Key.Z, UndoSubViewModel.UndoCommand, "Undo", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.D, SelectionSubViewModel.DeselectCommand, "Deselect all command", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.A, SelectionSubViewModel.SelectAllCommand, "Select all command", modifier: ModifierKeys.Control),
+                        new Shortcut(Key.D, SelectionSubViewModel.DeselectCommand, "Clear selection", modifier: ModifierKeys.Control),
+                        new Shortcut(Key.A, SelectionSubViewModel.SelectAllCommand, "Select all", modifier: ModifierKeys.Control),
                         new Shortcut(Key.C, ClipboardSubViewModel.CopyCommand, "Copy", modifier: ModifierKeys.Control),
                         new Shortcut(Key.C, ClipboardSubViewModel.CopyCommand, "Copy", modifier: ModifierKeys.Control),
                         new Shortcut(Key.V, ClipboardSubViewModel.PasteCommand, "Paste", modifier: ModifierKeys.Control),
                         new Shortcut(Key.V, ClipboardSubViewModel.PasteCommand, "Paste", modifier: ModifierKeys.Control),
                         new Shortcut(Key.J, ClipboardSubViewModel.DuplicateCommand, "Duplicate", modifier: ModifierKeys.Control),
                         new Shortcut(Key.J, ClipboardSubViewModel.DuplicateCommand, "Duplicate", modifier: ModifierKeys.Control),
                         new Shortcut(Key.X, ClipboardSubViewModel.CutCommand, "Cut", modifier: ModifierKeys.Control),
                         new Shortcut(Key.X, ClipboardSubViewModel.CutCommand, "Cut", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.Delete, DocumentSubViewModel.DeletePixelsCommand, "Delete selected pixels"),
-                        new Shortcut(Key.I, DocumentSubViewModel.OpenResizePopupCommand, "Resize document", modifier: ModifierKeys.Control | ModifierKeys.Shift),
+                        new Shortcut(Key.Delete, DocumentSubViewModel.DeletePixelsCommand, "Clear selected area"),
+                        new Shortcut(Key.I, DocumentSubViewModel.OpenResizePopupCommand, "Resize image", modifier: ModifierKeys.Control | ModifierKeys.Shift),
                         new Shortcut(Key.C, DocumentSubViewModel.OpenResizePopupCommand, "Resize canvas", "canvas", ModifierKeys.Control | ModifierKeys.Shift),
                         new Shortcut(Key.C, DocumentSubViewModel.OpenResizePopupCommand, "Resize canvas", "canvas", ModifierKeys.Control | ModifierKeys.Shift),
-                        new Shortcut(Key.F11, SystemCommands.MaximizeWindowCommand, "Maximize")),
+                        new Shortcut(Key.F11, SystemCommands.MaximizeWindowCommand, "Maximize window")),
                     new ShortcutGroup(
                     new ShortcutGroup(
                         "File",
                         "File",
-                        new Shortcut(Key.O, FileSubViewModel.OpenFileCommand, "Open a Document", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.S, FileSubViewModel.ExportFileCommand, "Export as image", modifier: ModifierKeys.Control | ModifierKeys.Shift | ModifierKeys.Alt),
-                        new Shortcut(Key.S, FileSubViewModel.SaveDocumentCommand, "Save Document", modifier: ModifierKeys.Control),
-                        new Shortcut(Key.S, FileSubViewModel.SaveDocumentCommand, "Save Document As New", "AsNew", ModifierKeys.Control | ModifierKeys.Shift),
-                        new Shortcut(Key.N, FileSubViewModel.OpenNewFilePopupCommand, "Create new Document", modifier: ModifierKeys.Control)),
+                        new Shortcut(Key.O, FileSubViewModel.OpenFileCommand, "Open image", modifier: ModifierKeys.Control),
+                        new Shortcut(Key.S, FileSubViewModel.ExportFileCommand, "Export image", modifier: ModifierKeys.Control | ModifierKeys.Shift | ModifierKeys.Alt),
+                        new Shortcut(Key.S, FileSubViewModel.SaveDocumentCommand, "Save", modifier: ModifierKeys.Control),
+                        new Shortcut(Key.S, FileSubViewModel.SaveDocumentCommand, "Save as new", "AsNew", ModifierKeys.Control | ModifierKeys.Shift),
+                        new Shortcut(Key.N, FileSubViewModel.OpenNewFilePopupCommand, "Create new image", modifier: ModifierKeys.Control)),
                     new ShortcutGroup(
                     new ShortcutGroup(
                         "Layers",
                         "Layers",
                         new Shortcut(Key.F2, LayersSubViewModel.RenameLayerCommand, "Rename active layer", BitmapManager.ActiveDocument?.ActiveLayerGuid)),
                         new Shortcut(Key.F2, LayersSubViewModel.RenameLayerCommand, "Rename active layer", BitmapManager.ActiveDocument?.ActiveLayerGuid)),
@@ -226,9 +226,11 @@ namespace PixiEditor.ViewModels
             ShortcutController.ShortcutGroups.Add(
             ShortcutController.ShortcutGroups.Add(
                     new ShortcutGroup(
                     new ShortcutGroup(
                         "Misc",
                         "Misc",
-                        new Shortcut(Key.F1, MiscSubViewModel.OpenShortcutWindowCommand, "Open the shortcut window", true)));
+                        new Shortcut(Key.F1, MiscSubViewModel.OpenShortcutWindowCommand, "Open shortcuts window", true)));
 
 
             BitmapManager.PrimaryColor = ColorsSubViewModel.PrimaryColor;
             BitmapManager.PrimaryColor = ColorsSubViewModel.PrimaryColor;
+
+            ToolsSubViewModel?.SetupToolsTooltipShortcuts(services);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -332,7 +334,7 @@ namespace PixiEditor.ViewModels
 
 
             if (!BitmapManager.ActiveDocument.ChangesSaved)
             if (!BitmapManager.ActiveDocument.ChangesSaved)
             {
             {
-                result = ConfirmationDialog.Show(DocumentViewModel.ConfirmationDialogMessage);
+                result = ConfirmationDialog.Show(DocumentViewModel.ConfirmationDialogMessage, DocumentViewModel.ConfirmationDialogTitle);
                 if (result == ConfirmationType.Yes)
                 if (result == ConfirmationType.Yes)
                 {
                 {
                     FileSubViewModel.SaveDocument(false);
                     FileSubViewModel.SaveDocument(false);
@@ -371,7 +373,7 @@ namespace PixiEditor.ViewModels
 
 
         private void ActiveDocument_DocumentSizeChanged(object sender, DocumentSizeChangedEventArgs e)
         private void ActiveDocument_DocumentSizeChanged(object sender, DocumentSizeChangedEventArgs e)
         {
         {
-            BitmapManager.ActiveDocument.ActiveSelection = new Selection(Array.Empty<Coordinates>());
+            BitmapManager.ActiveDocument.ActiveSelection = new Selection(Array.Empty<Coordinates>(), new PixelSize(e.NewWidth, e.NewHeight));
             BitmapManager.ActiveDocument.ChangesSaved = false;
             BitmapManager.ActiveDocument.ChangesSaved = false;
             BitmapManager.ActiveDocument.CenterViewportTrigger.Execute(this, new Size(BitmapManager.ActiveDocument.Width, BitmapManager.ActiveDocument.Height));
             BitmapManager.ActiveDocument.CenterViewportTrigger.Execute(this, new Size(BitmapManager.ActiveDocument.Width, BitmapManager.ActiveDocument.Height));
         }
         }

+ 27 - 23
PixiEditor/Views/Dialogs/ConfirmationPopup.xaml

@@ -3,9 +3,13 @@
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         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="ConfirmationPopup" Name="popup" WindowStartupLocation="CenterScreen" Height="200" Width="500"
+        xmlns:system="clr-namespace:System;assembly=System.Runtime" 
+        xmlns:behaviours="clr-namespace:PixiEditor.Helpers.Behaviours" 
+        xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
+        xmlns:dial="clr-namespace:PixiEditor.Views.Dialogs"
+        mc:Ignorable="d" d:Title="Unsaved changes"
+        Name="popup" WindowStartupLocation="CenterScreen" 
+        Height="180" Width="400" MinHeight="180" MinWidth="400"
         WindowStyle="None">
         WindowStyle="None">
 
 
     <WindowChrome.WindowChrome>
     <WindowChrome.WindowChrome>
@@ -13,40 +17,40 @@
                       ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
                       ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
     </WindowChrome.WindowChrome>
     </WindowChrome.WindowChrome>
 
 
-    <Grid Background="{StaticResource AccentColor}" Focusable="True">
-        <Grid.RowDefinitions>
-            <RowDefinition Height="35" />
-            <RowDefinition Height="34*" />
-            <RowDefinition Height="21*" />
-        </Grid.RowDefinitions>
+    <DockPanel Background="{StaticResource AccentColor}" Focusable="True">
         <i:Interaction.Behaviors>
         <i:Interaction.Behaviors>
             <behaviours:ClearFocusOnClickBehavior/>
             <behaviours:ClearFocusOnClickBehavior/>
         </i:Interaction.Behaviors>
         </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 Margin="10,0,10,0" Height="30" Width="60"
+
+        <dial:DialogTitleBar DockPanel.Dock="Top"
+            TitleText="{Binding ElementName=popup, Path=Title}" CloseCommand="{Binding DataContext.CancelCommand, ElementName=popup}" />
+
+        <StackPanel DockPanel.Dock="Bottom" Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center"
+                    Margin="0,0,10,15">
+            <Button Margin="10,0,10,0" Width="70" IsDefault="True"
                     Command="{Binding Path=DataContext.SetResultAndCloseCommand, ElementName=popup}"
                     Command="{Binding Path=DataContext.SetResultAndCloseCommand, ElementName=popup}"
                     Style="{StaticResource DarkRoundButton}" Content="Yes">
                     Style="{StaticResource DarkRoundButton}" Content="Yes">
                 <Button.CommandParameter>
                 <Button.CommandParameter>
                     <system:Boolean>True</system:Boolean>
                     <system:Boolean>True</system:Boolean>
                 </Button.CommandParameter>
                 </Button.CommandParameter>
             </Button>
             </Button>
-            <Button Height="30" Width="60"
+            <Button Width="70"
                     Command="{Binding Path=DataContext.SetResultAndCloseCommand, ElementName=popup}"
                     Command="{Binding Path=DataContext.SetResultAndCloseCommand, ElementName=popup}"
                     Style="{StaticResource DarkRoundButton}" Content="No">
                     Style="{StaticResource DarkRoundButton}" Content="No">
                 <Button.CommandParameter>
                 <Button.CommandParameter>
                     <system:Boolean>False</system:Boolean>
                     <system:Boolean>False</system:Boolean>
                 </Button.CommandParameter>
                 </Button.CommandParameter>
             </Button>
             </Button>
-            <Button Margin="10,0,10,0" Height="30" Width="80" Style="{StaticResource DarkRoundButton}" Content="Cancel"
+            <Button Margin="10,0,10,0" Width="70" Style="{StaticResource DarkRoundButton}" Content="Cancel"
                     Command="{Binding DataContext.CancelCommand, ElementName=popup}" />
                     Command="{Binding DataContext.CancelCommand, ElementName=popup}" />
         </StackPanel>
         </StackPanel>
-    </Grid>
-</Window>
+
+        <TextBlock Grid.Row="1" 
+                   Text="{Binding Body, ElementName=popup}" 
+                   HorizontalAlignment="Center" Margin="20,0" 
+                   TextWrapping="WrapWithOverflow" 
+                   TextTrimming="WordEllipsis"
+                   TextAlignment="Center"
+                   VerticalAlignment="Center" FontSize="15" Foreground="White" d:Text="The document has been modified. Do you want to save changes?"/>
+    </DockPanel>
+</Window>

+ 56 - 0
PixiEditor/Views/Dialogs/CrashReportDialog.xaml

@@ -0,0 +1,56 @@
+<Window x:Class="PixiEditor.Views.Dialogs.CrashReportDialog"
+        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:dial="clr-namespace:PixiEditor.Views.Dialogs"
+        d:DataContext="{d:DesignInstance vm:CrashReportViewModel}"
+        mc:Ignorable="d"
+        Background="{StaticResource AccentColor}" Foreground="White"
+        Title="PixiEditor has crashed!" WindowStyle="None"
+        MinWidth="480" MinHeight="195"
+        WindowStartupLocation="CenterScreen"
+        Width="480" Height="195">
+
+    <WindowChrome.WindowChrome>
+        <WindowChrome CaptionHeight="32" GlassFrameThickness="0.1"
+                      ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
+    </WindowChrome.WindowChrome>
+
+    <Window.CommandBindings>
+        <CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}" CanExecute="CommandBinding_CanExecute"
+                        Executed="CommandBinding_Executed_Close" />
+    </Window.CommandBindings>
+
+    <Grid>
+        <Grid.RowDefinitions>
+            <RowDefinition Height="Auto"/>
+            <RowDefinition/>
+        </Grid.RowDefinitions>
+        <dial:DialogTitleBar TitleText="PixiEditor has crashed!" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}" />
+        <Grid Grid.Row="1" Margin="30,30,30,0" >
+            <StackPanel>
+                <Grid Background="{StaticResource MainColor}">
+                    <StackPanel Margin="7" VerticalAlignment="Center">
+                        <TextBlock Text="{Binding DocumentCount, StringFormat={}{0} file(s) can be recovered}"
+                       d:Text="2 file(s) can be recovered"/>
+                        <TextBlock TextWrapping="Wrap">You can help the developers fix this bug by sending a crash report that was generated (you will still be able to recover the files).</TextBlock>
+                    </StackPanel>
+                </Grid>
+
+                <WrapPanel Margin="0,20,0,5" Orientation="Horizontal" HorizontalAlignment="Center">
+                    <Button Command="{Binding OpenSendCrashReportCommand}"
+                        Width="120"
+                        Style="{StaticResource DarkRoundButton}">Send report</Button>
+                    <Button Margin="5,0,5,0" Width="120"
+                        Command="{Binding RecoverDocumentsCommand}"
+                        Style="{StaticResource DarkRoundButton}">Recover files</Button>
+                    <Button Visibility="{Binding IsDebugBuild, Converter={BoolToVisibilityConverter}}"
+                    Style="{StaticResource DarkRoundButton}" Width="170"
+                    Command="{Binding AttachDebuggerCommand}">(Re)Attach debugger</Button>
+                </WrapPanel>
+            </StackPanel>
+        </Grid>
+    </Grid>
+</Window>

+ 9 - 6
PixiEditor/Views/Dialogs/PopupTemplate.xaml.cs → PixiEditor/Views/Dialogs/CrashReportDialog.xaml.cs

@@ -1,15 +1,18 @@
-using System.Windows;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.ViewModels;
+using System.Windows;
 using System.Windows.Input;
 using System.Windows.Input;
 
 
-namespace PixiEditor.Views
+namespace PixiEditor.Views.Dialogs
 {
 {
     /// <summary>
     /// <summary>
-    ///     Interaction logic for PopupTemplate.xaml
+    /// Interaction logic for CrashReportDialog.xaml
     /// </summary>
     /// </summary>
-    public partial class PopupTemplate : Window
+    public partial class CrashReportDialog : Window
     {
     {
-        public PopupTemplate()
+        public CrashReportDialog(CrashReport report)
         {
         {
+            DataContext = new CrashReportViewModel(report);
             InitializeComponent();
             InitializeComponent();
         }
         }
 
 
@@ -23,4 +26,4 @@ namespace PixiEditor.Views
             SystemCommands.CloseWindow(this);
             SystemCommands.CloseWindow(this);
         }
         }
     }
     }
-}
+}

+ 26 - 0
PixiEditor/Views/Dialogs/DialogTitleBar.xaml

@@ -0,0 +1,26 @@
+<UserControl x:Class="PixiEditor.Views.Dialogs.DialogTitleBar"
+             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
+             xmlns:local="clr-namespace:PixiEditor.Views.Dialogs"
+             mc:Ignorable="d"
+             x:Name="uc"
+             Height="35" d:DesignWidth="300">
+    <Grid Grid.Row="0" Background="{StaticResource MainColor}">
+        <Grid.ColumnDefinitions>
+            <ColumnDefinition/>
+            <ColumnDefinition/>
+        </Grid.ColumnDefinitions>
+        <TextBlock 
+            TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center" 
+            Text="{Binding ElementName=uc, Path=TitleText}"
+            Foreground="White" 
+            FontSize="15" 
+            Margin="5,0,0,0" 
+            Grid.Column="0" Grid.ColumnSpan="2"/>
+        <Button Grid.Column="1" HorizontalAlignment="Right" Style="{StaticResource CloseButtonStyle}" IsCancel="True"
+                    WindowChrome.IsHitTestVisibleInChrome="True" ToolTip="Close"
+                    Command="{Binding ElementName=uc, Path=CloseCommand}" />
+    </Grid>
+</UserControl>

部分文件因为文件数量过多而无法显示