CPKreuz 2 سال پیش
والد
کامیت
e74c072f40

+ 107 - 0
src/PixiEditor/Models/DataHolders/Guides/Guide.cs

@@ -0,0 +1,107 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows.Media;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.ViewModels.SubViewModels.Document;
+using PixiEditor.Views.UserControls;
+using PixiEditor.Views.UserControls.Guides;
+
+namespace PixiEditor.Models.DataHolders.Guides
+{
+    internal abstract class Guide : NotifyableObject
+    {
+        private string name;
+        private bool alwaysShowName;
+        private bool showExtended;
+        private bool showEditable;
+        private List<GuideRenderer> renderers = new();
+
+        public string Name
+        {
+            get => name;
+            set
+            {
+                if (SetProperty(ref name, value))
+                {
+                    InvalidateVisual();
+                }
+            }
+        }
+
+        public bool AlwaysShowName
+        {
+            get => alwaysShowName;
+            set
+            {
+                if (SetProperty(ref alwaysShowName, value))
+                {
+                    InvalidateVisual();
+                }
+            }
+        }
+
+        public bool ShowExtended
+        {
+            get => showExtended;
+            set
+            {
+                if (SetProperty(ref showExtended, value))
+                {
+                    InvalidateVisual();
+                }
+            }
+        }
+
+        public bool ShowEditable
+        {
+            get => showEditable;
+            set
+            {
+                if (SetProperty(ref showEditable, value))
+                {
+                    InvalidateVisual();
+                }
+            }
+        }
+
+        protected IReadOnlyCollection<GuideRenderer> Renderers => renderers;
+
+        public DocumentViewModel Document { get; }
+
+        public Guide(DocumentViewModel document)
+        {
+            Document = document;
+        }
+
+        public abstract void Draw(DrawingContext context, GuideRenderer renderer);
+
+        public void AttachRenderer(GuideRenderer renderer)
+        {
+            renderers.Add(renderer);
+            RendererAttached(renderer);
+        }
+
+        public void DetachRenderer(GuideRenderer renderer)
+        {
+            renderers.Remove(renderer);
+            RendererDetached(renderer);
+        }
+
+        protected virtual void RendererAttached(GuideRenderer renderer)
+        { }
+
+        protected virtual void RendererDetached(GuideRenderer renderer)
+        { }
+
+        protected void InvalidateVisual()
+        {
+            foreach (var renderer in renderers)
+            {
+                renderer.InvalidateVisual();
+            }
+        }
+    }
+}

+ 110 - 0
src/PixiEditor/Models/DataHolders/Guides/LineGuide.cs

@@ -0,0 +1,110 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Media;
+using Hardware.Info;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.ViewModels.SubViewModels.Document;
+using PixiEditor.Views.UserControls;
+using PixiEditor.Views.UserControls.Guides;
+
+namespace PixiEditor.Models.DataHolders.Guides
+{
+    internal class LineGuide : Guide
+    {
+        private VecD position;
+        private double rotation;
+        private Color color;
+
+        public VecD Position
+        {
+            get => position;
+            set
+            {
+                if (SetProperty(ref position, value))
+                {
+                    InvalidateVisual();
+                }
+            }
+        }
+
+        public double Rotation
+        {
+            get => rotation;
+            set
+            {
+                if (SetProperty(ref rotation, value))
+                {
+                    InvalidateVisual();
+                }
+            }
+        }
+
+        public Color Color
+        {
+            get => color;
+            set
+            {
+                if (SetProperty(ref color, value))
+                {
+                    InvalidateVisual();
+                }
+            }
+        }
+
+        public LineGuide(DocumentViewModel document) : base(document)
+        {
+            Color = Colors.CadetBlue;
+        }
+
+        public override void Draw(DrawingContext context, GuideRenderer renderer)
+        {
+            var documentSize = Document.SizeBindable;
+            var m = Math.Tan(Rotation);
+
+            var penThickness = renderer.ScreenUnit;
+
+            var brush = new SolidColorBrush(Color);
+            var pen = new Pen(brush, penThickness * 1.5d);
+            var points = GetIntersectionsInside(documentSize);
+            context.DrawLine(pen, points.Item1, points.Item2);
+
+            if (ShowExtended || ShowEditable)
+            {
+                var scale = ShowEditable ? 6 : 3;
+                context.DrawEllipse(Brushes.Aqua, null, new Point(Position.X, Position.Y), penThickness * scale, penThickness * scale);
+            }
+        }
+
+        private Point[] GetIntersections(VecI size)
+        {
+            var points = new Point[4];
+
+            var m = Math.Tan(Rotation);
+
+            points[0] = new Point(0, m * Position.X + Position.Y);
+            points[1] = new Point(Position.X + Position.Y / m, 0);
+            points[2] = new Point(size.X, -m * size.X + m * Position.X + Position.Y);
+            points[3] = new Point(Position.X + Position.Y / m - size.Y / m, size.Y);
+
+            return points;
+        }
+
+        private (Point, Point) GetIntersectionsInside(VecI size)
+        {
+            var points = GetIntersections(size).Where(x => PointInside(x, size)).ToArray();
+
+            if (points.Length < 2)
+            {
+                throw new IndexOutOfRangeException("Guide did not have enough intersection points");
+            }
+
+            return (points[0], points[1]);
+        }
+
+        private bool PointInside(Point point, VecI size) => point.X >= 0 && point.X <= size.X && point.Y >= 0 && point.Y <= size.Y;
+    }
+}

+ 7 - 0
src/PixiEditor/ViewModels/SubViewModels/Document/DocumentViewModel.cs

@@ -20,6 +20,7 @@ using PixiEditor.Helpers.Collections;
 using PixiEditor.Localization;
 using PixiEditor.Models.Controllers;
 using PixiEditor.Models.DataHolders;
+using PixiEditor.Models.DataHolders.Guides;
 using PixiEditor.Models.DocumentModels;
 using PixiEditor.Models.DocumentModels.Public;
 using PixiEditor.Models.Enums;
@@ -113,6 +114,7 @@ internal partial class DocumentViewModel : NotifyableObject
     private readonly HashSet<StructureMemberViewModel> softSelectedStructureMembers = new();
     public IReadOnlyCollection<StructureMemberViewModel> SoftSelectedStructureMembers => softSelectedStructureMembers;
 
+    public WpfObservableRangeCollection<Guide> Guides { get; } = new();
 
     public bool UpdateableChangeActive => Internals.ChangeController.IsChangeActive;
     public bool HasSavedUndo => Internals.Tracker.HasSavedUndo;
@@ -180,6 +182,11 @@ internal partial class DocumentViewModel : NotifyableObject
         PreviewSurface = DrawingSurface.Create(new ImageInfo(previewSize.X, previewSize.Y, ColorType.Bgra8888), PreviewBitmap.BackBuffer, PreviewBitmap.BackBufferStride);
 
         ReferenceLayerViewModel = new(this, Internals);
+        Guides.Add(new LineGuide(this)
+        {
+            Position = new VecD(16, 16),
+            Rotation = Math.PI / 4
+        });
     }
 
     /// <summary>

+ 62 - 0
src/PixiEditor/Views/UserControls/Guides/GuideRenderer.cs

@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+using PixiEditor;
+using PixiEditor.DrawingApi.Core.Numerics;
+using PixiEditor.Models.DataHolders.Guides;
+using PixiEditor.ViewModels.SubViewModels.Document;
+using PixiEditor.Views;
+using PixiEditor.Views.UserControls;
+using PixiEditor.Views.UserControls.Guides;
+
+namespace PixiEditor.Views.UserControls.Guides
+{
+    internal class GuideRenderer : Control
+    {
+        public static readonly DependencyProperty GuideProperty =
+            DependencyProperty.Register(nameof(Guide), typeof(Guide), typeof(GuideRenderer), new PropertyMetadata(GuideChanged));
+
+        public Guide Guide
+        {
+            get => (Guide)GetValue(GuideProperty);
+            set => SetValue(GuideProperty, value);
+        }
+
+        public static readonly DependencyProperty ZoomboxScaleProperty =
+            DependencyProperty.Register(nameof(ZoomboxScale), typeof(double), typeof(GuideRenderer), new FrameworkPropertyMetadata(0d, FrameworkPropertyMetadataOptions.AffectsRender));
+
+        public double ZoomboxScale
+        {
+            get => (double)GetValue(ZoomboxScaleProperty);
+            set => SetValue(ZoomboxScaleProperty, value);
+        }
+
+        public double ScreenUnit => 1.0 / ZoomboxScale;
+
+        protected override void OnRender(DrawingContext context)
+        {
+            Guide.Draw(context, this);
+        }
+
+        private static void GuideChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
+        {
+            var renderer = (GuideRenderer)d;
+
+            if (e.OldValue is Guide oldGuide)
+            {
+                oldGuide.AttachRenderer(renderer);
+            }
+
+            if (e.NewValue is Guide newGuide)
+            {
+                newGuide.DetachRenderer(renderer);
+            }
+        }
+    }
+}

+ 8 - 1
src/PixiEditor/Views/UserControls/Viewport.xaml

@@ -20,7 +20,7 @@
     xmlns:cmds="clr-namespace:PixiEditor.Models.Commands.XAML"
     xmlns:tools ="clr-namespace:PixiEditor.ViewModels.SubViewModels.Tools.Tools"
     xmlns:views="clr-namespace:PixiEditor.Views"
-    xmlns:subviews="clr-namespace:PixiEditor.ViewModels.SubViewModels.Document"
+    xmlns:subviews="clr-namespace:PixiEditor.ViewModels.SubViewModels.Document" xmlns:guides="clr-namespace:PixiEditor.Views.UserControls.Guides"
     mc:Ignorable="d"
     x:Name="vpUc"
     d:DesignHeight="450"
@@ -256,6 +256,13 @@
                         </Image.Style>
                     </Image>
                     <Grid ZIndex="5">
+                        <ItemsControl ItemsSource="{Binding Document.Guides}">
+                            <ItemsControl.ItemTemplate>
+                                <DataTemplate>
+                                    <guides:GuideRenderer Guide="{Binding}" ZoomboxScale="{Binding Zoombox.Scale, RelativeSource={RelativeSource FindAncestor, AncestorType=uc:Viewport}}"></guides:GuideRenderer>
+                                </DataTemplate>
+                            </ItemsControl.ItemTemplate>
+                        </ItemsControl>
                         <symOverlay:SymmetryOverlay
                             Focusable="False"
                             IsHitTestVisible="{Binding ZoomMode, Converter={converters:ZoomModeToHitTestVisibleConverter}}"