Forráskód Böngészése

Merge pull request #989 from PixiEditor/fixes/24.07

Fixes/24.07
Krzysztof Krysiński 1 hónapja
szülő
commit
7341c664e2
31 módosított fájl, 715 hozzáadás és 355 törlés
  1. 64 6
      src/PixiEditor.ChangeableDocument/Changes/NodeGraph/UpdateProperty_Change.cs
  2. 16 0
      src/PixiEditor.UI.Common/Controls/NumberInput.cs
  3. 2 2
      src/PixiEditor/Helpers/Extensions/ColorHelpers.cs
  4. 6 7
      src/PixiEditor/Helpers/Nodes/NodeAbbreviation.cs
  5. 4 2
      src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs
  6. 1 1
      src/PixiEditor/ViewModels/Document/DocumentViewModel.cs
  7. 11 1
      src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs
  8. 25 1
      src/PixiEditor/ViewModels/Nodes/NodePropertyViewModel.cs
  9. 82 41
      src/PixiEditor/ViewModels/Nodes/Properties/ColorMatrixPropertyViewModel.cs
  10. 66 11
      src/PixiEditor/ViewModels/Nodes/Properties/KernelPropertyViewModel.cs
  11. 85 41
      src/PixiEditor/ViewModels/Nodes/Properties/Matrix4x5FPropertyViewModel.cs
  12. 12 1
      src/PixiEditor/ViewModels/Nodes/Properties/SinglePropertyViewModel.cs
  13. 24 6
      src/PixiEditor/ViewModels/Nodes/Properties/Vec3DPropertyViewModel.cs
  14. 19 3
      src/PixiEditor/ViewModels/Nodes/Properties/VecDPropertyViewModel.cs
  15. 22 4
      src/PixiEditor/ViewModels/Nodes/Properties/VecIPropertyViewModel.cs
  16. 13 0
      src/PixiEditor/ViewModels/SubViewModels/NodeGraphManagerViewModel.cs
  17. 171 167
      src/PixiEditor/Views/Main/MainTitleBar.axaml
  18. 25 21
      src/PixiEditor/Views/Nodes/Properties/ColorMatrixPropertyView.axaml
  19. 1 1
      src/PixiEditor/Views/Nodes/Properties/ColorPropertyView.axaml
  20. 2 1
      src/PixiEditor/Views/Nodes/Properties/DoublePropertyView.axaml
  21. 1 1
      src/PixiEditor/Views/Nodes/Properties/FontFamilyNamePropertyView.axaml
  22. 1 1
      src/PixiEditor/Views/Nodes/Properties/GenericEnumPropertyView.axaml
  23. 1 0
      src/PixiEditor/Views/Nodes/Properties/GenericPropertyView.axaml
  24. 6 1
      src/PixiEditor/Views/Nodes/Properties/Int32PropertyView.axaml
  25. 6 2
      src/PixiEditor/Views/Nodes/Properties/KernelPropertyView.axaml
  26. 25 21
      src/PixiEditor/Views/Nodes/Properties/Matrix4x5FPropertyView.axaml
  27. 1 1
      src/PixiEditor/Views/Nodes/Properties/PaintablePropertyView.axaml
  28. 1 1
      src/PixiEditor/Views/Nodes/Properties/StringPropertyView.axaml
  29. 8 4
      src/PixiEditor/Views/Nodes/Properties/Vec3DPropertyView.axaml
  30. 7 3
      src/PixiEditor/Views/Nodes/Properties/VecDPropertyView.axaml
  31. 7 3
      src/PixiEditor/Views/Nodes/Properties/VecIPropertyView.axaml

+ 64 - 6
src/PixiEditor.ChangeableDocument/Changes/NodeGraph/UpdateProperty_Change.cs

@@ -1,17 +1,18 @@
-using PixiEditor.ChangeableDocument.Changeables.Graph;
+using Drawie.Backend.Core.Shaders.Generation;
+using PixiEditor.ChangeableDocument.Changeables.Graph;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.ChangeableDocument.ChangeInfos.NodeGraph;
 
 namespace PixiEditor.ChangeableDocument.Changes.NodeGraph;
 
-internal class UpdatePropertyValue_Change : Change
+internal class UpdatePropertyValue_Change : InterruptableUpdateableChange
 {
     private readonly Guid _nodeId;
     private readonly string _propertyName;
     private object? _value;
     private object? previousValue;
 
-    [GenerateMakeChangeAction]
+    [GenerateUpdateableChangeActions]
     public UpdatePropertyValue_Change(Guid nodeId, string property, object? value)
     {
         _nodeId = nodeId;
@@ -23,12 +24,69 @@ internal class UpdatePropertyValue_Change : Change
     {
         if (target.TryFindNode<Node>(_nodeId, out var node))
         {
-            return node.HasInputProperty(_propertyName);
+            var property = node.GetInputProperty(_propertyName);
+            if (property == null) return false;
+
+            previousValue = GetValue(property);
+            if (previousValue is ShaderExpressionVariable expr)
+            {
+                previousValue = expr.GetConstant();
+            }
+
+            return true;
         }
 
         return false;
     }
 
+    [UpdateChangeMethod]
+    public void UpdateValue(object? value)
+    {
+        _value = value;
+    }
+
+    public override OneOf<None, IChangeInfo, List<IChangeInfo>> ApplyTemporarily(Document target)
+    {
+        var node = target.NodeGraph.Nodes.First(x => x.Id == _nodeId);
+        var property = node.GetInputProperty(_propertyName);
+
+        int inputsHash = CalculateInputsHash(node);
+        int outputsHash = CalculateOutputsHash(node);
+
+        string errors = string.Empty;
+        if (!property.Validator.Validate(_value, out errors))
+        {
+            if (string.IsNullOrEmpty(errors))
+            {
+                _value = property.Validator.GetClosestValidValue(_value);
+            }
+
+            _value = SetValue(property, _value);
+        }
+        else
+        {
+            _value = SetValue(property, _value);
+        }
+
+        List<IChangeInfo> changes = new();
+        changes.Add(new PropertyValueUpdated_ChangeInfo(_nodeId, _propertyName, _value) { Errors = errors });
+
+        int newInputsHash = CalculateInputsHash(node);
+        int newOutputsHash = CalculateOutputsHash(node);
+
+        if (inputsHash != newInputsHash)
+        {
+            changes.Add(NodeInputsChanged_ChangeInfo.FromNode(node));
+        }
+
+        if (outputsHash != newOutputsHash)
+        {
+            changes.Add(NodeOutputsChanged_ChangeInfo.FromNode(node));
+        }
+
+        return changes;
+    }
+
     public override OneOf<None, IChangeInfo, List<IChangeInfo>> Apply(Document target, bool firstApply,
         out bool ignoreInUndo)
     {
@@ -38,7 +96,6 @@ internal class UpdatePropertyValue_Change : Change
         int inputsHash = CalculateInputsHash(node);
         int outputsHash = CalculateOutputsHash(node);
 
-        previousValue = GetValue(property);
         string errors = string.Empty;
         if (!property.Validator.Validate(_value, out errors))
         {
@@ -165,6 +222,7 @@ internal class UpdatePropertyValue_Change : Change
 
     public override bool IsMergeableWith(Change other)
     {
-        return other is UpdatePropertyValue_Change change && change._nodeId == _nodeId && change._propertyName == _propertyName;
+        return other is UpdatePropertyValue_Change change && change._nodeId == _nodeId &&
+               change._propertyName == _propertyName && _value == change._value;
     }
 }

+ 16 - 0
src/PixiEditor.UI.Common/Controls/NumberInput.cs

@@ -42,6 +42,15 @@ public partial class NumberInput : TextBox
     public static readonly StyledProperty<bool> EnableGrabberProperty = AvaloniaProperty.Register<NumberInput, bool>(
         nameof(EnableGrabber), true);
 
+    public static readonly StyledProperty<bool> DraggingGrabberProperty = AvaloniaProperty.Register<NumberInput, bool>(
+        nameof(DraggingGrabber));
+
+    public bool DraggingGrabber
+    {
+        get => GetValue(DraggingGrabberProperty);
+        private set => SetValue(DraggingGrabberProperty, value);
+    }
+
     public bool EnableGrabber
     {
         get => GetValue(EnableGrabberProperty);
@@ -211,6 +220,7 @@ public partial class NumberInput : TextBox
 
         grabber.PointerPressed += GrabberPressed;
         grabber.PointerMoved += GrabberMoved;
+        grabber.PointerReleased += GrabberReleased;
 
         return grabber;
     }
@@ -220,6 +230,7 @@ public partial class NumberInput : TextBox
         e.Pointer.Capture(leftGrabber);
         _pressedValue = Value;
         _pressedRelativeX = e.GetPosition(this).X;
+        DraggingGrabber = true;
         e.Handled = true;
     }
 
@@ -240,6 +251,11 @@ public partial class NumberInput : TextBox
         }
     }
 
+    private void GrabberReleased(object sender, PointerReleasedEventArgs e)
+    {
+        DraggingGrabber = false;
+    }
+
     private void BindTextBoxBehavior(TextBoxFocusBehavior behavior)
     {
         Binding focusNextBinding = new Binding(nameof(FocusNext)) { Source = this, Mode = BindingMode.OneWay };

+ 2 - 2
src/PixiEditor/Helpers/Extensions/ColorHelpers.cs

@@ -92,8 +92,8 @@ internal static class ColorHelpers
         RadialGradientPaintable radialGradientPaintable => new RadialGradientBrush
         {
             Center = new RelativePoint(radialGradientPaintable.Center.X, radialGradientPaintable.Center.Y, paintable.AbsoluteValues ? RelativeUnit.Absolute : RelativeUnit.Relative),
-            RadiusX = new RelativeScalar(radialGradientPaintable.Radius, RelativeUnit.Absolute),
-            RadiusY = new RelativeScalar(radialGradientPaintable.Radius, RelativeUnit.Absolute),
+            RadiusX = new RelativeScalar(radialGradientPaintable.Radius, paintable.AbsoluteValues ? RelativeUnit.Absolute : RelativeUnit.Relative),
+            RadiusY = new RelativeScalar(radialGradientPaintable.Radius, paintable.AbsoluteValues ? RelativeUnit.Absolute : RelativeUnit.Relative),
             GradientStops = ToAvaloniaGradientStops(radialGradientPaintable.GradientStops),
             Transform = radialGradientPaintable.Transform.HasValue ? new MatrixTransform(ToAvaloniaMatrix(radialGradientPaintable.Transform.Value)) : null
         },

+ 6 - 7
src/PixiEditor/Helpers/Nodes/NodeAbbreviation.cs

@@ -28,9 +28,10 @@ public static class NodeAbbreviation
     {
         var span = value.AsSpan();
 
+        string lookFor = value;
         if (!span.ContainsAny(SearchFor))
         {
-            return null;
+            return [allNodes.FirstOrDefault(SearchComparer)];
         }
         
         var list = new List<NodeTypeInfo>();
@@ -39,17 +40,15 @@ public static class NodeAbbreviation
 
         foreach (var name in enumerator)
         {
-            var lookFor = name.Name.ToString();
+            lookFor = name.Name.ToString();
             var node = allNodes.First(SearchComparer);
 
             list.Add(node);
-
-            continue;
-
-            bool SearchComparer(NodeTypeInfo x) =>
-                x.FinalPickerName.Value.Replace(" ", "").Contains(lookFor.Replace(" ", ""), StringComparison.OrdinalIgnoreCase);
         }
 
+        bool SearchComparer(NodeTypeInfo x) =>
+            x.FinalPickerName.Value.Replace(" ", "").Contains(lookFor.Replace(" ", ""), StringComparison.OrdinalIgnoreCase);
+
         return list;
     }
 

+ 4 - 2
src/PixiEditor/Models/DocumentModels/DocumentUpdater.cs

@@ -835,8 +835,10 @@ internal class DocumentUpdater
         property.Errors = info.Errors;
 
         ProcessStructureMemberProperty(info, property);
-
-        property.InternalSetValue(info.Value);
+        var toSet = property.IsFunc
+            ? (info.Value as ShaderExpressionVariable)?.GetConstant() ?? info.Value
+            : info.Value;
+        property.InternalSetValue(toSet);
 
         if (info.Property == CustomOutputNode.OutputNamePropertyName)
         {

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

@@ -445,7 +445,7 @@ internal partial class DocumentViewModel : PixiObservableObject, IDocument
                 {
                     object value =
                         SerializationUtil.Deserialize(propertyValue.Value, config, allFactories, serializerData);
-                    acc.AddActions(new UpdatePropertyValue_Action(guid, propertyValue.Key, value));
+                    acc.AddActions(new UpdatePropertyValue_Action(guid, propertyValue.Key, value), new EndUpdatePropertyValue_Action());
                 }
             }
 

+ 11 - 1
src/PixiEditor/ViewModels/Document/NodeGraphViewModel.cs

@@ -228,7 +228,17 @@ internal class NodeGraphViewModel : ViewModelBase, INodeGraphHandler, IDisposabl
 
     public void UpdatePropertyValue(INodeHandler node, string property, object? value)
     {
-        Internals.ActionAccumulator.AddFinishedActions(new UpdatePropertyValue_Action(node.Id, property, value));
+        Internals.ActionAccumulator.AddFinishedActions(new UpdatePropertyValue_Action(node.Id, property, value), new EndUpdatePropertyValue_Action());
+    }
+
+    public void BeginUpdatePropertyValue(INodeHandler node, string property, object value)
+    {
+        Internals.ActionAccumulator.AddActions(new UpdatePropertyValue_Action(node.Id, property, value));
+    }
+
+    public void EndUpdatePropertyValue()
+    {
+        Internals.ActionAccumulator.AddFinishedActions(new EndUpdatePropertyValue_Action());
     }
 
     public void RequestUpdateComputedPropertyValue(INodePropertyHandler property)

+ 25 - 1
src/PixiEditor/ViewModels/Nodes/NodePropertyViewModel.cs

@@ -19,6 +19,7 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
     private bool isFunc;
     private IBrush socketBrush;
     private string errors = string.Empty;
+    private bool mergeChanges = false;
 
     private object computedValue;
 
@@ -39,7 +40,15 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
         set
         {
             var oldValue = _value;
-            ViewModelMain.Current.NodeGraphManager.UpdatePropertyValue((node, PropertyName, value));
+            if (MergeChanges)
+            {
+                ViewModelMain.Current.NodeGraphManager.BeginUpdatePropertyValue((node, PropertyName, value));
+            }
+            else
+            {
+                ViewModelMain.Current.NodeGraphManager.UpdatePropertyValue((node, PropertyName, value));
+            }
+
             if (SetProperty(ref _value, value))
             {
                 ValueChanged?.Invoke(this, new NodePropertyValueChangedArgs(oldValue, value));
@@ -47,6 +56,21 @@ internal abstract class NodePropertyViewModel : ViewModelBase, INodePropertyHand
         }
     }
 
+    public bool MergeChanges
+    {
+        get => mergeChanges;
+        set
+        {
+            if (SetProperty(ref mergeChanges, value))
+            {
+                if (!value)
+                {
+                    ViewModelMain.Current.NodeGraphManager.EndUpdatePropertyValue();
+                }
+            }
+        }
+    }
+
     public object ComputedValue
     {
         get

+ 82 - 41
src/PixiEditor/ViewModels/Nodes/Properties/ColorMatrixPropertyViewModel.cs

@@ -1,9 +1,11 @@
-using Drawie.Numerics;
+using System.ComponentModel;
+using Drawie.Numerics;
 
 namespace PixiEditor.ViewModels.Nodes.Properties;
 
 internal class ColorMatrixPropertyViewModel : NodePropertyViewModel<ColorMatrix>
 {
+    private bool blockUpdates = false;
     public ColorMatrixPropertyViewModel(NodeViewModel node, Type valueType) : base(node, valueType)
     {
     }
@@ -11,160 +13,199 @@ internal class ColorMatrixPropertyViewModel : NodePropertyViewModel<ColorMatrix>
     public float M11
     {
         get => Value.M11;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (value, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M12
     {
         get => Value.M12;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, value, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M13
     {
         get => Value.M13;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, value, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M14
     {
         get => Value.M14;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, value, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M15
     {
         get => Value.M15;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, value), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M21
     {
         get => Value.M21;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (value, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M22
     {
         get => Value.M22;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, value, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M23
     {
         get => Value.M23;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, M22, value, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M24
     {
         get => Value.M24;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, M22, M23, value, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M25
     {
         get => Value.M25;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, value),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M31
     {
         get => Value.M31;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (value, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (value, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M32
     {
         get => Value.M32;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, value, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, value, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M33
     {
         get => Value.M33;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, value, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, value, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M34
     {
         get => Value.M34;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, value, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, value, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M35
     {
         get => Value.M35;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, value), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, value), (M41, M42, M43, M44, M45)));
     }
 
     public float M41
     {
         get => Value.M41;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (value, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (value, M42, M43, M44, M45)));
     }
 
     public float M42
     {
         get => Value.M42;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, value, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, value, M43, M44, M45)));
     }
 
     public float M43
     {
         get => Value.M43;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, value, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, value, M44, M45)));
     }
 
     public float M44
     {
         get => Value.M44;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, value, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, value, M45)));
     }
 
     public float M45
     {
         get => Value.M45;
-        set => Value = new ColorMatrix(
+        set => UpdateValue(new ColorMatrix(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, value));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, value)));
+    }
+
+    private void UpdateValue(ColorMatrix value)
+    {
+        if (blockUpdates)
+            return;
+
+        Value = value;
+    }
+
+    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
+    {
+        base.OnPropertyChanged(e);
+
+        blockUpdates = true;
+        if (e.PropertyName == nameof(Value))
+        {
+            OnPropertyChanged(nameof(M11));
+            OnPropertyChanged(nameof(M12));
+            OnPropertyChanged(nameof(M13));
+            OnPropertyChanged(nameof(M14));
+            OnPropertyChanged(nameof(M15));
+            OnPropertyChanged(nameof(M21));
+            OnPropertyChanged(nameof(M22));
+            OnPropertyChanged(nameof(M23));
+            OnPropertyChanged(nameof(M24));
+            OnPropertyChanged(nameof(M25));
+            OnPropertyChanged(nameof(M31));
+            OnPropertyChanged(nameof(M32));
+            OnPropertyChanged(nameof(M33));
+            OnPropertyChanged(nameof(M34));
+            OnPropertyChanged(nameof(M35));
+            OnPropertyChanged(nameof(M41));
+            OnPropertyChanged(nameof(M42));
+            OnPropertyChanged(nameof(M43));
+            OnPropertyChanged(nameof(M44));
+            OnPropertyChanged(nameof(M45));
+        }
+        blockUpdates = false;
     }
 }

+ 66 - 11
src/PixiEditor/ViewModels/Nodes/Properties/KernelPropertyViewModel.cs

@@ -8,9 +8,11 @@ namespace PixiEditor.ViewModels.Nodes.Properties;
 internal class KernelPropertyViewModel : NodePropertyViewModel<Kernel?>
 {
     public ObservableCollection<KernelVmReference> ReferenceCollections { get; }
-    
+
     public RelayCommand<int> AdjustSizeCommand { get; }
-    
+
+    private bool blockUpdates = false;
+
     public KernelPropertyViewModel(NodeViewModel node, Type valueType) : base(node, valueType)
     {
         ReferenceCollections = new ObservableCollection<KernelVmReference>();
@@ -26,7 +28,7 @@ internal class KernelPropertyViewModel : NodePropertyViewModel<Kernel?>
     }
 
     public int Width => Value.Width;
-    
+
     public int Height => Value.Height;
 
     public float Sum => Value.Sum;
@@ -35,34 +37,87 @@ internal class KernelPropertyViewModel : NodePropertyViewModel<Kernel?>
     {
         if (e.PropertyName != nameof(Value) || Value == null)
             return;
+        blockUpdates = true;
+
+        int requiredCount = Value.Height * Value.Width;
 
-        ReferenceCollections.Clear();
-        
-        for (int y = -Value.RadiusY; y <= Value.RadiusY; y++)
+        if (ReferenceCollections.Count != requiredCount)
         {
-            for (int x = -Value.RadiusX; x <= Value.RadiusX; x++)
+            ReferenceCollections.Clear();
+            for (int y = -Value.RadiusY; y <= Value.RadiusY; y++)
             {
-                ReferenceCollections.Add(new KernelVmReference(this, x, y));
+                for (int x = -Value.RadiusX; x <= Value.RadiusX; x++)
+                {
+                    if (ReferenceCollections.Count < requiredCount)
+                    {
+                        ReferenceCollections.Add(new KernelVmReference(this, x, y));
+                    }
+                    else
+                    {
+                        break;
+                    }
+                }
             }
         }
-        
+
         OnPropertyChanged(nameof(Width));
         OnPropertyChanged(nameof(Height));
         OnPropertyChanged(nameof(Sum));
+        OnPropertyChanged(nameof(ReferenceCollections));
+
+        for (int i = 0; i < ReferenceCollections.Count; i++)
+        {
+            var reference = ReferenceCollections[i];
+            reference.ValueChanged();
+        }
+
+        blockUpdates = false;
     }
 
     public class KernelVmReference(KernelPropertyViewModel viewModel, int x, int y) : PixiObservableObject
     {
+        public bool MergeChanges
+        {
+            get
+            {
+                return viewModel.MergeChanges;
+            }
+            set
+            {
+                viewModel.MergeChanges = value;
+                OnPropertyChanged();
+            }
+        }
+
         public float Value
         {
             get => viewModel.Value[x, y];
             set
             {
-                viewModel.Value[x, y] = value;
-                ViewModelMain.Current.NodeGraphManager.UpdatePropertyValue((viewModel.Node, viewModel.PropertyName, viewModel.Value));
+                if (viewModel.blockUpdates)
+                    return;
+
+                var newVal = viewModel.Value.Clone() as Kernel;
+                newVal[x, y] = value;
+                if (MergeChanges)
+                {
+                    ViewModelMain.Current.NodeGraphManager.BeginUpdatePropertyValue((viewModel.Node,
+                        viewModel.PropertyName, newVal));
+                }
+                else
+                {
+                    ViewModelMain.Current.NodeGraphManager.UpdatePropertyValue((viewModel.Node, viewModel.PropertyName,
+                        newVal));
+                }
+
                 viewModel.OnPropertyChanged(nameof(Sum));
                 OnPropertyChanged();
             }
         }
+
+        public void ValueChanged()
+        {
+            OnPropertyChanged(nameof(Value));
+        }
     }
 }

+ 85 - 41
src/PixiEditor/ViewModels/Nodes/Properties/Matrix4x5FPropertyViewModel.cs

@@ -1,9 +1,11 @@
-using Drawie.Numerics;
+using System.ComponentModel;
+using Drawie.Numerics;
 
 namespace PixiEditor.ViewModels.Nodes.Properties;
 
 internal class Matrix4x5FPropertyViewModel : NodePropertyViewModel<Matrix4x5F>
 {
+    private bool blockUpdates = false;
     public Matrix4x5FPropertyViewModel(NodeViewModel node, Type valueType) : base(node, valueType)
     {
     }
@@ -11,160 +13,202 @@ internal class Matrix4x5FPropertyViewModel : NodePropertyViewModel<Matrix4x5F>
     public float M11
     {
         get => Value.M11;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (value, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M12
     {
         get => Value.M12;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, value, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M13
     {
         get => Value.M13;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, value, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M14
     {
         get => Value.M14;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, value, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M15
     {
         get => Value.M15;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, value), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M21
     {
         get => Value.M21;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (value, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M22
     {
         get => Value.M22;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, value, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M23
     {
         get => Value.M23;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, M22, value, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M24
     {
         get => Value.M24;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, M22, M23, value, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M25
     {
         get => Value.M25;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, value),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M31
     {
         get => Value.M31;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (value, M32, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (value, M32, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M32
     {
         get => Value.M32;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, value, M33, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, value, M33, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M33
     {
         get => Value.M33;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, value, M34, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, value, M34, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M34
     {
         get => Value.M34;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, value, M35), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, value, M35), (M41, M42, M43, M44, M45)));
     }
 
     public float M35
     {
         get => Value.M35;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, value), (M41, M42, M43, M44, M45));
+            (M31, M32, M33, M34, value), (M41, M42, M43, M44, M45)));
     }
 
     public float M41
     {
         get => Value.M41;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (value, M42, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (value, M42, M43, M44, M45)));
     }
 
     public float M42
     {
         get => Value.M42;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, value, M43, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, value, M43, M44, M45)));
     }
 
     public float M43
     {
         get => Value.M43;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, value, M44, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, value, M44, M45)));
     }
 
     public float M44
     {
         get => Value.M44;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, value, M45));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, value, M45)));
     }
 
     public float M45
     {
         get => Value.M45;
-        set => Value = new Matrix4x5F(
+        set => UpdateValue(new Matrix4x5F(
             (M11, M12, M13, M14, M15), (M21, M22, M23, M24, M25),
-            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, value));
+            (M31, M32, M33, M34, M35), (M41, M42, M43, M44, value)));
+    }
+
+    private void UpdateValue(Matrix4x5F newValue)
+    {
+        if (blockUpdates)
+            return;
+
+        Value = newValue;
+    }
+
+    protected override void OnPropertyChanged(PropertyChangedEventArgs e)
+    {
+        base.OnPropertyChanged(e);
+        if (blockUpdates)
+            return;
+        blockUpdates = true;
+
+        if (e.PropertyName == nameof(Value))
+        {
+            OnPropertyChanged(nameof(M11));
+            OnPropertyChanged(nameof(M12));
+            OnPropertyChanged(nameof(M13));
+            OnPropertyChanged(nameof(M14));
+            OnPropertyChanged(nameof(M15));
+            OnPropertyChanged(nameof(M21));
+            OnPropertyChanged(nameof(M22));
+            OnPropertyChanged(nameof(M23));
+            OnPropertyChanged(nameof(M24));
+            OnPropertyChanged(nameof(M25));
+            OnPropertyChanged(nameof(M31));
+            OnPropertyChanged(nameof(M32));
+            OnPropertyChanged(nameof(M33));
+            OnPropertyChanged(nameof(M34));
+            OnPropertyChanged(nameof(M35));
+            OnPropertyChanged(nameof(M41));
+            OnPropertyChanged(nameof(M42));
+            OnPropertyChanged(nameof(M43));
+            OnPropertyChanged(nameof(M44));
+            OnPropertyChanged(nameof(M45));
+        }
+
+        blockUpdates = false;
     }
 }

+ 12 - 1
src/PixiEditor/ViewModels/Nodes/Properties/SinglePropertyViewModel.cs

@@ -23,7 +23,13 @@ internal class SinglePropertyViewModel : NodePropertyViewModel<float>
     public double DoubleValue
     {
         get => Value;
-        set => Value = (float)value;
+        set
+        {
+            if (updateBlocker)
+                return;
+
+            Value = (float)value;
+        }
     }
 
     public double Min
@@ -44,6 +50,8 @@ internal class SinglePropertyViewModel : NodePropertyViewModel<float>
         set => SetProperty(ref sliderSettings, value);
     }
 
+    private bool updateBlocker = false;
+
     public SinglePropertyViewModel(NodeViewModel node, Type valueType) : base(node, valueType)
     {
     }
@@ -51,9 +59,12 @@ internal class SinglePropertyViewModel : NodePropertyViewModel<float>
     protected override void OnPropertyChanged(PropertyChangedEventArgs e)
     {
         base.OnPropertyChanged(e);
+
+        updateBlocker = true;
         if (e.PropertyName == nameof(Value))
         {
             OnPropertyChanged(nameof(DoubleValue));
         }
+        updateBlocker = false;
     }
 }

+ 24 - 6
src/PixiEditor/ViewModels/Nodes/Properties/Vec3DPropertyViewModel.cs

@@ -5,6 +5,7 @@ namespace PixiEditor.ViewModels.Nodes.Properties;
 
 internal class Vec3DPropertyViewModel : NodePropertyViewModel<Vec3D>
 {
+    private bool updateBlocker = false;
     public Vec3DPropertyViewModel(NodeViewModel node, Type valueType) : base(node, valueType)
     {
         PropertyChanged += OnPropertyChanged;
@@ -16,27 +17,44 @@ internal class Vec3DPropertyViewModel : NodePropertyViewModel<Vec3D>
         {
             return;
         }
-        
+
+        updateBlocker = true;
         OnPropertyChanged(nameof(XValue));
         OnPropertyChanged(nameof(YValue));
         OnPropertyChanged(nameof(ZValue));
+        updateBlocker = false;
     }
 
     public double XValue
     {
         get => Value.X;
-        set => Value = new Vec3D(value, Value.Y, Value.Z);
+        set
+        {
+            if (updateBlocker)
+                return;
+            Value = new Vec3D(value, Value.Y, Value.Z);
+        }
     }
-    
+
     public double YValue
     {
         get => Value.Y;
-        set => Value = new Vec3D(Value.X, value, Value.Z);
+        set
+        {
+            if (updateBlocker)
+                return;
+            Value = new Vec3D(Value.X, value, Value.Z);
+        }
     }
-    
+
     public double ZValue
     {
         get => Value.Z;
-        set => Value = new Vec3D(Value.X, Value.Y, value);
+        set
+        {
+            if (updateBlocker)
+                return;
+            Value = new Vec3D(Value.X, Value.Y, value);
+        }
     }
 }

+ 19 - 3
src/PixiEditor/ViewModels/Nodes/Properties/VecDPropertyViewModel.cs

@@ -5,6 +5,7 @@ namespace PixiEditor.ViewModels.Nodes.Properties;
 
 internal class VecDPropertyViewModel : NodePropertyViewModel<VecD>
 {
+    private bool updateBlocker = false;
     public VecDPropertyViewModel(NodeViewModel node, Type valueType) : base(node, valueType)
     {
         PropertyChanged += OnPropertyChanged;
@@ -16,20 +17,35 @@ internal class VecDPropertyViewModel : NodePropertyViewModel<VecD>
         {
             return;
         }
+
+        updateBlocker = true;
         
         OnPropertyChanged(nameof(XValue));
         OnPropertyChanged(nameof(YValue));
+
+        updateBlocker = false;
     }
 
     public double XValue
     {
         get => Value.X;
-        set => Value = new VecD(value, Value.Y);
+        set
+        {
+            if (updateBlocker)
+                return;
+            Value = new VecD(value, Value.Y);
+        }
     }
-    
+
     public double YValue
     {
         get => Value.Y;
-        set => Value = new VecD(Value.X, value);
+        set
+        {
+            if (updateBlocker)
+                return;
+
+            Value = new VecD(Value.X, value);
+        }
     }
 }

+ 22 - 4
src/PixiEditor/ViewModels/Nodes/Properties/VecIPropertyViewModel.cs

@@ -5,6 +5,8 @@ namespace PixiEditor.ViewModels.Nodes.Properties;
 
 internal class VecIPropertyViewModel : NodePropertyViewModel<VecI>
 {
+    private bool updateBlocker = false;
+
     public VecIPropertyViewModel(NodeViewModel node, Type valueType) : base(node, valueType)
     {
         PropertyChanged += OnPropertyChanged;
@@ -16,20 +18,36 @@ internal class VecIPropertyViewModel : NodePropertyViewModel<VecI>
         {
             return;
         }
-        
+
+        updateBlocker = true;
+
         OnPropertyChanged(nameof(XValue));
         OnPropertyChanged(nameof(YValue));
+
+        updateBlocker = false;
     }
 
     public int XValue
     {
         get => Value.X;
-        set => Value = new VecI(value, Value.Y);
+        set
+        {
+            if (updateBlocker)
+                return;
+
+            Value = new VecI(value, Value.Y);
+        }
     }
-    
+
     public int YValue
     {
         get => Value.Y;
-        set => Value = new VecI(Value.X, value);
+        set
+        {
+            if (updateBlocker)
+                return;
+
+            Value = new VecI(Value.X, value);
+        }
     }
 }

+ 13 - 0
src/PixiEditor/ViewModels/SubViewModels/NodeGraphManagerViewModel.cs

@@ -85,6 +85,19 @@ internal class NodeGraphManagerViewModel : SubViewModel<ViewModelMain>
             args.value);
     }
 
+    [Command.Internal("PixiEditor.NodeGraph.BeginUpdateValue", AnalyticsTrack = true)]
+    public void BeginUpdatePropertyValue((INodeHandler node, string property, object value) args)
+    {
+        Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.BeginUpdatePropertyValue(args.node, args.property,
+            args.value);
+    }
+
+    [Command.Internal("PixiEditor.NodeGraph.EndUpdateValue", AnalyticsTrack = true)]
+    public void EndUpdatePropertyValue()
+    {
+        Owner.DocumentManagerSubViewModel.ActiveDocument?.NodeGraph.EndUpdatePropertyValue();
+    }
+
     [Command.Internal("PixiEditor.NodeGraph.EndChangeNodePos")]
     public void EndChangeNodePos()
     {

+ 171 - 167
src/PixiEditor/Views/Main/MainTitleBar.axaml

@@ -19,181 +19,185 @@
     <Design.DataContext>
         <menu:MenuBarViewModel />
     </Design.DataContext>
-    <Grid>
-        <dialogs:DialogTitleBar
-            DockPanel.Dock="Top">
-            <dialogs:DialogTitleBar.AdditionalElement>
-                <StackPanel Margin="5 0" Spacing="5" Orientation="Horizontal"
-                            Name="MainBarAdditionalElementPanel"
-                            DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType=main:MainTitleBar}}">
-                    <auth:UserAvatarToggle Width="26" Height="26" DataContext="{Binding Path=UserViewModel}" />
-                </StackPanel>
-            </dialogs:DialogTitleBar.AdditionalElement>
-        </dialogs:DialogTitleBar>
-        <Panel DockPanel.Dock="Left"
-               HorizontalAlignment="Left" Width="20" Height="20">
-            <Panel.Margin>
-                <OnPlatform>
-                    <OnPlatform.macOS>
-                        <Thickness>75, 0, 0, 0</Thickness>
-                    </OnPlatform.macOS>
-                    <OnPlatform.Default>
-                        <Thickness>10, 0, 0, 0</Thickness>
-                    </OnPlatform.Default>
-                </OnPlatform>
-            </Panel.Margin>
-            <ToggleButton Name="LogoButton" Padding="0" BorderThickness="0">
-                <Interaction.Behaviors>
-                    <behaviours:ShowFlyoutOnTrigger Trigger="{Binding OpenPixiEditorMenuTrigger}" />
-                </Interaction.Behaviors>
-                <ToggleButton.Background>
-                    <VisualBrush>
-                        <VisualBrush.Visual>
-                            <Svg Path="/Images/PixiEditorLogo.svg" />
-                        </VisualBrush.Visual>
-                    </VisualBrush>
-                </ToggleButton.Background>
-                <ToggleButton.Styles>
-                    <Style Selector="FlyoutPresenter.arrow">
-                        <Setter Property="Cursor" Value="Arrow" />
-                    </Style>
-                </ToggleButton.Styles>
-                <ToggleButton.Flyout>
-                    <Flyout Placement="BottomEdgeAlignedLeft" FlyoutPresenterClasses="arrow">
-                        <Flyout.Content>
-                            <StackPanel Spacing="5" Margin="5">
-                                <TextBlock Text="PixiEditor" Classes="h3" />
-                                <TextBlock Text="{Binding UpdateViewModel.VersionText}" />
-                                <TextBlock IsVisible="{Binding UpdateViewModel.SelfUpdatingAvailable}"
-                                           Text="{Binding UpdateViewModel.UpdateStateString}" />
-                                <Button ui:Translator.Key="DOWNLOAD_UPDATE"
-                                        Background="{DynamicResource ThemeAccentBrush}"
-                                        Command="{Binding UpdateViewModel.DownloadCommand}"
-                                        IsVisible="{Binding UpdateViewModel.IsUpdateAvailable}" />
-                                <ProgressBar IsVisible="{Binding UpdateViewModel.IsDownloading}" 
-                                             ShowProgressText="True" Value="{Binding UpdateViewModel.CurrentProgress}"
-                                              Maximum="100" Minimum="0"/>
-                                <Button ui:Translator.Key="SWITCH_TO_NEW_VERSION"
-                                        Background="{DynamicResource ThemeAccentBrush}"
-                                        Command="{Binding UpdateViewModel.InstallCommand}"
-                                        IsVisible="{Binding UpdateViewModel.UpdateReadyToInstall}" />
-                            </StackPanel>
-                        </Flyout.Content>
-                    </Flyout>
-                </ToggleButton.Flyout>
-            </ToggleButton>
-            <Panel ClipToBounds="False" Margin="10, 10, 0, 0"
-                   IsVisible="{Binding UpdateViewModel.SelfUpdatingAvailable}"
-                   IsHitTestVisible="False" VerticalAlignment="Bottom" HorizontalAlignment="Right">
-                <Panel.Styles>
-                    <Style Selector="TextBlock.pulse">
-                        <Style.Animations>
-                            <Animation Duration="0:0:1" DelayBetweenIterations="0:0:2.5" IterationCount="60">
-                                <Animation.Easing>
-                                    <CubicEaseInOut />
-                                </Animation.Easing>
-                                <KeyFrame Cue="0%">
-                                    <Setter Property="ScaleTransform.ScaleX" Value="1" />
-                                    <Setter Property="ScaleTransform.ScaleY" Value="1" />
-                                    <Setter Property="RotateTransform.Angle" Value="0" />
-                                </KeyFrame>
-                                <KeyFrame Cue="50%">
-                                    <Setter Property="ScaleTransform.ScaleX" Value="1.5" />
-                                    <Setter Property="ScaleTransform.ScaleY" Value="1.5" />
-                                    <Setter Property="RotateTransform.Angle" Value="45" />
-                                </KeyFrame>
-                                <KeyFrame Cue="75%">
-                                    <Setter Property="RotateTransform.Angle" Value="-45" />
-                                </KeyFrame>
-                                <KeyFrame Cue="100%">
-                                    <Setter Property="ScaleTransform.ScaleX" Value="1" />
-                                    <Setter Property="ScaleTransform.ScaleY" Value="1" />
-                                    <Setter Property="RotateTransform.Angle" Value="0" />
-                                </KeyFrame>
-                            </Animation>
-                        </Style.Animations>
-                    </Style>
-                </Panel.Styles>
-                <TextBlock Text="!" IsVisible="{Binding UpdateViewModel.IsUpdateAvailable}"
-                           ClipToBounds="False"
-                           FontWeight="SemiBold" Classes="pulse"
-                           Foreground="Yellow" FontSize="18" />
-                <TextBlock Text="{DynamicResource icon-settings}"
-                           ClipToBounds="False"
-                           Width="16" Height="16"
-                           IsVisible="{Binding UpdateViewModel.IsDownloading}"
-                           Classes="pixi-icon" Foreground="DarkGray" FontSize="16">
-                    <TextBlock.Styles>
-                        <Style Selector="TextBlock">
+    <Panel>
+        <Panel Background="{DynamicResource ThemeBackgroundBrush1}" IsHitTestVisible="False"/>
+        <DockPanel>
+            <dialogs:DialogTitleBar
+                DockPanel.Dock="Right">
+                <dialogs:DialogTitleBar.AdditionalElement>
+                    <StackPanel Margin="5 0" Spacing="5" Orientation="Horizontal"
+                                Name="MainBarAdditionalElementPanel"
+                                DataContext="{Binding Path=DataContext, RelativeSource={RelativeSource FindAncestor, AncestorType=main:MainTitleBar}}">
+                        <auth:UserAvatarToggle Width="26" Height="26" DataContext="{Binding Path=UserViewModel}" />
+                    </StackPanel>
+                </dialogs:DialogTitleBar.AdditionalElement>
+            </dialogs:DialogTitleBar>
+            <Panel DockPanel.Dock="Left"
+                   HorizontalAlignment="Left" Width="20" Height="20">
+                <Panel.Margin>
+                    <OnPlatform>
+                        <OnPlatform.macOS>
+                            <Thickness>75, 0, 0, 0</Thickness>
+                        </OnPlatform.macOS>
+                        <OnPlatform.Default>
+                            <Thickness>10, 0, 0, 0</Thickness>
+                        </OnPlatform.Default>
+                    </OnPlatform>
+                </Panel.Margin>
+                <ToggleButton Name="LogoButton" Padding="0" BorderThickness="0">
+                    <Interaction.Behaviors>
+                        <behaviours:ShowFlyoutOnTrigger Trigger="{Binding OpenPixiEditorMenuTrigger}" />
+                    </Interaction.Behaviors>
+                    <ToggleButton.Background>
+                        <VisualBrush>
+                            <VisualBrush.Visual>
+                                <Svg Path="/Images/PixiEditorLogo.svg" />
+                            </VisualBrush.Visual>
+                        </VisualBrush>
+                    </ToggleButton.Background>
+                    <ToggleButton.Styles>
+                        <Style Selector="FlyoutPresenter.arrow">
+                            <Setter Property="Cursor" Value="Arrow" />
+                        </Style>
+                    </ToggleButton.Styles>
+                    <ToggleButton.Flyout>
+                        <Flyout Placement="BottomEdgeAlignedLeft" FlyoutPresenterClasses="arrow">
+                            <Flyout.Content>
+                                <StackPanel Spacing="5" Margin="5">
+                                    <TextBlock Text="PixiEditor" Classes="h3" />
+                                    <TextBlock Text="{Binding UpdateViewModel.VersionText}" />
+                                    <TextBlock IsVisible="{Binding UpdateViewModel.SelfUpdatingAvailable}"
+                                               Text="{Binding UpdateViewModel.UpdateStateString}" />
+                                    <Button ui:Translator.Key="DOWNLOAD_UPDATE"
+                                            Background="{DynamicResource ThemeAccentBrush}"
+                                            Command="{Binding UpdateViewModel.DownloadCommand}"
+                                            IsVisible="{Binding UpdateViewModel.IsUpdateAvailable}" />
+                                    <ProgressBar IsVisible="{Binding UpdateViewModel.IsDownloading}"
+                                                 ShowProgressText="True"
+                                                 Value="{Binding UpdateViewModel.CurrentProgress}"
+                                                 Maximum="100" Minimum="0" />
+                                    <Button ui:Translator.Key="SWITCH_TO_NEW_VERSION"
+                                            Background="{DynamicResource ThemeAccentBrush}"
+                                            Command="{Binding UpdateViewModel.InstallCommand}"
+                                            IsVisible="{Binding UpdateViewModel.UpdateReadyToInstall}" />
+                                </StackPanel>
+                            </Flyout.Content>
+                        </Flyout>
+                    </ToggleButton.Flyout>
+                </ToggleButton>
+                <Panel ClipToBounds="False" Margin="10, 10, 0, 0"
+                       IsVisible="{Binding UpdateViewModel.SelfUpdatingAvailable}"
+                       IsHitTestVisible="False" VerticalAlignment="Bottom" HorizontalAlignment="Right">
+                    <Panel.Styles>
+                        <Style Selector="TextBlock.pulse">
                             <Style.Animations>
-                                <Animation Duration="0:0:5" IterationCount="Infinite">
+                                <Animation Duration="0:0:1" DelayBetweenIterations="0:0:2.5" IterationCount="60">
+                                    <Animation.Easing>
+                                        <CubicEaseInOut />
+                                    </Animation.Easing>
                                     <KeyFrame Cue="0%">
-                                        <Setter Property="RotateTransform.Angle" Value="0.0" />
+                                        <Setter Property="ScaleTransform.ScaleX" Value="1" />
+                                        <Setter Property="ScaleTransform.ScaleY" Value="1" />
+                                        <Setter Property="RotateTransform.Angle" Value="0" />
+                                    </KeyFrame>
+                                    <KeyFrame Cue="50%">
+                                        <Setter Property="ScaleTransform.ScaleX" Value="1.5" />
+                                        <Setter Property="ScaleTransform.ScaleY" Value="1.5" />
+                                        <Setter Property="RotateTransform.Angle" Value="45" />
+                                    </KeyFrame>
+                                    <KeyFrame Cue="75%">
+                                        <Setter Property="RotateTransform.Angle" Value="-45" />
                                     </KeyFrame>
                                     <KeyFrame Cue="100%">
-                                        <Setter Property="RotateTransform.Angle" Value="360" />
+                                        <Setter Property="ScaleTransform.ScaleX" Value="1" />
+                                        <Setter Property="ScaleTransform.ScaleY" Value="1" />
+                                        <Setter Property="RotateTransform.Angle" Value="0" />
                                     </KeyFrame>
                                 </Animation>
                             </Style.Animations>
                         </Style>
-                    </TextBlock.Styles>
-                </TextBlock>
-                <Ellipse
-                    ClipToBounds="False"
-                    Fill="{DynamicResource ThemeAccent3Brush}"
-                    IsVisible="{Binding UpdateViewModel.UpdateReadyToInstall}"
-                    Width="8" Height="8" />
+                    </Panel.Styles>
+                    <TextBlock Text="!" IsVisible="{Binding UpdateViewModel.IsUpdateAvailable}"
+                               ClipToBounds="False"
+                               FontWeight="SemiBold" Classes="pulse"
+                               Foreground="Yellow" FontSize="18" />
+                    <TextBlock Text="{DynamicResource icon-settings}"
+                               ClipToBounds="False"
+                               Width="16" Height="16"
+                               IsVisible="{Binding UpdateViewModel.IsDownloading}"
+                               Classes="pixi-icon" Foreground="DarkGray" FontSize="16">
+                        <TextBlock.Styles>
+                            <Style Selector="TextBlock">
+                                <Style.Animations>
+                                    <Animation Duration="0:0:5" IterationCount="Infinite">
+                                        <KeyFrame Cue="0%">
+                                            <Setter Property="RotateTransform.Angle" Value="0.0" />
+                                        </KeyFrame>
+                                        <KeyFrame Cue="100%">
+                                            <Setter Property="RotateTransform.Angle" Value="360" />
+                                        </KeyFrame>
+                                    </Animation>
+                                </Style.Animations>
+                            </Style>
+                        </TextBlock.Styles>
+                    </TextBlock>
+                    <Ellipse
+                        ClipToBounds="False"
+                        Fill="{DynamicResource ThemeAccent3Brush}"
+                        IsVisible="{Binding UpdateViewModel.UpdateReadyToInstall}"
+                        Width="8" Height="8" />
+                </Panel>
             </Panel>
-        </Panel>
 
-        <StackPanel Orientation="Horizontal">
-            <StackPanel.Margin>
-                <OnPlatform>
-                    <OnPlatform.macOS>
-                        <Thickness>95, 0, 0, 0</Thickness>
-                    </OnPlatform.macOS>
-                </OnPlatform>
-            </StackPanel.Margin>
-            <xaml:Menu IsVisible="{OnPlatform macOS=false, Default=true}"
-                       Margin="40, 0, 0, 0"
-                       DockPanel.Dock="Left"
-                       HorizontalAlignment="Left"
-                       VerticalAlignment="Center"
-                       ItemsSource="{Binding MenuEntries}"
-                       Background="Transparent" />
-            <Border DockPanel.Dock="Left" Height="25" Width="150"
-                    Background="{DynamicResource ThemeBackgroundBrush}"
-                    CornerRadius="5" BorderThickness="1"
-                    Margin="10,0,0,0"
-                    Cursor="IBeam">
-                <Border.Styles>
-                    <Style Selector="Border">
-                        <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
-                    </Style>
-                    <Style Selector="Border:pointerover">
-                        <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}" />
-                    </Style>
-                </Border.Styles>
-                <Interaction.Behaviors>
-                    <EventTriggerBehavior
-                        EventName="PointerPressed">
-                        <InvokeCommandAction
-                            Command="{xaml:Command PixiEditor.Search.Toggle}" />
-                    </EventTriggerBehavior>
-                </Interaction.Behaviors>
-                <Grid Margin="5,0" VerticalAlignment="Center">
-                    <TextBlock ui:Translator.Key="SEARCH" />
-                    <TextBlock
-                        Text="{xaml:ShortcutBinding PixiEditor.Search.Toggle, Converter={converters:KeyToStringConverter}}"
-                        HorizontalAlignment="Right" />
-                </Grid>
-            </Border>
-        </StackPanel>
-        <main:MiniAnimationPlayer
-            Name="MiniPlayer"
-            ActiveFrame="{Binding ActiveDocument.AnimationDataViewModel.ActiveFrameBindable, Source={viewModels:MainVM DocumentManagerSVM}, Mode=TwoWay}"
-            FramesCount="{Binding ActiveDocument.AnimationDataViewModel.FramesCount, Source={viewModels:MainVM DocumentManagerSVM}}"
-            IsPlaying="{Binding ActiveDocument.AnimationDataViewModel.IsPlayingBindable, Source={viewModels:MainVM DocumentManagerSVM}, Mode=TwoWay}"
-            Margin="0 4" />
-    </Grid>
+            <StackPanel Orientation="Horizontal">
+                <StackPanel.Margin>
+                    <OnPlatform>
+                        <OnPlatform.macOS>
+                            <Thickness>95, 0, 0, 0</Thickness>
+                        </OnPlatform.macOS>
+                    </OnPlatform>
+                </StackPanel.Margin>
+                <xaml:Menu IsVisible="{OnPlatform macOS=false, Default=true}"
+                           Margin="5, 0, 0, 0"
+                           DockPanel.Dock="Left"
+                           HorizontalAlignment="Left"
+                           VerticalAlignment="Center"
+                           ItemsSource="{Binding MenuEntries}"
+                           Background="Transparent" />
+                <Border DockPanel.Dock="Left" Height="25" Width="150"
+                        Background="{DynamicResource ThemeBackgroundBrush}"
+                        CornerRadius="5" BorderThickness="1"
+                        Margin="10,0,0,0"
+                        Cursor="IBeam">
+                    <Border.Styles>
+                        <Style Selector="Border">
+                            <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}" />
+                        </Style>
+                        <Style Selector="Border:pointerover">
+                            <Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}" />
+                        </Style>
+                    </Border.Styles>
+                    <Interaction.Behaviors>
+                        <EventTriggerBehavior
+                            EventName="PointerPressed">
+                            <InvokeCommandAction
+                                Command="{xaml:Command PixiEditor.Search.Toggle}" />
+                        </EventTriggerBehavior>
+                    </Interaction.Behaviors>
+                    <Grid Margin="5,0" VerticalAlignment="Center">
+                        <TextBlock ui:Translator.Key="SEARCH" />
+                        <TextBlock
+                            Text="{xaml:ShortcutBinding PixiEditor.Search.Toggle, Converter={converters:KeyToStringConverter}}"
+                            HorizontalAlignment="Right" />
+                    </Grid>
+                </Border>
+            </StackPanel>
+            <main:MiniAnimationPlayer
+                Name="MiniPlayer"
+                ActiveFrame="{Binding ActiveDocument.AnimationDataViewModel.ActiveFrameBindable, Source={viewModels:MainVM DocumentManagerSVM}, Mode=TwoWay}"
+                FramesCount="{Binding ActiveDocument.AnimationDataViewModel.FramesCount, Source={viewModels:MainVM DocumentManagerSVM}}"
+                IsPlaying="{Binding ActiveDocument.AnimationDataViewModel.IsPlayingBindable, Source={viewModels:MainVM DocumentManagerSVM}, Mode=TwoWay}"
+                Margin="0 4" />
+        </DockPanel>
+    </Panel>
 </UserControl>

+ 25 - 21
src/PixiEditor/Views/Nodes/Properties/ColorMatrixPropertyView.axaml

@@ -6,10 +6,14 @@
                              xmlns:ui="clr-namespace:PixiEditor.UI.Common.Localization;assembly=PixiEditor.UI.Common"
                              xmlns:input="clr-namespace:PixiEditor.UI.Common.Controls;assembly=PixiEditor.UI.Common"
                              xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
+                             xmlns:properties1="clr-namespace:PixiEditor.ViewModels.Nodes.Properties"
                              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
                              x:Class="PixiEditor.Views.Nodes.Properties.ColorMatrixPropertyView">
+    <Design.DataContext>
+        <properties1:ColorMatrixPropertyViewModel />
+    </Design.DataContext>
     <StackPanel HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-        <TextBlock ui:Translator.Key="{Binding DisplayName}" />
+        <TextBlock ui:Translator.TooltipKey="{Binding DisplayName}" ui:Translator.Key="{Binding DisplayName}" />
         <Grid IsVisible="{Binding ShowInputField}" ColumnDefinitions="Auto,*,*,*,*,*" RowDefinitions="Auto, Auto, Auto, Auto, Auto">
             <TextBlock Grid.Row="0" Grid.Column="0" Text="T\F" HorizontalAlignment="Center" VerticalAlignment="Center" TextAlignment="Center"  />
             
@@ -24,26 +28,26 @@
             <TextBlock Grid.Row="0" Grid.Column="4" Text="A" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="1,0" TextAlignment="Center" />
             <TextBlock Grid.Row="0" Grid.Column="5" Text="+" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="1,0" TextAlignment="Center" />
             
-            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="1" IsVisible="{Binding IsInput}" Value="{Binding M11, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="2" IsVisible="{Binding IsInput}" Value="{Binding M12, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="3" IsVisible="{Binding IsInput}" Value="{Binding M13, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="4" IsVisible="{Binding IsInput}" Value="{Binding M14, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="5" IsVisible="{Binding IsInput}" Value="{Binding M15, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="1" IsVisible="{Binding IsInput}" Value="{Binding M21, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="2" IsVisible="{Binding IsInput}" Value="{Binding M22, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="3" IsVisible="{Binding IsInput}" Value="{Binding M23, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="4" IsVisible="{Binding IsInput}" Value="{Binding M24, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="5" IsVisible="{Binding IsInput}" Value="{Binding M25, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="1" IsVisible="{Binding IsInput}" Value="{Binding M31, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="2" IsVisible="{Binding IsInput}" Value="{Binding M32, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="3" IsVisible="{Binding IsInput}" Value="{Binding M33, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="4" IsVisible="{Binding IsInput}" Value="{Binding M34, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="5" IsVisible="{Binding IsInput}" Value="{Binding M35, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="1" IsVisible="{Binding IsInput}" Value="{Binding M41, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="2" IsVisible="{Binding IsInput}" Value="{Binding M42, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="3" IsVisible="{Binding IsInput}" Value="{Binding M43, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="4" IsVisible="{Binding IsInput}" Value="{Binding M44, Mode=TwoWay}" />
-            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="5" IsVisible="{Binding IsInput}" Value="{Binding M45, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="1" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M11, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="2" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M12, Mode=TwoWay}"  />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="3" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M13, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="4" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M14, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="5" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M15, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="1" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M21, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="2" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M22, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="3" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M23, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="4" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M24, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="5" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M25, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="1" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M31, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="2" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M32, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="3" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M33, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="4" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M34, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="5" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M35, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="1" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M41, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="2" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M42, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="3" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M43, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="4" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M44, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="5" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M45, Mode=TwoWay}" />
         </Grid>
     </StackPanel>
 </properties:NodePropertyView>

+ 1 - 1
src/PixiEditor/Views/Nodes/Properties/ColorPropertyView.axaml

@@ -10,7 +10,7 @@
                              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
                              x:Class="PixiEditor.Views.Nodes.Properties.ColorPropertyView">
     <Grid HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}"/>
+        <TextBlock ui:Translator.TooltipKey="{Binding DisplayName}" VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}"/>
         <colorPicker:PortableColorPicker
             EnableGradientsTab="false"
             PointerPressed="InputElement_OnPointerPressed"

+ 2 - 1
src/PixiEditor/Views/Nodes/Properties/DoublePropertyView.axaml

@@ -20,9 +20,10 @@
                 HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
 
         <DockPanel>
-            <TextBlock VerticalAlignment="Center" DockPanel.Dock="Left" ui:Translator.Key="{Binding DisplayName}" />
+            <TextBlock ui:Translator.TooltipKey="{Binding DisplayName}" VerticalAlignment="Center" DockPanel.Dock="Left" ui:Translator.Key="{Binding DisplayName}" />
             <Panel HorizontalAlignment="Right" IsVisible="{Binding ShowInputField}">
                 <controls:NumberInput EnableScrollChange="False" Name="input"
+                                      DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}"
                                    MinWidth="100" Decimals="6"
                                    IsVisible="{Binding NumberPickerMode,
                                 Converter={converters:EnumBooleanConverter}, ConverterParameter=NumberInput}"

+ 1 - 1
src/PixiEditor/Views/Nodes/Properties/FontFamilyNamePropertyView.axaml

@@ -13,7 +13,7 @@
                              x:Class="PixiEditor.Views.Nodes.Properties.FontFamilyNamePropertyView">
     <DockPanel LastChildFill="True"
         HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}" />
+        <TextBlock ui:Translator.TooltipKey="{Binding DisplayName}" VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}" />
         <input:FontFamilyPicker Margin="5 0"
            FontIndex="{Binding FontFamilyIndex, Mode=TwoWay}"
                           IsVisible="{Binding ShowInputField}">

+ 1 - 1
src/PixiEditor/Views/Nodes/Properties/GenericEnumPropertyView.axaml

@@ -15,7 +15,7 @@
 
     <Grid
         HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}" />
+        <TextBlock ui:Translator.TooltipKey="{Binding DisplayName}" VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}" />
         <ComboBox PointerPressed="InputElement_OnPointerPressed" HorizontalAlignment="Right" MinWidth="100" IsVisible="{Binding ShowInputField}"
                   SelectedIndex="{Binding SelectedIndex, Mode=TwoWay}" ItemsSource="{Binding Values}">
             <ComboBox.ItemTemplate>

+ 1 - 0
src/PixiEditor/Views/Nodes/Properties/GenericPropertyView.axaml

@@ -10,6 +10,7 @@
     <Grid>
         <TextBlock
             VerticalAlignment="Center"
+            ui:Translator.TooltipKey="{Binding DisplayName}"
             ui:Translator.Key="{Binding DisplayName}"
             HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Left'}}" />
     </Grid>

+ 6 - 1
src/PixiEditor/Views/Nodes/Properties/Int32PropertyView.axaml

@@ -6,11 +6,16 @@
                              xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
                              xmlns:controls="clr-namespace:PixiEditor.UI.Common.Controls;assembly=PixiEditor.UI.Common"
                              xmlns:localization="clr-namespace:PixiEditor.UI.Common.Localization;assembly=PixiEditor.UI.Common"
+                             xmlns:properties1="clr-namespace:PixiEditor.ViewModels.Nodes.Properties"
                              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
                              x:Class="PixiEditor.Views.Nodes.Properties.Int32PropertyView">
+    <Design.DataContext>
+        <properties1:Int32PropertyViewModel />
+    </Design.DataContext>
     <Grid HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-        <TextBlock VerticalAlignment="Center" localization:Translator.Key="{Binding DisplayName}"/>
+        <TextBlock localization:Translator.TooltipKey="{Binding DisplayName}" VerticalAlignment="Center" localization:Translator.Key="{Binding DisplayName}"/>
         <controls:NumberInput EnableScrollChange="False" Decimals="0"
+                              DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}"
             HorizontalAlignment="Right" MinWidth="100" IsVisible="{Binding ShowInputField}" Value="{Binding Value, Mode=TwoWay}" />
     </Grid>
 </properties:NodePropertyView>

+ 6 - 2
src/PixiEditor/Views/Nodes/Properties/KernelPropertyView.axaml

@@ -8,12 +8,16 @@
                              xmlns:input="clr-namespace:PixiEditor.Views.Input"
                              xmlns:system="clr-namespace:System;assembly=System.Runtime"
                              xmlns:controls="clr-namespace:PixiEditor.UI.Common.Controls;assembly=PixiEditor.UI.Common"
+                             xmlns:properties1="clr-namespace:PixiEditor.ViewModels.Nodes.Properties"
                              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
                              x:Class="PixiEditor.Views.Nodes.Properties.KernelPropertyView">
+    <Design.DataContext>
+        <properties1:KernelPropertyViewModel />
+    </Design.DataContext>
 
     <StackPanel Margin="0,2">
         <Grid ColumnDefinitions="*,*,*" Margin="0,0,0,2">
-            <TextBlock ui:Translator.Key="{Binding DisplayName}" VerticalAlignment="Center"/>
+            <TextBlock ui:Translator.TooltipKey="{Binding DisplayName}" ui:Translator.Key="{Binding DisplayName}" VerticalAlignment="Center"/>
             <StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center">
                 <Button Padding="0" Content="-" Width="35" Command="{Binding AdjustSizeCommand}">
                     <Button.CommandParameter>
@@ -39,7 +43,7 @@
         <ItemsControl ItemsSource="{Binding ReferenceCollections}" Margin="0,1">
             <ItemsControl.ItemTemplate>
                 <DataTemplate>
-                    <controls:NumberInput EnableScrollChange="False" Value="{Binding Value, Mode=TwoWay}" Decimals="4" />
+                    <controls:NumberInput DraggingGrabber="{Binding Path=MergeChanges, Mode=OneWayToSource}" EnableScrollChange="False" Value="{Binding Value, Mode=TwoWay}" Decimals="4" />
                 </DataTemplate>
             </ItemsControl.ItemTemplate>
             <ItemsControl.ItemsPanel>

+ 25 - 21
src/PixiEditor/Views/Nodes/Properties/Matrix4x5FPropertyView.axaml

@@ -6,10 +6,14 @@
                              xmlns:input="clr-namespace:PixiEditor.UI.Common.Controls;assembly=PixiEditor.UI.Common"
                              xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
                              xmlns:localization="clr-namespace:PixiEditor.UI.Common.Localization;assembly=PixiEditor.UI.Common"
+                             xmlns:properties1="clr-namespace:PixiEditor.ViewModels.Nodes.Properties"
                              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
                              x:Class="PixiEditor.Views.Nodes.Properties.Matrix4x5FPropertyView">
+    <Design.DataContext>
+        <properties1:Matrix4x5FPropertyViewModel />
+    </Design.DataContext>
     <StackPanel HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-        <TextBlock localization:Translator.Key="{Binding DisplayName}" />
+        <TextBlock localization:Translator.TooltipKey="{Binding DisplayName}" localization:Translator.Key="{Binding DisplayName}" />
         <Grid IsVisible="{Binding ShowInputField}" ColumnDefinitions="Auto,*,*,*,*,*" RowDefinitions="Auto, Auto, Auto, Auto, Auto">
             <TextBlock Grid.Row="1" Grid.Column="0" Text="1" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="1,0" />
             <TextBlock Grid.Row="2" Grid.Column="0" Text="2" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="1,0" />
@@ -22,26 +26,26 @@
             <TextBlock Grid.Row="0" Grid.Column="4" Text="4" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="1,0" />
             <TextBlock Grid.Row="0" Grid.Column="5" Text="5" HorizontalAlignment="Center" VerticalAlignment="Center" Margin="1,0" />
             
-            <input:NumberInput Grid.Row="1" Grid.Column="1" IsVisible="{Binding IsInput}" Value="{Binding M11, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="1" Grid.Column="2" IsVisible="{Binding IsInput}" Value="{Binding M12, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="1" Grid.Column="3" IsVisible="{Binding IsInput}" Value="{Binding M13, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="1" Grid.Column="4" IsVisible="{Binding IsInput}" Value="{Binding M14, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="1" Grid.Column="5" IsVisible="{Binding IsInput}" Value="{Binding M15, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="2" Grid.Column="1" IsVisible="{Binding IsInput}" Value="{Binding M21, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="2" Grid.Column="2" IsVisible="{Binding IsInput}" Value="{Binding M22, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="2" Grid.Column="3" IsVisible="{Binding IsInput}" Value="{Binding M23, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="2" Grid.Column="4" IsVisible="{Binding IsInput}" Value="{Binding M24, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="2" Grid.Column="5" IsVisible="{Binding IsInput}" Value="{Binding M25, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="3" Grid.Column="1" IsVisible="{Binding IsInput}" Value="{Binding M31, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="3" Grid.Column="2" IsVisible="{Binding IsInput}" Value="{Binding M32, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="3" Grid.Column="3" IsVisible="{Binding IsInput}" Value="{Binding M33, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="3" Grid.Column="4" IsVisible="{Binding IsInput}" Value="{Binding M34, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="3" Grid.Column="5" IsVisible="{Binding IsInput}" Value="{Binding M35, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="4" Grid.Column="1" IsVisible="{Binding IsInput}" Value="{Binding M41, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="4" Grid.Column="2" IsVisible="{Binding IsInput}" Value="{Binding M42, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="4" Grid.Column="3" IsVisible="{Binding IsInput}" Value="{Binding M43, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="4" Grid.Column="4" IsVisible="{Binding IsInput}" Value="{Binding M44, Mode=TwoWay}" />
-            <input:NumberInput Grid.Row="4" Grid.Column="5" IsVisible="{Binding IsInput}" Value="{Binding M45, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="1" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M11, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="2" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M12, Mode=TwoWay}"  />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="3" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M13, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="4" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M14, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="1" Grid.Column="5" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M15, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="1" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M21, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="2" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M22, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="3" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M23, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="4" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M24, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="2" Grid.Column="5" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M25, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="1" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M31, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="2" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M32, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="3" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M33, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="4" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M34, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="3" Grid.Column="5" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M35, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="1" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M41, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="2" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M42, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="3" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M43, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="4" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M44, Mode=TwoWay}" />
+            <input:NumberInput EnableScrollChange="False" Grid.Row="4" Grid.Column="5" IsVisible="{Binding IsInput}" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding M45, Mode=TwoWay}" />
         </Grid>
     </StackPanel>
 </properties:NodePropertyView>

+ 1 - 1
src/PixiEditor/Views/Nodes/Properties/PaintablePropertyView.axaml

@@ -10,7 +10,7 @@
                              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
                              x:Class="PixiEditor.Views.Nodes.Properties.PaintablePropertyView">
     <Grid HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}"/>
+        <TextBlock ui:Translator.TooltipKey="{Binding DisplayName}" VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}"/>
         <colorPicker:PortableColorPicker
             EnableGradientsTab="{Binding EnableGradients}"
             PointerPressed="InputElement_OnPointerPressed"

+ 1 - 1
src/PixiEditor/Views/Nodes/Properties/StringPropertyView.axaml

@@ -14,7 +14,7 @@
     <Grid>
         <DockPanel LastChildFill="True"
                    HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-            <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}" />
+            <TextBlock ui:Translator.TooltipKey="{Binding DisplayName}" VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}" />
             <TextBox MaxWidth="110"
                      MaxLines="1"
                      Name="smallTextBox"

+ 8 - 4
src/PixiEditor/Views/Nodes/Properties/Vec3DPropertyView.axaml

@@ -7,14 +7,18 @@
                              xmlns:ui="clr-namespace:PixiEditor.UI.Common.Localization;assembly=PixiEditor.UI.Common"
                              xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
                              xmlns:controls="clr-namespace:PixiEditor.UI.Common.Controls;assembly=PixiEditor.UI.Common"
+                             xmlns:properties1="clr-namespace:PixiEditor.ViewModels.Nodes.Properties"
                              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
                              x:Class="PixiEditor.Views.Nodes.Properties.Vec3DPropertyView">
+    <Design.DataContext>
+        <properties1:Vec3DPropertyViewModel />
+    </Design.DataContext>
     <StackPanel HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}"/>
+        <TextBlock ui:Translator.TooltipKey="{Binding DisplayName}" VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}"/>
         <StackPanel IsVisible="{Binding ShowInputField}">
-            <controls:NumberInput EnableScrollChange="False" MinWidth="100" Value="{Binding XValue, Mode=TwoWay}" Margin="0,2" />
-            <controls:NumberInput EnableScrollChange="False" MinWidth="100" Value="{Binding YValue, Mode=TwoWay}" Margin="0,2" />
-            <controls:NumberInput EnableScrollChange="False" MinWidth="100" Value="{Binding ZValue, Mode=TwoWay}" Margin="0,2" />
+            <controls:NumberInput EnableScrollChange="False" MinWidth="100" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding XValue, Mode=TwoWay}" Margin="0,2" />
+            <controls:NumberInput EnableScrollChange="False" MinWidth="100" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding YValue, Mode=TwoWay}" Margin="0,2" />
+            <controls:NumberInput EnableScrollChange="False" MinWidth="100" DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" Value="{Binding ZValue, Mode=TwoWay}" Margin="0,2" />
         </StackPanel>
     </StackPanel>
 </properties:NodePropertyView>

+ 7 - 3
src/PixiEditor/Views/Nodes/Properties/VecDPropertyView.axaml

@@ -7,13 +7,17 @@
                              xmlns:ui="clr-namespace:PixiEditor.UI.Common.Localization;assembly=PixiEditor.UI.Common"
                              xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
                              xmlns:controls="clr-namespace:PixiEditor.UI.Common.Controls;assembly=PixiEditor.UI.Common"
+                             xmlns:properties1="clr-namespace:PixiEditor.ViewModels.Nodes.Properties"
                              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
                              x:Class="PixiEditor.Views.Nodes.Properties.VecDPropertyView">
+    <Design.DataContext>
+        <properties1:VecDPropertyViewModel />
+    </Design.DataContext>
     <StackPanel HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}"/>
+        <TextBlock ui:Translator.TooltipKey="{Binding DisplayName}" VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}"/>
         <StackPanel IsVisible="{Binding ShowInputField}">
-            <controls:NumberInput EnableScrollChange="False" MinWidth="100" Value="{Binding XValue, Mode=TwoWay}" Margin="0,2" />
-            <controls:NumberInput EnableScrollChange="False" MinWidth="100" Value="{Binding YValue, Mode=TwoWay}" />
+            <controls:NumberInput DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" EnableScrollChange="False" MinWidth="100" Value="{Binding XValue, Mode=TwoWay}" Margin="0,2" />
+            <controls:NumberInput DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" EnableScrollChange="False" MinWidth="100" Value="{Binding YValue, Mode=TwoWay}" />
         </StackPanel>
     </StackPanel>
 </properties:NodePropertyView>

+ 7 - 3
src/PixiEditor/Views/Nodes/Properties/VecIPropertyView.axaml

@@ -7,15 +7,19 @@
                              xmlns:converters="clr-namespace:PixiEditor.Helpers.Converters"
                              xmlns:input="clr-namespace:PixiEditor.Views.Input"
                              xmlns:controls="clr-namespace:PixiEditor.UI.Common.Controls;assembly=PixiEditor.UI.Common"
+                             xmlns:properties1="clr-namespace:PixiEditor.ViewModels.Nodes.Properties"
                              mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
                              x:Class="PixiEditor.Views.Nodes.Properties.VecIPropertyView">
+    <Design.DataContext>
+        <properties1:VecIPropertyViewModel />
+    </Design.DataContext>
     <StackPanel
         HorizontalAlignment="{Binding IsInput, Converter={converters:BoolToValueConverter FalseValue='Right', TrueValue='Stretch'}}">
-        <TextBlock VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}" />
+        <TextBlock ui:Translator.TooltipKey="{Binding DisplayName}" VerticalAlignment="Center" ui:Translator.Key="{Binding DisplayName}" />
         <StackPanel IsVisible="{Binding ShowInputField}">
-            <controls:NumberInput EnableScrollChange="False" MinWidth="100" Decimals="0"
+            <controls:NumberInput DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" EnableScrollChange="False" MinWidth="100" Decimals="0"
                                   Value="{Binding XValue, Mode=TwoWay}" Margin="0,2" />
-            <controls:NumberInput EnableScrollChange="False" MinWidth="100" Decimals="0"
+            <controls:NumberInput DraggingGrabber="{Binding MergeChanges, Mode=OneWayToSource}" EnableScrollChange="False" MinWidth="100" Decimals="0"
                                   Value="{Binding YValue, Mode=TwoWay}" />
         </StackPanel>
     </StackPanel>