Browse Source

Added Line overlay and unified a way of defining overlay resources

Krzysztof Krysiński 1 year ago
parent
commit
6afa57a359

+ 1 - 0
src/PixiEditor.AvaloniaUI/App.axaml

@@ -12,6 +12,7 @@
         <themes:PixiEditorTheme />
         <StyleInclude Source="/Styles/PixiEditor.Controls.axaml"/>
         <StyleInclude Source="/Styles/PixiEditor.Animators.axaml"/>
+        <StyleInclude Source="/Styles/PixiEditor.Handles.axaml"/>
     </Application.Styles>
 
 </Application>

+ 18 - 0
src/PixiEditor.AvaloniaUI/Helpers/PointerHelpers.cs

@@ -0,0 +1,18 @@
+using Avalonia;
+using Avalonia.Input;
+
+namespace PixiEditor.AvaloniaUI.Helpers;
+
+public static class PointerHelpers
+{
+    public static MouseButton GetMouseButton(this PointerPressedEventArgs e, Visual visual)
+    {
+        return e.GetCurrentPoint(visual).Properties.PointerUpdateKind switch
+        {
+            PointerUpdateKind.LeftButtonPressed => MouseButton.Left,
+            PointerUpdateKind.RightButtonPressed => MouseButton.Right,
+            PointerUpdateKind.MiddleButtonPressed => MouseButton.Middle,
+            _ => MouseButton.None
+        };
+    }
+}

+ 15 - 0
src/PixiEditor.AvaloniaUI/Styles/PixiEditor.Handles.axaml

@@ -0,0 +1,15 @@
+<Styles xmlns="https://github.com/avaloniaui"
+                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+    <Styles.Resources>
+        <ResourceDictionary>
+            <Path x:Key="MoveHandle"
+                  Data="M 0.50025839 0 0.4248062 0.12971572 0.34987079 0.25994821 h 0.1002584 V 0.45012906 H 0.25994831 V 0.34987066 L 0.12971577 0.42480604 0 0.5002582 0.12971577 0.57519373 0.25994831 0.65012926 V 0.5498709 H 0.45012919 V 0.74005175 H 0.34987079 L 0.42480619 0.87028439 0.50025839 1 0.57519399 0.87028439 0.65012959 0.74005175 H 0.54987119 V 0.5498709 H 0.74005211 V 0.65012926 L 0.87028423 0.57519358 1 0.5002582 0.87028423 0.42480604 0.74005169 0.34987066 v 0.1002584 H 0.54987077 V 0.25994821 h 0.1002584 L 0.5751938 0.12971572 Z"/>
+
+            <Path x:Key="MarkerHandle" Data="M -1.1146 -0.6603 c -0.1215 -0.1215 -0.3187 -0.1215 -0.4401 0 l -0.4401 0.4401 c -0.1215 0.1215 -0.1215 0.3187 0 0.4401 l 0.4401 0.4401 c 0.1215 0.1215 0.3187 0.1215 0.4401 0 l 0.4401 -0.4401 c 0.1215 -0.1215 0.1215 -0.3187 0 -0.4401 l -0.4401 -0.4401 Z M -0.5834 0.0012 l 0.5833 -0.0013"/>
+
+            <SolidColorBrush x:Key="HandleBrush" Color="{DynamicResource GlyphColor}"/>
+            <SolidColorBrush x:Key="HandleBackgroundBrush" Color="{DynamicResource GlyphBackground}"/>
+        </ResourceDictionary>
+    </Styles.Resources>
+
+</Styles>

+ 4 - 3
src/PixiEditor.AvaloniaUI/Views/Main/Viewport.axaml

@@ -19,6 +19,7 @@
     xmlns:viewModels="clr-namespace:PixiEditor.AvaloniaUI.ViewModels"
     xmlns:symmetryOverlay="clr-namespace:PixiEditor.AvaloniaUI.Views.Overlays.SymmetryOverlay"
     xmlns:overlays="clr-namespace:PixiEditor.Views.UserControls.Overlays"
+    xmlns:lineToolOverlay="clr-namespace:PixiEditor.AvaloniaUI.Views.Overlays.LineToolOverlay"
     mc:Ignorable="d"
     x:Name="vpUc"
     d:DesignHeight="450"
@@ -310,14 +311,14 @@
                             InternalState="{Binding Document.TransformViewModel.InternalState, Mode=TwoWay}"
                             ZoomboxScale="{Binding Zoombox.Scale}"
                             ZoomboxAngle="{Binding Zoombox.Angle}" />-->
-                        <!--<lineOverlay:LineToolOverlay
+                        <lineToolOverlay:LineToolOverlay
                             Focusable="False"
-                            Visibility="{Binding Document.LineToolOverlayViewModel.IsEnabled, Converter={converters:BoolToVisibilityConverter}}"
+                            IsVisible="{Binding Document.LineToolOverlayViewModel.IsEnabled}"
                             ActionCompleted="{Binding Document.LineToolOverlayViewModel.ActionCompletedCommand}"
                             LineStart="{Binding Document.LineToolOverlayViewModel.LineStart, Mode=TwoWay}"
                             LineEnd="{Binding Document.LineToolOverlayViewModel.LineEnd, Mode=TwoWay}"
                             ZoomboxScale="{Binding Zoombox.Scale}"
-                            FlowDirection="LeftToRight"/>-->
+                            FlowDirection="LeftToRight"/>
                     </Grid>
                     <Grid IsHitTestVisible="False" 
                         ShowGridLines="True" Width="{Binding Document.Width}" Height="{Binding Document.Height}" Panel.ZIndex="10" 

+ 183 - 0
src/PixiEditor.AvaloniaUI/Views/Overlays/LineToolOverlay/LineToolOverlay.cs

@@ -0,0 +1,183 @@
+using System.Windows.Input;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Avalonia.Media;
+using ChunkyImageLib.DataHolders;
+using PixiEditor.AvaloniaUI.Helpers;
+using PixiEditor.AvaloniaUI.Models.Controllers.InputDevice;
+using PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.Views.UserControls.Overlays.LineToolOverlay;
+
+namespace PixiEditor.AvaloniaUI.Views.Overlays.LineToolOverlay;
+internal class LineToolOverlay : Overlay
+{
+    public static readonly StyledProperty<double> ZoomboxScaleProperty =
+        AvaloniaProperty.Register<LineToolOverlay, double>(nameof(ZoomboxScale), defaultValue: 1.0);
+
+    public double ZoomboxScale
+    {
+        get => GetValue(ZoomboxScaleProperty);
+        set => SetValue(ZoomboxScaleProperty, value);
+    }
+
+    public static readonly StyledProperty<VecD> LineStartProperty =
+        AvaloniaProperty.Register<LineToolOverlay, VecD>(nameof(LineStart), defaultValue: VecD.Zero);
+
+    public VecD LineStart
+    {
+        get => GetValue(LineStartProperty);
+        set => SetValue(LineStartProperty, value);
+    }
+
+    public static readonly StyledProperty<VecD> LineEndProperty =
+        AvaloniaProperty.Register<LineToolOverlay, VecD>(nameof(LineEnd), defaultValue: VecD.Zero);
+
+    public VecD LineEnd
+    {
+        get => GetValue(LineEndProperty);
+        set => SetValue(LineEndProperty, value);
+    }
+
+    public static readonly StyledProperty<ICommand?> ActionCompletedProperty =
+        AvaloniaProperty.Register<LineToolOverlay, ICommand?>(nameof(ActionCompleted));
+
+    public ICommand? ActionCompleted
+    {
+        get => GetValue(ActionCompletedProperty);
+        set => SetValue(ActionCompletedProperty, value);
+    }
+
+    static LineToolOverlay()
+    {
+        AffectsRender<LineToolOverlay>(ZoomboxScaleProperty, LineStartProperty, LineEndProperty);
+
+        ZoomboxScaleProperty.Changed.Subscribe(OnZoomboxScaleChanged);
+    }
+
+    private Pen blackPen = new Pen(Brushes.Black, 1);
+
+    private VecD mouseDownPos = VecD.Zero;
+    private VecD lineStartOnMouseDown = VecD.Zero;
+    private VecD lineEndOnMouseDown = VecD.Zero;
+
+    private LineToolOverlayAnchor? capturedAnchor = null;
+    private bool dragging = false;
+    private bool movedWhileMouseDown = false;
+
+    private Geometry handleGeometry = GetHandleGeometry("MoveHandle");
+
+    private MouseUpdateController mouseUpdateController;
+
+    public LineToolOverlay()
+    {
+        Cursor = new Cursor(StandardCursorType.Arrow);
+        Loaded += OnLoaded;
+    }
+
+    private void OnLoaded(object sender, RoutedEventArgs e)
+    {
+        mouseUpdateController = new MouseUpdateController(this, MouseMoved);
+    }
+
+    private static void OnZoomboxScaleChanged(AvaloniaPropertyChangedEventArgs<double> args)
+    {
+        var self = (LineToolOverlay)args.Sender;
+        double newScale = args.NewValue.Value;
+        self.blackPen.Thickness = 1.0 / newScale;
+    }
+
+    public override void Render(DrawingContext context)
+    {
+        float scaleMultiplier = (float)(1.0 / ZoomboxScale);
+        float radius = 2.5f * scaleMultiplier;
+
+        context.DrawRectangle(BackgroundBrush, blackPen, TransformHelper.ToAnchorRect(LineStart, ZoomboxScale), radius, radius);
+        context.DrawRectangle(BackgroundBrush, blackPen, TransformHelper.ToAnchorRect(LineEnd, ZoomboxScale), radius, radius);
+
+        VecD handlePos = TransformHelper.GetDragHandlePos(new ShapeCorners(new RectD(LineStart, LineEnd - LineStart)), ZoomboxScale);
+        const double CrossSize = TransformHelper.MoveHandleSize - 1;
+        context.DrawRectangle(BackgroundBrush, blackPen, TransformHelper.ToHandleRect(handlePos, ZoomboxScale), radius, radius);
+        handleGeometry.Transform = new MatrixTransform(
+            new Matrix(
+            0, CrossSize / ZoomboxScale,
+            CrossSize / ZoomboxScale, 0,
+            handlePos.X - CrossSize / (ZoomboxScale * 2), handlePos.Y - CrossSize / (ZoomboxScale * 2))
+        );
+        context.DrawGeometry(HandleGlyphBrush, null, handleGeometry);
+    }
+
+    protected override void OnPointerPressed(PointerPressedEventArgs e)
+    {
+        base.OnPointerPressed(e);
+
+        MouseButton changedButton = e.GetMouseButton(this);
+
+        if (changedButton != MouseButton.Left)
+            return;
+
+        e.Handled = true;
+
+        VecD pos = TransformHelper.ToVecD(e.GetPosition(this));
+        VecD handlePos = TransformHelper.GetDragHandlePos(new ShapeCorners(new RectD(LineStart, LineEnd - LineStart)), ZoomboxScale);
+
+        if (TransformHelper.IsWithinAnchor(LineStart, pos, ZoomboxScale))
+            capturedAnchor = LineToolOverlayAnchor.Start;
+        else if (TransformHelper.IsWithinAnchor(LineEnd, pos, ZoomboxScale))
+            capturedAnchor = LineToolOverlayAnchor.End;
+        else if (TransformHelper.IsWithinTransformHandle(handlePos, pos, ZoomboxScale))
+            dragging = true;
+        movedWhileMouseDown = false;
+
+        mouseDownPos = pos;
+        lineStartOnMouseDown = LineStart;
+        lineEndOnMouseDown = LineEnd;
+
+        e.Pointer.Capture(this);
+    }
+
+    protected void MouseMoved(PointerEventArgs e)
+    {
+        VecD pos = TransformHelper.ToVecD(e.GetPosition(this));
+        if (capturedAnchor == LineToolOverlayAnchor.Start)
+        {
+            LineStart = pos;
+            movedWhileMouseDown = true;
+            return;
+        }
+
+        if (capturedAnchor == LineToolOverlayAnchor.End)
+        {
+            LineEnd = pos;
+            movedWhileMouseDown = true;
+            return;
+        }
+
+        if (dragging)
+        {
+            var delta = pos - mouseDownPos;
+            LineStart = lineStartOnMouseDown + delta;
+            LineEnd = lineEndOnMouseDown + delta;
+            movedWhileMouseDown = true;
+            return;
+        }
+    }
+
+    protected override void OnPointerReleased(PointerReleasedEventArgs e)
+    {
+        base.OnPointerReleased(e);
+        if (e.InitialPressMouseButton != MouseButton.Left)
+            return;
+
+        e.Handled = true;
+        capturedAnchor = null;
+        dragging = false;
+        if (movedWhileMouseDown && ActionCompleted is not null && ActionCompleted.CanExecute(null))
+            ActionCompleted.Execute(null);
+
+        e.Pointer.Capture(null);
+    }
+}

+ 6 - 0
src/PixiEditor.AvaloniaUI/Views/Overlays/LineToolOverlay/LineToolOverlayAnchor.cs

@@ -0,0 +1,6 @@
+namespace PixiEditor.Views.UserControls.Overlays.LineToolOverlay;
+internal enum LineToolOverlayAnchor
+{
+    Start,
+    End
+}

+ 32 - 0
src/PixiEditor.AvaloniaUI/Views/Overlays/Overlay.cs

@@ -0,0 +1,32 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Shapes;
+using Avalonia.Media;
+
+namespace PixiEditor.AvaloniaUI.Views.Overlays;
+
+public class Overlay : Control
+{
+    protected IBrush HandleGlyphBrush { get; } = GetBrush("HandleGlyphBrush");
+    protected IBrush BackgroundBrush { get; } = GetBrush("HandleBackgroundBrush");
+
+    protected static IBrush GetBrush(string key)
+    {
+        if (Application.Current.Styles.TryGetResource(key, null, out object brush))
+        {
+            return (IBrush)brush;
+        }
+
+        return Brushes.Black;
+    }
+
+    protected static Geometry GetHandleGeometry(string handleName)
+    {
+        if (Application.Current.Styles.TryGetResource(handleName, null, out object shape))
+        {
+            return ((Path)shape).Data;
+        }
+
+        return Geometry.Parse("M 0 0 L 1 0 M 0 0 L 0 1");
+    }
+}

+ 2 - 6
src/PixiEditor.AvaloniaUI/Views/Overlays/SymmetryOverlay/SymmetryOverlay.cs

@@ -13,7 +13,7 @@ using PixiEditor.Extensions.Common.Localization;
 
 namespace PixiEditor.AvaloniaUI.Views.Overlays.SymmetryOverlay;
 #nullable enable
-internal class SymmetryOverlay : Control
+internal class SymmetryOverlay : Overlay
 {
     public static readonly StyledProperty<double> HorizontalAxisYProperty =
         AvaloniaProperty.Register<SymmetryOverlay, double>(nameof(HorizontalAxisY), defaultValue: 0.0);
@@ -98,11 +98,7 @@ internal class SymmetryOverlay : Control
     }
 
     private const double HandleSize = 12;
-    private PathGeometry handleGeometry = new()
-    {
-        FillRule = FillRule.NonZero,
-        Figures = PathFigures.Parse($"M -1.1146 -0.6603 c -0.1215 -0.1215 -0.3187 -0.1215 -0.4401 0 l -0.4401 0.4401 c -0.1215 0.1215 -0.1215 0.3187 0 0.4401 l 0.4401 0.4401 c 0.1215 0.1215 0.3187 0.1215 0.4401 0 l 0.4401 -0.4401 c 0.1215 -0.1215 0.1215 -0.3187 0 -0.4401 l -0.4401 -0.4401 Z M -0.5834 0.0012 l 0.5833 -0.0013"),
-    };
+    private Geometry handleGeometry = GetHandleGeometry("MarkerHandle");
 
     private const double DashWidth = 10.0;
     const int RulerOffset = -35;

+ 2 - 2
src/PixiEditor.AvaloniaUI/Views/Overlays/TransformOverlay/TransformHelper.cs

@@ -8,8 +8,8 @@ using PixiEditor.DrawingApi.Core.Numerics;
 namespace PixiEditor.AvaloniaUI.Views.Overlays.TransformOverlay;
 internal static class TransformHelper
 {
-    public const double AnchorSize = 10;
-    public const double MoveHandleSize = 16;
+    public const double AnchorSize = 14;
+    public const double MoveHandleSize = 24;
 
     public static Rect ToAnchorRect(VecD pos, double zoomboxScale)
     {

+ 1 - 0
src/PixiEditor.UI.Common/Accents/Base.axaml

@@ -28,6 +28,7 @@
             <Color x:Key="ErrorColor">#B00020</Color>
 
             <Color x:Key="GlyphColor">#444</Color>
+            <Color x:Key="GlyphBackground">White</Color>
 
             <Color x:Key="NotificationCardBackgroundColor">#303030</Color>