2
0
Эх сурвалжийг харах

Merge pull request #482 from PixiEditor/1.0-bug-fixes

Fixed reference layer shape serialization and recent file not found
Krzysztof Krysiński 2 жил өмнө
parent
commit
28a7bb2b09
31 өөрчлөгдсөн 295 нэмэгдсэн , 115 устгасан
  1. 6 1
      src/PixiEditor.UpdateModule/UpdateChecker.cs
  2. 8 2
      src/PixiEditor/App.xaml.cs
  3. 10 2
      src/PixiEditor/Helpers/DocumentViewModelBuilder.cs
  4. 6 1
      src/PixiEditor/Helpers/Extensions/PixiParserDocumentEx.cs
  5. 5 0
      src/PixiEditor/Helpers/Extensions/SerializableDocumentEx.cs
  6. 9 0
      src/PixiEditor/Models/DataHolders/RecentlyOpenedDocument.cs
  7. 37 10
      src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs
  8. 30 0
      src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs
  9. 15 4
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/ShiftLayerExecutor.cs
  10. 16 4
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedAreaExecutor.cs
  11. 1 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/UpdateableChangeExecutor.cs
  12. 6 0
      src/PixiEditor/Models/Enums/ExecutorType.cs
  13. 5 0
      src/PixiEditor/Models/IO/Exporter.cs
  14. 1 1
      src/PixiEditor/PixiEditor.csproj
  15. 7 0
      src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.Serialization.cs
  16. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs
  17. 4 4
      src/PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs
  18. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs
  19. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/ToolViewModel.cs
  20. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/BrightnessToolViewModel.cs
  21. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ColorPickerToolViewModel.cs
  22. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/EllipseToolViewModel.cs
  23. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/FloodFillToolViewModel.cs
  24. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/LassoToolViewModel.cs
  25. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/LineToolViewModel.cs
  26. 33 0
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MoveToolViewModel.cs
  27. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/RectangleToolViewModel.cs
  28. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/SelectToolViewModel.cs
  29. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ZoomToolViewModel.cs
  30. 75 74
      src/PixiEditor/Views/MainWindow.xaml
  31. 9 0
      src/PixiEditor/Views/MainWindow.xaml.cs

+ 6 - 1
src/PixiEditor.UpdateModule/UpdateChecker.cs

@@ -81,12 +81,15 @@ public class UpdateChecker
 
     public bool CheckUpdateAvailable(ReleaseInfo latestRelease)
     {
+        if (latestRelease == null || string.IsNullOrEmpty(latestRelease.TagName)) return false;
+        if (CurrentVersionTag == null) return false;
+        
         return latestRelease.WasDataFetchSuccessful && VersionDifferent(CurrentVersionTag, latestRelease.TagName);
     }
 
     public bool IsUpdateCompatible(string[] incompatibleVersions)
     {
-        return !incompatibleVersions.Select(x => x.Trim()).Contains(CurrentVersionTag[..7].Trim());
+        return !incompatibleVersions.Select(x => x.Trim()).Contains(ExtractVersionString(CurrentVersionTag));
     }
 
     public async Task<bool> IsUpdateCompatible()
@@ -130,6 +133,8 @@ public class UpdateChecker
 
     private static string ExtractVersionString(string versionString)
     {
+        if (string.IsNullOrEmpty(versionString)) return string.Empty;
+        
         for (int i = 0; i < versionString.Length; i++)
         {
             if (!char.IsDigit(versionString[i]) && versionString[i] != '.')

+ 8 - 2
src/PixiEditor/App.xaml.cs

@@ -68,8 +68,14 @@ internal partial class App : Application
                                 if (mainWindow != null)
                                 {
                                     mainWindow.BringToForeground();
-                                    StartupArgs.Args = File.ReadAllText(passedArgsFile).Split(' ').ToList();
-                                    File.Delete(passedArgsFile);
+                                    List<string> args = new List<string>();
+                                    if (File.Exists(passedArgsFile))
+                                    {
+                                        args = File.ReadAllText(passedArgsFile).Split(' ').ToList();
+                                        File.Delete(passedArgsFile);
+                                    }
+                                    
+                                    StartupArgs.Args = args;
                                     StartupArgs.Args.Add("--openedInExisting");
                                     mainWindow.DataContext.OnStartupCommand.Execute(null);
                                 }

+ 10 - 2
src/PixiEditor/Helpers/DocumentViewModelBuilder.cs

@@ -5,6 +5,7 @@ using ChunkyImageLib.DataHolders;
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface;
+using PixiEditor.Parser;
 using BlendMode = PixiEditor.ChangeableDocument.Enums.BlendMode;
 
 namespace PixiEditor.Helpers;
@@ -351,9 +352,16 @@ internal class DocumentViewModelBuilder : ChildrenBuilder
             return this;
         }
 
-        public ReferenceLayerBuilder WithRect(VecD offset, VecD size)
+        public ReferenceLayerBuilder WithShape(Corners rect)
         {
-            Shape = new ShapeCorners(new RectD(offset, size));
+            Shape = new ShapeCorners
+            {
+                TopLeft = rect.TopLeft.ToVecD(), 
+                TopRight = rect.TopRight.ToVecD(), 
+                BottomLeft = rect.BottomLeft.ToVecD(), 
+                BottomRight = rect.BottomRight.ToVecD()
+            };
+            
             return this;
         }
     }

+ 6 - 1
src/PixiEditor/Helpers/Extensions/PixiParserDocumentEx.cs

@@ -9,6 +9,11 @@ namespace PixiEditor.Helpers.Extensions;
 
 internal static class PixiParserDocumentEx
 {
+    public static VecD ToVecD(this Vector2 vec)
+    {
+        return new VecD(vec.X, vec.Y);
+    }
+    
     public static DocumentViewModel ToDocument(this Document document)
     {
         return DocumentViewModel.Build(b =>
@@ -18,7 +23,7 @@ internal static class PixiParserDocumentEx
                 .WithSwatches(document.Swatches, x => new(x.R, x.G, x.B, x.A))
                 .WithReferenceLayer(document.ReferenceLayer, (r, builder) => builder
                     .WithIsVisible(r.Enabled)
-                    .WithRect(new VecD(r.OffsetX, r.OffsetY), new VecD(r.Width, r.Height))
+                    .WithShape(r.Corners)
                     .WithSurface(Surface.Load(r.ImageBytes)));
 
             BuildChildren(b, document.RootFolder.Children);

+ 5 - 0
src/PixiEditor/Helpers/Extensions/SerializableDocumentEx.cs

@@ -2,6 +2,7 @@
 using PixiEditor.DrawingApi.Core.ColorsImpl;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.DrawingApi.Core.Surface.ImageData;
+using PixiEditor.Parser;
 using PixiEditor.Parser.Collections.Deprecated;
 using PixiEditor.Parser.Deprecated;
 using PixiEditor.ViewModels.SubViewModels.Document;
@@ -10,6 +11,10 @@ namespace PixiEditor.Helpers.Extensions;
 
 internal static class SerializableDocumentEx
 {
+    public static Vector2 ToVector2(this VecD serializableVector2)
+    {
+        return new Vector2 { X = serializableVector2.X, Y = serializableVector2.Y };
+    }
     public static Image ToImage(this SerializableLayer serializableLayer)
     {
         if (serializableLayer.PngBytes == null)

+ 9 - 0
src/PixiEditor/Models/DataHolders/RecentlyOpenedDocument.cs

@@ -41,6 +41,10 @@ internal class RecentlyOpenedDocument : NotifyableObject
     {
         get
         {
+            if (!File.Exists(FilePath))
+            {
+                return "? (Not found)";
+            }
             if (Corrupt)
             {
                 return "? (Corrupt)";
@@ -71,6 +75,11 @@ internal class RecentlyOpenedDocument : NotifyableObject
 
     private WriteableBitmap LoadPreviewBitmap()
     {
+        if (!File.Exists(FilePath))
+        {
+            return null;
+        }
+        
         if (FileExtension == ".pixi")
         {
             SerializableDocument serializableDocument;

+ 37 - 10
src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs

@@ -27,6 +27,8 @@ internal class ChangeExecutionController
     private VecD lastPrecisePos;
 
     private UpdateableChangeExecutor? currentSession = null;
+    
+    private UpdateableChangeExecutor? _queuedExecutor = null;
 
     public ChangeExecutionController(DocumentViewModel document, DocumentInternalParts internals)
     {
@@ -44,32 +46,51 @@ internal class ChangeExecutionController
     public bool TryStartExecutor<T>(bool force = false)
         where T : UpdateableChangeExecutor, new()
     {
-        if (currentSession is not null && !force)
+        if (CanStartExecutor(force))
             return false;
         if (force)
             currentSession?.ForceStop();
+        
         T executor = new T();
-        executor.Initialize(document, internals, this, EndExecutor);
-        if (executor.Start() == ExecutionState.Success)
-        {
-            currentSession = executor;
-            return true;
-        }
-        return false;
+        return TryStartExecutorInternal(executor);
     }
 
     public bool TryStartExecutor(UpdateableChangeExecutor brandNewExecutor, bool force = false)
     {
-        if (currentSession is not null && !force)
+        if (CanStartExecutor(force))
             return false;
         if (force)
             currentSession?.ForceStop();
-        brandNewExecutor.Initialize(document, internals, this, EndExecutor);
+        
+        return TryStartExecutorInternal(brandNewExecutor);
+    }
+
+    private bool CanStartExecutor(bool force)
+    {
+        return (currentSession is not null || _queuedExecutor is not null) && !force;
+    }
+
+    private bool TryStartExecutorInternal(UpdateableChangeExecutor executor)
+    {
+        executor.Initialize(document, internals, this, EndExecutor);
+
+        if (executor.StartMode == ExecutorStartMode.OnMouseLeftButtonDown)
+        {
+            _queuedExecutor = executor;
+            return true;
+        }
+
+        return StartExecutor(executor);
+    }
+    
+    private bool StartExecutor(UpdateableChangeExecutor brandNewExecutor)
+    {
         if (brandNewExecutor.Start() == ExecutionState.Success)
         {
             currentSession = brandNewExecutor;
             return true;
         }
+
         return false;
     }
 
@@ -78,6 +99,7 @@ internal class ChangeExecutionController
         if (executor != currentSession)
             throw new InvalidOperationException();
         currentSession = null;
+        _queuedExecutor = null;
     }
 
     public bool TryStopActiveExecutor()
@@ -153,6 +175,11 @@ internal class ChangeExecutionController
         //update internal state
         LeftMouseState = MouseButtonState.Pressed;
 
+        if (_queuedExecutor != null && currentSession == null)
+        {
+            StartExecutor(_queuedExecutor);
+        }
+        
         //call session event
         currentSession?.OnLeftMouseButtonDown(canvasPos);
     }

+ 30 - 0
src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs

@@ -68,6 +68,36 @@ internal class DocumentStructureModule
             list.Add(doc.StructureRoot);
         return list;
     }
+    
+    /// <summary>
+    ///     Returns all layers in the document.
+    /// </summary>
+    /// <returns>List of LayerViewModels. Empty if no layers found.</returns>
+    public List<LayerViewModel> GetAllLayers()
+    {
+        List<LayerViewModel> layers = new List<LayerViewModel>();
+        foreach (StructureMemberViewModel? member in doc.StructureRoot.Children)
+        {
+            if (member is LayerViewModel layer)
+                layers.Add(layer);
+            else if (member is FolderViewModel folder)
+                layers.AddRange(GetAllLayers(folder, layers));
+        }
+        
+        return layers;
+    }
+    
+    private List<LayerViewModel> GetAllLayers(FolderViewModel folder, List<LayerViewModel> layers)
+    {
+        foreach (StructureMemberViewModel? member in folder.Children)
+        {
+            if (member is LayerViewModel layer)
+                layers.Add(layer);
+            else if (member is FolderViewModel innerFolder)
+                layers.AddRange(GetAllLayers(innerFolder, layers));
+        }
+        return layers;
+    }
 
     private bool FillPath(FolderViewModel folder, Guid guid, List<StructureMemberViewModel> toFill)
     {

+ 15 - 4
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/ShiftLayerExecutor.cs

@@ -13,17 +13,28 @@ internal class ShiftLayerExecutor : UpdateableChangeExecutor
     private VecI startPos;
     private MoveToolViewModel? tool;
 
+    public override ExecutorStartMode StartMode => ExecutorStartMode.OnMouseLeftButtonDown;
+
     public override ExecutionState Start()
     {
         ViewModelMain? vm = ViewModelMain.Current;
         StructureMemberViewModel? member = document!.SelectedStructureMember;
-        if(member != null)
-            _affectedMemberGuids.Add(member.GuidValue);
-        _affectedMemberGuids.AddRange(document!.SoftSelectedStructureMembers.Select(x => x.GuidValue));
+        
         tool = ViewModelMain.Current?.ToolsSubViewModel.GetTool<MoveToolViewModel>();
         if (vm is null || tool is null)
             return ExecutionState.Error;
-        
+
+        if (tool.MoveAllLayers)
+        {
+            _affectedMemberGuids.AddRange(document.StructureHelper.GetAllLayers().Select(x => x.GuidValue));
+        }
+        else
+        {
+            if (member != null)
+                _affectedMemberGuids.Add(member.GuidValue);
+            _affectedMemberGuids.AddRange(document!.SoftSelectedStructureMembers.Select(x => x.GuidValue));
+        }
+
         RemoveDrawOnMaskLayers(_affectedMemberGuids);
         
         startPos = controller!.LastPixelPosition;

+ 16 - 4
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedAreaExecutor.cs

@@ -11,7 +11,6 @@ internal class TransformSelectedAreaExecutor : UpdateableChangeExecutor
 {
     private Guid[]? membersToTransform;
     private MoveToolViewModel? tool;
-
     public override ExecutorType Type { get; }
 
     public TransformSelectedAreaExecutor(bool toolLinked)
@@ -25,10 +24,13 @@ internal class TransformSelectedAreaExecutor : UpdateableChangeExecutor
         if (tool is null || document!.SelectedStructureMember is null || document!.SelectionPathBindable.IsEmpty)
             return ExecutionState.Error;
 
-        var members = document.SoftSelectedStructureMembers
+        tool.TransformingSelectedArea = true;
+        List<StructureMemberViewModel> members = new();
+        
+        members = document.SoftSelectedStructureMembers
             .Append(document.SelectedStructureMember)
-            .Where(static m => m is LayerViewModel);
-
+            .Where(static m => m is LayerViewModel).ToList();
+        
         if (!members.Any())
             return ExecutionState.Error;
 
@@ -54,6 +56,11 @@ internal class TransformSelectedAreaExecutor : UpdateableChangeExecutor
 
     public override void OnTransformApplied()
     {
+        if (tool is not null)
+        {
+            tool.TransformingSelectedArea = false;
+        }
+        
         internals!.ActionAccumulator.AddActions(new EndTransformSelectedArea_Action());
         internals!.ActionAccumulator.AddFinishedActions();
         document!.TransformViewModel.HideTransform();
@@ -67,6 +74,11 @@ internal class TransformSelectedAreaExecutor : UpdateableChangeExecutor
 
     public override void ForceStop()
     {
+        if (tool is not null)
+        {
+            tool.TransformingSelectedArea = false;
+        }
+        
         internals!.ActionAccumulator.AddActions(new EndTransformSelectedArea_Action());
         internals!.ActionAccumulator.AddFinishedActions();
         document!.TransformViewModel.HideTransform();

+ 1 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/UpdateableChangeExecutor.cs

@@ -17,6 +17,7 @@ internal abstract class UpdateableChangeExecutor
 
     protected Action<UpdateableChangeExecutor>? onEnded;
     public virtual ExecutorType Type => ExecutorType.Regular;
+    public virtual ExecutorStartMode StartMode => ExecutorStartMode.RightAway;
 
     public void Initialize(DocumentViewModel document, DocumentInternalParts internals, ChangeExecutionController controller, Action<UpdateableChangeExecutor> onEnded)
     {

+ 6 - 0
src/PixiEditor/Models/Enums/ExecutorType.cs

@@ -5,3 +5,9 @@ internal enum ExecutorType
     Regular,
     ToolLinked,
 }
+
+internal enum ExecutorStartMode
+{
+    RightAway,
+    OnMouseLeftButtonDown,
+}

+ 5 - 0
src/PixiEditor/Models/IO/Exporter.cs

@@ -104,6 +104,11 @@ internal class Exporter
                 return SaveResult.ConcurrencyError;
             var bitmap = maybeBitmap.AsT1;
 
+            if (!encodersFactory.ContainsKey(typeFromPath))
+            {
+                return SaveResult.UnknownError;
+            }
+            
             if (!TrySaveAs(encodersFactory[typeFromPath](), pathWithExtension, bitmap, exportSize))
                 return SaveResult.UnknownError;
         }

+ 1 - 1
src/PixiEditor/PixiEditor.csproj

@@ -236,7 +236,7 @@
 		<PackageReference Include="Newtonsoft.Json" Version="13.0.2-beta2" />
 		<PackageReference Include="OneOf" Version="3.0.223" />
 		<PackageReference Include="PixiEditor.ColorPicker" Version="3.3.1" />
-		<PackageReference Include="PixiEditor.Parser" Version="3.2.0" />
+		<PackageReference Include="PixiEditor.Parser" Version="3.3.0" />
 		<PackageReference Include="PixiEditor.Parser.Skia" Version="3.0.0" />
 		<PackageReference Include="System.Drawing.Common" Version="7.0.0" />
 		<PackageReference Include="WpfAnimatedGif" Version="2.0.2" />

+ 7 - 0
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.Serialization.cs

@@ -69,6 +69,13 @@ internal partial class DocumentViewModel
             Height = (float)layer.Shape.RectSize.Y,
             OffsetX = (float)layer.Shape.TopLeft.X,
             OffsetY = (float)layer.Shape.TopLeft.Y,
+            Corners = new Corners
+            {
+                TopLeft = layer.Shape.TopLeft.ToVector2(), 
+                TopRight = layer.Shape.TopRight.ToVector2(), 
+                BottomLeft = layer.Shape.BottomLeft.ToVector2(), 
+                BottomRight = layer.Shape.BottomRight.ToVector2()
+            },
             Opacity = 1,
             ImageBytes = stream.ToArray()
         };

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs

@@ -157,8 +157,8 @@ internal class IoViewModel : SubViewModel<ViewModelMain>
             if (activeDocument == null)
                 return;
 
-            activeDocument.EventInlet.OnCanvasLeftMouseButtonDown(args.PositionOnCanvas);
             Owner.ToolsSubViewModel.LeftMouseButtonDownInlet(args.PositionOnCanvas);
+            activeDocument.EventInlet.OnCanvasLeftMouseButtonDown(args.PositionOnCanvas);
         }
     }
 

+ 4 - 4
src/PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs

@@ -130,9 +130,9 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>
             SelectedToolChanged?.Invoke(this, new SelectedToolEventArgs(LastActionTool, ActiveTool));
 
         //update old tool
-        LastActionTool?.UpdateActionDisplay(false, false, false);
+        LastActionTool?.ModifierKeyChanged(false, false, false);
         //update new tool
-        ActiveTool.UpdateActionDisplay(ctrlIsDown, shiftIsDown, altIsDown);
+        ActiveTool.ModifierKeyChanged(ctrlIsDown, shiftIsDown, altIsDown);
         ActiveTool.OnSelected();
 
         tool.IsActive = true;
@@ -225,11 +225,11 @@ internal class ToolsViewModel : SubViewModel<ViewModelMain>
 
     public void ConvertedKeyDownInlet(FilteredKeyEventArgs args)
     {
-        ActiveTool?.UpdateActionDisplay(args.IsCtrlDown, args.IsShiftDown, args.IsAltDown);
+        ActiveTool?.ModifierKeyChanged(args.IsCtrlDown, args.IsShiftDown, args.IsAltDown);
     }
 
     public void ConvertedKeyUpInlet(FilteredKeyEventArgs args)
     {
-        ActiveTool?.UpdateActionDisplay(args.IsCtrlDown, args.IsShiftDown, args.IsAltDown);
+        ActiveTool?.ModifierKeyChanged(args.IsCtrlDown, args.IsShiftDown, args.IsAltDown);
     }
 }

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

@@ -100,7 +100,7 @@ internal class UpdateViewModel : SubViewModel<ViewModelMain>
                 string dir = AppDomain.CurrentDomain.BaseDirectory;
                 
                 UpdateDownloader.CreateTempDirectory();
-                if(UpdateChecker.LatestReleaseInfo == null) return;
+                if(UpdateChecker.LatestReleaseInfo == null || string.IsNullOrEmpty(UpdateChecker.LatestReleaseInfo.TagName)) return;
                 bool updateFileExists = File.Exists(
                     Path.Join(UpdateDownloader.DownloadLocation, $"update-{UpdateChecker.LatestReleaseInfo.TagName}.zip"));
                 string exePath = Path.Join(UpdateDownloader.DownloadLocation,

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/ToolViewModel.cs

@@ -53,7 +53,7 @@ internal abstract class ToolViewModel : NotifyableObject
 
     public Toolbar Toolbar { get; set; } = new EmptyToolbar();
 
-    public virtual void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown) { }
+    public virtual void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown) { }
     public virtual void OnLeftMouseButtonDown(VecD pos) { }
     public virtual void OnSelected() 
     {

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/BrightnessToolViewModel.cs

@@ -35,7 +35,7 @@ internal class BrightnessToolViewModel : ToolViewModel
     
     public bool Darken { get; private set; } = false;
 
-    public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {
         if (!ctrlIsDown)
         {

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ColorPickerToolViewModel.cs

@@ -47,7 +47,7 @@ internal class ColorPickerToolViewModel : ToolViewModel
         ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseColorPickerTool();
     }
 
-    public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {
         if (ctrlIsDown)
         {

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/EllipseToolViewModel.cs

@@ -18,7 +18,7 @@ internal class EllipseToolViewModel : ShapeTool
     public override string Tooltip => $"Draws an ellipse on canvas ({Shortcut}). Hold Shift to draw a circle.";
     public bool DrawCircle { get; private set; }
 
-    public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {
         if (shiftIsDown)
         {

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/FloodFillToolViewModel.cs

@@ -22,7 +22,7 @@ internal class FloodFillToolViewModel : ToolViewModel
         ActionDisplay = defaultActionDisplay;
     }
 
-    public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {
         if (ctrlIsDown)
         {

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/LassoToolViewModel.cs

@@ -21,7 +21,7 @@ internal class LassoToolViewModel : ToolViewModel
     private SelectionMode modifierKeySelectionMode = SelectionMode.New;
     public SelectionMode ResultingSelectionMode => modifierKeySelectionMode != SelectionMode.New ? modifierKeySelectionMode : SelectMode;
 
-    public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {
         if (shiftIsDown)
         {

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/LineToolViewModel.cs

@@ -24,7 +24,7 @@ internal class LineToolViewModel : ShapeTool
 
     public bool Snap { get; private set; }
 
-    public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {
         if (shiftIsDown)
         {

+ 33 - 0
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MoveToolViewModel.cs

@@ -11,6 +11,10 @@ namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 internal class MoveToolViewModel : ToolViewModel
 {
     private string defaultActionDisplay = "Hold mouse to move selected pixels. Hold Ctrl to move all layers.";
+    private string transformingActionDisplay = "Click and hold mouse to move pixels in selected layers.";
+    private bool transformingSelectedArea = false;
+
+    public bool MoveAllLayers { get; set; }
 
     public MoveToolViewModel()
     {
@@ -27,11 +31,40 @@ internal class MoveToolViewModel : ToolViewModel
     public override BrushShape BrushShape => BrushShape.Hidden;
     public override bool HideHighlight => true;
 
+    public bool TransformingSelectedArea
+    {
+        get => transformingSelectedArea;
+        set
+        {
+            transformingSelectedArea = value;
+            ActionDisplay = value ? transformingActionDisplay : defaultActionDisplay;
+        }
+    }
+
     public override void OnLeftMouseButtonDown(VecD pos)
     {
         ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseShiftLayerTool();
     }
 
+    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+    {
+        if (TransformingSelectedArea)
+        {
+            return;
+        }
+        
+        if (ctrlIsDown)
+        {
+            ActionDisplay = "Hold mouse to move all layers.";
+            MoveAllLayers = true;
+        }
+        else
+        {
+            ActionDisplay = defaultActionDisplay;
+            MoveAllLayers = false;
+        }
+    }
+
     public override void OnSelected()
     {
         ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.Operations.TransformSelectedArea(true);

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/RectangleToolViewModel.cs

@@ -18,7 +18,7 @@ internal class RectangleToolViewModel : ShapeTool
 
     public bool Filled { get; set; } = false;
     public bool DrawSquare { get; private set; } = false;
-    public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {
         if (shiftIsDown)
         {

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/SelectToolViewModel.cs

@@ -24,7 +24,7 @@ internal class SelectToolViewModel : ToolViewModel
     private SelectionMode modifierKeySelectionMode = SelectionMode.New;
     public SelectionMode ResultingSelectionMode => modifierKeySelectionMode != SelectionMode.New ? modifierKeySelectionMode : SelectMode;
 
-    public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {
         if (shiftIsDown)
         {

+ 1 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ZoomToolViewModel.cs

@@ -27,7 +27,7 @@ internal class ZoomToolViewModel : ToolViewModel
 
     public override string Tooltip => $"Zooms viewport ({Shortcut}). Click to zoom in, hold alt and click to zoom out.";
 
-    public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {
         if (ctrlIsDown)
         {

+ 75 - 74
src/PixiEditor/Views/MainWindow.xaml

@@ -25,6 +25,7 @@
     xmlns:cmds="clr-namespace:PixiEditor.Models.Commands.XAML"
     xmlns:commandSearch="clr-namespace:PixiEditor.Views.UserControls.CommandSearch"
     xmlns:palettes="clr-namespace:PixiEditor.Views.UserControls.Palettes"
+    KeyDown="MainWindow_OnKeyDown"
     d:DataContext="{d:DesignInstance Type=vm:ViewModelMain}"
     mc:Ignorable="d"
     WindowStyle="None"
@@ -146,16 +147,16 @@
                             TargetType="{x:Type MenuItem}"
                             BasedOn="{StaticResource menuItemStyle}" />
                     </Menu.Resources>
-                    <MenuItem
-                        Header="_File">
+                    <MenuItem Focusable="False"
+                              Header="File">
                         <MenuItem
-                            Header="_New"
+                            Header="New"
                             cmds:Menu.Command="PixiEditor.File.New" />
                         <MenuItem
-                            Header="_Open"
+                            Header="Open"
                             cmds:Menu.Command="PixiEditor.File.Open" />
                         <MenuItem
-                            Header="_Recent"
+                            Header="Recent"
                             ItemsSource="{Binding FileSubViewModel.RecentlyOpened}"
                             x:Name="recentItemMenu"
                             IsEnabled="{Binding FileSubViewModel.HasRecent}">
@@ -180,104 +181,104 @@
                             </MenuItem.ItemTemplate>
                         </MenuItem>
                         <MenuItem
-                            Header="_Save (.pixi)"
+                            Header="Save (.pixi)"
                             cmds:Menu.Command="PixiEditor.File.Save" />
                         <MenuItem
-                            Header="_Save As... (.pixi)"
+                            Header="Save As... (.pixi)"
                             cmds:Menu.Command="PixiEditor.File.SaveAsNew" />
                         <MenuItem
-                            Header="_Export (.png, .jpeg, etc.)"
+                            Header="Export (.png, .jpeg, etc.)"
                             cmds:Menu.Command="PixiEditor.File.Export" />
                         <Separator />
                         <MenuItem
-                            Header="_Exit"
+                            Header="Exit"
                             Command="{x:Static SystemCommands.CloseWindowCommand}" />
                     </MenuItem>
-                    <MenuItem
-                        Header="_Edit">
+                    <MenuItem Focusable="False"
+                              Header="Edit">
                         <MenuItem
-                            Header="_Undo"
+                            Header="Undo"
                             cmds:Menu.Command="PixiEditor.Undo.Undo" />
                         <MenuItem
-                            Header="_Redo"
+                            Header="Redo"
                             cmds:Menu.Command="PixiEditor.Undo.Redo" />
                         <Separator />
                         <MenuItem
-                            Header="_Cut"
+                            Header="Cut"
                             cmds:Menu.Command="PixiEditor.Clipboard.Cut" />
                         <MenuItem
-                            Header="_Copy"
+                            Header="Copy"
                             cmds:Menu.Command="PixiEditor.Clipboard.Copy" />
                         <MenuItem
-                            Header="_Paste"
+                            Header="Paste"
                             cmds:Menu.Command="PixiEditor.Clipboard.Paste" />
                         <MenuItem
-                            Header="_Duplicate"
+                            Header="Duplicate"
                             cmds:Menu.Command="PixiEditor.Layer.DuplicateSelectedLayer" />
                         <Separator />
                         <MenuItem
-                            Header="_Delete Selected"
+                            Header="Delete Selected"
                             cmds:Menu.Command="PixiEditor.Document.DeletePixels" />
                         <Separator />
                         <MenuItem
-                            Header="_Settings"
+                            Header="Settings"
                             cmds:Menu.Command="PixiEditor.Window.OpenSettingsWindow" />
                     </MenuItem>
-                    <MenuItem
-                        Header="_Select">
+                    <MenuItem Focusable="False"
+                              Header="Select">
                         <MenuItem
-                            Header="_Select All"
+                            Header="Select All"
                             cmds:Menu.Command="PixiEditor.Selection.SelectAll" />
                         <MenuItem
-                            Header="_Deselect"
+                            Header="Deselect"
                             cmds:Menu.Command="PixiEditor.Selection.Clear" />
                         <MenuItem
-                            Header="_Invert"
+                            Header="Invert"
                             cmds:Menu.Command="PixiEditor.Selection.InvertSelection" />
                         <Separator/>
-                        <MenuItem Header="Selection _to Mask">
+                        <MenuItem Header="Selection to Mask">
                             <MenuItem
-                                Header="to _new mask"
+                                Header="to new mask"
                                 cmds:Menu.Command="PixiEditor.Selection.NewToMask" />
                             <MenuItem
-                                Header="_add to mask"
+                                Header="add to mask"
                                 cmds:Menu.Command="PixiEditor.Selection.AddToMask" />
                             <MenuItem
-                                Header="_subtract from mask"
+                                Header="subtract from mask"
                                 cmds:Menu.Command="PixiEditor.Selection.SubtractFromMask" />
                             <MenuItem
-                                Header="_intersect with mask"
+                                Header="intersect with mask"
                                 cmds:Menu.Command="PixiEditor.Selection.IntersectSelectionMask" />
                         </MenuItem>
                     </MenuItem>
-                    <MenuItem
-                        Header="_Image">
+                    <MenuItem Focusable="False"
+                              Header="Image">
                         <MenuItem
-                            Header="Resize _Image..."
+                            Header="Resize Image..."
                             cmds:Menu.Command="PixiEditor.Document.ResizeDocument" />
                         <MenuItem
-                            Header="Resize _Canvas..."
+                            Header="Resize Canvas..."
                             cmds:Menu.Command="PixiEditor.Document.ResizeCanvas" />
                         <Separator />
                         <MenuItem
-                            Header="Cli_p Canvas"
+                            Header="Clip Canvas"
                             cmds:Menu.Command="PixiEditor.Document.ClipCanvas" />
                         <MenuItem
-                            Header="Cente_r Content"
+                            Header="Center Content"
                             cmds:Menu.Command="PixiEditor.Document.CenterContent" />
                         <Separator />
                         <MenuItem
                             IsCheckable="True"
                             IsEnabled="{Binding DocumentManagerSubViewModel.ActiveDocument, Source={vm:MainVM}, Converter={converters:NotNullToBoolConverter}}"
                             IsChecked="{Binding DocumentManagerSubViewModel.ActiveDocument.HorizontalSymmetryAxisEnabledBindable}"
-                            Header="_Horizontal Line Symmetry"/>
+                            Header="Horizontal Line Symmetry"/>
                         <MenuItem
                             IsCheckable="True"
                             IsEnabled="{Binding DocumentManagerSubViewModel.ActiveDocument, Source={vm:MainVM}, Converter={converters:NotNullToBoolConverter}}"
                             IsChecked="{Binding DocumentManagerSubViewModel.ActiveDocument.VerticalSymmetryAxisEnabledBindable}"
-                            Header="_Vertical Line Symmetry"/>
+                            Header="Vertical Line Symmetry"/>
                         <Separator/>
-                        <MenuItem Header="_Rotation">
+                        <MenuItem Header="Rotation">
                             <MenuItem Header="Rotate Image 90&#186;" cmds:Menu.Command="PixiEditor.Document.Rotate90Deg"/>
                             <MenuItem Header="Rotate Image 180&#186;" cmds:Menu.Command="PixiEditor.Document.Rotate180Deg"/>
                             <MenuItem Header="Rotate Image -90&#186;" cmds:Menu.Command="PixiEditor.Document.Rotate270Deg"/>
@@ -287,84 +288,84 @@
                             <MenuItem Header="Rotate Selected Layers 180&#186;" cmds:Menu.Command="PixiEditor.Document.Rotate180DegLayers"/>
                             <MenuItem Header="Rotate Selected Layers -90&#186;" cmds:Menu.Command="PixiEditor.Document.Rotate270DegLayers"/>
                         </MenuItem>
-                        <MenuItem Header="_Flip">
-                            <MenuItem Header="Flip Image _Horizontally" cmds:Menu.Command="PixiEditor.Document.FlipImageHorizontal"/>
-                            <MenuItem Header="Flip Image _Vertically" cmds:Menu.Command="PixiEditor.Document.FlipImageVertical"/>
-                            <MenuItem Header="Flip Selected Layers _Horizontally" cmds:Menu.Command="PixiEditor.Document.FlipLayersHorizontal"/>
-                            <MenuItem Header="Flip Selected Layers _Vertically" cmds:Menu.Command="PixiEditor.Document.FlipLayersVertical"/>
+                        <MenuItem Header="Flip">
+                            <MenuItem Header="Flip Image Horizontally" cmds:Menu.Command="PixiEditor.Document.FlipImageHorizontal"/>
+                            <MenuItem Header="Flip Image Vertically" cmds:Menu.Command="PixiEditor.Document.FlipImageVertical"/>
+                            <MenuItem Header="Flip Selected Layers Horizontally" cmds:Menu.Command="PixiEditor.Document.FlipLayersHorizontal"/>
+                            <MenuItem Header="Flip Selected Layers Vertically" cmds:Menu.Command="PixiEditor.Document.FlipLayersVertical"/>
                         </MenuItem>
                     </MenuItem>
-                    <MenuItem
-                        Header="_View">
+                    <MenuItem Focusable="False"
+                              Header="View">
                         <MenuItem
                             Header="New window for current image"
                             cmds:Menu.Command="PixiEditor.Window.CreateNewViewport" />
                         <Separator/>
                         <MenuItem
-                            Header="Open _Startup Window"
+                            Header="Open Startup Window"
                             ToolTip="Hello there!"
                             cmds:Menu.Command="PixiEditor.Window.OpenStartupWindow" />
                         <MenuItem
-                            Header="Open _Navigation Window"
+                            Header="Open Navigation Window"
                             cmds:Menu.Command="PixiEditor.Window.OpenNavigationWindow" />
                         <MenuItem
-                            Header="Open Short_cuts Window"
+                            Header="Open Shortcuts Window"
                             cmds:Menu.Command="PixiEditor.Window.OpenShortcutWindow" />
                         <Separator/>
                         <MenuItem
-                            Header="Show _Grid Lines"
+                            Header="Show Grid Lines"
                             IsChecked="{Binding ViewportSubViewModel.GridLinesEnabled, Mode=TwoWay}"
                             IsCheckable="True"
                             InputGestureText="{cmds:ShortcutBinding PixiEditor.View.ToggleGrid}" />
                     </MenuItem>
-                    <MenuItem
-                        Header="_Help">
+                    <MenuItem Focusable="False"
+                              Header="Help">
                         <MenuItem
-                            Header="_Documentation"
+                            Header="Documentation"
                             cmds:Menu.Command="PixiEditor.Links.OpenDocumentation" />
                         <MenuItem
-                            Header="_Website"
+                            Header="Website"
                             cmds:Menu.Command="PixiEditor.Links.OpenWebsite" />
                         <MenuItem
-                            Header="_Repository"
+                            Header="Repository"
                             cmds:Menu.Command="PixiEditor.Links.OpenRepository" />
                         <Separator />
                         <MenuItem
-                            Header="_License"
+                            Header="License"
                             cmds:Menu.Command="PixiEditor.Links.OpenLicense" />
                         <MenuItem
-                            Header="_Third Party Licenses"
+                            Header="Third Party Licenses"
                             cmds:Menu.Command="PixiEditor.Links.OpenOtherLicenses" />
                         <Separator/>
                         <MenuItem
-                            Header="_About"
+                            Header="About"
                             cmds:Menu.Command="PixiEditor.Window.OpenAboutWindow" />
                     </MenuItem>
-                    <MenuItem
-                        Header="_Debug"
-                        Visibility="{Binding DebugSubViewModel.UseDebug, Converter={StaticResource BoolToVisibilityConverter}}">
+                    <MenuItem Focusable="False"
+                              Header="Debug"
+                              Visibility="{Binding DebugSubViewModel.UseDebug, Converter={StaticResource BoolToVisibilityConverter}}">
                         <MenuItem
                             Header="Open Command Debug Window"
                             cmds:Menu.Command="PixiEditor.Debug.OpenCommandDebugWindow"/>
                         <Separator/>
                         <MenuItem
-                            Header="Open _Local App Data"
+                            Header="Open Local App Data"
                             cmds:Menu.Command="PixiEditor.Debug.OpenLocalAppDataDirectory" />
                         <MenuItem
-                            Header="Open _Roaming App Data"
+                            Header="Open Roaming App Data"
                             cmds:Menu.Command="PixiEditor.Debug.OpenRoamingAppDataDirectory" />
                         <MenuItem
-                            Header="Open _Temp App Data"
+                            Header="Open Temp App Data"
                             cmds:Menu.Command="PixiEditor.Debug.OpenTempDirectory" />
                         <MenuItem
-                            Header="Open _Install Location"
+                            Header="Open Install Location"
                             cmds:Menu.Command="PixiEditor.Debug.OpenInstallDirectory" />
                         <MenuItem
-                            Header="Open Crash _Reports Location"
+                            Header="Open Crash Reports Location"
                             cmds:Menu.Command="PixiEditor.Debug.OpenCrashReportsDirectory" />
                         <Separator />
                         <MenuItem
-                            Header="_Crash"
+                            Header="Crash"
                             cmds:Menu.Command="PixiEditor.Debug.Crash" />
                         <MenuItem
                             Header="Delete">
@@ -379,7 +380,7 @@
                                 cmds:Menu.Command="PixiEditor.Debug.DeleteEditorData" />
                             <Separator/>
                             <MenuItem
-                                Header="_Clear recent documents"
+                                Header="Clear recent documents"
                                 cmds:Menu.Command="PixiEditor.Debug.ClearRecentDocument"/>
                         </MenuItem>
                     </MenuItem>
@@ -590,24 +591,24 @@
                                                                     <Border Grid.Column="1" BorderThickness="0 0 1 0" BorderBrush="Black">
                                                                         <StackPanel Orientation="Vertical" Grid.Column="0">
                                                                             <MenuItem
-																		Header="_Select All"
+                                                                                Header="Select All"
 																		cmds:ContextMenu.Command="PixiEditor.Selection.SelectAll" />
                                                                             <MenuItem
-																		Header="_Deselect"
+																		Header="Deselect"
 																		cmds:ContextMenu.Command="PixiEditor.Selection.Clear" />
                                                                             <Separator />
                                                                             <MenuItem
-																		Header="_Cut"
+																		Header="Cut"
 																		cmds:ContextMenu.Command="PixiEditor.Clipboard.Cut" />
                                                                             <MenuItem
-																		Header="_Copy"
+																		Header="Copy"
 																		cmds:ContextMenu.Command="PixiEditor.Clipboard.Copy" />
                                                                             <MenuItem
-																		Header="_Paste"
+																		Header="Paste"
 																		cmds:ContextMenu.Command="PixiEditor.Clipboard.Paste" />
                                                                             <Separator />
-                                                                            <MenuItem Header="Flip _Horizontally" cmds:Menu.Command="PixiEditor.Document.FlipLayersHorizontal"/>
-                                                                            <MenuItem Header="Flip _Vertically" cmds:Menu.Command="PixiEditor.Document.FlipLayersVertical"/>
+                                                                            <MenuItem Header="Flip Horizontally" cmds:Menu.Command="PixiEditor.Document.FlipLayersHorizontal"/>
+                                                                            <MenuItem Header="Flip Vertically" cmds:Menu.Command="PixiEditor.Document.FlipLayersVertical"/>
                                                                             <Separator />
                                                                             <MenuItem Header="Rotate 90&#186;" cmds:Menu.Command="PixiEditor.Document.Rotate90DegLayers"/>
                                                                             <MenuItem Header="Rotate 180&#186;" cmds:Menu.Command="PixiEditor.Document.Rotate180DegLayers"/>

+ 9 - 0
src/PixiEditor/Views/MainWindow.xaml.cs

@@ -1,5 +1,6 @@
 using System.ComponentModel;
 using System.Windows;
+using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Interop;
 using System.Windows.Media.Imaging;
@@ -235,4 +236,12 @@ internal partial class MainWindow : Window
     {
         DataContext.ActionDisplays[nameof(MainWindow_Drop)] = null;
     }
+
+    private void MainWindow_OnKeyDown(object sender, KeyEventArgs e)
+    {
+        if (e.Key == Key.System) // Disables alt menu item navigation, I hope it won't break anything else.
+        {
+            e.Handled = true;
+        }
+    }
 }