Quellcode durchsuchen

Added double click quick selection, and text cursor clicking

flabbet vor 7 Monaten
Ursprung
Commit
ca166e2b37
19 geänderte Dateien mit 170 neuen und 23 gelöschten Zeilen
  1. 9 0
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/Shapes/IReadOnlyTextData.cs
  2. 2 1
      src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/TextVectorData.cs
  3. 1 0
      src/PixiEditor.Extensions/UI/Overlays/OverlayPointerArgs.cs
  4. 6 7
      src/PixiEditor/Data/Configs/ToolSetsConfig.json
  5. 2 1
      src/PixiEditor/Data/Localization/Languages/en.json
  6. 3 3
      src/PixiEditor/Models/Controllers/InputDevice/MouseInputFilter.cs
  7. 10 6
      src/PixiEditor/Models/Controllers/InputDevice/MouseOnCanvasEventArgs.cs
  8. 21 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedExecutor.cs
  9. 2 0
      src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorTextToolExecutor.cs
  10. 1 0
      src/PixiEditor/Models/Handlers/ILayerHandler.cs
  11. 1 0
      src/PixiEditor/Models/Handlers/ITextOverlayHandler.cs
  12. 4 0
      src/PixiEditor/ViewModels/Document/Nodes/ImageLayerNodeViewModel.cs
  13. 29 0
      src/PixiEditor/ViewModels/Document/Nodes/VectorLayerNodeViewModel.cs
  14. 19 0
      src/PixiEditor/ViewModels/Document/TransformOverlays/TextOverlayViewModel.cs
  15. 3 3
      src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs
  16. 6 0
      src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs
  17. 44 1
      src/PixiEditor/Views/Overlays/TextOverlay/TextOverlay.cs
  18. 5 1
      src/PixiEditor/Views/Overlays/TransformOverlay/TransformOverlay.cs
  19. 2 0
      src/PixiEditor/Views/Rendering/Scene.cs

+ 9 - 0
src/PixiEditor.ChangeableDocument/Changeables/Graph/Interfaces/Shapes/IReadOnlyTextData.cs

@@ -0,0 +1,9 @@
+using Drawie.Numerics;
+
+namespace PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces.Shapes;
+
+public interface IReadOnlyTextData
+{
+    public string Text { get; }
+    public VecD Position { get; }
+}

+ 2 - 1
src/PixiEditor.ChangeableDocument/Changeables/Graph/Nodes/Shapes/Data/TextVectorData.cs

@@ -4,10 +4,11 @@ using Drawie.Backend.Core.Surfaces.PaintImpl;
 using Drawie.Backend.Core.Text;
 using Drawie.Backend.Core.Vector;
 using Drawie.Numerics;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces.Shapes;
 
 namespace PixiEditor.ChangeableDocument.Changeables.Graph.Nodes.Shapes.Data;
 
-public class TextVectorData : ShapeVectorData, IDisposable
+public class TextVectorData : ShapeVectorData, IReadOnlyTextData, IDisposable
 {
     private string text;
 

+ 1 - 0
src/PixiEditor.Extensions/UI/Overlays/OverlayPointerArgs.cs

@@ -11,4 +11,5 @@ public class OverlayPointerArgs
     public MouseButton InitialPressMouseButton { get; set; }
     public IOverlayPointer Pointer { get; set; }
     public bool Handled { get; set; }
+    public int ClickCount { get; set; }
 }

+ 6 - 7
src/PixiEditor/Data/Configs/ToolSetsConfig.json

@@ -20,16 +20,16 @@
       "RasterEllipse",
       "RasterRectangle",
       {
-        "ToolName": "Eraser",
+        "ToolName": "Text",
         "Settings": {
-          "Spacing": 0
+          "AntiAliasing": false,
+          "ForceLowDpiRendering": true
         }
       },
       {
-        "ToolName": "Text",
+        "ToolName": "Eraser",
         "Settings": {
-          "AntiAliasing": false,
-          "ForceLowDpiRendering": true
+          "Spacing": 0
         }
       },
       "ColorPicker",
@@ -119,8 +119,7 @@
       "VectorRectangle",
       {
         "ToolName": "Text",
-        "Settings": 
-        {
+        "Settings": {
           "AntiAliasing": true,
           "ForceLowDpiRendering": false
         }

+ 2 - 1
src/PixiEditor/Data/Localization/Languages/en.json

@@ -844,5 +844,6 @@
   "SPACING_LABEL": "Spacing",
   "TEXT_TOOL": "Text",
   "MISSING_FONT": "Missing font",
-  "TEXT_LAYER_NAME": "Text"
+  "TEXT_LAYER_NAME": "Text",
+  "TEXT_TOOL_TOOLTIP": "Create text ({0})."
 }

+ 3 - 3
src/PixiEditor/Models/Controllers/InputDevice/MouseInputFilter.cs

@@ -51,13 +51,13 @@ internal class MouseInputFilter
 
     public void DeactivatedInlet(object? sender, EventArgs e)
     {
-        MouseOnCanvasEventArgs argsLeft = new(MouseButton.Left, VecD.Zero, KeyModifiers.None);
+        MouseOnCanvasEventArgs argsLeft = new(MouseButton.Left, VecD.Zero, KeyModifiers.None, 0);
         MouseUpInlet(argsLeft);
         
-        MouseOnCanvasEventArgs argsMiddle = new(MouseButton.Middle, VecD.Zero, KeyModifiers.None);
+        MouseOnCanvasEventArgs argsMiddle = new(MouseButton.Middle, VecD.Zero, KeyModifiers.None, 0);
         MouseUpInlet(argsMiddle);
         
-        MouseOnCanvasEventArgs argsRight = new(MouseButton.Right, VecD.Zero, KeyModifiers.None);
+        MouseOnCanvasEventArgs argsRight = new(MouseButton.Right, VecD.Zero, KeyModifiers.None, 0);
         MouseUpInlet(argsRight);
     }
 }

+ 10 - 6
src/PixiEditor/Models/Controllers/InputDevice/MouseOnCanvasEventArgs.cs

@@ -3,17 +3,21 @@ using Drawie.Backend.Core.Numerics;
 using Drawie.Numerics;
 
 namespace PixiEditor.Models.Controllers.InputDevice;
+
 internal class MouseOnCanvasEventArgs : EventArgs
 {
-    public MouseOnCanvasEventArgs(MouseButton button, VecD positionOnCanvas, KeyModifiers keyModifiers)
+    public MouseButton Button { get; }
+    public VecD PositionOnCanvas { get; }
+    public KeyModifiers KeyModifiers { get; }
+    public bool Handled { get; set; }
+    
+    public int ClickCount { get; set; } = 1;
+
+    public MouseOnCanvasEventArgs(MouseButton button, VecD positionOnCanvas, KeyModifiers keyModifiers, int clickCount)
     {
         Button = button;
         PositionOnCanvas = positionOnCanvas;
         KeyModifiers = keyModifiers;
+        ClickCount = clickCount;
     }
-
-    public MouseButton Button { get; }
-    public VecD PositionOnCanvas { get; }
-    public KeyModifiers KeyModifiers { get; }
-    public bool Handled { get; set; }
 }

+ 21 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/TransformSelectedExecutor.cs

@@ -16,6 +16,7 @@ using PixiEditor.ChangeableDocument.Actions;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Models.Controllers.InputDevice;
 using PixiEditor.Models.DocumentPassthroughActions;
+using PixiEditor.ViewModels;
 using PixiEditor.ViewModels.Document.Nodes;
 
 namespace PixiEditor.Models.DocumentModels.UpdateableChangeExecutors;
@@ -142,6 +143,15 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
     public override void OnLeftMouseButtonDown(MouseOnCanvasEventArgs args)
     {
         args.Handled = true;
+
+        if (args.ClickCount >= 2)
+        {
+            if (SwitchToLayerTool())
+            {
+                return;
+            }
+        }
+
         var allLayers = document.StructureHelper.GetAllLayers();
         var topMostWithinClick = allLayers.Where(x =>
                 x is { IsVisibleBindable: true, TightBounds: not null } &&
@@ -178,6 +188,17 @@ internal class TransformSelectedExecutor : UpdateableChangeExecutor, ITransforma
         }
     }
 
+    private bool SwitchToLayerTool()
+    {
+        if (document.SelectedStructureMember is ILayerHandler layerHandler && layerHandler.QuickEditTool != null)
+        {
+            ViewModelMain.Current.ToolsSubViewModel.SetActiveTool(layerHandler.QuickEditTool, false);
+            return true;
+        }
+
+        return false;
+    }
+
     public void OnTransformStopped()
     {
         DuplicateIfRequired();

+ 2 - 0
src/PixiEditor/Models/DocumentModels/UpdateableChangeExecutors/VectorTextToolExecutor.cs

@@ -55,6 +55,8 @@ internal class VectorTextToolExecutor : UpdateableChangeExecutor, ITextOverlayEv
             lastText = textData.Text;
             position = textData.Position;
             lastMatrix = textData.TransformationMatrix;
+
+            document.TextOverlayHandler.SetCursorPosition(internals.ChangeController.LastPrecisePosition);
         }
         else if (shape is null)
         {

+ 1 - 0
src/PixiEditor/Models/Handlers/ILayerHandler.cs

@@ -3,4 +3,5 @@
 internal interface ILayerHandler : IStructureMemberHandler
 {
     public bool ShouldDrawOnMask { get; set; }
+    public Type? QuickEditTool { get; }
 }

+ 1 - 0
src/PixiEditor/Models/Handlers/ITextOverlayHandler.cs

@@ -11,4 +11,5 @@ public interface ITextOverlayHandler : IHandler
     public Font Font { get; set; }
     public VecD Position { get; set; }
     public double? Spacing { get; set; }
+    public void SetCursorPosition(VecD closestToPosition);
 }

+ 4 - 0
src/PixiEditor/ViewModels/Document/Nodes/ImageLayerNodeViewModel.cs

@@ -2,6 +2,7 @@
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Models.Handlers;
 using PixiEditor.ViewModels.Nodes;
+using PixiEditor.ViewModels.Tools.Tools;
 
 namespace PixiEditor.ViewModels.Document.Nodes;
 
@@ -14,6 +15,7 @@ internal class ImageLayerNodeViewModel : StructureMemberViewModel<ImageLayerNode
         this.lockTransparency = lockTransparency;
         OnPropertyChanged(nameof(LockTransparencyBindable));
     }
+    
     public bool LockTransparencyBindable
     {
         get => lockTransparency;
@@ -36,4 +38,6 @@ internal class ImageLayerNodeViewModel : StructureMemberViewModel<ImageLayerNode
             OnPropertyChanged(nameof(ShouldDrawOnMask));
         }
     }
+
+    public Type? QuickEditTool { get; } = typeof(PenToolViewModel);
 }

+ 29 - 0
src/PixiEditor/ViewModels/Document/Nodes/VectorLayerNodeViewModel.cs

@@ -1,15 +1,26 @@
 using PixiEditor.ChangeableDocument.Actions.Generated;
 using PixiEditor.ChangeableDocument.Changeables.Animations;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces;
+using PixiEditor.ChangeableDocument.Changeables.Graph.Interfaces.Shapes;
 using PixiEditor.ChangeableDocument.Changeables.Graph.Nodes;
 using PixiEditor.Models.Handlers;
 using PixiEditor.ViewModels.Nodes;
+using PixiEditor.ViewModels.Tools.Tools;
 
 namespace PixiEditor.ViewModels.Document.Nodes;
 
 [NodeViewModel("VECTOR_LAYER", "STRUCTURE", "\ue916")]
 internal class VectorLayerNodeViewModel : StructureMemberViewModel<VectorLayerNode>, IVectorLayerHandler
 {
+    private Dictionary<Type, Type> quickToolsMap = new Dictionary<Type, Type>()
+    {
+        { typeof(IReadOnlyEllipseData), typeof(VectorEllipseToolViewModel) },
+        { typeof(IReadOnlyRectangleData), typeof(VectorRectangleToolViewModel) },
+        { typeof(IReadOnlyLineData), typeof(VectorLineToolViewModel) },
+        { typeof(IReadOnlyTextData), typeof(TextToolViewModel) },
+        { typeof(IReadOnlyPathData), typeof(VectorPathToolViewModel) }
+    };
+    
     bool lockTransparency;
     public void SetLockTransparency(bool lockTransparency)
     {
@@ -39,6 +50,24 @@ internal class VectorLayerNodeViewModel : StructureMemberViewModel<VectorLayerNo
         }
     }
 
+    public Type? QuickEditTool
+    {
+        get
+        {
+            var shapeData = GetShapeData(Document.AnimationDataViewModel.ActiveFrameTime);
+            if (shapeData is null)
+                return null;
+
+            foreach (var tool in quickToolsMap)
+            {
+                if(shapeData.GetType().IsAssignableTo(tool.Key))
+                    return tool.Value;
+            }
+            
+            return null;
+        }
+    }
+
     public IReadOnlyShapeVectorData? GetShapeData(KeyFrameTime frameTime)
     {
         return ((IReadOnlyVectorNode)Internals.Tracker.Document.FindMember(Id))?.ShapeData;

+ 19 - 0
src/PixiEditor/ViewModels/Document/TransformOverlays/TextOverlayViewModel.cs

@@ -16,6 +16,7 @@ internal class TextOverlayViewModel : ObservableObject, ITextOverlayHandler
     private ExecutionTrigger<string> requestEditTextTrigger;
     private Matrix3X3 matrix = Matrix3X3.Identity;
     private double? spacing;
+    private int cursorPosition;
 
     public event Action<string>? TextChanged;
 
@@ -67,6 +68,24 @@ internal class TextOverlayViewModel : ObservableObject, ITextOverlayHandler
         get => spacing;
         set => SetProperty(ref spacing, value);
     }
+    
+    public int CursorPosition
+    {
+        get => cursorPosition;
+        set => SetProperty(ref cursorPosition, value);
+    }
+
+    public void SetCursorPosition(VecD closestToPosition)
+    {
+        VecD mapped = Matrix.Invert().MapPoint(closestToPosition);
+        RichText richText = new(Text);
+        var positions = richText.GetGlyphPositions(Font);
+        int indexOfClosest = positions.Select((pos, index) => (pos, index))
+            .OrderBy(pos => ((pos.pos + Position - new VecD(0, Font.Size / 2f)) - mapped).LengthSquared)
+            .First().index;
+        
+        CursorPosition = indexOfClosest;
+    }
 
     public TextOverlayViewModel()
     {

+ 3 - 3
src/PixiEditor/Views/Main/ViewportControls/Viewport.axaml.cs

@@ -443,7 +443,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
         var pos = e.GetPosition(Scene);
         VecD scenePos = Scene.ToZoomboxSpace(new VecD(pos.X, pos.Y));
-        MouseOnCanvasEventArgs? parameter = new MouseOnCanvasEventArgs(mouseButton, scenePos, e.KeyModifiers);
+        MouseOnCanvasEventArgs? parameter = new MouseOnCanvasEventArgs(mouseButton, scenePos, e.KeyModifiers, e.ClickCount);
 
         if (MouseDownCommand.CanExecute(parameter))
             MouseDownCommand.Execute(parameter);
@@ -458,7 +458,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
         MouseButton mouseButton = e.GetMouseButton(this);
 
-        MouseOnCanvasEventArgs parameter = new(mouseButton, conv, e.KeyModifiers);
+        MouseOnCanvasEventArgs parameter = new(mouseButton, conv, e.KeyModifiers, 0);
 
         if (MouseMoveCommand.CanExecute(parameter))
             MouseMoveCommand.Execute(parameter);
@@ -471,7 +471,7 @@ internal partial class Viewport : UserControl, INotifyPropertyChanged
 
         Point pos = e.GetPosition(Scene);
         VecD conv = Scene.ToZoomboxSpace(new VecD(pos.X, pos.Y));
-        MouseOnCanvasEventArgs parameter = new(e.InitialPressMouseButton, conv, e.KeyModifiers);
+        MouseOnCanvasEventArgs parameter = new(e.InitialPressMouseButton, conv, e.KeyModifiers, 0);
         if (MouseUpCommand.CanExecute(parameter))
             MouseUpCommand.Execute(parameter);
     }

+ 6 - 0
src/PixiEditor/Views/Main/ViewportControls/ViewportOverlays.cs

@@ -488,6 +488,11 @@ internal class ViewportOverlays
             Source = Viewport, Path = "Document.TextOverlayViewModel.Spacing", Mode = BindingMode.OneWay
         };
 
+        Binding cursorPositionBinding = new()
+        {
+            Source = Viewport, Path = "Document.TextOverlayViewModel.CursorPosition", Mode = BindingMode.TwoWay
+        };
+        
         textOverlay.Bind(Visual.IsVisibleProperty, isVisibleBinding);
         textOverlay.Bind(TextOverlay.TextProperty, textBinding);
         textOverlay.Bind(TextOverlay.PositionProperty, positionBinding);
@@ -495,6 +500,7 @@ internal class ViewportOverlays
         textOverlay.Bind(TextOverlay.RequestEditTextProperty, requestEditTextBinding);
         textOverlay.Bind(TextOverlay.MatrixProperty, matrixBinding);
         textOverlay.Bind(TextOverlay.SpacingProperty, spacingBinding);
+        textOverlay.Bind(TextOverlay.CursorPositionProperty, cursorPositionBinding);
     }
 }
 

+ 44 - 1
src/PixiEditor/Views/Overlays/TextOverlay/TextOverlay.cs

@@ -4,6 +4,7 @@ using Avalonia.Threading;
 using Drawie.Backend.Core.Numerics;
 using Drawie.Backend.Core.Text;
 using Drawie.Numerics;
+using PixiEditor.Extensions.UI.Overlays;
 using PixiEditor.Helpers.UI;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.Input;
@@ -129,7 +130,8 @@ internal class TextOverlay : Overlay
             { new KeyCombination(Key.Left, KeyModifiers.None), () => MoveCursorBy(new VecI(-1, 0)) },
             { new KeyCombination(Key.Right, KeyModifiers.None), () => MoveCursorBy(new VecI(1, 0)) },
             { new KeyCombination(Key.Up, KeyModifiers.None), () => MoveCursorBy(new VecI(0, -1)) },
-            { new KeyCombination(Key.Down, KeyModifiers.None), () => MoveCursorBy(new VecI(0, 1)) }
+            { new KeyCombination(Key.Down, KeyModifiers.None), () => MoveCursorBy(new VecI(0, 1)) },
+            { new KeyCombination(Key.Escape, KeyModifiers.None), () => IsEditing = false }
         };
     }
 
@@ -156,6 +158,46 @@ internal class TextOverlay : Overlay
         Refresh();
     }
 
+    public override bool TestHit(VecD point)
+    {
+        VecD mapped = Matrix.Invert().MapPoint(point);
+        return richText != null && richText.MeasureBounds(Font).Offset(Position).ContainsInclusive(mapped);
+    }
+
+    protected override void OnOverlayPointerPressed(OverlayPointerArgs args)
+    {
+        if (args.PointerButton == MouseButton.Left)
+        {
+            if (!IsEditing)
+            {
+                IsEditing = true;
+            }
+            
+            SetCursorPosToPosition(args.Point);
+        }
+    }
+
+    protected override void OnOverlayPointerEntered(OverlayPointerArgs args)
+    {
+        Cursor = new Cursor(StandardCursorType.Ibeam);
+    }
+
+    protected override void OnOverlayPointerExited(OverlayPointerArgs args)
+    {
+        Cursor = new Cursor(StandardCursorType.Arrow);
+    }
+
+    private void SetCursorPosToPosition(VecD point)
+    {
+        VecD mapped = Matrix.Invert().MapPoint(point);
+        var positions = richText.GetGlyphPositions(Font);
+        int indexOfClosest = positions.Select((pos, index) => (pos, index))
+            .OrderBy(pos => ((pos.pos + Position - new VecD(0, Font.Size / 2f)) - mapped).LengthSquared)
+            .First().index;
+        
+        CursorPosition = indexOfClosest;
+    }
+
     protected override void OnKeyPressed(Key key, KeyModifiers keyModifiers, string? keySymbol)
     {
         if (!IsEditing) return;
@@ -326,6 +368,7 @@ internal class TextOverlay : Overlay
     {
         TextOverlay textOverlay = sender as TextOverlay;
         if (textOverlay == null) return newPos;
+        if (textOverlay.Text == null) return 0;
 
         return Math.Clamp(newPos, 0, textOverlay.Text.Length);
     }

+ 5 - 1
src/PixiEditor/Views/Overlays/TransformOverlay/TransformOverlay.cs

@@ -293,6 +293,7 @@ internal class TransformOverlay : Overlay
     private VecD lastSize;
     private bool actuallyMoved = false;
     private bool isShearing = false;
+    private int lastClickCount = 0;
 
     public TransformOverlay()
     {
@@ -549,6 +550,8 @@ internal class TransformOverlay : Overlay
     {
         if (args.PointerButton != MouseButton.Left)
             return;
+        
+        lastClickCount = args.ClickCount;
 
         if (Handles.Any(x => x.IsWithinHandle(x.Position, args.Point, ZoomScale))) return;
 
@@ -652,8 +655,9 @@ internal class TransformOverlay : Overlay
 
         if (!isRotating && !actuallyMoved)
         {
-            MouseOnCanvasEventArgs args = new(MouseButton.Left, e.Point, e.Modifiers);
+            MouseOnCanvasEventArgs args = new(MouseButton.Left, e.Point, e.Modifiers, lastClickCount);
             PassthroughPointerPressedCommand?.Execute(args);
+            lastClickCount = 0;
         }
 
         if (isRotating)

+ 2 - 0
src/PixiEditor/Views/Rendering/Scene.cs

@@ -492,6 +492,8 @@ internal class Scene : Zoombox.Zoombox, ICustomHitTest
             InitialPressMouseButton = e is PointerReleasedEventArgs released
                 ? released.InitialPressMouseButton
                 : MouseButton.None,
+            ClickCount = e is PointerPressedEventArgs pressed ? pressed.ClickCount : 0
+            
         };
     }