Procházet zdrojové kódy

Merge pull request #531 from PixiEditor/picker-and-localization-improvements

Color Picker, localization and transform improvements
Krzysztof Krysiński před 2 roky
rodič
revize
310579040c
46 změnil soubory, kde provedl 344 přidání a 127 odebrání
  1. 46 1
      src/ChunkyImageLib/DataHolders/ShapeCorners.cs
  2. 3 0
      src/PixiEditor.DrawingApi.Core/Numerics/VecD.cs
  3. 2 2
      src/PixiEditor/Data/Localization/Languages/ar.json
  4. 2 2
      src/PixiEditor/Data/Localization/Languages/cs.json
  5. 2 2
      src/PixiEditor/Data/Localization/Languages/de.json
  6. 10 3
      src/PixiEditor/Data/Localization/Languages/en.json
  7. 2 2
      src/PixiEditor/Data/Localization/Languages/es.json
  8. 2 2
      src/PixiEditor/Data/Localization/Languages/pl.json
  9. 2 2
      src/PixiEditor/Data/Localization/Languages/uk.json
  10. 2 2
      src/PixiEditor/Data/Localization/Languages/zh.json
  11. 20 2
      src/PixiEditor/Helpers/Converters/NotNullToVisibilityConverter.cs
  12. 12 1
      src/PixiEditor/Helpers/CrashHelper.cs
  13. 1 1
      src/PixiEditor/Helpers/InputKeyHelpers.cs
  14. 1 1
      src/PixiEditor/Localization/ILocalizationProvider.cs
  15. 1 1
      src/PixiEditor/Localization/LocalizedString.cs
  16. 1 1
      src/PixiEditor/Models/Commands/XAML/ContextMenu.cs
  17. 1 1
      src/PixiEditor/Models/Commands/XAML/Menu.cs
  18. 6 3
      src/PixiEditor/Models/Commands/XAML/ShortcutBinding.cs
  19. 7 1
      src/PixiEditor/Models/Enums/DocumentTransformMode.cs
  20. 3 1
      src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs
  21. 21 7
      src/PixiEditor/ViewModels/SubViewModels/Document/TransformOverlays/DocumentTransformViewModel.cs
  22. 80 12
      src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ColorPickerToolViewModel.cs
  23. 20 2
      src/PixiEditor/ViewModels/ViewModelMain.cs
  24. 3 2
      src/PixiEditor/Views/Dialogs/AboutPopup.xaml
  25. 2 1
      src/PixiEditor/Views/Dialogs/ConfirmationPopup.xaml
  26. 1 1
      src/PixiEditor/Views/Dialogs/CrashReportDialog.xaml
  27. 4 2
      src/PixiEditor/Views/Dialogs/DebugDialogs/CommandDebugPopup.xaml
  28. 2 2
      src/PixiEditor/Views/Dialogs/DebugDialogs/Localization/LocalizationDebugWindow.xaml
  29. 1 1
      src/PixiEditor/Views/Dialogs/DialogTitleBar.xaml
  30. 8 5
      src/PixiEditor/Views/Dialogs/DialogTitleBar.xaml.cs
  31. 3 2
      src/PixiEditor/Views/Dialogs/ExportFilePopup.xaml
  32. 3 2
      src/PixiEditor/Views/Dialogs/ImportFilePopup.xaml
  33. 4 33
      src/PixiEditor/Views/Dialogs/ImportShortcutTemplatePopup.xaml
  34. 3 2
      src/PixiEditor/Views/Dialogs/NewFilePopup.xaml
  35. 2 1
      src/PixiEditor/Views/Dialogs/NoticePopup.xaml
  36. 4 2
      src/PixiEditor/Views/Dialogs/OptionsPopup.xaml
  37. 0 1
      src/PixiEditor/Views/Dialogs/OptionsPopup.xaml.cs
  38. 3 2
      src/PixiEditor/Views/Dialogs/PalettesBrowser.xaml
  39. 3 2
      src/PixiEditor/Views/Dialogs/ResizeCanvasPopup.xaml
  40. 3 2
      src/PixiEditor/Views/Dialogs/ResizeDocumentPopup.xaml
  41. 1 1
      src/PixiEditor/Views/Dialogs/SendCrashReportWindow.xaml
  42. 4 3
      src/PixiEditor/Views/Dialogs/SettingsWindow.xaml
  43. 3 2
      src/PixiEditor/Views/Dialogs/ShortcutPopup.xaml
  44. 1 1
      src/PixiEditor/Views/MainWindow.xaml
  45. 28 0
      src/PixiEditor/Views/Translator.cs
  46. 11 5
      src/PixiEditor/Views/UserControls/Viewport.xaml

+ 46 - 1
src/ChunkyImageLib/DataHolders/ShapeCorners.cs

@@ -1,6 +1,9 @@
-using PixiEditor.DrawingApi.Core.Numerics;
+using System.Diagnostics;
+using PixiEditor.DrawingApi.Core.Numerics;
 
 namespace ChunkyImageLib.DataHolders;
+
+[DebuggerDisplay("TL: {TopLeft}, TR: {TopRight}, BL: {BottomLeft}, BR: {BottomRight}")]
 public struct ShapeCorners
 {
     private const double epsilon = 0.001;
@@ -177,4 +180,46 @@ public struct ShapeCorners
             BottomLeft.AlmostEquals(other.BottomLeft, epsilon) &&
             BottomRight.AlmostEquals(other.BottomRight, epsilon);
     }
+    
+    public bool Intersects(RectD rect)
+    {
+        // Get all corners
+        VecD[] corners1 = { TopLeft, TopRight, BottomRight, BottomLeft };
+        VecD[] corners2 = { rect.TopLeft, rect.TopRight, rect.BottomRight, rect.BottomLeft };
+
+        // For each pair of corners
+        for (int i = 0; i < 4; i++)
+        {
+            VecD axis = corners1[i] - corners1[(i + 1) % 4]; // Create an edge
+            axis = new VecD(-axis.Y, axis.X); // Get perpendicular axis
+
+            // Project corners of first shape onto axis
+            double min1 = double.MaxValue, max1 = double.MinValue;
+            foreach (VecD corner in corners1)
+            {
+                double projection = corner.Dot(axis);
+                min1 = Math.Min(min1, projection);
+                max1 = Math.Max(max1, projection);
+            }
+
+            // Project corners of second shape onto axis
+            double min2 = double.MaxValue, max2 = double.MinValue;
+            foreach (VecD corner in corners2)
+            {
+                double projection = corner.Dot(axis);
+                min2 = Math.Min(min2, projection);
+                max2 = Math.Max(max2, projection);
+            }
+
+            // Check for overlap
+            if (max1 < min2 || max2 < min1)
+            {
+                // The projections do not overlap, so the shapes do not intersect
+                return false;
+            }
+        }
+
+        // All projections overlap, so the shapes intersect
+        return true;
+    }
 }

+ 3 - 0
src/PixiEditor.DrawingApi.Core/Numerics/VecD.cs

@@ -134,6 +134,9 @@ public struct VecD : IEquatable<VecD>
     {
         return (X * other.Y) - (Y * other.X);
     }
+
+    public double Dot(VecD other) => (X * other.X) + (Y * other.Y);
+    
     public VecD Multiply(VecD other)
     {
         return new VecD(X * other.X, Y * other.Y);

+ 2 - 2
src/PixiEditor/Data/Localization/Languages/ar.json

@@ -303,8 +303,8 @@
   "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "ارسم بالبكسل لجعلها أكثر قتامة. اترك Ctrl لتفتيح.",
   "COLOR_PICKER_TOOLTIP": "يختار اللون الأساسي من الصورة. ({0})",
   "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "انقر لاختيار الألوان. اضغط باستمرار على Ctrl لإخفاء الصورة. اضغط مع الاستمرار على Shift لإخفاء الطبقة المرجعية",
-  "COLOR_PICKER_ACTION_DISPLAY_CTRL": "انقر لاختيار الألوان من الطبقة المرجعية.",
-  "COLOR_PICKER_ACTION_DISPLAY_SHIFT": "انقر لاختيار الألوان من الصورة.",
+  "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "انقر لاختيار الألوان من الطبقة المرجعية.",
+  "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "انقر لاختيار الألوان من الصورة.",
   "ELLIPSE_TOOL_TOOLTIP": "رسم قطع ناقص على الصورة ({0}). اضغط مع الاستمرار على Shift لرسم دائرة.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "انقر وحرك الماوس لرسم شكل بيضاوي. اضغط مع الاستمرار على Shift لرسم دائرة.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "انقر وحرك الماوس لرسم دائرة.",

+ 2 - 2
src/PixiEditor/Data/Localization/Languages/cs.json

@@ -303,8 +303,8 @@
     "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Kreslením na pixely je ztmavíš. Pusť \"Ctrl\" pro jejich zesvětlení.",
     "COLOR_PICKER_TOOLTIP": "Vybere hlavní barvu z canvasu. ({0})",
     "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Klikni pro výběr barev. Podrž \"Ctrl\" abys schoval canvas. Podrž \"Shift\" pro schování referenční vrstvy.",
-    "COLOR_PICKER_ACTION_DISPLAY_CTRL": "Klikni pro vybrání barvy z referenční vrstvy.",
-    "COLOR_PICKER_ACTION_DISPLAY_SHIFT": "Klikni pro vybrání barvy z canvasu.",
+    "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Klikni pro vybrání barvy z referenční vrstvy.",
+    "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Klikni pro vybrání barvy z canvasu.",
     "ELLIPSE_TOOL_TOOLTIP": "Vytvoří elipsu na canvas ({0}). Podrž \"Shift\" pro přidání kružnice.",
     "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Klikni a posuň myší pro vytvoření elipsy. Podrž \"Shift\" pro vytvoření kružnice.",
     "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Klikni a posuň myší pro vytvoření kružnice.",

+ 2 - 2
src/PixiEditor/Data/Localization/Languages/de.json

@@ -303,8 +303,8 @@
   "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Zeichne auf Pixel um sie dunkler zu machen. Lasse Strg los um sie zu heller zu machen.",
   "COLOR_PICKER_TOOLTIP": "Pickt die Farbe von der Leinwand. ({0})",
   "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Klick um eine Farbe zu picken. Halte Strg um die Referenzebene zu verstecken.",
-  "COLOR_PICKER_ACTION_DISPLAY_CTRL": "Klick um eine Farbe von der Referenzebene zu picken.",
-  "COLOR_PICKER_ACTION_DISPLAY_SHIFT": "Klick um eine Farbe von der Leinwand zu picken.",
+  "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Klick um eine Farbe von der Referenzebene zu picken.",
+  "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Klick um eine Farbe von der Leinwand zu picken.",
   "ELLIPSE_TOOL_TOOLTIP": "Zeichnet eine Ellipse auf die Leinwand ({0}). Halte die Umschalttaste/Shift um einen Kreis zu zeichnen.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Klicke und halte die Maus um eine Ellipse zu zeichnen. Halte die Umschalttaste/Shift um einen Kreis zu zeichnen.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Klicke und bewege die Maus um einen Kreis zu zeichnen.",

+ 10 - 3
src/PixiEditor/Data/Localization/Languages/en.json

@@ -304,8 +304,8 @@
     "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Draw on pixels to make them darker. Release Ctrl to brighten.",
     "COLOR_PICKER_TOOLTIP": "Picks the primary color from the canvas. ({0})",
     "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Click to pick colors. Hold Ctrl to hide the canvas. Hold Shift to hide the reference layer",
-    "COLOR_PICKER_ACTION_DISPLAY_CTRL": "Click to pick colors from the reference layer.",
-    "COLOR_PICKER_ACTION_DISPLAY_SHIFT": "Click to pick colors from the canvas.",
+    "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Click to pick colors from the reference layer.",
+    "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Click to pick colors from the canvas.",
     "ELLIPSE_TOOL_TOOLTIP": "Draws an ellipse on canvas ({0}). Hold Shift to draw a circle.",
     "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Click and move mouse to draw an ellipse. Hold Shift to draw a circle.",
     "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Click and move mouse to draw a circle.",
@@ -555,5 +555,12 @@
   "SOURCE_UNSET_OR_MISSING": "Source missing/unset",
   "SOURCE_NEWER": "Source newer",
   "SOURCE_UP_TO_DATE": "Source is up to date",
-  "SOURCE_OLDER": "Cloud newer"
+  "SOURCE_OLDER": "Cloud newer",
+  "LOCALIZATION_DEBUG_WINDOW_TITLE": "Localization Debug Window",
+  "COMMAND_DEBUG_WINDOW_TITLE": "Command Debug Window",
+  "SHORTCUTS_TITLE": "Shortcuts",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_PERSPECTIVE": "Drag handles to scale transform. Hold Ctrl and drag a handle to move the handle freely. Hold Shift to scale proportionally. Hold Alt and drag a side handle to shear. Drag outside handles to rotate.",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_SHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Shift to scale proportionally. Hold Alt and drag a side handle to shear. Drag outside handles to rotate.",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Shift to scale proportionally. Drag outside handles to rotate.",
+  "TRANSFORM_ACTION_DISPLAY_SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE": "Drag handles to scale transform. Hold Shift to scale proportionally."
 }

+ 2 - 2
src/PixiEditor/Data/Localization/Languages/es.json

@@ -303,8 +303,8 @@
     "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Dibuja sobre los píxeles para oscurecerlos. Suelte Ctrl para aclarar.",
     "COLOR_PICKER_TOOLTIP": "Elige el color primario del canvas. ({0})",
     "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Haz clic para elegir colores. Mantenga pulsada la tecla Ctrl para ocultar el canvas. Mantenga pulsada la tecla Shift para ocultar la referencia.",
-    "COLOR_PICKER_ACTION_DISPLAY_CTRL": "Haga clic para seleccionar los colores de la capa de referencia.",
-    "COLOR_PICKER_ACTION_DISPLAY_SHIFT": "Haz clic para elegir colores del canvas.",
+    "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Haga clic para seleccionar los colores de la capa de referencia.",
+    "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Haz clic para elegir colores del canvas.",
     "ELLIPSE_TOOL_TOOLTIP": "Dibuja una elipse en el canvas ({0}). Mantenga pulsada la tecla Shift para dibujar un círculo.",
     "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Haz clic y mueve el ratón para dibujar una elipse. Mantenga pulsada la tecla Shift para dibujar un círculo.",
     "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Haz clic y mueve el ratón para dibujar un círculo.",

+ 2 - 2
src/PixiEditor/Data/Localization/Languages/pl.json

@@ -303,8 +303,8 @@
   "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Rysuj po pikselach aby je przyciemnić. Puść Ctrl aby rozjaśnić.",
   "COLOR_PICKER_TOOLTIP": "Pobiera kolor z płótna i ustawia go jako pierwszorzędny. ({0})",
   "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Kliknij aby pobrać kolor. Przytrzymaj Ctrl aby ukryć płótno. Przytrzymaj Shift aby ukryć warstwę referencyjną.",
-  "COLOR_PICKER_ACTION_DISPLAY_CTRL": "Kliknij aby pobrać kolory z warstwy referencyjnej.",
-  "COLOR_PICKER_ACTION_DISPLAY_SHIFT": "Kliknij aby pobrać kolory z płótna.",
+  "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Kliknij aby pobrać kolory z warstwy referencyjnej.",
+  "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Kliknij aby pobrać kolory z płótna.",
   "ELLIPSE_TOOL_TOOLTIP": "Rysuje elipsę na płótnie ({0}). Przytrzymaj Shift aby narysować koło.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Kliknij i porusz myszką aby narysować elipsę. Przytrzymaj Shift aby narysować koło.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Kliknij i porusz myszką aby narysować koło.",

+ 2 - 2
src/PixiEditor/Data/Localization/Languages/uk.json

@@ -303,8 +303,8 @@
   "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "Малюйте на пікселях, щоб зробити їх темнішими. Відпустіть Ctrl, щоб зробити яскравішими.",
   "COLOR_PICKER_TOOLTIP": "Вибір основного кольору з полотна. ({0})",
   "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "Натисніть, щоб вибрати кольори. Утримуйте Ctrl, щоб приховати полотно. Утримуйте Shift, щоб приховати опорний шар",
-  "COLOR_PICKER_ACTION_DISPLAY_CTRL": "Натисніть, щоб вибрати кольори з опорного шару.",
-  "COLOR_PICKER_ACTION_DISPLAY_SHIFT": "Натисніть, щоб вибрати кольори з полотна.",
+  "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "Натисніть, щоб вибрати кольори з опорного шару.",
+  "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "Натисніть, щоб вибрати кольори з полотна.",
   "ELLIPSE_TOOL_TOOLTIP": "Малювати еліпс на полотні ({0}). Утримуйте Shift, щоб намалювати коло.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "Натисніть і перемістіть мишу, щоб намалювати еліпс. Утримуйте Shift, щоб намалювати коло.",
   "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "Натисніть і перемістіть мишу, щоб намалювати коло.",

+ 2 - 2
src/PixiEditor/Data/Localization/Languages/zh.json

@@ -303,8 +303,8 @@
   "BRIGHTNESS_TOOL_ACTION_DISPLAY_CTRL": "在像素上绘画以将其变暗。松开Ctrl以变亮。",
   "COLOR_PICKER_TOOLTIP": "从画布中选取主要颜色。({0})",
   "COLOR_PICKER_ACTION_DISPLAY_DEFAULT": "单击以选取颜色。按住Ctrl以隐藏画布。按住Shift以隐藏参考画布。",
-  "COLOR_PICKER_ACTION_DISPLAY_CTRL": "单击以从参考图层中选取颜色。",
-  "COLOR_PICKER_ACTION_DISPLAY_SHIFT": "单击以从画布中选取颜色。",
+  "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY": "单击以从参考图层中选取颜色。",
+  "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY": "单击以从画布中选取颜色。",
   "ELLIPSE_TOOL_TOOLTIP": "在画布上绘制椭圆({0})。按住Shift以绘制圆形。",
   "ELLIPSE_TOOL_ACTION_DISPLAY_DEFAULT": "单击并拖动以绘制椭圆。按住Shift以绘制圆形。",
   "ELLIPSE_TOOL_ACTION_DISPLAY_SHIFT": "单击并拖动以绘制圆形。",

+ 20 - 2
src/PixiEditor/Helpers/Converters/NotNullToVisibilityConverter.cs

@@ -12,13 +12,31 @@ internal class NotNullToVisibilityConverter
 
     public override object Convert(object value, Type targetType, object parameter, CultureInfo culture)
     {
-        bool isNull = value is not null;
+        bool isNull = IsDefaultValue(value);
 
         if (Inverted)
         {
             isNull = !isNull;
         }
 
-        return isNull ? Visibility.Visible : Visibility.Collapsed;
+        return isNull ? Visibility.Collapsed : Visibility.Visible;
+    }
+    
+    bool IsDefaultValue(object obj)
+    {
+        if (obj is null)
+        {
+            return true;
+        }
+        
+        var type = obj.GetType();
+
+        if (type.IsValueType)
+        {
+            object defaultValue = Activator.CreateInstance(type);
+            return obj.Equals(defaultValue);
+        }
+
+        return false;
     }
 }

+ 12 - 1
src/PixiEditor/Helpers/CrashHelper.cs

@@ -3,9 +3,11 @@ using System.IO;
 using System.Net.Http;
 using System.Runtime.CompilerServices;
 using System.Text;
+using System.Windows.Input;
 using ByteSizeLib;
 using Hardware.Info;
 using PixiEditor.Models.DataHolders;
+using Mouse = System.Windows.Input.Mouse;
 
 namespace PixiEditor.Helpers;
 
@@ -15,7 +17,16 @@ internal class CrashHelper
 
     public static void SaveCrashInfo(Exception exception)
     {
-        CrashReport report = CrashReport.Generate(exception);
+        try
+        {
+            Mouse.OverrideCursor = Cursors.Wait;
+        }
+        catch (Exception e)
+        {
+            exception = new AggregateException(exception, e);
+        }
+        
+        var report = CrashReport.Generate(exception);
         report.TrySave();
         report.RestartToCrashReport();
     }

+ 1 - 1
src/PixiEditor/Helpers/InputKeyHelpers.cs

@@ -25,7 +25,7 @@ internal static class InputKeyHelpers
         >= Key.NumPad0 and <= Key.Divide => $"Num {GetMappedKey(key, forceInvariant)}",
         Key.Space => nameof(Key.Space),
         Key.Tab => nameof(Key.Tab),
-        Key.Return => "Enter",
+        Key.Return => "",
         Key.Back => "Backspace",
         Key.Escape => "Esc",
         _ => GetMappedKey(key, forceInvariant),

+ 1 - 1
src/PixiEditor/Localization/ILocalizationProvider.cs

@@ -4,7 +4,7 @@ namespace PixiEditor.Localization;
 
 public interface ILocalizationProvider
 {
-    public static ILocalizationProvider Current => ViewModelMain.Current.LocalizationProvider;
+    public static ILocalizationProvider Current => ViewModelMain.Current?.LocalizationProvider;
     
     public string LocalizationDataPath { get; }
     public LocalizationData LocalizationData { get; }

+ 1 - 1
src/PixiEditor/Localization/LocalizedString.cs

@@ -16,7 +16,7 @@ public struct LocalizedString
             #if DEBUG_LOCALIZATION
             Value = key;
             #else
-            Value = ViewModelMain.Current.DebugSubViewModel?.LocalizationKeyShowMode switch
+            Value = ViewModelMain.Current?.DebugSubViewModel?.LocalizationKeyShowMode switch
             {
                 LocalizationKeyShowMode.Key => Key,
                 LocalizationKeyShowMode.ValueKey => $"{GetValue(value)} ({Key})",

+ 1 - 1
src/PixiEditor/Models/Commands/XAML/ContextMenu.cs

@@ -36,7 +36,7 @@ internal class ContextMenu : System.Windows.Controls.ContextMenu
         var command = CommandController.Current.Commands[value];
 
         item.Command = Command.GetICommand(command, false);
-        item.SetBinding(MenuItem.InputGestureTextProperty, ShortcutBinding.GetBinding(command));
+        item.SetBinding(MenuItem.InputGestureTextProperty, ShortcutBinding.GetBinding(command, null));
     }
 
     private static void HandleDesignMode(MenuItem item, string name)

+ 1 - 1
src/PixiEditor/Models/Commands/XAML/Menu.cs

@@ -54,7 +54,7 @@ internal class Menu : System.Windows.Controls.Menu
 
         item.Command = Command.GetICommand(command, false);
         item.Icon = icon;
-        item.SetBinding(MenuItem.InputGestureTextProperty, ShortcutBinding.GetBinding(command));
+        item.SetBinding(MenuItem.InputGestureTextProperty, ShortcutBinding.GetBinding(command, null));
     }
 
     private static void HandleDesignMode(MenuItem item, string name)

+ 6 - 3
src/PixiEditor/Models/Commands/XAML/ShortcutBinding.cs

@@ -12,6 +12,8 @@ internal class ShortcutBinding : MarkupExtension
 
     public string Name { get; set; }
 
+    public IValueConverter Converter { get; set; }
+    
     public ShortcutBinding() { }
 
     public ShortcutBinding(string name) => Name = name;
@@ -25,14 +27,15 @@ internal class ShortcutBinding : MarkupExtension
         }
 
         commandController ??= ViewModelMain.Current.CommandController;
-        return GetBinding(commandController.Commands[Name]).ProvideValue(serviceProvider);
+        return GetBinding(commandController.Commands[Name], Converter).ProvideValue(serviceProvider);
     }
 
-    public static Binding GetBinding(ActualCommand command) => new Binding
+    public static Binding GetBinding(ActualCommand command, IValueConverter converter) => new Binding
     {
         Source = command,
         Path = new("Shortcut"),
         Mode = BindingMode.OneWay,
-        StringFormat = ""
+        StringFormat = "",
+        Converter = converter
     };
 }

+ 7 - 1
src/PixiEditor/Models/Enums/DocumentTransformMode.cs

@@ -1,8 +1,14 @@
-namespace PixiEditor.Models.Enums;
+using System.ComponentModel;
+
+namespace PixiEditor.Models.Enums;
 internal enum DocumentTransformMode
 {
+    [Description("SCALE_NOROTATE_NOSHEAR_NOPERSPECTIVE")]
     Scale_NoRotate_NoShear_NoPerspective,
+    [Description("SCALE_ROTATE_NOSHEAR_NOPERSPECTIVE")]
     Scale_Rotate_NoShear_NoPerspective,
+    [Description("SCALE_ROTATE_SHEAR_NOPERSPECTIVE")]
     Scale_Rotate_Shear_NoPerspective,
+    [Description("SCALE_ROTATE_SHEAR_PERSPECTIVE")]
     Scale_Rotate_Shear_Perspective
 }

+ 3 - 1
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs

@@ -16,6 +16,7 @@ using PixiEditor.DrawingApi.Core.Surface;
 using PixiEditor.DrawingApi.Core.Surface.ImageData;
 using PixiEditor.DrawingApi.Core.Surface.Vector;
 using PixiEditor.Helpers;
+using PixiEditor.Helpers.Collections;
 using PixiEditor.Localization;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
@@ -122,6 +123,7 @@ internal partial class DocumentViewModel : NotifyableObject
     public DocumentToolsModule Tools { get; }
     public DocumentOperationsModule Operations { get; }
     public DocumentEventsModule EventInlet { get; }
+    public ActionDisplayList ActionDisplays { get; } = new(() => ViewModelMain.Current.NotifyToolActionDisplayChanged());
 
     public StructureMemberViewModel? SelectedStructureMember { get; private set; } = null;
 
@@ -159,7 +161,7 @@ internal partial class DocumentViewModel : NotifyableObject
 
         StructureRoot = new FolderViewModel(this, Internals, Internals.Tracker.Document.StructureRoot.GuidValue);
 
-        TransformViewModel = new();
+        TransformViewModel = new(this);
         TransformViewModel.TransformMoved += (_, args) => Internals.ChangeController.TransformMovedInlet(args);
 
         LineToolOverlayViewModel = new();

+ 21 - 7
src/PixiEditor/ViewModels/SubViewModels/Document/TransformOverlays/DocumentTransformViewModel.cs

@@ -1,19 +1,17 @@
 using System.Windows.Input;
 using ChunkyImageLib.DataHolders;
-using PixiEditor;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Helpers;
+using PixiEditor.Localization;
 using PixiEditor.Models.Enums;
-using PixiEditor.ViewModels;
-using PixiEditor.ViewModels.SubViewModels;
-using PixiEditor.ViewModels.SubViewModels.Document;
-using PixiEditor.ViewModels.SubViewModels.Document.TransformOverlays;
 using PixiEditor.Views.UserControls.Overlays.TransformOverlay;
 
 namespace PixiEditor.ViewModels.SubViewModels.Document.TransformOverlays;
 #nullable enable
 internal class DocumentTransformViewModel : NotifyableObject
 {
+    private DocumentViewModel document;
+    
     private TransformOverlayUndoStack<(ShapeCorners, TransformState)>? undoStack = null;
 
     private TransformState internalState;
@@ -55,7 +53,22 @@ internal class DocumentTransformViewModel : NotifyableObject
     public bool TransformActive
     {
         get => transformActive;
-        set => SetProperty(ref transformActive, value);
+        set
+        {
+            if (!SetProperty(ref transformActive, value))
+            {
+                return;
+            }
+
+            if (value)
+            {
+                document.ActionDisplays[nameof(DocumentTransformViewModel)] = new LocalizedString($"TRANSFORM_ACTION_DISPLAY_{activeTransformMode.GetDescription()}");
+            }
+            else
+            {
+                document.ActionDisplays[nameof(DocumentTransformViewModel)] = null;
+            }
+        }
     }
 
     private bool showTransformControls;
@@ -106,8 +119,9 @@ internal class DocumentTransformViewModel : NotifyableObject
 
     private DocumentTransformMode activeTransformMode = DocumentTransformMode.Scale_Rotate_NoShear_NoPerspective;
 
-    public DocumentTransformViewModel()
+    public DocumentTransformViewModel(DocumentViewModel document)
     {
+        this.document = document;
         ActionCompletedCommand = new RelayCommand((_) =>
         {
             if (undoStack is null)

+ 80 - 12
src/PixiEditor/ViewModels/SubViewModels/Tools/Tools/ColorPickerToolViewModel.cs

@@ -1,9 +1,12 @@
-using System.Windows.Input;
-using ChunkyImageLib.DataHolders;
+using System.ComponentModel;
+using System.Windows.Input;
 using PixiEditor.DrawingApi.Core.Numerics;
 using PixiEditor.Localization;
 using PixiEditor.Models.Commands.Attributes.Commands;
 using PixiEditor.Models.Enums;
+using PixiEditor.Models.Events;
+using PixiEditor.ViewModels.SubViewModels.Document;
+using PixiEditor.ViewModels.SubViewModels.Document.TransformOverlays;
 using PixiEditor.ViewModels.SubViewModels.Tools.ToolSettings.Toolbars;
 using PixiEditor.Views.UserControls.Overlays.BrushShapeOverlay;
 
@@ -12,8 +15,10 @@ namespace PixiEditor.ViewModels.SubViewModels.Tools.Tools;
 [Command.Tool(Key = Key.O, Transient = Key.LeftAlt)]
 internal class ColorPickerToolViewModel : ToolViewModel
 {
-    private readonly string defaultActionDisplay = "COLOR_PICKER_ACTION_DISPLAY_DEFAULT";
-
+    private readonly string defaultReferenceActionDisplay = "COLOR_PICKER_ACTION_DISPLAY_DEFAULT";
+    
+    private readonly string defaultActionDisplay = "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY";
+    
     public override bool HideHighlight => true;
 
     public override string ToolNameLocalizationKey => "COLOR_PICKER_TOOL";
@@ -56,35 +61,98 @@ internal class ColorPickerToolViewModel : ToolViewModel
     {
         ActionDisplay = defaultActionDisplay;
         Toolbar = ToolbarFactory.Create<ColorPickerToolViewModel, EmptyToolbar>(this);
+        ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocumentChanged += DocumentChanged;
     }
 
-    public override void OnLeftMouseButtonDown(VecD pos)
+    private void DocumentChanged(object sender, DocumentChangedEventArgs e)
     {
-        ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseColorPickerTool();
+        if (e.OldDocument != null)
+        {
+            e.OldDocument.ReferenceLayerViewModel.PropertyChanged -= ReferenceLayerChanged;
+            e.OldDocument.TransformViewModel.PropertyChanged -= TransformViewModelOnPropertyChanged;
+        }
+
+        if (e.NewDocument != null)
+        {
+            e.NewDocument.ReferenceLayerViewModel.PropertyChanged += ReferenceLayerChanged;
+            e.NewDocument.TransformViewModel.PropertyChanged += TransformViewModelOnPropertyChanged;
+        }
+
+        UpdateActionDisplay();
+    }
+
+    private void TransformViewModelOnPropertyChanged(object sender, PropertyChangedEventArgs e)
+    {
+        if (e.PropertyName is nameof(DocumentTransformViewModel.TransformActive))
+        {
+            UpdateActionDisplay();
+        }
+    }
+
+    private void ReferenceLayerChanged(object sender, PropertyChangedEventArgs e)
+    {
+        if (e.PropertyName is nameof(ReferenceLayerViewModel.ReferenceBitmap) or nameof(ReferenceLayerViewModel.ReferenceShapeBindable))
+        {
+            UpdateActionDisplay();
+        }
     }
 
-    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown)
+    private void UpdateActionDisplay()
     {
+        bool ctrlDown = (Keyboard.Modifiers & ModifierKeys.Control) != 0;
+        bool shiftDown = (Keyboard.Modifiers & ModifierKeys.Shift) != 0;
+        
+        UpdateActionDisplay(ctrlDown, shiftDown);
+    }
+    
+    private void UpdateActionDisplay(bool ctrlIsDown, bool shiftIsDown)
+    {
+        var document = ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument;
+
+        if (document == null)
+        {
+            return;
+        }
+
+        var documentBounds = new RectD(default, document.SizeBindable);
+        var referenceLayer = document.ReferenceLayerViewModel;
+        
+        if (referenceLayer.ReferenceBitmap == null || document.TransformViewModel.TransformActive || !referenceLayer.ReferenceShapeBindable.Intersects(documentBounds))
+        {
+            PickFromCanvas = true;
+            PickFromReferenceLayer = true;
+            ActionDisplay = defaultActionDisplay;
+            return;
+        }
+    
         if (ctrlIsDown)
         {
             PickFromCanvas = false;
             PickFromReferenceLayer = true;
-            ActionDisplay = "COLOR_PICKER_ACTION_DISPLAY_CTRL";
+            ActionDisplay = "COLOR_PICKER_ACTION_DISPLAY_REFERENCE_ONLY";
         }
         else if (shiftIsDown)
         {
             PickFromCanvas = true;
             PickFromReferenceLayer = false;
-            ActionDisplay = "COLOR_PICKER_ACTION_DISPLAY_SHIFT";
+            ActionDisplay = "COLOR_PICKER_ACTION_DISPLAY_CANVAS_ONLY";
             return;
         }
         else
         {
             PickFromCanvas = true;
             PickFromReferenceLayer = true;
-            ActionDisplay = defaultActionDisplay;
+            ActionDisplay = defaultReferenceActionDisplay;
         }
-        
-        ViewModelMain.Current.DocumentManagerSubViewModel.ActiveDocument?.ReferenceLayerViewModel.RaiseShowHighestChanged();
+
+        referenceLayer.RaiseShowHighestChanged();
     }
+
+    public override void OnLeftMouseButtonDown(VecD pos)
+    {
+        ViewModelMain.Current?.DocumentManagerSubViewModel.ActiveDocument?.Tools.UseColorPickerTool();
+    }
+
+    public override void ModifierKeyChanged(bool ctrlIsDown, bool shiftIsDown, bool altIsDown) =>
+        UpdateActionDisplay(ctrlIsDown, shiftIsDown);
 }

+ 20 - 2
src/PixiEditor/ViewModels/ViewModelMain.cs

@@ -75,7 +75,24 @@ internal class ViewModelMain : ViewModelBase
     public IPreferences Preferences { get; set; }
     public ILocalizationProvider LocalizationProvider { get; set; }
 
-    public string ActiveActionDisplay => ActionDisplays.HasActive() ? ActionDisplays.GetActive() : ToolsSubViewModel.ActiveTool?.ActionDisplay;
+    public LocalizedString ActiveActionDisplay
+    {
+        get
+        {
+            if (ActionDisplays.HasActive())
+            {
+                return ActionDisplays.GetActive();
+            }
+
+            var documentDisplay = DocumentManagerSubViewModel.ActiveDocument?.ActionDisplays;
+            if (documentDisplay != null && documentDisplay.HasActive())
+            {
+                return documentDisplay.GetActive();
+            }
+
+            return ToolsSubViewModel.ActiveTool?.ActionDisplay ?? default;
+        }
+    }
 
     public ActionDisplayList ActionDisplays { get; }
 
@@ -173,7 +190,7 @@ internal class ViewModelMain : ViewModelBase
         }
     }
 
-    private void NotifyToolActionDisplayChanged()
+    public void NotifyToolActionDisplayChanged()
     {
         if (!ActionDisplays.Any()) RaisePropertyChanged(nameof(ActiveActionDisplay));
     }
@@ -260,6 +277,7 @@ internal class ViewModelMain : ViewModelBase
 
     private void OnActiveDocumentChanged(object sender, DocumentChangedEventArgs e)
     {
+        NotifyToolActionDisplayChanged();
         if (e.OldDocument is not null)
             e.OldDocument.SizeChanged -= ActiveDocument_DocumentSizeChanged;
         if (e.NewDocument is not null)

+ 3 - 2
src/PixiEditor/Views/Dialogs/AboutPopup.xaml

@@ -11,7 +11,8 @@
         xmlns:views="clr-namespace:PixiEditor.Views"
         xmlns:helpers="clr-namespace:PixiEditor.Helpers"
         mc:Ignorable="d" WindowStyle="None"
-        Title="About" WindowStartupLocation="CenterScreen"
+        views:Translator.Key="ABOUT"
+        WindowStartupLocation="CenterScreen"
         Height="510" Width="400" Name="aboutPopup" MinWidth="100" MinHeight="100"
         FlowDirection="{helpers:Localization FlowDirection}">
     <Window.CommandBindings>
@@ -30,7 +31,7 @@
         </b:Interaction.Behaviors>
 
         <local:DialogTitleBar DockPanel.Dock="Top"
-                             TitleText="{Binding ElementName=aboutPopup, Path=Title}" 
+                             TitleKey="ABOUT" 
                              CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
         <StackPanel DataContext="{Binding ElementName=aboutPopup}" Orientation="Vertical" DockPanel.Dock="Bottom" Margin="10">
             <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Top">

+ 2 - 1
src/PixiEditor/Views/Dialogs/ConfirmationPopup.xaml

@@ -14,6 +14,7 @@
         Name="popup" WindowStartupLocation="CenterScreen" 
         Height="180" Width="400" MinHeight="180" MinWidth="400"
         WindowStyle="None"
+        views:Translator.Key="{Binding ElementName=popup, Path=Title}"
         FlowDirection="{helpers:Localization FlowDirection}"
         d:DataContext="{d:DesignInstance dial:ConfirmationPopup}">
 
@@ -28,7 +29,7 @@
         </i:Interaction.Behaviors>
 
         <dial:DialogTitleBar DockPanel.Dock="Top"
-            TitleText="{Binding ElementName=popup, Path=Title}" CloseCommand="{Binding DataContext.CancelCommand, ElementName=popup}" />
+            TitleKey="{Binding ElementName=popup, Path=Title}" CloseCommand="{Binding DataContext.CancelCommand, ElementName=popup}" />
 
         <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Center"
                     Margin="0,0,10,15">

+ 1 - 1
src/PixiEditor/Views/Dialogs/CrashReportDialog.xaml

@@ -29,7 +29,7 @@
             <RowDefinition Height="Auto"/>
             <RowDefinition/>
         </Grid.RowDefinitions>
-        <dial:DialogTitleBar TitleText="PixiEditor has crashed!" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}" />
+        <dial:DialogTitleBar TitleKey="PixiEditor has crashed!" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}" />
         <Grid Grid.Row="1" Margin="30,30,30,0" >
             <StackPanel>
                 <Grid Background="{StaticResource MainColor}">

+ 4 - 2
src/PixiEditor/Views/Dialogs/DebugDialogs/CommandDebugPopup.xaml

@@ -9,11 +9,13 @@
         xmlns:command="clr-namespace:PixiEditor.Models.Commands"
         xmlns:cmds="clr-namespace:PixiEditor.Models.Commands.XAML" xmlns:usercontrols="clr-namespace:PixiEditor.Views.UserControls" xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
         xmlns:helpers="clr-namespace:PixiEditor.Helpers"
+        xmlns:views="clr-namespace:PixiEditor.Views"
         WindowStyle="None"
         mc:Ignorable="d"
         x:Name="uc"
         Foreground="White"
-        Title="Command Debug" Height="450" Width="800"
+        views:Translator.Key="COMMAND_DEBUG_WINDOW_TITLE"
+        Height="450" Width="800"
         FlowDirection="{helpers:Localization FlowDirection}">
 
     <Window.CommandBindings>
@@ -33,7 +35,7 @@
 
         <local:DialogTitleBar DockPanel.Dock="Top"
                               CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"
-                              TitleText="Command Debug" />
+                              TitleKey="COMMAND_DEBUG_WINDOW_TITLE" />
 
         <Grid>
             <Grid.RowDefinitions>

+ 2 - 2
src/PixiEditor/Views/Dialogs/DebugDialogs/Localization/LocalizationDebugWindow.xaml

@@ -17,7 +17,7 @@
         x:Name="popup"
         mc:Ignorable="d"
         Foreground="White"
-        Title="LocalizationDebugWindow"
+        views:Translator.Key="LOCALIZATION_DEBUG_WINDOW_TITLE"
         MinHeight="240" MinWidth="465"
         Height="350" Width="465"
         FlowDirection="{helpers:Localization FlowDirection}"
@@ -47,7 +47,7 @@
             <behaviours:ClearFocusOnClickBehavior />
         </b:Interaction.Behaviors>
 
-        <dialogs:DialogTitleBar TitleText="Localization Debug Window"
+        <dialogs:DialogTitleBar TitleKey="LOCALIZATION_DEBUG_WINDOW_TITLE"
                                 CloseCommand="{x:Static SystemCommands.CloseWindowCommand}" />
 
         <StackPanel Grid.Row="1" Margin="5">

+ 1 - 1
src/PixiEditor/Views/Dialogs/DialogTitleBar.xaml

@@ -16,7 +16,7 @@
         </Grid.ColumnDefinitions>
         <TextBlock 
             TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Center" 
-            views:Translator.Key="{Binding ElementName=uc, Path=TitleText}"
+            views:Translator.Key="{Binding ElementName=uc, Path=TitleKey}"
             Foreground="White" 
             FontSize="15" 
             Margin="5,0,0,0" 

+ 8 - 5
src/PixiEditor/Views/Dialogs/DialogTitleBar.xaml.cs

@@ -7,8 +7,8 @@ namespace PixiEditor.Views.Dialogs;
 
 internal partial class DialogTitleBar : UserControl
 {
-    public static readonly DependencyProperty TitleTextProperty =
-        DependencyProperty.Register(nameof(TitleText), typeof(string), typeof(DialogTitleBar), new PropertyMetadata(""));
+    public static readonly DependencyProperty TitleKeyProperty =
+        DependencyProperty.Register(nameof(TitleKey), typeof(string), typeof(DialogTitleBar), new PropertyMetadata(""));
 
     public static readonly DependencyProperty CloseCommandProperty =
         DependencyProperty.Register(nameof(CloseCommand), typeof(ICommand), typeof(DialogTitleBar), new PropertyMetadata(null));
@@ -19,10 +19,13 @@ internal partial class DialogTitleBar : UserControl
         set { SetValue(CloseCommandProperty, value); }
     }
 
-    public string TitleText
+    /// <summary>
+    /// The localization key of the window's title
+    /// </summary>
+    public string TitleKey
     {
-        get { return (string)GetValue(TitleTextProperty); }
-        set { SetValue(TitleTextProperty, value); }
+        get { return (string)GetValue(TitleKeyProperty); }
+        set { SetValue(TitleKeyProperty, value); }
     }
 
     public DialogTitleBar()

+ 3 - 2
src/PixiEditor/Views/Dialogs/ExportFilePopup.xaml

@@ -11,7 +11,8 @@
         xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
         xmlns:helpers="clr-namespace:PixiEditor.Helpers"
         mc:Ignorable="d" BorderBrush="Black" BorderThickness="1"
-        Title="SaveFilePopup" WindowStyle="None" MinHeight="330" MinWidth="310" Width="310"
+        WindowStyle="None" MinHeight="330" MinWidth="310" Width="310"
+        local:Translator.Key="EXPORT_IMAGE"
         SizeToContent="Height"
         WindowStartupLocation="CenterScreen" Name="saveFilePopup"
         FlowDirection="{helpers:Localization FlowDirection}">
@@ -32,7 +33,7 @@
 
 
         <dial:DialogTitleBar DockPanel.Dock="Top"
-            TitleText="EXPORT_IMAGE" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
+            TitleKey="EXPORT_IMAGE" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
 
         <Button DockPanel.Dock="Bottom" HorizontalAlignment="Center" IsDefault="True"
                     Margin="15" Style="{StaticResource DarkRoundButton}" local:Translator.Key="EXPORT" Command="{Binding OkCommand}"

+ 3 - 2
src/PixiEditor/Views/Dialogs/ImportFilePopup.xaml

@@ -12,11 +12,12 @@
         xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
         xmlns:helpers="clr-namespace:PixiEditor.Helpers"
         mc:Ignorable="d" BorderBrush="Black" BorderThickness="1"
-        Title="ImportFilePopup" ShowInTaskbar="False" 
+        ShowInTaskbar="False" 
         MinHeight="250" MinWidth="300" Height="250" Width="300" 
         WindowStyle="None" 
         WindowStartupLocation="CenterScreen" 
         Name="importFilePopup"
+        local:Translator.Key="IMPORT_FILE_TITLE"
         DataContext="{DynamicResource ImportFilePopupViewModel}"
         FlowDirection="{helpers:Localization FlowDirection}">
     <Window.Resources>
@@ -38,7 +39,7 @@
         </i:Interaction.Behaviors>
 
         <dial:DialogTitleBar DockPanel.Dock="Top"
-            TitleText="Import image" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
+            TitleKey="IMPORT_FILE_TITLE" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
         <Button DockPanel.Dock="Bottom" Width="70" HorizontalAlignment="Center" IsDefault="True"
                     Margin="15" Style="{StaticResource DarkRoundButton}" Content="Import" Command="{Binding OkCommand}"
                     CommandParameter="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />

+ 4 - 33
src/PixiEditor/Views/Dialogs/ImportShortcutTemplatePopup.xaml

@@ -5,9 +5,9 @@
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
         xmlns:diag="clr-namespace:PixiEditor.Views.Dialogs"
-        xmlns:xaml="clr-namespace:PixiEditor.Models.Commands.XAML"
         xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
         xmlns:helpers="clr-namespace:PixiEditor.Helpers"
+        xmlns:views="clr-namespace:PixiEditor.Views"
         mc:Ignorable="d"
         Title="Import from template" Foreground="White"
         WindowStartupLocation="CenterOwner"
@@ -15,7 +15,8 @@
         SizeToContent="WidthAndHeight" WindowStyle="None"
         Background="{StaticResource AccentColor}"
         x:Name="window"
-        FlowDirection="{helpers:Localization FlowDirection}">
+        FlowDirection="{helpers:Localization FlowDirection}"
+        views:Translator.Key="IMPORT_FROM_TEMPLATE">
 
     <Window.CommandBindings>
         <CommandBinding Command="{x:Static SystemCommands.CloseWindowCommand}" CanExecute="CommandBinding_CanExecute"
@@ -33,7 +34,7 @@
             <RowDefinition Height="*"/>
         </Grid.RowDefinitions>
         <diag:DialogTitleBar DockPanel.Dock="Top"
-                             TitleText="IMPORT_FROM_TEMPLATE" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
+                             TitleKey="IMPORT_FROM_TEMPLATE" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
         <ItemsControl Grid.Row="1" ItemsSource="{Binding Templates, ElementName=window}"
                       Margin="10,10,10,5">
             <ItemsControl.ItemsPanel>
@@ -48,36 +49,6 @@
                         Logo="{Binding LogoPath}" Cursor="Hand" 
                         MouseLeftButtonUp="OnTemplateCardLeftMouseButtonDown"
                         HoverLogo="{Binding Path=HoverLogoPath}"/>
-                    <!--<Grid Margin="0,5,0,0">
-                        <Grid.ColumnDefinitions>
-                            <ColumnDefinition Width="*"/>
-                            <ColumnDefinition Width="Auto"/>
-                        </Grid.ColumnDefinitions>
-                        <TextBlock Text="{Binding Name}" VerticalAlignment="Center"
-                                   ToolTip="{Binding Description}"/>
-                        <Image Source="{Binding LogoPath}" Width="48" Height="48"/>
-                        <StackPanel Grid.Column="1" Orientation="Horizontal">
-                            <StackPanel.Resources>
-                                <Style TargetType="Button" BasedOn="{StaticResource DarkRoundButton}">
-                                    <Setter Property="Width" Value="120"/>
-                                    <Setter Property="Height" Value="Auto"/>
-                                    <Setter Property="Padding" Value="5"/>
-                                    <Setter Property="FontSize" Value="12"/>
-                                    <Setter Property="Margin" Value="5,0, 0, 0"/>
-                                </Style>
-                            </StackPanel.Resources>
-                            <Button Command="{xaml:Command PixiEditor.Shortcuts.Provider.ImportInstallation, UseProvided=True}"
-                                    CommandParameter="{Binding}" Content="From installed"
-                                    Visibility="{Binding ProvidesFromInstallation, Converter={BoolToVisibilityConverter}}"
-                                    IsEnabled="{Binding HasInstallationPresent}"/>
-                            <Button Command="{xaml:Command PixiEditor.Shortcuts.Provider.ImportFile, UseProvided=True}"
-                                    CommandParameter="{Binding}" Content="Import file"
-                                    Visibility="{Binding ProvidesImport, Converter={BoolToVisibilityConverter}}"/>
-                            <Button Command="{xaml:Command PixiEditor.Shortcuts.Provider.ImportDefault, UseProvided=True}"
-                                    CommandParameter="{Binding}" Content="Import defaults"
-                                    Visibility="{Binding HasDefaultShortcuts, Converter={BoolToVisibilityConverter}}"/>
-                        </StackPanel>
-                    </Grid>-->
                 </DataTemplate>
             </ItemsControl.ItemTemplate>
         </ItemsControl>

+ 3 - 2
src/PixiEditor/Views/Dialogs/NewFilePopup.xaml

@@ -19,7 +19,8 @@
         SizeToContent="Width"
         Name="newFilePopup" 
         BorderBrush="Black" BorderThickness="1"
-        FlowDirection="{helpers:Localization FlowDirection}">
+        FlowDirection="{helpers:Localization FlowDirection}"
+        local:Translator.Key="CREATE_NEW_IMAGE">
     <Window.Resources>
         <vm:NewFileMenuViewModel x:Key="NewFileMenuViewModel" />
     </Window.Resources>
@@ -39,7 +40,7 @@
         </i:Interaction.Behaviors>
 
         <dial:DialogTitleBar DockPanel.Dock="Top"
-            TitleText="CREATE_NEW_IMAGE" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}" />
+            TitleKey="CREATE_NEW_IMAGE" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}" />
 
         <Button DockPanel.Dock="Bottom" Margin="0,15,0,15" HorizontalAlignment="Center"
                 IsDefault="True" local:Translator.Key="CREATE" x:Name="createButton"

+ 2 - 1
src/PixiEditor/Views/Dialogs/NoticePopup.xaml

@@ -14,6 +14,7 @@
         d:Title="Notice" Height="180" Width="400" MinHeight="180" MinWidth="400"
         WindowStartupLocation="CenterScreen"
         x:Name="popup"
+        views:Translator.Key="{Binding ElementName=popup, Path=Title}"
         FlowDirection="{helpers:Localization FlowDirection}">
 
     <WindowChrome.WindowChrome>
@@ -27,7 +28,7 @@
         </i:Interaction.Behaviors>
 
         <dial:DialogTitleBar DockPanel.Dock="Top"
-            TitleText="{Binding ElementName=popup, Path=Title}" CloseCommand="{Binding DataContext.CancelCommand, ElementName=popup}" />
+            TitleKey="{Binding ElementName=popup, Path=Title}" CloseCommand="{Binding DataContext.CancelCommand, ElementName=popup}" />
 
         <StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,15">
             <Button Width="70" IsDefault="True" Click="OkButton_Close" Style="{StaticResource DarkRoundButton}" views:Translator.Key="CLOSE"/>

+ 4 - 2
src/PixiEditor/Views/Dialogs/OptionsPopup.xaml

@@ -7,12 +7,14 @@
         xmlns:local="clr-namespace:PixiEditor.Views.Dialogs"
         xmlns:uc="clr-namespace:PixiEditor.Views.UserControls"
         xmlns:helpers="clr-namespace:PixiEditor.Helpers"
+        xmlns:views="clr-namespace:PixiEditor.Views"
         mc:Ignorable="d"
         WindowStartupLocation="CenterScreen"
         SizeToContent="WidthAndHeight"
         x:Name="popup"
         Background="{StaticResource AccentColor}" Foreground="White"
-        FlowDirection="{helpers:Localization FlowDirection}">
+        FlowDirection="{helpers:Localization FlowDirection}"
+        views:Translator.Key="{Binding Title, ElementName=popup}">
 
     <WindowChrome.WindowChrome>
         <WindowChrome CaptionHeight="32"  GlassFrameThickness="0.1"
@@ -26,7 +28,7 @@
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
 
-        <local:DialogTitleBar TitleText="{Binding Title, ElementName=popup}" CloseCommand="{Binding CancelCommand, ElementName=popup}"/>
+        <local:DialogTitleBar TitleKey="{Binding Title, ElementName=popup}" CloseCommand="{Binding CancelCommand, ElementName=popup}"/>
 
         <ContentPresenter Content="{Binding PopupContent, ElementName=popup}"
                           Grid.Row="1" Margin="15"/>

+ 0 - 1
src/PixiEditor/Views/Dialogs/OptionsPopup.xaml.cs

@@ -39,7 +39,6 @@ internal partial class OptionPopup : Window
 
     public OptionPopup(string title, object content, ObservableCollection<object> options)
     {
-        Title = title;
         PopupContent = content;
         Options = options;
         CancelCommand = new RelayCommand(Cancel);

+ 3 - 2
src/PixiEditor/Views/Dialogs/PalettesBrowser.xaml

@@ -20,7 +20,8 @@
     Height="600" Width="850" 
     WindowStyle="None"
     x:Name="palettesBrowser"
-    FlowDirection="{helpers:Localization FlowDirection}">
+    FlowDirection="{helpers:Localization FlowDirection}"
+    views:Translator.Key="PALETTE_BROWSER">
     <Window.Resources>
         <BooleanToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
     </Window.Resources>
@@ -41,7 +42,7 @@
             <RowDefinition Height="1*"/>
         </Grid.RowDefinitions>
 
-        <dialogs:DialogTitleBar TitleText="PALETTE_BROWSER" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
+        <dialogs:DialogTitleBar TitleKey="PALETTE_BROWSER" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
 
         <DockPanel Background="{StaticResource MainColor}" Grid.Row="1">
             <StackPanel HorizontalAlignment="Left" Margin="10" Orientation="Horizontal" VerticalAlignment="Center">

+ 3 - 2
src/PixiEditor/Views/Dialogs/ResizeCanvasPopup.xaml

@@ -13,7 +13,8 @@
         xmlns:helpers="clr-namespace:PixiEditor.Helpers"
         mc:Ignorable="d" 
         x:Name="window"
-        Title="ResizeCanvasPopup" ShowInTaskbar="False" WindowStartupLocation="CenterScreen"
+        ShowInTaskbar="False" WindowStartupLocation="CenterScreen"
+        local:Translator.Key="RESIZE_CANVAS"
         Height="420" Width="320" MinHeight="420" MinWidth="320" 
         WindowStyle="None"
         FlowDirection="{helpers:Localization FlowDirection}">
@@ -34,7 +35,7 @@
         </i:Interaction.Behaviors>
 
         <dial:DialogTitleBar DockPanel.Dock="Top"
-            TitleText="RESIZE_CANVAS" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}" />
+            TitleKey="RESIZE_CANVAS" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}" />
 
         <Button DockPanel.Dock="Bottom" Padding="5 0" HorizontalAlignment="Center" Margin="15"
                 Style="{StaticResource DarkRoundButton}" local:Translator.Key="RESIZE" Click="Button_Click" IsDefault="True" />

+ 3 - 2
src/PixiEditor/Views/Dialogs/ResizeDocumentPopup.xaml

@@ -9,7 +9,8 @@
         xmlns:behaviors="clr-namespace:PixiEditor.Helpers.Behaviours"
         xmlns:dial="clr-namespace:PixiEditor.Views.Dialogs"
         mc:Ignorable="d" x:Name="window"
-        Title="ResizeDocumentPopup" ShowInTaskbar="False" WindowStartupLocation="CenterScreen"
+        ShowInTaskbar="False" WindowStartupLocation="CenterScreen"
+        local:Translator.Key="RESIZE_IMAGE"
         Height="305" Width="310" MinHeight="305" MinWidth="310"
         xmlns:base="clr-namespace:PixiEditor.Views"
         xmlns:userControls="clr-namespace:PixiEditor.Views.UserControls"
@@ -33,7 +34,7 @@
         </i:Interaction.Behaviors>
 
         <dial:DialogTitleBar DockPanel.Dock="Top"
-            TitleText="RESIZE_IMAGE" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
+            TitleKey="RESIZE_IMAGE" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
 
         <Button DockPanel.Dock="Bottom" Padding="5 0" HorizontalAlignment="Center" Margin="15"
                 Style="{StaticResource DarkRoundButton}" local:Translator.Key="RESIZE" Click="Button_Click" IsDefault="True" />

+ 1 - 1
src/PixiEditor/Views/Dialogs/SendCrashReportWindow.xaml

@@ -33,7 +33,7 @@
     </Window.CommandBindings>
 
     <StackPanel>
-        <dial:DialogTitleBar TitleText="Send crash report" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}" />
+        <dial:DialogTitleBar TitleKey="Send crash report" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}" />
         <StackPanel Margin="10">
             <TextBlock>You can find the report here:</TextBlock>
             <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">

+ 4 - 3
src/PixiEditor/Views/Dialogs/SettingsWindow.xaml

@@ -19,14 +19,15 @@
         xmlns:localization="clr-namespace:PixiEditor.Localization"
         xmlns:helpers="clr-namespace:PixiEditor.Helpers"
         mc:Ignorable="d"
-        Title="Settings" Name="window" 
+        Name="window" 
         Height="688" Width="780"
         MinHeight="500" MinWidth="665"
         WindowStyle="None" DataContext="{DynamicResource SettingsWindowViewModel}"
         WindowStartupLocation="CenterScreen"
         BorderBrush="Black" BorderThickness="1"
         Background="{StaticResource AccentColor}"
-        FlowDirection="{helpers:Localization FlowDirection}">
+        FlowDirection="{helpers:Localization FlowDirection}"
+        views:Translator.Key="SETTINGS">
     <Window.Resources>
         <viewmodels:SettingsWindowViewModel x:Key="SettingsWindowViewModel"/>
         <BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
@@ -48,7 +49,7 @@
         </i:Interaction.Behaviors>
 
         <dial:DialogTitleBar DockPanel.Dock="Top"
-            TitleText="SETTINGS" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
+            TitleKey="SETTINGS" CloseCommand="{x:Static SystemCommands.CloseWindowCommand}"/>
 
         <ListBox DockPanel.Dock="Left" x:Name="pages" ItemsSource="{Binding Pages}"
                  Background="Transparent" BorderThickness="0" Width="180" ItemContainerStyle="{StaticResource PixiListBoxItemStyle}"

+ 3 - 2
src/PixiEditor/Views/Dialogs/ShortcutPopup.xaml

@@ -16,7 +16,8 @@
         mc:Ignorable="d"
         WindowStartupLocation="CenterScreen"
         SizeToContent="Height"
-        Title="ShortcutPopup" WindowStyle="None"
+        WindowStyle="None"
+        views:Translator.Key="SHORTCUTS_TITLE"
         MinHeight="780" MinWidth="620" Topmost="{Binding IsTopmost}"
         Width="950" MaxHeight="1000"
         FlowDirection="{helpers:Localization FlowDirection}"
@@ -82,7 +83,7 @@
                           WindowChrome.IsHitTestVisibleInChrome="True" ToolTip="Makes this window always on top"/>
         </DockPanel>
 
-        <TextBlock Grid.Row="0" FontSize="15" VerticalAlignment="Center" HorizontalAlignment="Center">Shortcuts</TextBlock>
+        <TextBlock Grid.Row="0" FontSize="15" VerticalAlignment="Center" HorizontalAlignment="Center" views:Translator.Key="SHORTCUTS_TITLE"/>
 
         <DockPanel Grid.Row="3">
             <TextBlock FontSize="14" Margin="10" Foreground="LightGray" HorizontalAlignment="Left" DockPanel.Dock="Bottom">

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

@@ -870,7 +870,7 @@
                 </Grid.ColumnDefinitions>
                 <DockPanel>
                     <TextBlock
-                        Text="{Binding ActiveActionDisplay}"
+                        views:Translator.LocalizedString="{Binding ActiveActionDisplay}"
                         Foreground="White"
                         FontSize="15"
                         Margin="10,0,0,0"

+ 28 - 0
src/PixiEditor/Views/Translator.cs

@@ -56,6 +56,12 @@ public class Translator : UIElement
     private static void TooltipKeyPropertyChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
     {
         d.SetValue(FrameworkElement.ToolTipProperty, new LocalizedString(GetTooltipKey(d)).Value);
+
+        if (ILocalizationProvider.Current == null)
+        {
+            return;
+        }
+        
         ILocalizationProvider.Current.OnLanguageChanged += (lang) => OnLanguageChangedTooltipKey(d, lang);
     }
 
@@ -92,6 +98,12 @@ public class Translator : UIElement
         if (e.NewValue is string key)
         {
             UpdateKey(d, key);
+            
+            if (ILocalizationProvider.Current == null)
+            {
+                return;
+            }
+
             ILocalizationProvider.Current.OnLanguageChanged += (lang) => OnLanguageChangedKey(d, lang);
         }
     }
@@ -123,6 +135,16 @@ public class Translator : UIElement
         {
             run.SetBinding(Run.TextProperty, binding);
         }
+        else if (d is Window window)
+        {
+            window.SetBinding(Window.TitleProperty, binding);
+        }
+        #if DEBUG
+        else if (d is DialogTitleBar)
+        {
+            throw new ArgumentException($"Use {nameof(DialogTitleBar)}.{nameof(DialogTitleBar.TitleKey)} to set the localization key for the title");
+        }
+        #endif
         else if (d is ContentControl contentControl)
         {
             contentControl.SetBinding(ContentControl.ContentProperty, binding);
@@ -135,6 +157,12 @@ public class Translator : UIElement
         {
             layoutContent.SetValue(LayoutContent.TitleProperty, localizedString.Value);
         }
+        #if DEBUG
+        else
+        {
+            throw new ArgumentException($"'{d.GetType().Name}' does not support {nameof(Translator)}.Key");
+        }
+        #endif
 
         d.SetValue(ValueProperty, localizedString.Value);
     }

+ 11 - 5
src/PixiEditor/Views/UserControls/Viewport.xaml

@@ -404,13 +404,13 @@
         </zoombox:Zoombox>
         <Button 
             Panel.ZIndex="99999"
-            DockPanel.Dock="Bottom" 
-            Width="140" 
-            Height="28" 
-            Margin="5" 
+            DockPanel.Dock="Bottom"
+            Margin="5"
+            Padding="8,5,5,5"
             VerticalAlignment="Bottom" 
+            HorizontalAlignment="Center"
             Style="{StaticResource GrayRoundButton}"
-            Command="{cmds:Command PixiEditor.Tools.ApplyTransform}" views:Translator.Key="APPLY_TRANSFORM">
+            Command="{cmds:Command PixiEditor.Tools.ApplyTransform}">
             <Button.Visibility>
                 <MultiBinding Converter="{converters:BoolOrToVisibilityConverter}">
                     <MultiBinding.Bindings>
@@ -419,6 +419,12 @@
                     </MultiBinding.Bindings>
                 </MultiBinding>
             </Button.Visibility>
+            <StackPanel Orientation="Horizontal">
+                <TextBlock views:Translator.Key="APPLY_TRANSFORM" VerticalAlignment="Center" Margin="0,0,5,0" />
+                <Border Padding="10,3" CornerRadius="5" Background="{StaticResource AccentColor}" Visibility="{cmds:ShortcutBinding PixiEditor.Tools.ApplyTransform, Converter={converters:NotNullToVisibilityConverter}}">
+                    <TextBlock Text="{cmds:ShortcutBinding PixiEditor.Tools.ApplyTransform}" />
+                </Border>
+            </StackPanel>
         </Button>
     </Grid>
 </UserControl>