Browse Source

Implemented selection overlay

Krzysztof Krysiński 1 year ago
parent
commit
6b43ad8980

+ 14 - 0
src/PixiEditor.AvaloniaUI/Animation/Animators.cs

@@ -0,0 +1,14 @@
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using Avalonia.Styling;
+using PixiEditor.AvaloniaUI.Animators;
+
+namespace PixiEditor.AvaloniaUI.Animation;
+
+public class Animators : Styles
+{
+    public Animators(IServiceProvider? sp = null)
+    {
+        Avalonia.Animation.Animation.RegisterCustomAnimator<IDashStyle, SelectionDashAnimator>();
+    }
+}

+ 18 - 0
src/PixiEditor.AvaloniaUI/Animation/SelectionDashAnimator.cs

@@ -0,0 +1,18 @@
+using Avalonia.Animation;
+using Avalonia.Media;
+
+namespace PixiEditor.AvaloniaUI.Animators;
+
+public sealed class SelectionDashAnimator : InterpolatingAnimator<IDashStyle>
+{
+    public override IDashStyle Interpolate(double progress, IDashStyle oldValue, IDashStyle newValue)
+    {
+        return Interpolate(progress);
+    }
+
+    public static IDashStyle Interpolate(double progress)
+    {
+        var newDashStyle = new DashStyle(new double[] { 2, 4 }, progress * 6);
+        return newDashStyle;
+    }
+}

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

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

+ 11 - 0
src/PixiEditor.AvaloniaUI/Helpers/Converters/IsSelectionToolConverter.cs

@@ -0,0 +1,11 @@
+using System.Globalization;
+using PixiEditor.AvaloniaUI.Helpers.Converters;
+using PixiEditor.AvaloniaUI.ViewModels.Tools.Tools;
+
+namespace PixiEditor.Helpers.Converters;
+
+internal class IsSelectionToolConverter : SingleInstanceConverter<IsSelectionToolConverter>
+{
+    public override object Convert(object value, Type targetType, object parameter, CultureInfo culture) =>
+        value is SelectToolViewModel or LassoToolViewModel or MagicWandToolViewModel;
+}

+ 6 - 0
src/PixiEditor.AvaloniaUI/Styles/PixiEditor.Animators.axaml

@@ -0,0 +1,6 @@
+<Styles xmlns="https://github.com/avaloniaui"
+        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+        xmlns:animation="clr-namespace:PixiEditor.AvaloniaUI.Animation">
+
+    <animation:Animators/>
+</Styles>

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

@@ -18,6 +18,7 @@
     xmlns:brushShapeOverlay="clr-namespace:PixiEditor.AvaloniaUI.Views.Overlays.BrushShapeOverlay"
     xmlns:viewModels="clr-namespace:PixiEditor.AvaloniaUI.ViewModels"
     xmlns:symmetryOverlay="clr-namespace:PixiEditor.AvaloniaUI.Views.Overlays.SymmetryOverlay"
+    xmlns:overlays="clr-namespace:PixiEditor.Views.UserControls.Overlays"
     mc:Ignorable="d"
     x:Name="vpUc"
     d:DesignHeight="450"
@@ -275,12 +276,12 @@
                             DragEndCommand="{xaml:Command PixiEditor.Document.EndDragSymmetry, UseProvided=True}"
                             DragStartCommand="{xaml:Command PixiEditor.Document.StartDragSymmetry, UseProvided=True}"
                             FlowDirection="LeftToRight" />
-                        <!--<overlays:SelectionOverlay
+                        <overlays:SelectionOverlay
                             Focusable="False"
-                            ShowFill="{Binding ToolsSubViewModel.ActiveTool, Source={vm:MainVM}, Converter={converters:IsSelectionToolConverter}}"
+                            ShowFill="{Binding ToolsSubViewModel.ActiveTool, Source={viewModels:MainVM}, Converter={converters:IsSelectionToolConverter}}"
                             Path="{Binding Document.SelectionPathBindable}"
                             ZoomboxScale="{Binding Zoombox.Scale}"
-                            FlowDirection="LeftToRight" />-->
+                            FlowDirection="LeftToRight" />
                         <brushShapeOverlay:BrushShapeOverlay
                             Focusable="False"
                             IsHitTestVisible="False"

+ 125 - 0
src/PixiEditor.AvaloniaUI/Views/Overlays/SelectionOverlay/SelectionOverlay.cs

@@ -0,0 +1,125 @@
+using System.Threading;
+using System.Threading.Tasks;
+using Avalonia;
+using Avalonia.Animation;
+using Avalonia.Animation.Easings;
+using Avalonia.Controls;
+using Avalonia.Media;
+using Avalonia.Rendering.Composition;
+using Avalonia.Rendering.Composition.Animations;
+using Avalonia.Styling;
+using PixiEditor.AvaloniaUI.Animators;
+using PixiEditor.DrawingApi.Core.Surface.Vector;
+
+namespace PixiEditor.Views.UserControls.Overlays;
+#nullable enable
+internal class SelectionOverlay : Control
+{
+    public static readonly StyledProperty<VectorPath?> PathProperty =
+        AvaloniaProperty.Register<SelectionOverlay, VectorPath?>(nameof(Path));
+
+    public VectorPath? Path
+    {
+        get => GetValue(PathProperty);
+        set => SetValue(PathProperty, value);
+    }
+
+    public static readonly StyledProperty<double> ZoomboxScaleProperty =
+        AvaloniaProperty.Register<SelectionOverlay, double>(nameof(ZoomboxScale), defaultValue: 1.0);
+
+    public double ZoomboxScale
+    {
+        get => GetValue(ZoomboxScaleProperty);
+        set => SetValue(ZoomboxScaleProperty, value);
+    }
+
+    public static readonly StyledProperty<bool> ShowFillProperty =
+        AvaloniaProperty.Register<SelectionOverlay, bool>(nameof(ShowFill), defaultValue: true);
+
+    public bool ShowFill
+    {
+        get => GetValue(ShowFillProperty);
+        set => SetValue(ShowFillProperty, value);
+    }
+
+    public static readonly DirectProperty<SelectionOverlay, IDashStyle> BlackDashedPenProperty =
+        AvaloniaProperty.RegisterDirect<SelectionOverlay, IDashStyle>("BlackDashedPen",
+            overlay => overlay.blackDashedPen.DashStyle,
+            (overlay, pen) => overlay.blackDashedPen.DashStyle = pen);
+
+    static SelectionOverlay()
+    {
+        AffectsRender<SelectionOverlay>(PathProperty);
+        ZoomboxScaleProperty.Changed.Subscribe(OnZoomboxScaleChanged);
+        ShowFillProperty.Changed.Subscribe(OnShowFillChanged);
+    }
+
+    private Pen whitePen = new Pen(Brushes.White, 1);
+    private Pen blackDashedPen = new Pen(Brushes.Black, 1) { DashStyle = startingFrame };
+    private Brush fillBrush = new SolidColorBrush(Color.FromArgb(80, 0, 80, 255));
+
+    private static DashStyle startingFrame = new DashStyle(new double[] { 2, 4 }, 6);
+
+    private Geometry renderPath = new PathGeometry();
+
+    public SelectionOverlay()
+    {
+        IsHitTestVisible = false;
+
+        Animation animation = new Animation()
+        {
+            Duration = new TimeSpan(0, 0, 0, 2, 0),
+            IterationCount = IterationCount.Infinite,
+        };
+
+        float step = 1f / 7f;
+
+        for (int i = 0; i < 7; i++)
+        {
+            Cue cue = new Cue(i * step);
+            animation.Children.Add(new KeyFrame()
+            {
+                Cue = cue,
+                Setters = { new Setter(BlackDashedPenProperty, SelectionDashAnimator.Interpolate(cue.CueValue)) }
+            });
+        }
+
+        animation.RunAsync(this);
+    }
+
+    public override void Render(DrawingContext drawingContext)
+    {
+        base.Render(drawingContext);
+        if (Path is null)
+            return;
+
+        try
+        {
+            renderPath = new PathGeometry()
+            {
+                FillRule = FillRule.EvenOdd,
+                Figures = (PathFigures?)PathFigures.Parse(Path.ToSvgPathData()),
+            };
+        }
+        catch (FormatException)
+        {
+            return;
+        }
+        drawingContext.DrawGeometry(null, whitePen, renderPath);
+        drawingContext.DrawGeometry(fillBrush, blackDashedPen, renderPath);
+    }
+
+    private static void OnZoomboxScaleChanged(AvaloniaPropertyChangedEventArgs<double> args)
+    {
+        var self = (SelectionOverlay)args.Sender;
+        double newScale = args.NewValue.Value;
+        self.whitePen.Thickness = 1.0 / newScale;
+        self.blackDashedPen.Thickness = 1.0 / newScale;
+    }
+
+    private static void OnShowFillChanged(AvaloniaPropertyChangedEventArgs<bool> args)
+    {
+        var self = (SelectionOverlay)args.Sender;
+        self.fillBrush.Opacity = args.NewValue.Value ? 1 : 0;
+    }
+}

+ 1 - 1
src/PixiEditor.UI.Common/Controls/CheckBox.axaml

@@ -50,7 +50,7 @@
                                       BorderThickness="1">
             <Panel>
                 <Path FlowDirection="LeftToRight" Width="9" Height="9" x:Name="checkMark" Margin="2 2 0 0"
-                      Stroke="{DynamicResource AccentColor}" StrokeThickness="1.5"
+                      Stroke="{DynamicResource ThemeAccentBrush}" StrokeThickness="1.5"
                       Data="{StaticResource Empty}">
                 </Path>
             </Panel>