Browse Source

Merge branch 'master' into localization

Krzysztof Krysiński 2 years ago
parent
commit
bd0484998d
32 changed files with 232 additions and 45 deletions
  1. 2 2
      README.md
  2. 6 1
      src/PixiEditor.UpdateModule/UpdateChecker.cs
  3. 8 2
      src/PixiEditor/App.xaml.cs
  4. 10 2
      src/PixiEditor/Helpers/DocumentViewModelBuilder.cs
  5. 6 1
      src/PixiEditor/Helpers/Extensions/PixiParserDocumentEx.cs
  6. 5 0
      src/PixiEditor/Helpers/Extensions/SerializableDocumentEx.cs
  7. 9 0
      src/PixiEditor/Models/DataHolders/RecentlyOpenedDocument.cs
  8. 37 10
      src/PixiEditor/Models/DocumentModels/ChangeExecutionController.cs
  9. 30 0
      src/PixiEditor/Models/DocumentModels/Public/DocumentStructureModule.cs
  10. 15 4
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/ShiftLayerExecutor.cs
  11. 16 4
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedAreaExecutor.cs
  12. 1 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/UpdateableChangeExecutor.cs
  13. 6 0
      src/PixiEditor/Models/Enums/ExecutorType.cs
  14. 5 0
      src/PixiEditor/Models/IO/Exporter.cs
  15. 1 1
      src/PixiEditor/PixiEditor.csproj
  16. 7 0
      src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.Serialization.cs
  17. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Main/IoViewModel.cs
  18. 4 4
      src/PixiEditor/ViewModels/SubViewModels/Main/ToolsViewModel.cs
  19. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Main/UpdateViewModel.cs
  20. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/ToolViewModel.cs
  21. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/BrightnessToolViewModel.cs
  22. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ColorPickerToolViewModel.cs
  23. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/EllipseToolViewModel.cs
  24. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/FloodFillToolViewModel.cs
  25. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/LassoToolViewModel.cs
  26. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/LineToolViewModel.cs
  27. 34 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MoveToolViewModel.cs
  28. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/RectangleToolViewModel.cs
  29. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/SelectToolViewModel.cs
  30. 1 1
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ZoomToolViewModel.cs
  31. 9 1
      src/PixiEditor/Views/MainWindow.xaml
  32. 9 0
      src/PixiEditor/Views/MainWindow.xaml.cs

+ 2 - 2
README.md

@@ -38,9 +38,9 @@ PixiEditor started in 2018 and it's been actively developed since. We continuous
 
 <a href='//www.microsoft.com/store/apps/9NDDRHS8PBRN?cid=storebadge&ocid=badge'><img src='https://developer.microsoft.com/store/badges/images/English_get-it-from-MS.png' alt='Microsoft Store badge' width="184"/></a>
 
-Wishlist on Steam now!
+Get it on Steam now!
 
-[![wishlist_steam](https://user-images.githubusercontent.com/25402427/214952291-d81a4d79-bb75-44f2-bd24-10d7c3404997.png)](https://store.steampowered.com/app/2218560/PixiEditor__Pixel_Art_Editor?utm_source=GitHub)
+[![Get PixiEditor on Steam](https://user-images.githubusercontent.com/121322/228988640-32fe5bd3-9dd0-4f3b-a8f2-f744bd9b50b5.png)](https://store.steampowered.com/app/2218560/PixiEditor__Pixel_Art_Editor?utm_source=GitHub)
 
 **Or**
 

+ 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

@@ -69,8 +69,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

@@ -101,7 +101,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

@@ -55,7 +55,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

@@ -37,7 +37,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

@@ -49,7 +49,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

@@ -20,7 +20,7 @@ internal class EllipseToolViewModel : ShapeTool
     public override LocalizedString Tooltip => new LocalizedString("ELLIPSE_TOOL_TOOLTIP", Shortcut);
     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

@@ -24,7 +24,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

@@ -22,7 +22,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

@@ -26,7 +26,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)
         {

+ 34 - 1
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/MoveToolViewModel.cs

@@ -12,8 +12,12 @@ namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 internal class MoveToolViewModel : ToolViewModel
 {
     private string defaultActionDisplay = new LocalizedString("MOVE_TOOL_ACTION_DISPLAY");
-
     public override string ToolNameLocalizationKey => "MOVE_TOOL";
+    
+    private string transformingActionDisplay = new LocalizedString("MOVE_TOOL_ACTION_DISPLAY_TRANSFORMING")
+    private bool transformingSelectedArea = false;
+
+    public bool MoveAllLayers { get; set; }
 
     public MoveToolViewModel()
     {
@@ -30,11 +34,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 = nwe LocalizedString("MOVE_TOOL_ACTION_DISPLAY_CTRL")
+            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

@@ -20,7 +20,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

@@ -25,7 +25,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

@@ -29,7 +29,7 @@ internal class ZoomToolViewModel : ToolViewModel
 
     public override LocalizedString Tooltip => new LocalizedString("ZOOM_TOOL_TOOLTIP", Shortcut);
 
-    public override void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
     {
         if (ctrlIsDown)
         {

+ 9 - 1
src/PixiEditor/Views/MainWindow.xaml

@@ -27,6 +27,7 @@
     xmlns:palettes="clr-namespace:PixiEditor.Views.UserControls.Palettes"
     xmlns:views="clr-namespace:PixiEditor.Views"
     xmlns:system="clr-namespace:System;assembly=System.Runtime"
+    KeyDown="MainWindow_OnKeyDown"
     d:DataContext="{d:DesignInstance Type=vm:ViewModelMain}"
     mc:Ignorable="d"
     WindowStyle="None"
@@ -149,7 +150,8 @@
                             BasedOn="{StaticResource menuItemStyle}" />
                     </Menu.Resources>
                     <MenuItem
-                        views:Translator.Key="FILE">
+                        views:Translator.Key="FILE"
+                        Focusable="False">
                         <MenuItem
                             views:Translator.Key="NEW_FILE"
                             cmds:Menu.Command="PixiEditor.File.New" />
@@ -182,6 +184,7 @@
                             </MenuItem.ItemTemplate>
                         </MenuItem>
                         <MenuItem
+                            Focusable="False"
                             views:Translator.Key="SAVE_PIXI"
                             cmds:Menu.Command="PixiEditor.File.Save" />
                         <MenuItem
@@ -196,6 +199,7 @@
                             Command="{x:Static SystemCommands.CloseWindowCommand}" />
                     </MenuItem>
                     <MenuItem
+                        Focusable="False"
                         views:Translator.Key="EDIT">
                         <MenuItem
                             views:Translator.Key="UNDO"
@@ -226,6 +230,7 @@
                             cmds:Menu.Command="PixiEditor.Window.OpenSettingsWindow" />
                     </MenuItem>
                     <MenuItem
+                        Focusable="False"
                         views:Translator.Key="SELECT">
                         <MenuItem
                             views:Translator.Key="SELECT_ALL"
@@ -253,6 +258,7 @@
                         </MenuItem>
                     </MenuItem>
                     <MenuItem
+                        Focusable="False"
                         views:Translator.Key="IMAGE">
                         <MenuItem
                             views:Translator.Key="RESIZE_IMAGE"
@@ -298,6 +304,7 @@
                         </MenuItem>
                     </MenuItem>
                     <MenuItem
+                        Focusable="False"
                         views:Translator.Key="VIEW">
                         <MenuItem
                             views:Translator.Key="NEW_WINDOW_FOR_IMG"
@@ -321,6 +328,7 @@
                             InputGestureText="{cmds:ShortcutBinding PixiEditor.View.ToggleGrid}" />
                     </MenuItem>
                     <MenuItem
+                        Focusable="False"
                         views:Translator.Key="HELP">
                         <MenuItem
                             views:Translator.Key="DOCUMENTATION"

+ 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;
+        }
+    }
 }