Browse Source

Merge branch 'master' into multiple-layers-selection

Krzysztof Krysiński 4 years ago
parent
commit
3cfe364157

+ 15 - 0
PixiEditor/Helpers/Extensions/ToolbarHelpers.cs

@@ -0,0 +1,15 @@
+using System;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+
+namespace PixiEditor.Helpers.Extensions
+{
+    public static class ToolbarHelpers
+    {
+        public static EnumSetting<TEnum> GetEnumSetting<TEnum>(this Toolbar toolbar, string name)
+            where TEnum : struct, Enum
+        {
+            return toolbar.GetSetting<EnumSetting<TEnum>>(name);
+        }
+    }
+}

+ 43 - 0
PixiEditor/Helpers/SelectionHelpers.cs

@@ -0,0 +1,43 @@
+using System.Collections.Generic;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Undo;
+
+namespace PixiEditor.Helpers
+{
+    public static class SelectionHelpers
+    {
+        public static void AddSelectionUndoStep(Document document, IEnumerable<Coordinates> oldPoints, SelectionType mode)
+        {
+#pragma warning disable SA1117 // Parameters should be on same line or separate lines. Justification: Making it readable
+            if (mode == SelectionType.New && document.ActiveSelection.SelectedPoints.Count != 0)
+            {
+                // Add empty selection as the old one get's fully deleted first
+                document.UndoManager.AddUndoChange(
+                    new Change(
+                        SetSelectionProcess, new object[] { document, new List<Coordinates>(oldPoints) },
+                        SetSelectionProcess, new object[] { document, new List<Coordinates>() }));
+                document.UndoManager.AddUndoChange(
+                    new Change(
+                        SetSelectionProcess, new object[] { document, new List<Coordinates>() },
+                        SetSelectionProcess, new object[] { document, new List<Coordinates>(document.ActiveSelection.SelectedPoints) }));
+            }
+            else
+            {
+                document.UndoManager.AddUndoChange(
+                    new Change(
+                        SetSelectionProcess, new object[] { document, new List<Coordinates>(oldPoints) },
+                        SetSelectionProcess, new object[] { document, new List<Coordinates>(document.ActiveSelection.SelectedPoints) }));
+#pragma warning restore SA1117 // Parameters should be on same line or separate lines
+            }
+        }
+
+        private static void SetSelectionProcess(object[] arguments)
+        {
+            Document document = (Document)arguments[0];
+
+            document.ActiveSelection.SetSelection((IEnumerable<Coordinates>)arguments[1], SelectionType.New);
+        }
+    }
+}

+ 2 - 0
PixiEditor/Models/DataHolders/Selection.cs

@@ -1,5 +1,6 @@
 using System.Collections.Generic;
 using System.Collections.ObjectModel;
+using System.Diagnostics;
 using System.Linq;
 using System.Windows.Media;
 using PixiEditor.Helpers;
@@ -9,6 +10,7 @@ using PixiEditor.Models.Position;
 
 namespace PixiEditor.Models.DataHolders
 {
+    [DebuggerDisplay("{SelectedPoints.Count} selected Pixels")]
     public class Selection : NotifyableObject
     {
         private readonly Color selectionBlue;

+ 96 - 0
PixiEditor/Models/Tools/ToolSettings/Settings/EnumSetting.cs

@@ -0,0 +1,96 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Controls.Primitives;
+using System.Windows.Data;
+
+namespace PixiEditor.Models.Tools.ToolSettings.Settings
+{
+    public class EnumSetting<TEnum> : Setting<TEnum, ComboBox>
+        where TEnum : struct, Enum
+    {
+        private int selectedIndex = 0;
+
+        /// <summary>
+        /// Gets or sets the selected Index of the <see cref="ComboBox"/>.
+        /// </summary>
+        public int SelectedIndex
+        {
+            get => selectedIndex;
+            set
+            {
+                if (SetProperty(ref selectedIndex, value))
+                {
+                    RaisePropertyChanged(nameof(Value));
+                }
+            }
+        }
+
+        /// <summary>
+        /// Gets or sets the selected value of the <see cref="ComboBox"/>.
+        /// </summary>
+        public new TEnum Value
+        {
+            get => (TEnum)(SettingControl.SelectedItem as ComboBoxItem).Tag;
+            set
+            {
+                SettingControl.SelectedItem = SettingControl.Items.Cast<ComboBoxItem>().First(x => x.Tag == (object)value);
+                RaisePropertyChanged(nameof(Value));
+            }
+        }
+
+        public EnumSetting(string name, string label)
+            : base(name)
+        {
+            SettingControl = GenerateDropdown();
+
+            Label = label;
+        }
+
+        public EnumSetting(string name, string label, TEnum defaultValue)
+            : this(name, label)
+        {
+            Value = defaultValue;
+        }
+
+        private static ComboBox GenerateDropdown()
+        {
+            ComboBox combobox = new ComboBox
+            {
+                VerticalAlignment = VerticalAlignment.Center
+            };
+
+            GenerateItems(combobox);
+
+            Binding binding = new Binding(nameof(SelectedIndex))
+            {
+                Mode = BindingMode.TwoWay
+            };
+
+            combobox.SetBinding(Selector.SelectedIndexProperty, binding);
+
+            return combobox;
+        }
+
+        private static void GenerateItems(ComboBox comboBox)
+        {
+            string[] names = Enum.GetNames<TEnum>();
+            TEnum[] values = Enum.GetValues<TEnum>();
+
+            for (int i = 0; i < names.Length; i++)
+            {
+                ComboBoxItem item = new ComboBoxItem
+                {
+                    Content = names[i],
+                    Tag = values[i]
+                };
+
+                comboBox.Items.Add(item);
+            }
+        }
+    }
+}

+ 17 - 4
PixiEditor/Models/Tools/ToolSettings/Settings/Setting.cs

@@ -2,12 +2,25 @@
 using System.Windows.Controls;
 using PixiEditor.Helpers;
 
+#pragma warning disable SA1402 // File may only contain a single type, Justification: "Same class with generic value"
+
 namespace PixiEditor.Models.Tools.ToolSettings.Settings
 {
-    [System.Diagnostics.CodeAnalysis.SuppressMessage(
-        "StyleCop.CSharp.MaintainabilityRules",
-        "SA1402:File may only contain a single type",
-        Justification = "Same class with generic value")]
+    public abstract class Setting<T, TControl> : Setting<T>
+        where TControl : Control
+    {
+        protected Setting(string name)
+            : base(name)
+        {
+        }
+
+        public new TControl SettingControl
+        {
+            get => (TControl)base.SettingControl;
+            set => base.SettingControl = value;
+        }
+    }
+
     public abstract class Setting<T> : Setting
     {
         protected Setting(string name)

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

@@ -1,4 +1,5 @@
-using PixiEditor.Models.Tools.ToolSettings.Settings;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
 
 namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
 {
@@ -6,7 +7,7 @@ namespace PixiEditor.Models.Tools.ToolSettings.Toolbars
     {
         public SelectToolToolbar()
         {
-            Settings.Add(new DropdownSetting("SelectMode", new[] { "New", "Add", "Subtract" }, "Selection type"));
+            Settings.Add(new EnumSetting<SelectionType>("SelectMode", "Selection type"));
         }
     }
 }

+ 88 - 100
PixiEditor/Models/Tools/Tools/SelectTool.cs

@@ -1,101 +1,89 @@
-using System;
-using System.Collections.Generic;
-using System.Collections.ObjectModel;
-using System.Linq;
-using System.Windows.Controls;
-using System.Windows.Input;
-using PixiEditor.Models.Controllers;
-using PixiEditor.Models.DataHolders;
-using PixiEditor.Models.Enums;
-using PixiEditor.Models.Position;
-using PixiEditor.Models.Tools.ToolSettings.Settings;
-using PixiEditor.Models.Tools.ToolSettings.Toolbars;
-using PixiEditor.Models.Undo;
-using PixiEditor.ViewModels;
-
-namespace PixiEditor.Models.Tools.Tools
-{
-    public class SelectTool : ReadonlyTool
-    {
-        private Selection oldSelection;
-
-        public SelectTool()
-        {
-            ActionDisplay = "Click and move to select an area.";
-            Tooltip = "Selects area. (M)";
-            Toolbar = new SelectToolToolbar();
-        }
-
-        public SelectionType SelectionType { get; set; } = SelectionType.Add;
-
-        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
-        {
-            Enum.TryParse((Toolbar.GetSetting<DropdownSetting>("SelectMode")?.Value as ComboBoxItem)?.Content as string, out SelectionType selectionType);
-            SelectionType = selectionType;
-
-            oldSelection = null;
-            Selection selection = ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection;
-            if (selection != null && selection.SelectedPoints != null)
-            {
-                oldSelection = selection;
-            }
-        }
-
-        public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
-        {
-            if (ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints.Count() <= 1)
-            {
-                // If we have not selected multiple points, clear the selection
-                ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.Clear();
-            }
-
-            if (oldSelection != null)
-            {
-                ViewModelMain.Current.BitmapManager.ActiveDocument.UndoManager.AddUndoChange(
-                    new Change(
-                        "SelectedPoints",
-                        oldSelection.SelectedPoints,
-                        new ObservableCollection<Coordinates>(ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints),
-                        "Select pixels",
-                        ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection));
-            }
-        }
-
-        public override void Use(Coordinates[] pixels)
-        {
-            Select(pixels);
-        }
-
-        public IEnumerable<Coordinates> GetRectangleSelectionForPoints(Coordinates start, Coordinates end)
-        {
-            RectangleTool rectangleTool = new RectangleTool();
-            List<Coordinates> selection = rectangleTool.CreateRectangle(start, end, 1).ToList();
-            selection.AddRange(rectangleTool.CalculateFillForRectangle(start, end, 1));
-            return selection;
-        }
-
-        /// <summary>
-        ///     Gets coordinates of every pixel in root layer.
-        /// </summary>
-        /// <returns>Coordinates array of pixels.</returns>
-        public IEnumerable<Coordinates> GetAllSelection()
-        {
-            return GetAllSelection(ViewModelMain.Current.BitmapManager.ActiveDocument);
-        }
-
-        /// <summary>
-        ///     Gets coordinates of every pixel in chosen document.
-        /// </summary>
-        /// <returns>Coordinates array of pixels.</returns>
-        public IEnumerable<Coordinates> GetAllSelection(Document document)
-        {
-            return GetRectangleSelectionForPoints(new Coordinates(0, 0), new Coordinates(document.Width - 1, document.Height - 1));
-        }
-
-        private void Select(Coordinates[] pixels)
-        {
-            IEnumerable<Coordinates> selection = GetRectangleSelectionForPoints(pixels[^1], pixels[0]);
-            ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(selection, SelectionType);
-        }
-    }
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Linq;
+using System.Windows.Controls;
+using System.Windows.Input;
+using PixiEditor.Helpers;
+using PixiEditor.Helpers.Extensions;
+using PixiEditor.Models.Controllers;
+using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.Enums;
+using PixiEditor.Models.Position;
+using PixiEditor.Models.Tools.ToolSettings.Settings;
+using PixiEditor.Models.Tools.ToolSettings.Toolbars;
+using PixiEditor.Models.Undo;
+using PixiEditor.ViewModels;
+
+namespace PixiEditor.Models.Tools.Tools
+{
+    public class SelectTool : ReadonlyTool
+    {
+        private IEnumerable<Coordinates> oldSelectedPoints;
+
+        private static Selection ActiveSelection { get => ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection; }
+
+        public SelectTool()
+        {
+            ActionDisplay = "Click and move to select an area.";
+            Tooltip = "Selects area. (M)";
+            Toolbar = new SelectToolToolbar();
+        }
+
+        public SelectionType SelectionType { get; set; } = SelectionType.Add;
+
+        public override void OnRecordingLeftMouseDown(MouseEventArgs e)
+        {
+            SelectionType = Toolbar.GetEnumSetting<SelectionType>("SelectMode").Value;
+
+            oldSelectedPoints = new ReadOnlyCollection<Coordinates>(ActiveSelection.SelectedPoints);
+        }
+
+        public override void OnStoppedRecordingMouseUp(MouseEventArgs e)
+        {
+            if (ActiveSelection.SelectedPoints.Count <= 1)
+            {
+                // If we have not selected multiple points, clear the selection
+                ActiveSelection.Clear();
+            }
+
+            SelectionHelpers.AddSelectionUndoStep(ViewModelMain.Current.BitmapManager.ActiveDocument, oldSelectedPoints, SelectionType);
+
+        public override void Use(Coordinates[] pixels)
+        {
+            Select(pixels);
+        }
+
+        public IEnumerable<Coordinates> GetRectangleSelectionForPoints(Coordinates start, Coordinates end)
+        {
+            RectangleTool rectangleTool = new RectangleTool();
+            List<Coordinates> selection = rectangleTool.CreateRectangle(start, end, 1).ToList();
+            selection.AddRange(rectangleTool.CalculateFillForRectangle(start, end, 1));
+            return selection;
+        }
+
+        /// <summary>
+        ///     Gets coordinates of every pixel in root layer.
+        /// </summary>
+        /// <returns>Coordinates array of pixels.</returns>
+        public IEnumerable<Coordinates> GetAllSelection()
+        {
+            return GetAllSelection(ViewModelMain.Current.BitmapManager.ActiveDocument);
+        }
+
+        /// <summary>
+        ///     Gets coordinates of every pixel in chosen document.
+        /// </summary>
+        /// <returns>Coordinates array of pixels.</returns>
+        public IEnumerable<Coordinates> GetAllSelection(Document document)
+        {
+            return GetRectangleSelectionForPoints(new Coordinates(0, 0), new Coordinates(document.Width - 1, document.Height - 1));
+        }
+
+        private void Select(Coordinates[] pixels)
+        {
+            IEnumerable<Coordinates> selection = GetRectangleSelectionForPoints(pixels[^1], pixels[0]);
+            ViewModelMain.Current.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(selection, SelectionType);
+        }
+    }
 }

+ 14 - 0
PixiEditor/NotifyableObject.cs

@@ -1,5 +1,7 @@
 using System;
+using System.Collections.Generic;
 using System.ComponentModel;
+using System.Runtime.CompilerServices;
 
 namespace PixiEditor.Helpers
 {
@@ -16,5 +18,17 @@ namespace PixiEditor.Helpers
                 PropertyChanged(this, new PropertyChangedEventArgs(property));
             }
         }
+
+        protected bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName] string propertyName = "")
+        {
+            if (EqualityComparer<T>.Default.Equals(backingStore, value))
+            {
+                return false;
+            }
+
+            backingStore = value;
+            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
+            return true;
+        }
     }
 }

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

@@ -5,7 +5,7 @@ using PixiEditor.Models.UserPreferences;
 
 namespace PixiEditor.ViewModels.SubViewModels.Main
 {
-    public class DiscordViewModel : SubViewModel<ViewModelMain>
+    public class DiscordViewModel : SubViewModel<ViewModelMain>, IDisposable
     {
         private DiscordRpcClient client;
         private string clientId;
@@ -81,12 +81,11 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             Owner.BitmapManager.DocumentChanged += DocumentChanged;
             this.clientId = clientId;
 
-            Enabled = IPreferences.Current.GetPreference<bool>("EnableRichPresence");
+            Enabled = IPreferences.Current.GetPreference("EnableRichPresence", true);
             IPreferences.Current.AddCallback("EnableRichPresence", x => Enabled = (bool)x);
             IPreferences.Current.AddCallback(nameof(ShowDocumentName), x => ShowDocumentName = (bool)x);
             IPreferences.Current.AddCallback(nameof(ShowDocumentSize), x => ShowDocumentSize = (bool)x);
             IPreferences.Current.AddCallback(nameof(ShowLayerCount), x => ShowLayerCount = (bool)x);
-
             AppDomain.CurrentDomain.ProcessExit += (_, _) => Enabled = false;
         }
 
@@ -142,6 +141,12 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
             client.SetPresence(richPresence);
         }
 
+        public void Dispose()
+        {
+            Enabled = false;
+            GC.SuppressFinalize(this);
+        }
+
         private static RichPresence NewDefaultRP()
         {
             return new RichPresence

+ 9 - 0
PixiEditor/ViewModels/SubViewModels/Main/SelectionViewModel.cs

@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 using PixiEditor.Helpers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Enums;
@@ -23,12 +24,20 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         public void SelectAll(object parameter)
         {
             SelectTool select = new SelectTool();
+
+            var oldSelection = new List<Coordinates>(Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints);
+
             Owner.BitmapManager.ActiveDocument.ActiveSelection.SetSelection(select.GetAllSelection(), SelectionType.New);
+            SelectionHelpers.AddSelectionUndoStep(Owner.BitmapManager.ActiveDocument, oldSelection, SelectionType.New);
         }
 
         public void Deselect(object parameter)
         {
+            var oldSelection = new List<Coordinates>(Owner.BitmapManager.ActiveDocument.ActiveSelection.SelectedPoints);
+
             Owner.BitmapManager.ActiveDocument.ActiveSelection?.Clear();
+
+            SelectionHelpers.AddSelectionUndoStep(Owner.BitmapManager.ActiveDocument, oldSelection, SelectionType.New);
         }
 
         public bool SelectionIsNotEmpty(object property)

+ 0 - 1
PixiEditor/ViewModels/SubViewModels/Main/UndoViewModel.cs

@@ -75,7 +75,6 @@ namespace PixiEditor.ViewModels.SubViewModels.Main
         /// <param name="parameter">CommandParameter.</param>
         public void Undo(object parameter)
         {
-            Owner.SelectionSubViewModel.Deselect(null);
             Owner.BitmapManager.ActiveDocument.UndoManager.Undo();
         }
 

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

@@ -37,6 +37,11 @@ namespace PixiEditor
             viewModel.CloseAction = Close;
         }
 
+        protected override void OnClosing(CancelEventArgs e)
+        {
+            viewModel.DiscordViewModel.Dispose();
+        }
+
         private void CommandBinding_CanExecute(object sender, CanExecuteRoutedEventArgs e)
         {
             e.CanExecute = true;

+ 20 - 0
PixiEditorTests/ModelsTests/DataHoldersTests/SelectionTests.cs

@@ -1,4 +1,6 @@
 using System;
+using System.Collections.Generic;
+using PixiEditor.Helpers;
 using PixiEditor.Models.DataHolders;
 using PixiEditor.Models.Enums;
 using PixiEditor.Models.Position;
@@ -55,5 +57,23 @@ namespace PixiEditorTests.ModelsTests.DataHoldersTests
             Assert.Empty(selection.SelectedPoints);
             Assert.Equal(0, selection.SelectionLayer.Width + selection.SelectionLayer.Height);
         }
+
+        [Fact]
+        public void TestThatUndoWorks()
+        {
+            Document document = new Document(10, 10);
+
+            IEnumerable<Coordinates> oldSelection = new List<Coordinates>(document.ActiveSelection.SelectedPoints);
+
+            document.ActiveSelection.SetSelection(new[] { new Coordinates(0, 0), new Coordinates(5, 7) }, SelectionType.Add);
+
+            Assert.NotEqual(oldSelection, document.ActiveSelection.SelectedPoints);
+
+            SelectionHelpers.AddSelectionUndoStep(document, oldSelection, SelectionType.Add);
+
+            document.UndoManager.Undo();
+
+            Assert.Equal(oldSelection, document.ActiveSelection.SelectedPoints);
+        }
     }
 }