From 56642ff7d354e6847bb872c2e38b0b8be20662fe Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Tue, 12 Dec 2023 09:58:07 +1100 Subject: [PATCH 01/27] Added Tests --- .../Behaviors/ResizeBehaviorTest.cs | 233 ++++++++++++++++++ .../Behaviors/ScrollBehaviorTests.cs | 96 +++++--- 2 files changed, 294 insertions(+), 35 deletions(-) create mode 100644 tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs new file mode 100644 index 00000000..22c5c2d5 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs @@ -0,0 +1,233 @@ +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using FluentAssertions; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Behaviors +{ + public class ResizeBehaviorTest + { + [Fact] + public void ShouldRecalculateSizeAndPosition_TopLeft() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.TopLeft); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(15); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(185); + } + + [Fact] + public void ShouldRecalculateSizeAndPosition_TopRight() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.TopRight); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(15); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(185); + } + + [Fact] + public void ShouldRecalculateSizeAndPosition_BottomLeft() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.BottomLeft); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(215); + } + + [Fact] + public void ShouldRecalculateSizeAndPosition_BottomRight() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.BottomRight); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(215); + } + + [Fact] + public void ShouldStopResizingSmallerThanMinimumSize() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.TopLeft); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(node.MinimumDimensions.Width); + node.Size.Height.Should().Be(node.MinimumDimensions.Height); + } + + [Fact] + public void ShouldStopResizeOnPointerUp() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.TopLeft); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(15); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(185); + + diagram.TriggerPointerUp(null, eventArgs); + + // move pointer after pointer up + eventArgs = new PointerEventArgs(30, 50, 1, 1, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // should be no change + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(15); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(185); + } + + [Fact] + public void NodeUpdatesWhenScrollingWhileResizing() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.TopLeft); + + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // Act + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.Options.Zoom.ScaleFactor = 1.05; + node.Size = new Size(100, 200); + diagram.Nodes.Add(node); + + diagram.SelectModel(node, false); + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + + // Assert + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(-175); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(185); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs index acd78bbd..61265c0d 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs @@ -1,43 +1,69 @@ using Blazor.Diagrams.Core.Behaviors; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; using Xunit; namespace Blazor.Diagrams.Core.Tests.Behaviors { - public class ScrollBehaviorTests - { - [Fact] - public void Behavior_WhenBehaviorEnabled_ShouldScroll() - { - // Arrange - var diagram = new TestDiagram(); - diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); - diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); - diagram.Options.Zoom.ScaleFactor = 1.05; - - // Act - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); - - // Assert - Assert.Equal(-95, diagram.Pan.X, 0); - Assert.Equal(-190, diagram.Pan.Y, 0); - } - - [Fact] - public void Behavior_WhenBehaviorDisabled_ShouldNotScroll() - { - // Arrange - var diagram = new TestDiagram(); - diagram.BehaviorOptions.DiagramWheelBehavior = null; - diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); - - // Act - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); - - // Assert - Assert.Equal(0, diagram.Pan.X); - Assert.Equal(0, diagram.Pan.Y); - } - } + public class ScrollBehaviorTests + { + [Fact] + public void Behavior_WhenBehaviorEnabled_ShouldScroll() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + diagram.Options.Zoom.ScaleFactor = 1.05; + + // Act + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + + // Assert + Assert.Equal(-95, diagram.Pan.X, 0); + Assert.Equal(-190, diagram.Pan.Y, 0); + } + + [Fact] + public void Behavior_WhenBehaviorDisabled_ShouldNotScroll() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = null; + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + + // Act + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + + // Assert + Assert.Equal(0, diagram.Pan.X); + Assert.Equal(0, diagram.Pan.Y); + } + + [Fact] + public void NodeUpdatesWhenScrollingWhileDragging() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + diagram.Options.Zoom.ScaleFactor = 1.05; + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + diagram.Nodes.Add(node); + + // Act + diagram.SelectModel(node, false); + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + + // Assert + Assert.Equal(-95, diagram.Pan.X, 0); + Assert.Equal(-190, diagram.Pan.Y, 0); + Assert.Equal(-190, node.Position.Y); + } + + } } From cb3c5dbaa41cfbfe385e152d1b152adcf271f320 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Wed, 13 Dec 2023 13:35:52 +1100 Subject: [PATCH 02/27] First commit --- .../Behaviors/DragMovablesBehavior.cs | 154 ++++++- .../Behaviors/ScrollBehavior.cs | 30 +- src/Blazor.Diagrams.Core/Diagram.cs | 412 +++++++++++++++++- 3 files changed, 573 insertions(+), 23 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index face95c3..7e778c09 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -1,15 +1,16 @@ -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Behaviors.Base; using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; using System; using System.Collections.Generic; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Behaviors.Base; namespace Blazor.Diagrams.Core.Behaviors; public class DragMovablesBehavior : Behavior { +<<<<<<< HEAD private readonly Dictionary _initialPositions; private double? _lastClientX; private double? _lastClientY; @@ -112,4 +113,149 @@ public override void Dispose() Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; } +======= + public class DragMovablesBehavior : Behavior + { + private readonly Dictionary _initialPositions; + private double? _lastClientX; + private double? _lastClientY; + private bool _moved; + private double _totalScrollX = 0; + private double _totalScrollY = 0; + + public DragMovablesBehavior(Diagram diagram) : base(diagram) + { + _initialPositions = new Dictionary(); + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + Diagram.Wheel += OnPointerMove; + } + + private void OnPointerDown(Model? model, PointerEventArgs e) + { + if (model is not MovableModel) + return; + + _initialPositions.Clear(); + foreach (var sm in Diagram.GetSelectedModels()) + { + if (sm is not MovableModel movable || movable.Locked) + continue; + + // Special case: groups without auto size on + if (sm is NodeModel node && node.Group != null && !node.Group.AutoSize) + continue; + + var position = movable.Position; + if (Diagram.Options.GridSnapToCenter && movable is NodeModel n) + { + position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2, + movable.Position.Y + (n.Size?.Height ?? 0) / 2); + } + + _initialPositions.Add(movable, position); + } + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + _moved = false; + } + + public void OnPointerMove(Model? model, PointerEventArgs e) + { + if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) + return; + + _moved = true; + + var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; + var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; + + moveNodes(model, deltaX, deltaY); + } + + public void OnPointerMove(WheelEventArgs e) + { + if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) + return; + + _moved = true; + + _totalScrollX += e.DeltaX; + _totalScrollY += e.DeltaY; + + // Use _totalScrollX and _totalScrollY for moving nodes + moveNodes(null, _totalScrollX, _totalScrollY); + + Console.WriteLine($"TotalScrollX: {_totalScrollX}, TotalScrollY: {_totalScrollY}"); + + _lastClientX -= e.DeltaX; + _lastClientY -= e.DeltaY; + + Console.WriteLine($"Updated LastClientX: {_lastClientX}, Updated LastClientY: {_lastClientY}"); + } + + private void moveNodes(Model? model, double deltaX, double deltaY) + { + Console.WriteLine($"DeltaX: {deltaX}, DeltaY: {deltaY}"); + foreach (var (movable, initialPosition) in _initialPositions) + { + var ndx = ApplyGridSize(deltaX + initialPosition.X); + var ndy = ApplyGridSize(deltaY + initialPosition.Y); + if (Diagram.Options.GridSnapToCenter && movable is NodeModel node) + { + node.SetPosition(ndx - (node.Size?.Width ?? 0) / 2, ndy - (node.Size?.Height ?? 0) / 2); + } + else + { + movable.SetPosition(ndx, ndy); + } + } + } + + private void OnPointerUp(Model? model, PointerEventArgs e) + { + if (_initialPositions.Count == 0) + return; + + if (_moved) + { + foreach (var (movable, _) in _initialPositions) + { + movable.TriggerMoved(); + } + } + + _initialPositions.Clear(); + _totalScrollX = 0; + _totalScrollY = 0; + _lastClientX = null; + _lastClientY = null; + } + + private double ApplyGridSize(double n) + { + if (Diagram.Options.GridSize == null) + return n; + + var gridSize = Diagram.Options.GridSize.Value; + + // 20 * floor((100 + 10) / 20) = 20 * 5 = 100 + // 20 * floor((105 + 10) / 20) = 20 * 5 = 100 + // 20 * floor((110 + 10) / 20) = 20 * 6 = 120 + return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); + } + + public override void Dispose() + { + _initialPositions.Clear(); + + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + Diagram.Wheel -= OnPointerMove; + } + } +>>>>>>> 7bf2db0 (First commit) } diff --git a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs index fa3ef63e..9b563428 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs @@ -4,22 +4,22 @@ namespace Blazor.Diagrams.Core.Behaviors { - public class ScrollBehavior : WheelBehavior - { - public ScrollBehavior(Diagram diagram) - : base(diagram) - { - } + public class ScrollBehavior : WheelBehavior + { + public ScrollBehavior(Diagram diagram) + : base(diagram) + { + } - protected override void OnDiagramWheel(WheelEventArgs e) - { - if (Diagram.Container == null || !IsBehaviorEnabled(e)) - return; + protected override void OnDiagramWheel(WheelEventArgs e) + { + if (Diagram.Container == null || !IsBehaviorEnabled(e)) + return; - var x = Diagram.Pan.X - (e.DeltaX / Diagram.Options.Zoom.ScaleFactor); - var y = Diagram.Pan.Y - (e.DeltaY / Diagram.Options.Zoom.ScaleFactor); + var x = Diagram.Pan.X - (e.DeltaX / Diagram.Options.Zoom.ScaleFactor); + var y = Diagram.Pan.Y - (e.DeltaY / Diagram.Options.Zoom.ScaleFactor); - Diagram.SetPan(x, y); - } - } + Diagram.SetPan(x, y); + } + } } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 61b8b09f..edc44fd7 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -1,16 +1,16 @@ using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Behaviors.Base; +using Blazor.Diagrams.Core.Controls; +using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Layers; using Blazor.Diagrams.Core.Models.Base; -using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Options; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using Blazor.Diagrams.Core.Options; -using Blazor.Diagrams.Core.Controls; -using Blazor.Diagrams.Core.Behaviors.Base; [assembly: InternalsVisibleTo("Blazor.Diagrams")] [assembly: InternalsVisibleTo("Blazor.Diagrams.Tests")] @@ -20,6 +20,7 @@ namespace Blazor.Diagrams.Core; public abstract class Diagram { +<<<<<<< HEAD private readonly Dictionary _behaviors; private readonly List _orderedSelectables; @@ -416,4 +417,407 @@ private void OnModelOrderChanged(Model model) public void TriggerPointerDoubleClick(Model? model, PointerEventArgs e) => PointerDoubleClick?.Invoke(model, e); #endregion +======= + public abstract class Diagram + { + private readonly Dictionary _behaviors; + private readonly List _orderedSelectables; + + public event Action? PointerDown; + public event Action? PointerMove; + public event Action? PointerUp; + public event Action? PointerEnter; + public event Action? PointerLeave; + public event Action? KeyDown; + public event Action? Wheel; + public event Action? PointerClick; + public event Action? PointerDoubleClick; + + public event Action? SelectionChanged; + public event Action? PanChanged; + public event Action? ZoomChanged; + public event Action? ContainerChanged; + public event Action? Changed; + + protected Diagram() + { + _behaviors = new Dictionary(); + _orderedSelectables = new List(); + + Nodes = new NodeLayer(this); + Links = new LinkLayer(this); + Groups = new GroupLayer(this); + Controls = new ControlsLayer(); + BehaviorOptions = new DiagramBehaviorOptions(); + + Nodes.Added += OnSelectableAdded; + Links.Added += OnSelectableAdded; + Groups.Added += OnSelectableAdded; + + Nodes.Removed += OnSelectableRemoved; + Links.Removed += OnSelectableRemoved; + Groups.Removed += OnSelectableRemoved; + + RegisterDefaultBehaviors(); + + BehaviorOptions.DiagramDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramShiftDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramWheelBehavior ??= GetBehavior(); + } + + public abstract DiagramOptions Options { get; } + public DiagramBehaviorOptions BehaviorOptions { get; } + public NodeLayer Nodes { get; } + public LinkLayer Links { get; } + public GroupLayer Groups { get; } + public ControlsLayer Controls { get; } + public Rectangle? Container { get; private set; } + public Point Pan { get; private set; } = Point.Zero; + public double Zoom { get; private set; } = 1; + public bool SuspendRefresh { get; set; } + public bool SuspendSorting { get; set; } + public IReadOnlyList OrderedSelectables => _orderedSelectables; + + public void Refresh() + { + if (SuspendRefresh) + return; + + Changed?.Invoke(); + } + + public void Batch(Action action) + { + if (SuspendRefresh) + { + // If it's already suspended, just execute the action and leave it suspended + // It's probably handled by an outer batch + action(); + return; + } + + SuspendRefresh = true; + action(); + SuspendRefresh = false; + Refresh(); + } + + #region Selection + + public IEnumerable GetSelectedModels() + { + foreach (var node in Nodes) + { + if (node.Selected) + yield return node; + } + + foreach (var link in Links) + { + if (link.Selected) + yield return link; + + foreach (var vertex in link.Vertices) + { + if (vertex.Selected) + yield return vertex; + } + } + + foreach (var group in Groups) + { + if (group.Selected) + yield return group; + } + } + + public void SelectModel(SelectableModel model, bool unselectOthers) + { + if (model.Selected) + return; + + if (unselectOthers) + UnselectAll(); + + model.Selected = true; + model.Refresh(); + SelectionChanged?.Invoke(model); + } + + public void UnselectModel(SelectableModel model) + { + if (!model.Selected) + return; + + model.Selected = false; + model.Refresh(); + SelectionChanged?.Invoke(model); + } + + public void UnselectAll() + { + foreach (var model in GetSelectedModels()) + { + model.Selected = false; + model.Refresh(); + // Todo: will result in many events, maybe one event for all of them? + SelectionChanged?.Invoke(model); + } + } + + #endregion + + #region Behaviors + + void RegisterDefaultBehaviors() + { + RegisterBehavior(new SelectionBehavior(this)); + RegisterBehavior(new DragMovablesBehavior(this)); + RegisterBehavior(new DragNewLinkBehavior(this)); + RegisterBehavior(new PanBehavior(this)); + RegisterBehavior(new ZoomBehavior(this)); + RegisterBehavior(new EventsBehavior(this)); + RegisterBehavior(new KeyboardShortcutsBehavior(this)); + RegisterBehavior(new ControlsBehavior(this)); + RegisterBehavior(new VirtualizationBehavior(this)); + RegisterBehavior(new ScrollBehavior(this)); + RegisterBehavior(new SelectionBoxBehavior(this)); + RegisterBehavior(new ResizeBehavior(this)); + } + + public void RegisterBehavior(Behavior behavior) + { + var type = behavior.GetType(); + if (_behaviors.ContainsKey(type)) + throw new Exception($"Behavior '{type.Name}' already registered"); + + _behaviors.Add(type, behavior); + } + + public T? GetBehavior() where T : Behavior + { + var type = typeof(T); + return (T?)(_behaviors.ContainsKey(type) ? _behaviors[type] : null); + } + + public void UnregisterBehavior() where T : Behavior + { + var type = typeof(T); + if (!_behaviors.ContainsKey(type)) + return; + + _behaviors[type].Dispose(); + _behaviors.Remove(type); + } + + #endregion + + public void ZoomToFit(double margin = 10) + { + if (Container == null || Nodes.Count == 0) + return; + + var selectedNodes = Nodes.Where(s => s.Selected); + var nodesToUse = selectedNodes.Any() ? selectedNodes : Nodes; + var bounds = nodesToUse.GetBounds(); + var width = bounds.Width + 2 * margin; + var height = bounds.Height + 2 * margin; + var minX = bounds.Left - margin; + var minY = bounds.Top - margin; + + SuspendRefresh = true; + + var xf = Container.Width / width; + var yf = Container.Height / height; + SetZoom(Math.Min(xf, yf)); + + var nx = Container.Left + Pan.X + minX * Zoom; + var ny = Container.Top + Pan.Y + minY * Zoom; + UpdatePan(Container.Left - nx, Container.Top - ny); + + SuspendRefresh = false; + Refresh(); + } + + public void SetPan(double x, double y) + { + Pan = new Point(x, y); + PanChanged?.Invoke(); + Refresh(); + } + + public void UpdatePan(double deltaX, double deltaY) + { + Pan = Pan.Add(deltaX, deltaY); + PanChanged?.Invoke(); + Refresh(); + } + + public void SetZoom(double newZoom) + { + if (newZoom <= 0) + throw new ArgumentException($"{nameof(newZoom)} cannot be equal or lower than 0"); + + if (newZoom < Options.Zoom.Minimum) + newZoom = Options.Zoom.Minimum; + + Zoom = newZoom; + ZoomChanged?.Invoke(); + Refresh(); + } + + public void SetContainer(Rectangle? newRect) + { + if (Equals(newRect, Container)) + return; + + Container = newRect; + ContainerChanged?.Invoke(); + Refresh(); + } + + public Point GetRelativeMousePoint(double clientX, double clientY) + { + if (Container == null) + throw new Exception( + "Container not available. Make sure you're not using this method before the diagram is fully loaded"); + + return new Point((clientX - Container.Left - Pan.X) / Zoom, (clientY - Container.Top - Pan.Y) / Zoom); + } + + public Point GetRelativePoint(double clientX, double clientY) + { + if (Container == null) + throw new Exception( + "Container not available. Make sure you're not using this method before the diagram is fully loaded"); + + return new Point(clientX - Container.Left, clientY - Container.Top); + } + + public Point GetScreenPoint(double clientX, double clientY) + { + if (Container == null) + throw new Exception( + "Container not available. Make sure you're not using this method before the diagram is fully loaded"); + + return new Point(Zoom * clientX + Container.Left + Pan.X, Zoom * clientY + Container.Top + Pan.Y); + } + + #region Ordering + + public void SendToBack(SelectableModel model) + { + var minOrder = GetMinOrder(); + if (model.Order == minOrder) + return; + + if (!_orderedSelectables.Remove(model)) + return; + + _orderedSelectables.Insert(0, model); + + // Todo: can optimize this by only updating the order of items before model + Batch(() => + { + SuspendSorting = true; + for (var i = 0; i < _orderedSelectables.Count; i++) + { + _orderedSelectables[i].Order = i + 1; + } + SuspendSorting = false; + }); + } + + public void SendToFront(SelectableModel model) + { + var maxOrder = GetMaxOrder(); + if (model.Order == maxOrder) + return; + + if (!_orderedSelectables.Remove(model)) + return; + + _orderedSelectables.Add(model); + + SuspendSorting = true; + model.Order = maxOrder + 1; + SuspendSorting = false; + Refresh(); + } + + public int GetMinOrder() + { + return _orderedSelectables.Count > 0 ? _orderedSelectables[0].Order : 0; + } + + public int GetMaxOrder() + { + return _orderedSelectables.Count > 0 ? _orderedSelectables[^1].Order : 0; + } + + /// + /// Sorts the list of selectables based on their order + /// + public void RefreshOrders(bool refresh = true) + { + _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); + + if (refresh) + { + Refresh(); + } + } + + private void OnSelectableAdded(SelectableModel model) + { + var maxOrder = GetMaxOrder(); + _orderedSelectables.Add(model); + + if (model.Order == 0) + { + model.Order = maxOrder + 1; + } + + model.OrderChanged += OnModelOrderChanged; + } + + private void OnSelectableRemoved(SelectableModel model) + { + model.OrderChanged -= OnModelOrderChanged; + _orderedSelectables.Remove(model); + } + + private void OnModelOrderChanged(Model model) + { + if (SuspendSorting) + return; + + RefreshOrders(); + } + + #endregion + + #region Events + + public void TriggerPointerDown(Model? model, PointerEventArgs e) => PointerDown?.Invoke(model, e); + + public void TriggerPointerMove(Model? model, PointerEventArgs e) => PointerMove?.Invoke(model, e); + + public void TriggerPointerUp(Model? model, PointerEventArgs e) => PointerUp?.Invoke(model, e); + + public void TriggerPointerEnter(Model? model, PointerEventArgs e) => PointerEnter?.Invoke(model, e); + + public void TriggerPointerLeave(Model? model, PointerEventArgs e) => PointerLeave?.Invoke(model, e); + + public void TriggerKeyDown(KeyboardEventArgs e) => KeyDown?.Invoke(e); + + public void TriggerWheel(WheelEventArgs e) => Wheel?.Invoke(e); + + public void TriggerPointerClick(Model? model, PointerEventArgs e) => PointerClick?.Invoke(model, e); + + public void TriggerPointerDoubleClick(Model? model, PointerEventArgs e) => PointerDoubleClick?.Invoke(model, e); + + #endregion + } +>>>>>>> 7bf2db0 (First commit) } \ No newline at end of file From b817d57e68ed16ad6617a93dd0d18d9f6e1b1a15 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Wed, 13 Dec 2023 13:41:57 +1100 Subject: [PATCH 03/27] Updated test --- .../Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs index 61265c0d..b82c98d8 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs @@ -62,7 +62,7 @@ public void NodeUpdatesWhenScrollingWhileDragging() // Assert Assert.Equal(-95, diagram.Pan.X, 0); Assert.Equal(-190, diagram.Pan.Y, 0); - Assert.Equal(-190, node.Position.Y); + Assert.Equal(200, node.Position.Y); } } From f4dfbfb4d6177c1d65d63260fc98e942f94db588 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Wed, 13 Dec 2023 14:49:12 +1100 Subject: [PATCH 04/27] Drag and scroll fixed --- .../Behaviors/DragMovablesBehavior.cs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 7e778c09..fa0a9203 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -120,8 +120,8 @@ public class DragMovablesBehavior : Behavior private double? _lastClientX; private double? _lastClientY; private bool _moved; - private double _totalScrollX = 0; - private double _totalScrollY = 0; + private double _totalMovedX = 0; + private double _totalMovedY = 0; public DragMovablesBehavior(Diagram diagram) : base(diagram) { @@ -172,7 +172,14 @@ public void OnPointerMove(Model? model, PointerEventArgs e) var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; - moveNodes(model, deltaX, deltaY); + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + moveNodes(model, _totalMovedX, _totalMovedY); + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + } public void OnPointerMove(WheelEventArgs e) @@ -182,27 +189,19 @@ public void OnPointerMove(WheelEventArgs e) _moved = true; - _totalScrollX += e.DeltaX; - _totalScrollY += e.DeltaY; + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; - // Use _totalScrollX and _totalScrollY for moving nodes - moveNodes(null, _totalScrollX, _totalScrollY); - - Console.WriteLine($"TotalScrollX: {_totalScrollX}, TotalScrollY: {_totalScrollY}"); - - _lastClientX -= e.DeltaX; - _lastClientY -= e.DeltaY; - - Console.WriteLine($"Updated LastClientX: {_lastClientX}, Updated LastClientY: {_lastClientY}"); + moveNodes(null, _totalMovedX, _totalMovedY); } private void moveNodes(Model? model, double deltaX, double deltaY) { - Console.WriteLine($"DeltaX: {deltaX}, DeltaY: {deltaY}"); foreach (var (movable, initialPosition) in _initialPositions) { var ndx = ApplyGridSize(deltaX + initialPosition.X); var ndy = ApplyGridSize(deltaY + initialPosition.Y); + if (Diagram.Options.GridSnapToCenter && movable is NodeModel node) { node.SetPosition(ndx - (node.Size?.Width ?? 0) / 2, ndy - (node.Size?.Height ?? 0) / 2); @@ -228,8 +227,8 @@ private void OnPointerUp(Model? model, PointerEventArgs e) } _initialPositions.Clear(); - _totalScrollX = 0; - _totalScrollY = 0; + _totalMovedX = 0; + _totalMovedY = 0; _lastClientX = null; _lastClientY = null; } From 4d710a4a1ab16698cd22342db5e841fc0aaac45f Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 14 Dec 2023 10:20:46 +1100 Subject: [PATCH 05/27] fixed cherry pick errors --- .../Behaviors/DragMovablesBehavior.cs | 309 ++++++------------ .../Behaviors/ResizeBehaviorTest.cs | 233 ------------- 2 files changed, 99 insertions(+), 443 deletions(-) delete mode 100644 tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index fa0a9203..a69d4001 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -10,251 +10,140 @@ namespace Blazor.Diagrams.Core.Behaviors; public class DragMovablesBehavior : Behavior { -<<<<<<< HEAD - private readonly Dictionary _initialPositions; - private double? _lastClientX; - private double? _lastClientY; - private bool _moved; - - public DragMovablesBehavior(Diagram diagram) : base(diagram) - { - _initialPositions = new Dictionary(); - Diagram.PointerDown += OnPointerDown; - Diagram.PointerMove += OnPointerMove; - Diagram.PointerUp += OnPointerUp; - } - - private void OnPointerDown(Model? model, PointerEventArgs e) - { - if (model is not MovableModel) - return; - - _initialPositions.Clear(); - foreach (var sm in Diagram.GetSelectedModels()) - { - if (sm is not MovableModel movable || movable.Locked) - continue; - - // Special case: groups without auto size on - if (sm is NodeModel node && node.Group != null && !node.Group.AutoSize) - continue; - - var position = movable.Position; - if (Diagram.Options.GridSnapToCenter && movable is NodeModel n) - { - position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2, - movable.Position.Y + (n.Size?.Height ?? 0) / 2); - } - - _initialPositions.Add(movable, position); - } - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _moved = false; - } - - private void OnPointerMove(Model? model, PointerEventArgs e) - { - if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) - return; - - _moved = true; - var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; - var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; - - foreach (var (movable, initialPosition) in _initialPositions) - { - var ndx = ApplyGridSize(deltaX + initialPosition.X); - var ndy = ApplyGridSize(deltaY + initialPosition.Y); - if (Diagram.Options.GridSnapToCenter && movable is NodeModel node) - { - node.SetPosition(ndx - (node.Size?.Width ?? 0) / 2, ndy - (node.Size?.Height ?? 0) / 2); - } - else - { - movable.SetPosition(ndx, ndy); - } - } - } - - private void OnPointerUp(Model? model, PointerEventArgs e) - { - if (_initialPositions.Count == 0) - return; - - if (_moved) - { - foreach (var (movable, _) in _initialPositions) - { - movable.TriggerMoved(); - } - } - - _initialPositions.Clear(); - _lastClientX = null; - _lastClientY = null; - } - - private double ApplyGridSize(double n) - { - if (Diagram.Options.GridSize == null) - return n; - - var gridSize = Diagram.Options.GridSize.Value; - return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); - } + private readonly Dictionary _initialPositions; + private double? _lastClientX; + private double? _lastClientY; + private bool _moved; + private double _totalMovedX = 0; + private double _totalMovedY = 0; + + public DragMovablesBehavior(Diagram diagram) : base(diagram) + { + _initialPositions = new Dictionary(); + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + Diagram.Wheel += OnPointerMove; + } - public override void Dispose() - { - _initialPositions.Clear(); - - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - } -======= - public class DragMovablesBehavior : Behavior + private void OnPointerDown(Model? model, PointerEventArgs e) { - private readonly Dictionary _initialPositions; - private double? _lastClientX; - private double? _lastClientY; - private bool _moved; - private double _totalMovedX = 0; - private double _totalMovedY = 0; + if (model is not MovableModel) + return; - public DragMovablesBehavior(Diagram diagram) : base(diagram) + _initialPositions.Clear(); + foreach (var sm in Diagram.GetSelectedModels()) { - _initialPositions = new Dictionary(); - Diagram.PointerDown += OnPointerDown; - Diagram.PointerMove += OnPointerMove; - Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnPointerMove; - } + if (sm is not MovableModel movable || movable.Locked) + continue; - private void OnPointerDown(Model? model, PointerEventArgs e) - { - if (model is not MovableModel) - return; + // Special case: groups without auto size on + if (sm is NodeModel node && node.Group != null && !node.Group.AutoSize) + continue; - _initialPositions.Clear(); - foreach (var sm in Diagram.GetSelectedModels()) + var position = movable.Position; + if (Diagram.Options.GridSnapToCenter && movable is NodeModel n) { - if (sm is not MovableModel movable || movable.Locked) - continue; - - // Special case: groups without auto size on - if (sm is NodeModel node && node.Group != null && !node.Group.AutoSize) - continue; - - var position = movable.Position; - if (Diagram.Options.GridSnapToCenter && movable is NodeModel n) - { - position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2, - movable.Position.Y + (n.Size?.Height ?? 0) / 2); - } - - _initialPositions.Add(movable, position); + position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2, + movable.Position.Y + (n.Size?.Height ?? 0) / 2); } - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _moved = false; + _initialPositions.Add(movable, position); } - public void OnPointerMove(Model? model, PointerEventArgs e) - { - if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) - return; + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + _moved = false; + } - _moved = true; + public void OnPointerMove(Model? model, PointerEventArgs e) + { + if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) + return; - var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; - var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; + _moved = true; - _totalMovedX += deltaX; - _totalMovedY += deltaY; + var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; + var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; - moveNodes(model, _totalMovedX, _totalMovedY); + _totalMovedX += deltaX; + _totalMovedY += deltaY; - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; + moveNodes(model, _totalMovedX, _totalMovedY); - } + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; - public void OnPointerMove(WheelEventArgs e) - { - if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) - return; + } - _moved = true; + public void OnPointerMove(WheelEventArgs e) + { + if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) + return; - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; + _moved = true; - moveNodes(null, _totalMovedX, _totalMovedY); - } + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; - private void moveNodes(Model? model, double deltaX, double deltaY) + moveNodes(null, _totalMovedX, _totalMovedY); + } + + private void moveNodes(Model? model, double deltaX, double deltaY) + { + foreach (var (movable, initialPosition) in _initialPositions) { - foreach (var (movable, initialPosition) in _initialPositions) - { - var ndx = ApplyGridSize(deltaX + initialPosition.X); - var ndy = ApplyGridSize(deltaY + initialPosition.Y); + var ndx = ApplyGridSize(deltaX + initialPosition.X); + var ndy = ApplyGridSize(deltaY + initialPosition.Y); - if (Diagram.Options.GridSnapToCenter && movable is NodeModel node) - { - node.SetPosition(ndx - (node.Size?.Width ?? 0) / 2, ndy - (node.Size?.Height ?? 0) / 2); - } - else - { - movable.SetPosition(ndx, ndy); - } + if (Diagram.Options.GridSnapToCenter && movable is NodeModel node) + { + node.SetPosition(ndx - (node.Size?.Width ?? 0) / 2, ndy - (node.Size?.Height ?? 0) / 2); + } + else + { + movable.SetPosition(ndx, ndy); } } + } - private void OnPointerUp(Model? model, PointerEventArgs e) - { - if (_initialPositions.Count == 0) - return; + private void OnPointerUp(Model? model, PointerEventArgs e) + { + if (_initialPositions.Count == 0) + return; - if (_moved) + if (_moved) + { + foreach (var (movable, _) in _initialPositions) { - foreach (var (movable, _) in _initialPositions) - { - movable.TriggerMoved(); - } + movable.TriggerMoved(); } - - _initialPositions.Clear(); - _totalMovedX = 0; - _totalMovedY = 0; - _lastClientX = null; - _lastClientY = null; } - private double ApplyGridSize(double n) - { - if (Diagram.Options.GridSize == null) - return n; + _initialPositions.Clear(); + _totalMovedX = 0; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; + } + + private double ApplyGridSize(double n) + { + if (Diagram.Options.GridSize == null) + return n; - var gridSize = Diagram.Options.GridSize.Value; + var gridSize = Diagram.Options.GridSize.Value; - // 20 * floor((100 + 10) / 20) = 20 * 5 = 100 - // 20 * floor((105 + 10) / 20) = 20 * 5 = 100 - // 20 * floor((110 + 10) / 20) = 20 * 6 = 120 - return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); - } + return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); + } - public override void Dispose() - { - _initialPositions.Clear(); + public override void Dispose() + { + _initialPositions.Clear(); - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnPointerMove; - } + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + Diagram.Wheel -= OnPointerMove; } ->>>>>>> 7bf2db0 (First commit) } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs deleted file mode 100644 index 22c5c2d5..00000000 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs +++ /dev/null @@ -1,233 +0,0 @@ -using Blazor.Diagrams.Core.Behaviors; -using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; -using FluentAssertions; -using System; -using System.Threading.Tasks; -using Xunit; - -namespace Blazor.Diagrams.Core.Tests.Behaviors -{ - public class ResizeBehaviorTest - { - [Fact] - public void ShouldRecalculateSizeAndPosition_TopLeft() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.TopLeft); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - - // after resize - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(15); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(185); - } - - [Fact] - public void ShouldRecalculateSizeAndPosition_TopRight() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.TopRight); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(15); - node.Size.Width.Should().Be(110); - node.Size.Height.Should().Be(185); - } - - [Fact] - public void ShouldRecalculateSizeAndPosition_BottomLeft() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.BottomLeft); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(215); - } - - [Fact] - public void ShouldRecalculateSizeAndPosition_BottomRight() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.BottomRight); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(110); - node.Size.Height.Should().Be(215); - } - - [Fact] - public void ShouldStopResizingSmallerThanMinimumSize() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.TopLeft); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(node.MinimumDimensions.Width); - node.Size.Height.Should().Be(node.MinimumDimensions.Height); - } - - [Fact] - public void ShouldStopResizeOnPointerUp() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.TopLeft); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(15); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(185); - - diagram.TriggerPointerUp(null, eventArgs); - - // move pointer after pointer up - eventArgs = new PointerEventArgs(30, 50, 1, 1, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // should be no change - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(15); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(185); - } - - [Fact] - public void NodeUpdatesWhenScrollingWhileResizing() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.TopLeft); - - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // Act - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); - diagram.Options.Zoom.ScaleFactor = 1.05; - node.Size = new Size(100, 200); - diagram.Nodes.Add(node); - - diagram.SelectModel(node, false); - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); - - // Assert - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(-175); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(185); - } - } -} From 191dd7ca5bd6731ffb3dd698cb66a3cbd7e6fece Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 14 Dec 2023 13:32:47 +1100 Subject: [PATCH 06/27] Arrows now update when scrolling --- .../Behaviors/DragNewLinkBehavior.cs | 344 +++--- src/Blazor.Diagrams.Core/Diagram.cs | 1004 +++++------------ .../Behaviors/DragNewLinkBehaviorTests.cs | 894 ++++++++------- 3 files changed, 943 insertions(+), 1299 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 7efa83dc..2f0d92ac 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -1,172 +1,188 @@ -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; -using Blazor.Diagrams.Core.Events; -using System.Linq; -using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Anchors; using Blazor.Diagrams.Core.Behaviors.Base; +using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using System; +using System.Linq; namespace Blazor.Diagrams.Core.Behaviors; public class DragNewLinkBehavior : Behavior { - private PositionAnchor? _targetPositionAnchor; - - public BaseLinkModel? OngoingLink { get; private set; } - - public DragNewLinkBehavior(Diagram diagram) : base(diagram) - { - Diagram.PointerDown += OnPointerDown; - Diagram.PointerMove += OnPointerMove; - Diagram.PointerUp += OnPointerUp; - } - - public void StartFrom(ILinkable source, double clientX, double clientY) - { - if (OngoingLink != null) - return; - - _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); - OngoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor); - if (OngoingLink == null) - return; - - Diagram.Links.Add(OngoingLink); - } - - public void StartFrom(BaseLinkModel link, double clientX, double clientY) - { - if (OngoingLink != null) - return; - - _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); - OngoingLink = link; - OngoingLink.SetTarget(_targetPositionAnchor); - OngoingLink.Refresh(); - OngoingLink.RefreshLinks(); - } - - private void OnPointerDown(Model? model, MouseEventArgs e) - { - if (e.Button != (int)MouseEventButton.Left) - return; - - OngoingLink = null; - _targetPositionAnchor = null; - - if (model is PortModel port) - { - if (port.Locked) - return; - - _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(e.ClientX, e.ClientY)); - OngoingLink = Diagram.Options.Links.Factory(Diagram, port, _targetPositionAnchor); - if (OngoingLink == null) - return; - - OngoingLink.SetTarget(_targetPositionAnchor); - Diagram.Links.Add(OngoingLink); - } - } - - private void OnPointerMove(Model? model, MouseEventArgs e) - { - if (OngoingLink == null || model != null) - return; - - _targetPositionAnchor!.SetPosition(CalculateTargetPosition(e.ClientX, e.ClientY)); - - if (Diagram.Options.Links.EnableSnapping) - { - var nearPort = FindNearPortToAttachTo(); - if (nearPort != null || OngoingLink.Target is not PositionAnchor) - { - OngoingLink.SetTarget(nearPort is null ? _targetPositionAnchor : new SinglePortAnchor(nearPort)); - } - } - - OngoingLink.Refresh(); - OngoingLink.RefreshLinks(); - } - - private void OnPointerUp(Model? model, MouseEventArgs e) - { - if (OngoingLink == null) - return; - - if (OngoingLink.IsAttached) // Snapped already - { - OngoingLink.TriggerTargetAttached(); - OngoingLink = null; - return; - } - - if (model is ILinkable linkable && (OngoingLink.Source.Model == null || OngoingLink.Source.Model.CanAttachTo(linkable))) - { - var targetAnchor = Diagram.Options.Links.TargetAnchorFactory(Diagram, OngoingLink, linkable); - OngoingLink.SetTarget(targetAnchor); - OngoingLink.TriggerTargetAttached(); - OngoingLink.Refresh(); - OngoingLink.RefreshLinks(); - } - else if (Diagram.Options.Links.RequireTarget) - { - Diagram.Links.Remove(OngoingLink); - } - else if (!Diagram.Options.Links.RequireTarget) - { - OngoingLink.Refresh(); - } - - OngoingLink = null; - } - - private Point CalculateTargetPosition(double clientX, double clientY) - { - var target = Diagram.GetRelativeMousePoint(clientX, clientY); - - if (OngoingLink == null) - { - return target; - } - - var source = OngoingLink.Source.GetPlainPosition()!; - var dirVector = target.Subtract(source).Normalize(); - var change = dirVector.Multiply(5); - return target.Subtract(change); - } - - private PortModel? FindNearPortToAttachTo() - { - if (OngoingLink is null || _targetPositionAnchor is null) - return null; - - PortModel? nearestSnapPort = null; - var nearestSnapPortDistance = double.PositiveInfinity; - - var position = _targetPositionAnchor!.GetPosition(OngoingLink)!; - - foreach (var port in Diagram.Nodes.SelectMany((NodeModel n) => n.Ports)) - { - var distance = position.DistanceTo(port.Position); - - if (distance <= Diagram.Options.Links.SnappingRadius && (OngoingLink.Source.Model?.CanAttachTo(port) != false)) - { - if (distance < nearestSnapPortDistance) - { - nearestSnapPortDistance = distance; - nearestSnapPort = port; - } - } - } - - return nearestSnapPort; - } - - public override void Dispose() - { - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - } + private PositionAnchor? _targetPositionAnchor; + + public BaseLinkModel? OngoingLink { get; private set; } + + public DragNewLinkBehavior(Diagram diagram) : base(diagram) + { + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + Diagram.Wheel += OnPointerMove; + } + + public void StartFrom(ILinkable source, double clientX, double clientY) + { + if (OngoingLink != null) + return; + + _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); + OngoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor); + if (OngoingLink == null) + return; + + Diagram.Links.Add(OngoingLink); + } + + public void StartFrom(BaseLinkModel link, double clientX, double clientY) + { + if (OngoingLink != null) + return; + + _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); + OngoingLink = link; + OngoingLink.SetTarget(_targetPositionAnchor); + OngoingLink.Refresh(); + OngoingLink.RefreshLinks(); + } + + private void OnPointerDown(Model? model, MouseEventArgs e) + { + if (e.Button != (int)MouseEventButton.Left) + return; + + OngoingLink = null; + _targetPositionAnchor = null; + + if (model is PortModel port) + { + if (port.Locked) + return; + + _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(e.ClientX, e.ClientY)); + OngoingLink = Diagram.Options.Links.Factory(Diagram, port, _targetPositionAnchor); + if (OngoingLink == null) + return; + + OngoingLink.SetTarget(_targetPositionAnchor); + Diagram.Links.Add(OngoingLink); + } + } + + private void OnPointerMove(Model? model, MouseEventArgs e) + { + if (OngoingLink == null || model != null) + return; + + UpdateLinkPosition(e.ClientX, e.ClientY); + } + + private void OnPointerMove(WheelEventArgs e) + { + if (OngoingLink == null) + return; + + UpdateLinkPosition(e.ClientX + e.DeltaX, e.ClientY + e.DeltaY); + } + + private void UpdateLinkPosition(double clientX, double clientY) + { + _targetPositionAnchor!.SetPosition(CalculateTargetPosition(clientX, clientY)); + + if (Diagram.Options.Links.EnableSnapping) + { + var nearPort = FindNearPortToAttachTo(); + if (nearPort != null || OngoingLink!.Target is not PositionAnchor) + { + OngoingLink!.SetTarget(nearPort is null ? _targetPositionAnchor : new SinglePortAnchor(nearPort)); + } + } + + OngoingLink!.Refresh(); + OngoingLink!.RefreshLinks(); + } + + private void OnPointerUp(Model? model, MouseEventArgs e) + { + if (OngoingLink == null) + return; + + if (OngoingLink.IsAttached) // Snapped already + { + OngoingLink.TriggerTargetAttached(); + OngoingLink = null; + return; + } + + if (model is ILinkable linkable && (OngoingLink.Source.Model == null || OngoingLink.Source.Model.CanAttachTo(linkable))) + { + var targetAnchor = Diagram.Options.Links.TargetAnchorFactory(Diagram, OngoingLink, linkable); + OngoingLink.SetTarget(targetAnchor); + OngoingLink.TriggerTargetAttached(); + OngoingLink.Refresh(); + OngoingLink.RefreshLinks(); + } + else if (Diagram.Options.Links.RequireTarget) + { + Diagram.Links.Remove(OngoingLink); + } + else if (!Diagram.Options.Links.RequireTarget) + { + OngoingLink.Refresh(); + } + + OngoingLink = null; + } + + private Point CalculateTargetPosition(double clientX, double clientY) + { + var target = Diagram.GetRelativeMousePoint(clientX, clientY); + + if (OngoingLink == null) + { + return target; + } + + var source = OngoingLink.Source.GetPlainPosition()!; + var dirVector = target.Subtract(source).Normalize(); + var change = dirVector.Multiply(5); + return target.Subtract(change); + } + + private PortModel? FindNearPortToAttachTo() + { + if (OngoingLink is null || _targetPositionAnchor is null) + return null; + + PortModel? nearestSnapPort = null; + var nearestSnapPortDistance = double.PositiveInfinity; + + var position = _targetPositionAnchor!.GetPosition(OngoingLink)!; + + foreach (var port in Diagram.Nodes.SelectMany((NodeModel n) => n.Ports)) + { + var distance = position.DistanceTo(port.Position); + + if (distance <= Diagram.Options.Links.SnappingRadius && (OngoingLink.Source.Model?.CanAttachTo(port) != false)) + { + if (distance < nearestSnapPortDistance) + { + nearestSnapPortDistance = distance; + nearestSnapPort = port; + } + } + } + + return nearestSnapPort; + } + + public override void Dispose() + { + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + Diagram.Wheel -= OnPointerMove; + } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index edc44fd7..fb5ab2ed 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -20,804 +20,400 @@ namespace Blazor.Diagrams.Core; public abstract class Diagram { -<<<<<<< HEAD - private readonly Dictionary _behaviors; - private readonly List _orderedSelectables; - - public event Action? PointerDown; - public event Action? PointerMove; - public event Action? PointerUp; - public event Action? PointerEnter; - public event Action? PointerLeave; - public event Action? KeyDown; - public event Action? Wheel; - public event Action? PointerClick; - public event Action? PointerDoubleClick; - - public event Action? SelectionChanged; - public event Action? PanChanged; - public event Action? ZoomChanged; - public event Action? ContainerChanged; - public event Action? Changed; - - protected Diagram() - { - _behaviors = new Dictionary(); - _orderedSelectables = new List(); - - Nodes = new NodeLayer(this); - Links = new LinkLayer(this); - Groups = new GroupLayer(this); - Controls = new ControlsLayer(); - BehaviorOptions = new DiagramBehaviorOptions(); - - Nodes.Added += OnSelectableAdded; - Links.Added += OnSelectableAdded; - Groups.Added += OnSelectableAdded; - - Nodes.Removed += OnSelectableRemoved; - Links.Removed += OnSelectableRemoved; - Groups.Removed += OnSelectableRemoved; - - RegisterDefaultBehaviors(); - - BehaviorOptions.DiagramDragBehavior ??= GetBehavior(); - BehaviorOptions.DiagramShiftDragBehavior ??= GetBehavior(); - BehaviorOptions.DiagramWheelBehavior ??= GetBehavior(); - } - - public abstract DiagramOptions Options { get; } - public DiagramBehaviorOptions BehaviorOptions { get; } - public NodeLayer Nodes { get; } - public LinkLayer Links { get; } - public GroupLayer Groups { get; } - public ControlsLayer Controls { get; } - public Rectangle? Container { get; private set; } - public Point Pan { get; private set; } = Point.Zero; - public double Zoom { get; private set; } = 1; - public bool SuspendRefresh { get; set; } - public bool SuspendSorting { get; set; } - public IReadOnlyList OrderedSelectables => _orderedSelectables; - - public void Refresh() - { - if (SuspendRefresh) - return; - - Changed?.Invoke(); - } - - public void Batch(Action action) - { - if (SuspendRefresh) - { - // If it's already suspended, just execute the action and leave it suspended - // It's probably handled by an outer batch - action(); - return; - } - - SuspendRefresh = true; - action(); - SuspendRefresh = false; - Refresh(); - } - - #region Selection - - public IEnumerable GetSelectedModels() - { - foreach (var node in Nodes) - { - if (node.Selected) - yield return node; - } - - foreach (var link in Links) - { - if (link.Selected) - yield return link; - - foreach (var vertex in link.Vertices) - { - if (vertex.Selected) - yield return vertex; - } - } - - foreach (var group in Groups) - { - if (group.Selected) - yield return group; - } - } - - public void SelectModel(SelectableModel model, bool unselectOthers) - { - if (model.Selected) - return; - - if (unselectOthers) - UnselectAll(); - - model.Selected = true; - model.Refresh(); - SelectionChanged?.Invoke(model); - } - - public void UnselectModel(SelectableModel model) - { - if (!model.Selected) - return; - - model.Selected = false; - model.Refresh(); - SelectionChanged?.Invoke(model); - } - - public void UnselectAll() - { - foreach (var model in GetSelectedModels()) - { - model.Selected = false; - model.Refresh(); - // Todo: will result in many events, maybe one event for all of them? - SelectionChanged?.Invoke(model); - } - } - - #endregion - - #region Behaviors - void RegisterDefaultBehaviors() - { - RegisterBehavior(new SelectionBehavior(this)); - RegisterBehavior(new DragMovablesBehavior(this)); - RegisterBehavior(new DragNewLinkBehavior(this)); - RegisterBehavior(new PanBehavior(this)); - RegisterBehavior(new ZoomBehavior(this)); - RegisterBehavior(new EventsBehavior(this)); - RegisterBehavior(new KeyboardShortcutsBehavior(this)); - RegisterBehavior(new ControlsBehavior(this)); - RegisterBehavior(new VirtualizationBehavior(this)); - RegisterBehavior(new ScrollBehavior(this)); - RegisterBehavior(new SelectionBoxBehavior(this)); - } - - public void RegisterBehavior(Behavior behavior) + private readonly Dictionary _behaviors; + private readonly List _orderedSelectables; + + public event Action? PointerDown; + public event Action? PointerMove; + public event Action? PointerUp; + public event Action? PointerEnter; + public event Action? PointerLeave; + public event Action? KeyDown; + public event Action? Wheel; + public event Action? PointerClick; + public event Action? PointerDoubleClick; + + public event Action? SelectionChanged; + public event Action? PanChanged; + public event Action? ZoomChanged; + public event Action? ContainerChanged; + public event Action? Changed; + + protected Diagram() { - var type = behavior.GetType(); - if (_behaviors.ContainsKey(type)) - throw new Exception($"Behavior '{type.Name}' already registered"); - - _behaviors.Add(type, behavior); - } - - public T? GetBehavior() where T : Behavior - { - var type = typeof(T); - return (T?)(_behaviors.ContainsKey(type) ? _behaviors[type] : null); - } - - public void UnregisterBehavior() where T : Behavior - { - var type = typeof(T); - if (!_behaviors.ContainsKey(type)) - return; - - _behaviors[type].Dispose(); - _behaviors.Remove(type); - } - - #endregion - - public void ZoomToFit(double margin = 10) - { - if (Container == null || Nodes.Count == 0) - return; - - var selectedNodes = Nodes.Where(s => s.Selected); - var nodesToUse = selectedNodes.Any() ? selectedNodes : Nodes; - var bounds = nodesToUse.GetBounds(); - var width = bounds.Width + 2 * margin; - var height = bounds.Height + 2 * margin; - var minX = bounds.Left - margin; - var minY = bounds.Top - margin; - - SuspendRefresh = true; - - var xf = Container.Width / width; - var yf = Container.Height / height; - SetZoom(Math.Min(xf, yf)); - - var nx = Container.Left + Pan.X + minX * Zoom; - var ny = Container.Top + Pan.Y + minY * Zoom; - UpdatePan(Container.Left - nx, Container.Top - ny); - - SuspendRefresh = false; - Refresh(); - } - - public void SetPan(double x, double y) - { - Pan = new Point(x, y); - PanChanged?.Invoke(); - Refresh(); - } - - public void UpdatePan(double deltaX, double deltaY) - { - Pan = Pan.Add(deltaX, deltaY); - PanChanged?.Invoke(); - Refresh(); - } - - public void SetZoom(double newZoom) - { - if (newZoom <= 0) - throw new ArgumentException($"{nameof(newZoom)} cannot be equal or lower than 0"); - - if (newZoom < Options.Zoom.Minimum) - newZoom = Options.Zoom.Minimum; - - Zoom = newZoom; - ZoomChanged?.Invoke(); - Refresh(); - } - - public void SetContainer(Rectangle? newRect) - { - if (Equals(newRect, Container)) - return; - - Container = newRect; - ContainerChanged?.Invoke(); - Refresh(); - } - - public Point GetRelativeMousePoint(double clientX, double clientY) - { - if (Container == null) - throw new Exception( - "Container not available. Make sure you're not using this method before the diagram is fully loaded"); - - return new Point((clientX - Container.Left - Pan.X) / Zoom, (clientY - Container.Top - Pan.Y) / Zoom); - } - - public Point GetRelativePoint(double clientX, double clientY) - { - if (Container == null) - throw new Exception( - "Container not available. Make sure you're not using this method before the diagram is fully loaded"); - - return new Point(clientX - Container.Left, clientY - Container.Top); - } - - public Point GetScreenPoint(double clientX, double clientY) - { - if (Container == null) - throw new Exception( - "Container not available. Make sure you're not using this method before the diagram is fully loaded"); - - return new Point(Zoom * clientX + Container.Left + Pan.X, Zoom * clientY + Container.Top + Pan.Y); - } - - #region Ordering - - public void SendToBack(SelectableModel model) - { - var minOrder = GetMinOrder(); - if (model.Order == minOrder) - return; - - if (!_orderedSelectables.Remove(model)) - return; - - _orderedSelectables.Insert(0, model); - - // Todo: can optimize this by only updating the order of items before model - Batch(() => - { - SuspendSorting = true; - for (var i = 0; i < _orderedSelectables.Count; i++) - { - _orderedSelectables[i].Order = i + 1; - } - SuspendSorting = false; - }); - } - - public void SendToFront(SelectableModel model) - { - var maxOrder = GetMaxOrder(); - if (model.Order == maxOrder) - return; - - if (!_orderedSelectables.Remove(model)) - return; - - _orderedSelectables.Add(model); - - SuspendSorting = true; - model.Order = maxOrder + 1; - SuspendSorting = false; - Refresh(); - } - - public int GetMinOrder() - { - return _orderedSelectables.Count > 0 ? _orderedSelectables[0].Order : 0; - } - - public int GetMaxOrder() - { - return _orderedSelectables.Count > 0 ? _orderedSelectables[^1].Order : 0; - } - - /// - /// Sorts the list of selectables based on their order - /// - public void RefreshOrders(bool refresh = true) - { - _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); - - if (refresh) - { - Refresh(); - } - } - - private void OnSelectableAdded(SelectableModel model) - { - var maxOrder = GetMaxOrder(); - _orderedSelectables.Add(model); - - if (model.Order == 0) - { - model.Order = maxOrder + 1; - } - - model.OrderChanged += OnModelOrderChanged; - } + _behaviors = new Dictionary(); + _orderedSelectables = new List(); - private void OnSelectableRemoved(SelectableModel model) - { - model.OrderChanged -= OnModelOrderChanged; - _orderedSelectables.Remove(model); - } + Nodes = new NodeLayer(this); + Links = new LinkLayer(this); + Groups = new GroupLayer(this); + Controls = new ControlsLayer(); + BehaviorOptions = new DiagramBehaviorOptions(); - private void OnModelOrderChanged(Model model) - { - if (SuspendSorting) - return; + Nodes.Added += OnSelectableAdded; + Links.Added += OnSelectableAdded; + Groups.Added += OnSelectableAdded; - RefreshOrders(); - } + Nodes.Removed += OnSelectableRemoved; + Links.Removed += OnSelectableRemoved; + Groups.Removed += OnSelectableRemoved; - #endregion + RegisterDefaultBehaviors(); - #region Events - - public void TriggerPointerDown(Model? model, PointerEventArgs e) => PointerDown?.Invoke(model, e); - - public void TriggerPointerMove(Model? model, PointerEventArgs e) => PointerMove?.Invoke(model, e); - - public void TriggerPointerUp(Model? model, PointerEventArgs e) => PointerUp?.Invoke(model, e); - - public void TriggerPointerEnter(Model? model, PointerEventArgs e) => PointerEnter?.Invoke(model, e); - - public void TriggerPointerLeave(Model? model, PointerEventArgs e) => PointerLeave?.Invoke(model, e); - - public void TriggerKeyDown(KeyboardEventArgs e) => KeyDown?.Invoke(e); + BehaviorOptions.DiagramDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramShiftDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramWheelBehavior ??= GetBehavior(); + } - public void TriggerWheel(WheelEventArgs e) => Wheel?.Invoke(e); + public abstract DiagramOptions Options { get; } + public DiagramBehaviorOptions BehaviorOptions { get; } + public NodeLayer Nodes { get; } + public LinkLayer Links { get; } + public GroupLayer Groups { get; } + public ControlsLayer Controls { get; } + public Rectangle? Container { get; private set; } + public Point Pan { get; private set; } = Point.Zero; + public double Zoom { get; private set; } = 1; + public bool SuspendRefresh { get; set; } + public bool SuspendSorting { get; set; } + public IReadOnlyList OrderedSelectables => _orderedSelectables; + + public void Refresh() + { + if (SuspendRefresh) + return; - public void TriggerPointerClick(Model? model, PointerEventArgs e) => PointerClick?.Invoke(model, e); + Changed?.Invoke(); + } - public void TriggerPointerDoubleClick(Model? model, PointerEventArgs e) => PointerDoubleClick?.Invoke(model, e); - - #endregion -======= - public abstract class Diagram + public void Batch(Action action) { - private readonly Dictionary _behaviors; - private readonly List _orderedSelectables; - - public event Action? PointerDown; - public event Action? PointerMove; - public event Action? PointerUp; - public event Action? PointerEnter; - public event Action? PointerLeave; - public event Action? KeyDown; - public event Action? Wheel; - public event Action? PointerClick; - public event Action? PointerDoubleClick; - - public event Action? SelectionChanged; - public event Action? PanChanged; - public event Action? ZoomChanged; - public event Action? ContainerChanged; - public event Action? Changed; - - protected Diagram() + if (SuspendRefresh) { - _behaviors = new Dictionary(); - _orderedSelectables = new List(); - - Nodes = new NodeLayer(this); - Links = new LinkLayer(this); - Groups = new GroupLayer(this); - Controls = new ControlsLayer(); - BehaviorOptions = new DiagramBehaviorOptions(); - - Nodes.Added += OnSelectableAdded; - Links.Added += OnSelectableAdded; - Groups.Added += OnSelectableAdded; - - Nodes.Removed += OnSelectableRemoved; - Links.Removed += OnSelectableRemoved; - Groups.Removed += OnSelectableRemoved; + // If it's already suspended, just execute the action and leave it suspended + // It's probably handled by an outer batch + action(); + return; + } - RegisterDefaultBehaviors(); + SuspendRefresh = true; + action(); + SuspendRefresh = false; + Refresh(); + } - BehaviorOptions.DiagramDragBehavior ??= GetBehavior(); - BehaviorOptions.DiagramShiftDragBehavior ??= GetBehavior(); - BehaviorOptions.DiagramWheelBehavior ??= GetBehavior(); - } + #region Selection - public abstract DiagramOptions Options { get; } - public DiagramBehaviorOptions BehaviorOptions { get; } - public NodeLayer Nodes { get; } - public LinkLayer Links { get; } - public GroupLayer Groups { get; } - public ControlsLayer Controls { get; } - public Rectangle? Container { get; private set; } - public Point Pan { get; private set; } = Point.Zero; - public double Zoom { get; private set; } = 1; - public bool SuspendRefresh { get; set; } - public bool SuspendSorting { get; set; } - public IReadOnlyList OrderedSelectables => _orderedSelectables; - - public void Refresh() + public IEnumerable GetSelectedModels() + { + foreach (var node in Nodes) { - if (SuspendRefresh) - return; - - Changed?.Invoke(); + if (node.Selected) + yield return node; } - public void Batch(Action action) + foreach (var link in Links) { - if (SuspendRefresh) + if (link.Selected) + yield return link; + + foreach (var vertex in link.Vertices) { - // If it's already suspended, just execute the action and leave it suspended - // It's probably handled by an outer batch - action(); - return; + if (vertex.Selected) + yield return vertex; } - - SuspendRefresh = true; - action(); - SuspendRefresh = false; - Refresh(); } - #region Selection - - public IEnumerable GetSelectedModels() + foreach (var group in Groups) { - foreach (var node in Nodes) - { - if (node.Selected) - yield return node; - } + if (group.Selected) + yield return group; + } + } - foreach (var link in Links) - { - if (link.Selected) - yield return link; - - foreach (var vertex in link.Vertices) - { - if (vertex.Selected) - yield return vertex; - } - } + public void SelectModel(SelectableModel model, bool unselectOthers) + { + if (model.Selected) + return; - foreach (var group in Groups) - { - if (group.Selected) - yield return group; - } - } + if (unselectOthers) + UnselectAll(); - public void SelectModel(SelectableModel model, bool unselectOthers) - { - if (model.Selected) - return; + model.Selected = true; + model.Refresh(); + SelectionChanged?.Invoke(model); + } - if (unselectOthers) - UnselectAll(); + public void UnselectModel(SelectableModel model) + { + if (!model.Selected) + return; - model.Selected = true; - model.Refresh(); - SelectionChanged?.Invoke(model); - } + model.Selected = false; + model.Refresh(); + SelectionChanged?.Invoke(model); + } - public void UnselectModel(SelectableModel model) + public void UnselectAll() + { + foreach (var model in GetSelectedModels()) { - if (!model.Selected) - return; - model.Selected = false; model.Refresh(); + // Todo: will result in many events, maybe one event for all of them? SelectionChanged?.Invoke(model); } + } - public void UnselectAll() - { - foreach (var model in GetSelectedModels()) - { - model.Selected = false; - model.Refresh(); - // Todo: will result in many events, maybe one event for all of them? - SelectionChanged?.Invoke(model); - } - } + #endregion - #endregion + #region Behaviors + void RegisterDefaultBehaviors() + { + RegisterBehavior(new SelectionBehavior(this)); + RegisterBehavior(new DragMovablesBehavior(this)); + RegisterBehavior(new DragNewLinkBehavior(this)); + RegisterBehavior(new PanBehavior(this)); + RegisterBehavior(new ZoomBehavior(this)); + RegisterBehavior(new EventsBehavior(this)); + RegisterBehavior(new KeyboardShortcutsBehavior(this)); + RegisterBehavior(new ControlsBehavior(this)); + RegisterBehavior(new VirtualizationBehavior(this)); + RegisterBehavior(new ScrollBehavior(this)); + RegisterBehavior(new SelectionBoxBehavior(this)); + } - #region Behaviors + public void RegisterBehavior(Behavior behavior) + { + var type = behavior.GetType(); + if (_behaviors.ContainsKey(type)) + throw new Exception($"Behavior '{type.Name}' already registered"); - void RegisterDefaultBehaviors() - { - RegisterBehavior(new SelectionBehavior(this)); - RegisterBehavior(new DragMovablesBehavior(this)); - RegisterBehavior(new DragNewLinkBehavior(this)); - RegisterBehavior(new PanBehavior(this)); - RegisterBehavior(new ZoomBehavior(this)); - RegisterBehavior(new EventsBehavior(this)); - RegisterBehavior(new KeyboardShortcutsBehavior(this)); - RegisterBehavior(new ControlsBehavior(this)); - RegisterBehavior(new VirtualizationBehavior(this)); - RegisterBehavior(new ScrollBehavior(this)); - RegisterBehavior(new SelectionBoxBehavior(this)); - RegisterBehavior(new ResizeBehavior(this)); - } + _behaviors.Add(type, behavior); + } - public void RegisterBehavior(Behavior behavior) - { - var type = behavior.GetType(); - if (_behaviors.ContainsKey(type)) - throw new Exception($"Behavior '{type.Name}' already registered"); + public T? GetBehavior() where T : Behavior + { + var type = typeof(T); + return (T?)(_behaviors.ContainsKey(type) ? _behaviors[type] : null); + } - _behaviors.Add(type, behavior); - } + public void UnregisterBehavior() where T : Behavior + { + var type = typeof(T); + if (!_behaviors.ContainsKey(type)) + return; - public T? GetBehavior() where T : Behavior - { - var type = typeof(T); - return (T?)(_behaviors.ContainsKey(type) ? _behaviors[type] : null); - } + _behaviors[type].Dispose(); + _behaviors.Remove(type); + } - public void UnregisterBehavior() where T : Behavior - { - var type = typeof(T); - if (!_behaviors.ContainsKey(type)) - return; + #endregion - _behaviors[type].Dispose(); - _behaviors.Remove(type); - } + public void ZoomToFit(double margin = 10) + { + if (Container == null || Nodes.Count == 0) + return; - #endregion + var selectedNodes = Nodes.Where(s => s.Selected); + var nodesToUse = selectedNodes.Any() ? selectedNodes : Nodes; + var bounds = nodesToUse.GetBounds(); + var width = bounds.Width + 2 * margin; + var height = bounds.Height + 2 * margin; + var minX = bounds.Left - margin; + var minY = bounds.Top - margin; - public void ZoomToFit(double margin = 10) - { - if (Container == null || Nodes.Count == 0) - return; + SuspendRefresh = true; - var selectedNodes = Nodes.Where(s => s.Selected); - var nodesToUse = selectedNodes.Any() ? selectedNodes : Nodes; - var bounds = nodesToUse.GetBounds(); - var width = bounds.Width + 2 * margin; - var height = bounds.Height + 2 * margin; - var minX = bounds.Left - margin; - var minY = bounds.Top - margin; + var xf = Container.Width / width; + var yf = Container.Height / height; + SetZoom(Math.Min(xf, yf)); - SuspendRefresh = true; + var nx = Container.Left + Pan.X + minX * Zoom; + var ny = Container.Top + Pan.Y + minY * Zoom; + UpdatePan(Container.Left - nx, Container.Top - ny); - var xf = Container.Width / width; - var yf = Container.Height / height; - SetZoom(Math.Min(xf, yf)); + SuspendRefresh = false; + Refresh(); + } - var nx = Container.Left + Pan.X + minX * Zoom; - var ny = Container.Top + Pan.Y + minY * Zoom; - UpdatePan(Container.Left - nx, Container.Top - ny); + public void SetPan(double x, double y) + { + Pan = new Point(x, y); + PanChanged?.Invoke(); + Refresh(); + } - SuspendRefresh = false; - Refresh(); - } + public void UpdatePan(double deltaX, double deltaY) + { + Pan = Pan.Add(deltaX, deltaY); + PanChanged?.Invoke(); + Refresh(); + } - public void SetPan(double x, double y) - { - Pan = new Point(x, y); - PanChanged?.Invoke(); - Refresh(); - } + public void SetZoom(double newZoom) + { + if (newZoom <= 0) + throw new ArgumentException($"{nameof(newZoom)} cannot be equal or lower than 0"); - public void UpdatePan(double deltaX, double deltaY) - { - Pan = Pan.Add(deltaX, deltaY); - PanChanged?.Invoke(); - Refresh(); - } + if (newZoom < Options.Zoom.Minimum) + newZoom = Options.Zoom.Minimum; - public void SetZoom(double newZoom) - { - if (newZoom <= 0) - throw new ArgumentException($"{nameof(newZoom)} cannot be equal or lower than 0"); + Zoom = newZoom; + ZoomChanged?.Invoke(); + Refresh(); + } - if (newZoom < Options.Zoom.Minimum) - newZoom = Options.Zoom.Minimum; + public void SetContainer(Rectangle? newRect) + { + if (Equals(newRect, Container)) + return; - Zoom = newZoom; - ZoomChanged?.Invoke(); - Refresh(); - } + Container = newRect; + ContainerChanged?.Invoke(); + Refresh(); + } - public void SetContainer(Rectangle? newRect) - { - if (Equals(newRect, Container)) - return; + public Point GetRelativeMousePoint(double clientX, double clientY) + { + if (Container == null) + throw new Exception( + "Container not available. Make sure you're not using this method before the diagram is fully loaded"); - Container = newRect; - ContainerChanged?.Invoke(); - Refresh(); - } + return new Point((clientX - Container.Left - Pan.X) / Zoom, (clientY - Container.Top - Pan.Y) / Zoom); + } - public Point GetRelativeMousePoint(double clientX, double clientY) - { - if (Container == null) - throw new Exception( - "Container not available. Make sure you're not using this method before the diagram is fully loaded"); + public Point GetRelativePoint(double clientX, double clientY) + { + if (Container == null) + throw new Exception( + "Container not available. Make sure you're not using this method before the diagram is fully loaded"); - return new Point((clientX - Container.Left - Pan.X) / Zoom, (clientY - Container.Top - Pan.Y) / Zoom); - } + return new Point(clientX - Container.Left, clientY - Container.Top); + } - public Point GetRelativePoint(double clientX, double clientY) - { - if (Container == null) - throw new Exception( - "Container not available. Make sure you're not using this method before the diagram is fully loaded"); + public Point GetScreenPoint(double clientX, double clientY) + { + if (Container == null) + throw new Exception( + "Container not available. Make sure you're not using this method before the diagram is fully loaded"); - return new Point(clientX - Container.Left, clientY - Container.Top); - } + return new Point(Zoom * clientX + Container.Left + Pan.X, Zoom * clientY + Container.Top + Pan.Y); + } - public Point GetScreenPoint(double clientX, double clientY) - { - if (Container == null) - throw new Exception( - "Container not available. Make sure you're not using this method before the diagram is fully loaded"); + #region Ordering - return new Point(Zoom * clientX + Container.Left + Pan.X, Zoom * clientY + Container.Top + Pan.Y); - } + public void SendToBack(SelectableModel model) + { + var minOrder = GetMinOrder(); + if (model.Order == minOrder) + return; - #region Ordering + if (!_orderedSelectables.Remove(model)) + return; - public void SendToBack(SelectableModel model) + _orderedSelectables.Insert(0, model); + + // Todo: can optimize this by only updating the order of items before model + Batch(() => { - var minOrder = GetMinOrder(); - if (model.Order == minOrder) - return; + SuspendSorting = true; + for (var i = 0; i < _orderedSelectables.Count; i++) + { + _orderedSelectables[i].Order = i + 1; + } + SuspendSorting = false; + }); + } - if (!_orderedSelectables.Remove(model)) - return; + public void SendToFront(SelectableModel model) + { + var maxOrder = GetMaxOrder(); + if (model.Order == maxOrder) + return; - _orderedSelectables.Insert(0, model); + if (!_orderedSelectables.Remove(model)) + return; - // Todo: can optimize this by only updating the order of items before model - Batch(() => - { - SuspendSorting = true; - for (var i = 0; i < _orderedSelectables.Count; i++) - { - _orderedSelectables[i].Order = i + 1; - } - SuspendSorting = false; - }); - } + _orderedSelectables.Add(model); - public void SendToFront(SelectableModel model) - { - var maxOrder = GetMaxOrder(); - if (model.Order == maxOrder) - return; + SuspendSorting = true; + model.Order = maxOrder + 1; + SuspendSorting = false; + Refresh(); + } - if (!_orderedSelectables.Remove(model)) - return; + public int GetMinOrder() + { + return _orderedSelectables.Count > 0 ? _orderedSelectables[0].Order : 0; + } - _orderedSelectables.Add(model); + public int GetMaxOrder() + { + return _orderedSelectables.Count > 0 ? _orderedSelectables[^1].Order : 0; + } - SuspendSorting = true; - model.Order = maxOrder + 1; - SuspendSorting = false; - Refresh(); - } + /// + /// Sorts the list of selectables based on their order + /// + public void RefreshOrders(bool refresh = true) + { + _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); - public int GetMinOrder() + if (refresh) { - return _orderedSelectables.Count > 0 ? _orderedSelectables[0].Order : 0; + Refresh(); } + } - public int GetMaxOrder() - { - return _orderedSelectables.Count > 0 ? _orderedSelectables[^1].Order : 0; - } + private void OnSelectableAdded(SelectableModel model) + { + var maxOrder = GetMaxOrder(); + _orderedSelectables.Add(model); - /// - /// Sorts the list of selectables based on their order - /// - public void RefreshOrders(bool refresh = true) + if (model.Order == 0) { - _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); - - if (refresh) - { - Refresh(); - } + model.Order = maxOrder + 1; } - private void OnSelectableAdded(SelectableModel model) - { - var maxOrder = GetMaxOrder(); - _orderedSelectables.Add(model); - - if (model.Order == 0) - { - model.Order = maxOrder + 1; - } - - model.OrderChanged += OnModelOrderChanged; - } + model.OrderChanged += OnModelOrderChanged; + } - private void OnSelectableRemoved(SelectableModel model) - { - model.OrderChanged -= OnModelOrderChanged; - _orderedSelectables.Remove(model); - } + private void OnSelectableRemoved(SelectableModel model) + { + model.OrderChanged -= OnModelOrderChanged; + _orderedSelectables.Remove(model); + } - private void OnModelOrderChanged(Model model) - { - if (SuspendSorting) - return; + private void OnModelOrderChanged(Model model) + { + if (SuspendSorting) + return; - RefreshOrders(); - } + RefreshOrders(); + } - #endregion + #endregion - #region Events + #region Events - public void TriggerPointerDown(Model? model, PointerEventArgs e) => PointerDown?.Invoke(model, e); + public void TriggerPointerDown(Model? model, PointerEventArgs e) => PointerDown?.Invoke(model, e); - public void TriggerPointerMove(Model? model, PointerEventArgs e) => PointerMove?.Invoke(model, e); + public void TriggerPointerMove(Model? model, PointerEventArgs e) => PointerMove?.Invoke(model, e); - public void TriggerPointerUp(Model? model, PointerEventArgs e) => PointerUp?.Invoke(model, e); + public void TriggerPointerUp(Model? model, PointerEventArgs e) => PointerUp?.Invoke(model, e); - public void TriggerPointerEnter(Model? model, PointerEventArgs e) => PointerEnter?.Invoke(model, e); + public void TriggerPointerEnter(Model? model, PointerEventArgs e) => PointerEnter?.Invoke(model, e); - public void TriggerPointerLeave(Model? model, PointerEventArgs e) => PointerLeave?.Invoke(model, e); + public void TriggerPointerLeave(Model? model, PointerEventArgs e) => PointerLeave?.Invoke(model, e); - public void TriggerKeyDown(KeyboardEventArgs e) => KeyDown?.Invoke(e); + public void TriggerKeyDown(KeyboardEventArgs e) => KeyDown?.Invoke(e); - public void TriggerWheel(WheelEventArgs e) => Wheel?.Invoke(e); + public void TriggerWheel(WheelEventArgs e) => Wheel?.Invoke(e); - public void TriggerPointerClick(Model? model, PointerEventArgs e) => PointerClick?.Invoke(model, e); + public void TriggerPointerClick(Model? model, PointerEventArgs e) => PointerClick?.Invoke(model, e); - public void TriggerPointerDoubleClick(Model? model, PointerEventArgs e) => PointerDoubleClick?.Invoke(model, e); + public void TriggerPointerDoubleClick(Model? model, PointerEventArgs e) => PointerDoubleClick?.Invoke(model, e); - #endregion - } ->>>>>>> 7bf2db0 (First commit) + #endregion } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index 18f466ee..73fa046e 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -10,435 +10,467 @@ namespace Blazor.Diagrams.Core.Tests.Behaviors; public class DragNewLinkBehaviorTests { - [Fact] - public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnPort() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(100, 50)); - var port = node.AddPort(new PortModel(node) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - var link = diagram.Links.Single(); - var source = link.Source as SinglePortAnchor; - source.Should().NotBeNull(); - source!.Port.Should().BeSameAs(port); - var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().Be(100); - ongoingPosition.Y.Should().Be(100); - } - - [Fact] - public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var factoryCalled = false; - diagram.Options.Links.Factory = (d, s, ta) => - { - factoryCalled = true; - return new LinkModel(new SinglePortAnchor((s as PortModel)!), ta); - }; - var node = new NodeModel(position: new Point(100, 50)); - var port = node.AddPort(new PortModel(node) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - factoryCalled.Should().BeTrue(); - var link = diagram.Links.Single(); - var source = link.Source as SinglePortAnchor; - source.Should().NotBeNull(); - source!.Port.Should().BeSameAs(port); - var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().Be(100); - ongoingPosition.Y.Should().Be(100); - } - - [Fact] - public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(100, 50)); - var linkRefreshed = false; - var port = node.AddPort(new PortModel(node) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - var link = diagram.Links.Single(); - link.Changed += _ => linkRefreshed = true; - diagram.TriggerPointerMove(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - var source = link.Source as SinglePortAnchor; - var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().BeGreaterThan(145); - ongoingPosition.Y.Should().BeGreaterThan(145); - linkRefreshed.Should().BeTrue(); - } - - [Fact] - public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoomIsChanged() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - diagram.SetZoom(1.5); - var node = new NodeModel(position: new Point(100, 50)); - var linkRefreshed = false; - var port = node.AddPort(new PortModel(node) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - var link = diagram.Links.Single(); - link.Changed += _ => linkRefreshed = true; - diagram.TriggerPointerMove(null, - new PointerEventArgs(160, 160, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - var source = link.Source as SinglePortAnchor; - var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().BeApproximately(107.7, 0.1); - ongoingPosition.Y.Should().BeApproximately(101.7, 0.1); - linkRefreshed.Should().BeTrue(); - } - - [Fact] - public void Behavior_ShouldSnapToClosestPortAndRefreshPort_WhenSnappingIsEnabledAndPortIsInRadius() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - diagram.Options.Links.EnableSnapping = true; - diagram.Options.Links.SnappingRadius = 60; - var node1 = new NodeModel(position: new Point(100, 50)); - var node2 = new NodeModel(position: new Point(160, 50)); - diagram.Nodes.Add(new[] { node1, node2 }); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - var port2 = node2.AddPort(new PortModel(node2) - { - Initialized = true, - Position = new Point(170, 60), - Size = new Size(10, 20) - }); - var port2Refreshed = false; - port2.Changed += _ => port2Refreshed = true; - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - var link = diagram.Links.Single(); - var target = link.Target as SinglePortAnchor; - target.Should().NotBeNull(); - target!.Port.Should().BeSameAs(port2); - port2Refreshed.Should().BeTrue(); - } - - [Fact] - public void Behavior_ShouldNotSnapToPort_WhenSnappingIsEnabledAndPortIsNotInRadius() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - diagram.Options.Links.EnableSnapping = true; - diagram.Options.Links.SnappingRadius = 50; - var node1 = new NodeModel(position: new Point(100, 50)); - var node2 = new NodeModel(position: new Point(160, 50)); - diagram.Nodes.Add(new[] { node1, node2 }); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - var port2 = node2.AddPort(new PortModel(node2) - { - Initialized = true, - Position = new Point(170, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - var link = diagram.Links.Single(); - link.Target.Should().BeOfType(); - } - - [Fact] - public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNotInRadiusAnymore() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - diagram.Options.Links.EnableSnapping = true; - diagram.Options.Links.SnappingRadius = 56; - var node1 = new NodeModel(position: new Point(100, 50)); - var node2 = new NodeModel(position: new Point(160, 50)); - diagram.Nodes.Add(new[] { node1, node2 }); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - var port2 = node2.AddPort(new PortModel(node2) - { - Initialized = true, - Position = new Point(170, 60), - Size = new Size(10, 20) - }); - var port2Refreshes = 0; - port2.Changed += _ => port2Refreshes++; - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, - true)); // Move towards the other port - diagram.TriggerPointerMove(null, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, - true)); // Move back to unsnap - - // Assert - var link = diagram.Links.Single(); - var target = link.Target as SinglePortAnchor; - target.Should().BeNull(); - port2Refreshes.Should().Be(2); - } - - [Fact] - public void Behavior_ShouldRemoveLink_WhenMouseUpOnCanvasAndRequireTargetIsTrue() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(100, 50)); - var port = node.AddPort(new PortModel(node) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port, - new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerUp(null, - new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - diagram.Links.Should().BeEmpty(); - } - - [Fact] - public void Behavior_ShouldRemoveLink_WhenMouseUpOnSamePort() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(100, 50)); - var port = node.AddPort(new PortModel(node) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port, - new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerUp(port, - new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - diagram.Links.Should().BeEmpty(); - } - - [Fact] - public void Behavior_ShouldSetTarget_WhenMouseUp() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node1 = new NodeModel(position: new Point(100, 50)); - var node2 = new NodeModel(position: new Point(160, 50)); - diagram.Nodes.Add(new[] { node1, node2 }); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - var port2 = node2.AddPort(new PortModel(node2) - { - Initialized = true, - Position = new Point(170, 60), - Size = new Size(10, 20) - }); - var port2Refreshes = 0; - port2.Changed += _ => port2Refreshes++; - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerUp(port2, - new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - var link = diagram.Links.Single(); - var target = link.Target as SinglePortAnchor; - target.Should().NotBeNull(); - target!.Port.Should().BeSameAs(port2); - port2Refreshes.Should().Be(1); - } - - [Fact] - public void Behavior_ShouldNotCreateOngoingLink_WhenFactoryReturnsNull() - { - // Arrange - var diagram = new TestDiagram(); - diagram.Options.Links.Factory = (d, s, ta) => null; - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - - var node1 = new NodeModel(position: new Point(100, 50)); - diagram.Nodes.Add(node1); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - diagram.Links.Should().HaveCount(0); - } - - [Fact] - public void Behavior_ShouldTriggerLinkTargetAttached_WhenMouseUpOnOtherPort() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node1 = new NodeModel(position: new Point(100, 50)); - var node2 = new NodeModel(position: new Point(160, 50)); - diagram.Nodes.Add(new[] { node1, node2 }); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - var port2 = node2.AddPort(new PortModel(node2) - { - Initialized = true, - Position = new Point(170, 60), - Size = new Size(10, 20) - }); - var targetAttachedTriggers = 0; - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - diagram.Links.Single().TargetAttached += _ => targetAttachedTriggers++; - - diagram.TriggerPointerUp(port2, - new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - targetAttachedTriggers.Should().Be(1); - } - - [Fact] - public void Behavior_ShouldTriggerLinkTargetAttached_WhenLinkSnappedToPortAndMouseUp() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - diagram.Options.Links.EnableSnapping = true; - diagram.Options.Links.SnappingRadius = 60; - var node1 = new NodeModel(position: new Point(100, 50)); - var node2 = new NodeModel(position: new Point(160, 50)); - diagram.Nodes.Add(new[] { node1, node2 }); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - var port2 = node2.AddPort(new PortModel(node2) - { - Initialized = true, - Position = new Point(170, 60), - Size = new Size(10, 20) - }); - var targetAttachedTriggers = 0; - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - diagram.TriggerPointerMove(null, - new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - diagram.Links.Single().TargetAttached += _ => targetAttachedTriggers++; - - diagram.TriggerPointerUp(null, - new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - targetAttachedTriggers.Should().Be(1); - } + [Fact] + public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnPort() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(100, 50)); + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + var link = diagram.Links.Single(); + var source = link.Source as SinglePortAnchor; + source.Should().NotBeNull(); + source!.Port.Should().BeSameAs(port); + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().Be(100); + ongoingPosition.Y.Should().Be(100); + } + + [Fact] + public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var factoryCalled = false; + diagram.Options.Links.Factory = (d, s, ta) => + { + factoryCalled = true; + return new LinkModel(new SinglePortAnchor((s as PortModel)!), ta); + }; + var node = new NodeModel(position: new Point(100, 50)); + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + factoryCalled.Should().BeTrue(); + var link = diagram.Links.Single(); + var source = link.Source as SinglePortAnchor; + source.Should().NotBeNull(); + source!.Port.Should().BeSameAs(port); + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().Be(100); + ongoingPosition.Y.Should().Be(100); + } + + [Fact] + public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(100, 50)); + var linkRefreshed = false; + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + var link = diagram.Links.Single(); + link.Changed += _ => linkRefreshed = true; + diagram.TriggerPointerMove(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + var source = link.Source as SinglePortAnchor; + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().BeGreaterThan(145); + ongoingPosition.Y.Should().BeGreaterThan(145); + linkRefreshed.Should().BeTrue(); + } + + [Fact] + public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoomIsChanged() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + diagram.SetZoom(1.5); + var node = new NodeModel(position: new Point(100, 50)); + var linkRefreshed = false; + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + var link = diagram.Links.Single(); + link.Changed += _ => linkRefreshed = true; + diagram.TriggerPointerMove(null, + new PointerEventArgs(160, 160, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + var source = link.Source as SinglePortAnchor; + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().BeApproximately(107.7, 0.1); + ongoingPosition.Y.Should().BeApproximately(101.7, 0.1); + linkRefreshed.Should().BeTrue(); + } + + [Fact] + public void Behavior_ShouldSnapToClosestPortAndRefreshPort_WhenSnappingIsEnabledAndPortIsInRadius() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + diagram.Options.Links.EnableSnapping = true; + diagram.Options.Links.SnappingRadius = 60; + var node1 = new NodeModel(position: new Point(100, 50)); + var node2 = new NodeModel(position: new Point(160, 50)); + diagram.Nodes.Add(new[] { node1, node2 }); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) + { + Initialized = true, + Position = new Point(170, 60), + Size = new Size(10, 20) + }); + var port2Refreshed = false; + port2.Changed += _ => port2Refreshed = true; + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + var link = diagram.Links.Single(); + var target = link.Target as SinglePortAnchor; + target.Should().NotBeNull(); + target!.Port.Should().BeSameAs(port2); + port2Refreshed.Should().BeTrue(); + } + + [Fact] + public void Behavior_ShouldNotSnapToPort_WhenSnappingIsEnabledAndPortIsNotInRadius() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + diagram.Options.Links.EnableSnapping = true; + diagram.Options.Links.SnappingRadius = 50; + var node1 = new NodeModel(position: new Point(100, 50)); + var node2 = new NodeModel(position: new Point(160, 50)); + diagram.Nodes.Add(new[] { node1, node2 }); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) + { + Initialized = true, + Position = new Point(170, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + var link = diagram.Links.Single(); + link.Target.Should().BeOfType(); + } + + [Fact] + public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNotInRadiusAnymore() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + diagram.Options.Links.EnableSnapping = true; + diagram.Options.Links.SnappingRadius = 56; + var node1 = new NodeModel(position: new Point(100, 50)); + var node2 = new NodeModel(position: new Point(160, 50)); + diagram.Nodes.Add(new[] { node1, node2 }); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) + { + Initialized = true, + Position = new Point(170, 60), + Size = new Size(10, 20) + }); + var port2Refreshes = 0; + port2.Changed += _ => port2Refreshes++; + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, + true)); // Move towards the other port + diagram.TriggerPointerMove(null, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, + true)); // Move back to unsnap + + // Assert + var link = diagram.Links.Single(); + var target = link.Target as SinglePortAnchor; + target.Should().BeNull(); + port2Refreshes.Should().Be(2); + } + + [Fact] + public void Behavior_ShouldRemoveLink_WhenMouseUpOnCanvasAndRequireTargetIsTrue() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(100, 50)); + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerUp(null, + new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + diagram.Links.Should().BeEmpty(); + } + + [Fact] + public void Behavior_ShouldRemoveLink_WhenMouseUpOnSamePort() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(100, 50)); + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerUp(port, + new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + diagram.Links.Should().BeEmpty(); + } + + [Fact] + public void Behavior_ShouldSetTarget_WhenMouseUp() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node1 = new NodeModel(position: new Point(100, 50)); + var node2 = new NodeModel(position: new Point(160, 50)); + diagram.Nodes.Add(new[] { node1, node2 }); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) + { + Initialized = true, + Position = new Point(170, 60), + Size = new Size(10, 20) + }); + var port2Refreshes = 0; + port2.Changed += _ => port2Refreshes++; + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerUp(port2, + new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + var link = diagram.Links.Single(); + var target = link.Target as SinglePortAnchor; + target.Should().NotBeNull(); + target!.Port.Should().BeSameAs(port2); + port2Refreshes.Should().Be(1); + } + + [Fact] + public void Behavior_ShouldNotCreateOngoingLink_WhenFactoryReturnsNull() + { + // Arrange + var diagram = new TestDiagram(); + diagram.Options.Links.Factory = (d, s, ta) => null; + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + + var node1 = new NodeModel(position: new Point(100, 50)); + diagram.Nodes.Add(node1); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + diagram.Links.Should().HaveCount(0); + } + + [Fact] + public void Behavior_ShouldTriggerLinkTargetAttached_WhenMouseUpOnOtherPort() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node1 = new NodeModel(position: new Point(100, 50)); + var node2 = new NodeModel(position: new Point(160, 50)); + diagram.Nodes.Add(new[] { node1, node2 }); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) + { + Initialized = true, + Position = new Point(170, 60), + Size = new Size(10, 20) + }); + var targetAttachedTriggers = 0; + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + diagram.Links.Single().TargetAttached += _ => targetAttachedTriggers++; + + diagram.TriggerPointerUp(port2, + new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + targetAttachedTriggers.Should().Be(1); + } + + [Fact] + public void Behavior_ShouldTriggerLinkTargetAttached_WhenLinkSnappedToPortAndMouseUp() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + diagram.Options.Links.EnableSnapping = true; + diagram.Options.Links.SnappingRadius = 60; + var node1 = new NodeModel(position: new Point(100, 50)); + var node2 = new NodeModel(position: new Point(160, 50)); + diagram.Nodes.Add(new[] { node1, node2 }); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) + { + Initialized = true, + Position = new Point(170, 60), + Size = new Size(10, 20) + }); + var targetAttachedTriggers = 0; + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + diagram.TriggerPointerMove(null, + new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + diagram.Links.Single().TargetAttached += _ => targetAttachedTriggers++; + + diagram.TriggerPointerUp(null, + new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + targetAttachedTriggers.Should().Be(1); + } + + [Fact] + public void Behavior_ShouldUpdateOngoingPosition_WhenWheelIsTriggered() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(100, 50)); + var linkRefreshed = false; + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + var link = diagram.Links.Single(); + link.Changed += _ => linkRefreshed = true; + diagram.TriggerPointerMove(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerWheel(new WheelEventArgs(150, 150, 0, 0, false, false, false, 100, 100, 0, 0)); + + // Assert + var source = link.Source as SinglePortAnchor; + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().BeGreaterThan(245); + ongoingPosition.Y.Should().BeGreaterThan(245); + linkRefreshed.Should().BeTrue(); + } } \ No newline at end of file From 5dbd0193571a7bc1279a3e67b539efc014483a09 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 14 Dec 2023 13:46:32 +1100 Subject: [PATCH 07/27] Moved scroll test --- .../Behaviors/DragMovablesBehaviorTests.cs | 278 ++++++++++-------- .../Behaviors/ScrollBehaviorTests.cs | 25 -- 2 files changed, 148 insertions(+), 155 deletions(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs index ba73788b..657b08b2 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -10,134 +10,152 @@ namespace Blazor.Diagrams.Core.Tests.Behaviors; public class DragMovablesBehaviorTests { - [Fact] - public void Behavior_ShouldCallSetPosition() - { - // Arrange - var diagram = new TestDiagram(); - var nodeMock = new Mock(Point.Zero); - var node = diagram.Nodes.Add(nodeMock.Object); - diagram.SelectModel(node, false); - - // Act - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - nodeMock.Verify(n => n.SetPosition(50, 50), Times.Once); - } - - [Theory] - [InlineData(false, 0, 0, 45, 45)] - [InlineData(true, 0, 0, 35, 35)] - [InlineData(false, 3, 3, 45, 45)] - [InlineData(true, 3, 3, 50, 50)] - public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, double initialX, double initialY, double deltaX, double deltaY) - { - // Arrange - var diagram = new TestDiagram(new DiagramOptions - { - GridSize = 15, - GridSnapToCenter = gridSnapToCenter - }); - var nodeMock = new Mock(Point.Zero); - var node = diagram.Nodes.Add(nodeMock.Object); - node.Size = new Size(20, 20); - node.Position = new Point(initialX, initialY); - diagram.SelectModel(node, false); - - // Act - //Move 40px in X and Y - diagram.TriggerPointerDown(node, - new PointerEventArgs(20, 20, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(60, 60, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - nodeMock.Verify(n => n.SetPosition(deltaX, deltaY), Times.Once); - } - - [Fact] - public void Behavior_ShouldTriggerMoved() - { - // Arrange - var diagram = new TestDiagram(); - var node = diagram.Nodes.Add(new NodeModel(Point.Zero)); - var movedTrigger = false; - node.Moved += m => movedTrigger = true; - diagram.SelectModel(node, false); - - // Act - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerUp(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - movedTrigger.Should().BeTrue(); - } - - [Fact] - public void Behavior_ShouldNotTriggerMoved_WhenMovableDidntMove() - { - // Arrange - var diagram = new TestDiagram(); - var node = diagram.Nodes.Add(new NodeModel(Point.Zero)); - var movedTrigger = false; - node.Moved += m => movedTrigger = true; - diagram.SelectModel(node, false); - - // Act - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerUp(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - movedTrigger.Should().BeFalse(); - } - - [Fact] - public void Behavior_ShouldNotCallSetPosition_WhenGroupHasNoAutoSize() - { - // Arrange - var diagram = new TestDiagram(); - var nodeMock = new Mock(Point.Zero); - var group = new GroupModel(new[] { nodeMock.Object }, autoSize: false); - var node = diagram.Nodes.Add(nodeMock.Object); - diagram.SelectModel(node, false); - - // Act - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - nodeMock.Verify(n => n.SetPosition(50, 50), Times.Never); - } - - [Fact] - public void Behavior_ShouldCallSetPosition_WhenGroupHasAutoSize() - { - // Arrange - var diagram = new TestDiagram(); - var nodeMock = new Mock(Point.Zero); - var group = new GroupModel(new[] { nodeMock.Object }, autoSize: true); - var node = diagram.Nodes.Add(nodeMock.Object); - diagram.SelectModel(node, false); - - // Act - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - nodeMock.Verify(n => n.SetPosition(50, 50), Times.Once); - } + [Fact] + public void Behavior_ShouldCallSetPosition() + { + // Arrange + var diagram = new TestDiagram(); + var nodeMock = new Mock(Point.Zero); + var node = diagram.Nodes.Add(nodeMock.Object); + diagram.SelectModel(node, false); + + // Act + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + nodeMock.Verify(n => n.SetPosition(50, 50), Times.Once); + } + + [Theory] + [InlineData(false, 0, 0, 45, 45)] + [InlineData(true, 0, 0, 35, 35)] + [InlineData(false, 3, 3, 45, 45)] + [InlineData(true, 3, 3, 50, 50)] + public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, double initialX, double initialY, double deltaX, double deltaY) + { + // Arrange + var diagram = new TestDiagram(new DiagramOptions + { + GridSize = 15, + GridSnapToCenter = gridSnapToCenter + }); + var nodeMock = new Mock(Point.Zero); + var node = diagram.Nodes.Add(nodeMock.Object); + node.Size = new Size(20, 20); + node.Position = new Point(initialX, initialY); + diagram.SelectModel(node, false); + + // Act + //Move 40px in X and Y + diagram.TriggerPointerDown(node, + new PointerEventArgs(20, 20, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(60, 60, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + nodeMock.Verify(n => n.SetPosition(deltaX, deltaY), Times.Once); + } + + [Fact] + public void Behavior_ShouldTriggerMoved() + { + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel(Point.Zero)); + var movedTrigger = false; + node.Moved += m => movedTrigger = true; + diagram.SelectModel(node, false); + + // Act + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerUp(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + movedTrigger.Should().BeTrue(); + } + + [Fact] + public void Behavior_ShouldNotTriggerMoved_WhenMovableDidntMove() + { + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel(Point.Zero)); + var movedTrigger = false; + node.Moved += m => movedTrigger = true; + diagram.SelectModel(node, false); + + // Act + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerUp(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + movedTrigger.Should().BeFalse(); + } + + [Fact] + public void Behavior_ShouldNotCallSetPosition_WhenGroupHasNoAutoSize() + { + // Arrange + var diagram = new TestDiagram(); + var nodeMock = new Mock(Point.Zero); + var group = new GroupModel(new[] { nodeMock.Object }, autoSize: false); + var node = diagram.Nodes.Add(nodeMock.Object); + diagram.SelectModel(node, false); + + // Act + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + nodeMock.Verify(n => n.SetPosition(50, 50), Times.Never); + } + + [Fact] + public void Behavior_ShouldCallSetPosition_WhenGroupHasAutoSize() + { + // Arrange + var diagram = new TestDiagram(); + var nodeMock = new Mock(Point.Zero); + var group = new GroupModel(new[] { nodeMock.Object }, autoSize: true); + var node = diagram.Nodes.Add(nodeMock.Object); + diagram.SelectModel(node, false); + + // Act + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + nodeMock.Verify(n => n.SetPosition(50, 50), Times.Once); + } + + [Fact] + public void Behavior_ShouldCallSetPosition_WhenWheelIsTriggered() + { + // Arrange + var diagram = new TestDiagram(); + var nodeMock = new Mock(Point.Zero); + var node = diagram.Nodes.Add(nodeMock.Object); + diagram.SelectModel(node, false); + + // Act + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 100, 0, 0)); + + // Assert + nodeMock.Verify(n => n.SetPosition(100, 100), Times.Once); + } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs index b82c98d8..a1f09a89 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs @@ -40,30 +40,5 @@ public void Behavior_WhenBehaviorDisabled_ShouldNotScroll() Assert.Equal(0, diagram.Pan.X); Assert.Equal(0, diagram.Pan.Y); } - - [Fact] - public void NodeUpdatesWhenScrollingWhileDragging() - { - // Arrange - var diagram = new TestDiagram(); - diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); - diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); - diagram.Options.Zoom.ScaleFactor = 1.05; - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - diagram.Nodes.Add(node); - - // Act - diagram.SelectModel(node, false); - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); - - // Assert - Assert.Equal(-95, diagram.Pan.X, 0); - Assert.Equal(-190, diagram.Pan.Y, 0); - Assert.Equal(200, node.Position.Y); - } - } } From 56f36fffa00b96befeec4d019fe00e22fcda7d7d Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Fri, 15 Dec 2023 15:24:33 +1100 Subject: [PATCH 08/27] Top Right and Bottom Right implemented --- .../Controls/Default/ResizeControl.cs | 48 ++++++------- .../Resizing/BottomRightResizerProvider.cs | 58 +++++++++++++--- .../Positions/Resizing/IResizerProvider.cs | 15 ++-- .../Resizing/TopRightResizerProvider.cs | 68 ++++++++++++++++--- 4 files changed, 141 insertions(+), 48 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs index 4517f673..fa02cac2 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs @@ -6,33 +6,35 @@ namespace Blazor.Diagrams.Core.Controls.Default { - public class ResizeControl : ExecutableControl - { - private readonly IResizerProvider _resizeProvider; + public class ResizeControl : ExecutableControl + { + private readonly IResizerProvider _resizeProvider; - public ResizeControl(IResizerProvider resizeProvider) - { - _resizeProvider = resizeProvider; - } + public ResizeControl(IResizerProvider resizeProvider) + { + _resizeProvider = resizeProvider; + } - public override Point? GetPosition(Model model) => _resizeProvider.GetPosition(model); + public override Point? GetPosition(Model model) => _resizeProvider.GetPosition(model); - public string? Class => _resizeProvider.Class; + public string? Class => _resizeProvider.Class; - public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) - { - _resizeProvider.OnResizeStart(diagram, model, e); - diagram.PointerMove += _resizeProvider.OnPointerMove; - diagram.PointerUp += _resizeProvider.OnResizeEnd; - diagram.PointerUp += (_, _) => OnResizeEnd(diagram); + public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) + { + _resizeProvider.OnResizeStart(diagram, model, e); + diagram.PointerMove += _resizeProvider.OnPointerMove; + diagram.Wheel += _resizeProvider.OnPointerMove; + diagram.PointerUp += _resizeProvider.OnResizeEnd; + diagram.PointerUp += (_, _) => OnResizeEnd(diagram); - return ValueTask.CompletedTask; - } + return ValueTask.CompletedTask; + } - void OnResizeEnd(Diagram diagram) - { - diagram.PointerMove -= _resizeProvider.OnPointerMove; - diagram.PointerUp -= _resizeProvider.OnResizeEnd; - } - } + void OnResizeEnd(Diagram diagram) + { + diagram.PointerMove -= _resizeProvider.OnPointerMove; + diagram.Wheel -= _resizeProvider.OnPointerMove; + diagram.PointerUp -= _resizeProvider.OnResizeEnd; + } + } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs index f55e26d7..843b1741 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -10,10 +10,13 @@ public class BottomRightResizerProvider : IResizerProvider public string? Class => "bottomright"; private Size _originalSize = null!; - private Point _originalMousePosition = null!; + private double? _lastClientX; + private double? _lastClientY; private NodeModel _nodeModel = null!; + private double _totalMovedX = 0; + private double _totalMovedY = 0; - public Point? GetPosition(Model model) + public Point? GetPosition(Model model) { if (model is NodeModel nodeModel && nodeModel.Size is not null) { @@ -22,25 +25,61 @@ public class BottomRightResizerProvider : IResizerProvider return null; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs) + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) { if (model is NodeModel nodeModel) { - _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; _originalSize = nodeModel.Size!; _nodeModel = nodeModel; } } - public void OnPointerMove(Model? model, PointerEventArgs args) + public void OnPointerMove(Model? model, PointerEventArgs e) + { + if (_nodeModel is null || _lastClientX == null || _lastClientY == null) + { + return; + } + + + var deltaX = (e.ClientX - _lastClientX.Value); + var deltaY = (e.ClientY - _lastClientY.Value); + + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + var height = _originalSize.Height + _totalMovedY; + var width = _originalSize.Width + _totalMovedX; + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + } + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + _nodeModel.SetSize(width, height); + } + + public void OnPointerMove(WheelEventArgs e) { if (_nodeModel is null) { return; } - var height = _originalSize.Height + (args.ClientY - _originalMousePosition.Y); - var width = _originalSize.Width + (args.ClientX - _originalMousePosition.X); + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; + + var height = _originalSize.Height + _totalMovedY; + var width = _originalSize.Width + _totalMovedX; if (width < _nodeModel.MinimumDimensions.Width) { @@ -58,8 +97,11 @@ public void OnResizeEnd(Model? model, PointerEventArgs args) { _nodeModel?.TriggerSizeChanged(); _originalSize = null!; - _originalMousePosition = null!; _nodeModel = null!; + _totalMovedX = 0; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs index 65a2ecdd..c15331e4 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs @@ -4,11 +4,12 @@ namespace Blazor.Diagrams.Core.Positions.Resizing { - public interface IResizerProvider : IPositionProvider - { - public string? Class { get; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); - public void OnPointerMove(Model? model, PointerEventArgs args); - public void OnResizeEnd(Model? model, PointerEventArgs args); - } + public interface IResizerProvider : IPositionProvider + { + public string? Class { get; } + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); + public void OnPointerMove(Model? model, PointerEventArgs args); + public void OnPointerMove(WheelEventArgs args); + public void OnResizeEnd(Model? model, PointerEventArgs args); + } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs index e9ea4e14..987b149b 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -7,12 +7,15 @@ namespace Blazor.Diagrams.Core.Positions.Resizing { public class TopRightResizerProvider : IResizerProvider { - public string? Class => "topright"; + public string? Class => "topright"; - private Size _originalSize = null!; + private Size _originalSize = null!; private Point _originalPosition = null!; - private Point _originalMousePosition = null!; private NodeModel _nodeModel = null!; + private double? _lastClientX; + private double? _lastClientY; + private double _totalMovedX = 0; + private double _totalMovedY = 0; public Point? GetPosition(Model model) { @@ -23,28 +26,70 @@ public class TopRightResizerProvider : IResizerProvider return null; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs) + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) { if (model is NodeModel nodeModel) { + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); _originalSize = nodeModel.Size!; _nodeModel = nodeModel; } } - public void OnPointerMove(Model? model, PointerEventArgs args) + public void OnPointerMove(Model? model, PointerEventArgs e) + { + if (_nodeModel is null || _lastClientX == null || _lastClientY == null) + { + return; + } + + var deltaX = (e.ClientX - _lastClientX.Value); + var deltaY = (e.ClientY - _lastClientY.Value); + + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + var height = _originalSize.Height - _totalMovedY; + var width = _originalSize.Width + _totalMovedX; + + var positionX = _originalPosition.X; + var positionY = _originalPosition.Y + _totalMovedY; + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + positionX = _nodeModel.Position.X; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + positionY = _nodeModel.Position.Y; + } + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + _nodeModel.SetPosition(positionX, positionY); + _nodeModel.SetSize(width, height); + } + + public void OnPointerMove(WheelEventArgs e) { if (_nodeModel is null) { return; } - var height = _originalSize.Height - (args.ClientY - _originalMousePosition.Y); - var width = _originalSize.Width + (args.ClientX - _originalMousePosition.X); + + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; + + var height = _originalSize.Height - _totalMovedY; + var width = _originalSize.Width + _totalMovedX; var positionX = _originalPosition.X; - var positionY = _originalPosition.Y + (args.ClientY - _originalMousePosition.Y); + var positionY = _originalPosition.Y + _totalMovedY; if (width < _nodeModel.MinimumDimensions.Width) { @@ -66,7 +111,10 @@ public void OnResizeEnd(Model? model, PointerEventArgs args) _nodeModel?.TriggerSizeChanged(); _originalSize = null!; _originalPosition = null!; - _originalMousePosition = null!; + _totalMovedX = 0; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; _nodeModel = null!; } From c0518e92b2ffd7bc11eb7368cf66bc0a767f09f7 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Fri, 15 Dec 2023 16:45:17 +1100 Subject: [PATCH 09/27] Bottom and top left implemented --- .../Resizing/BottomLeftResizerProvider.cs | 67 ++++++++++++++--- .../Resizing/TopLeftResizerProvider.cs | 72 +++++++++++++++---- 2 files changed, 117 insertions(+), 22 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs index c4e576c9..66f34fe7 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -7,12 +7,15 @@ namespace Blazor.Diagrams.Core.Positions.Resizing { public class BottomLeftResizerProvider : IResizerProvider { - public string? Class => "bottomleft"; + public string? Class => "bottomleft"; - private Size _originalSize = null!; + private Size _originalSize = null!; private Point _originalPosition = null!; - private Point _originalMousePosition = null!; private NodeModel _nodeModel = null!; + private double? _lastClientX; + private double? _lastClientY; + private double _totalMovedX = 0; + private double _totalMovedY = 0; public Point? GetPosition(Model model) { @@ -23,28 +26,70 @@ public class BottomLeftResizerProvider : IResizerProvider return null; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs) + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) { if (model is NodeModel nodeModel) { + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); _originalSize = nodeModel.Size!; _nodeModel = nodeModel; } } - public void OnPointerMove(Model? model, PointerEventArgs args) + public void OnPointerMove(Model? model, PointerEventArgs e) + { + if (_nodeModel is null || _lastClientX == null || _lastClientY == null) + { + return; + } + + var deltaX = (e.ClientX - _lastClientX.Value); + var deltaY = (e.ClientY - _lastClientY.Value); + + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + var height = _originalSize.Height + _totalMovedY; + var width = _originalSize.Width - _totalMovedX; + + var positionX = _originalPosition.X + _totalMovedX; + + var positionY = _originalPosition.Y; + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + positionX = _nodeModel.Position.X; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + positionY = _nodeModel.Position.Y; + } + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + _nodeModel.SetPosition(positionX, positionY); + _nodeModel.SetSize(width, height); + } + + public void OnPointerMove(WheelEventArgs e) { if (_nodeModel is null) { return; } - var height = _originalSize.Height + (args.ClientY - _originalMousePosition.Y); - var width = _originalSize.Width - (args.ClientX - _originalMousePosition.X); + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; + + var height = _originalSize.Height + _totalMovedY; + var width = _originalSize.Width - _totalMovedX; - var positionX = _originalPosition.X + (args.ClientX - _originalMousePosition.X); + var positionX = _originalPosition.X + _totalMovedX; var positionY = _originalPosition.Y; if (width < _nodeModel.MinimumDimensions.Width) @@ -67,7 +112,9 @@ public void OnResizeEnd(Model? model, PointerEventArgs args) _nodeModel?.TriggerSizeChanged(); _originalSize = null!; _originalPosition = null!; - _originalMousePosition = null!; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; _nodeModel = null!; } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs index 7c3afe5c..6a0525c8 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -7,12 +7,15 @@ namespace Blazor.Diagrams.Core.Positions.Resizing { public class TopLeftResizerProvider : IResizerProvider { - public string? Class => "topleft"; + public string? Class => "topleft"; - private Size _originalSize = null!; + private Size _originalSize = null!; private Point _originalPosition = null!; - private Point _originalMousePosition = null!; private NodeModel _nodeModel = null!; + private double? _lastClientX; + private double? _lastClientY; + private double _totalMovedX = 0; + private double _totalMovedY = 0; public Point? GetPosition(Model model) { @@ -23,29 +26,71 @@ public class TopLeftResizerProvider : IResizerProvider return null; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs) + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) { if (model is NodeModel nodeModel) { + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); _originalSize = nodeModel.Size!; _nodeModel = nodeModel; } } - public void OnPointerMove(Model? model, PointerEventArgs args) + public void OnPointerMove(Model? model, PointerEventArgs e) { - if (_nodeModel is null) + if (_nodeModel is null || _lastClientX == null || _lastClientY == null) { return; } - var height = _originalSize.Height - (args.ClientY - _originalMousePosition.Y); - var width = _originalSize.Width - (args.ClientX - _originalMousePosition.X); + var deltaX = (e.ClientX - _lastClientX.Value); + var deltaY = (e.ClientY - _lastClientY.Value); - var positionX = _originalPosition.X + (args.ClientX - _originalMousePosition.X); - var positionY = _originalPosition.Y + (args.ClientY - _originalMousePosition.Y); + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + + var height = _originalSize.Height - _totalMovedY; + var width = _originalSize.Width - _totalMovedX; + + var positionX = _originalPosition.X + _totalMovedX; + var positionY = _originalPosition.Y + _totalMovedY; + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + positionX = _nodeModel.Position.X; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + positionY = _nodeModel.Position.Y; + } + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + _nodeModel.SetPosition(positionX, positionY); + _nodeModel.SetSize(width, height); + } + + public void OnPointerMove(WheelEventArgs e) + { + if (_nodeModel is null || _lastClientX == null || _lastClientY == null) + { + return; + } + + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; + + var height = _originalSize.Height - _totalMovedY; + var width = _originalSize.Width - _totalMovedX; + + var positionX = _originalPosition.X + _totalMovedX; + var positionY = _originalPosition.Y + _totalMovedY; if (width < _nodeModel.MinimumDimensions.Width) { @@ -67,7 +112,10 @@ public void OnResizeEnd(Model? model, PointerEventArgs args) _nodeModel?.TriggerSizeChanged(); _originalSize = null!; _originalPosition = null!; - _originalMousePosition = null!; + _totalMovedX = 0; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; _nodeModel = null!; } From d2690172a1eede0d6c0ed0c40b427230ac82be8c Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Mon, 18 Dec 2023 14:02:46 +1100 Subject: [PATCH 10/27] Updated file to use spaces --- .../Behaviors/DragMovablesBehavior.cs | 272 +++++++++--------- 1 file changed, 136 insertions(+), 136 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index a69d4001..8ffeff84 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -10,140 +10,140 @@ namespace Blazor.Diagrams.Core.Behaviors; public class DragMovablesBehavior : Behavior { - private readonly Dictionary _initialPositions; - private double? _lastClientX; - private double? _lastClientY; - private bool _moved; - private double _totalMovedX = 0; - private double _totalMovedY = 0; - - public DragMovablesBehavior(Diagram diagram) : base(diagram) - { - _initialPositions = new Dictionary(); - Diagram.PointerDown += OnPointerDown; - Diagram.PointerMove += OnPointerMove; - Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnPointerMove; - } - - private void OnPointerDown(Model? model, PointerEventArgs e) - { - if (model is not MovableModel) - return; - - _initialPositions.Clear(); - foreach (var sm in Diagram.GetSelectedModels()) - { - if (sm is not MovableModel movable || movable.Locked) - continue; - - // Special case: groups without auto size on - if (sm is NodeModel node && node.Group != null && !node.Group.AutoSize) - continue; - - var position = movable.Position; - if (Diagram.Options.GridSnapToCenter && movable is NodeModel n) - { - position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2, - movable.Position.Y + (n.Size?.Height ?? 0) / 2); - } - - _initialPositions.Add(movable, position); - } - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _moved = false; - } - - public void OnPointerMove(Model? model, PointerEventArgs e) - { - if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) - return; - - _moved = true; - - var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; - var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; - - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - moveNodes(model, _totalMovedX, _totalMovedY); - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - - } - - public void OnPointerMove(WheelEventArgs e) - { - if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) - return; - - _moved = true; - - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; - - moveNodes(null, _totalMovedX, _totalMovedY); - } - - private void moveNodes(Model? model, double deltaX, double deltaY) - { - foreach (var (movable, initialPosition) in _initialPositions) - { - var ndx = ApplyGridSize(deltaX + initialPosition.X); - var ndy = ApplyGridSize(deltaY + initialPosition.Y); - - if (Diagram.Options.GridSnapToCenter && movable is NodeModel node) - { - node.SetPosition(ndx - (node.Size?.Width ?? 0) / 2, ndy - (node.Size?.Height ?? 0) / 2); - } - else - { - movable.SetPosition(ndx, ndy); - } - } - } - - private void OnPointerUp(Model? model, PointerEventArgs e) - { - if (_initialPositions.Count == 0) - return; - - if (_moved) - { - foreach (var (movable, _) in _initialPositions) - { - movable.TriggerMoved(); - } - } - - _initialPositions.Clear(); - _totalMovedX = 0; - _totalMovedY = 0; - _lastClientX = null; - _lastClientY = null; - } - - private double ApplyGridSize(double n) - { - if (Diagram.Options.GridSize == null) - return n; - - var gridSize = Diagram.Options.GridSize.Value; - - return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); - } - - public override void Dispose() - { - _initialPositions.Clear(); - - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnPointerMove; - } + private readonly Dictionary _initialPositions; + private double? _lastClientX; + private double? _lastClientY; + private bool _moved; + private double _totalMovedX = 0; + private double _totalMovedY = 0; + + public DragMovablesBehavior(Diagram diagram) : base(diagram) + { + _initialPositions = new Dictionary(); + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + Diagram.Wheel += OnPointerMove; + } + + private void OnPointerDown(Model? model, PointerEventArgs e) + { + if (model is not MovableModel) + return; + + _initialPositions.Clear(); + foreach (var sm in Diagram.GetSelectedModels()) + { + if (sm is not MovableModel movable || movable.Locked) + continue; + + // Special case: groups without auto size on + if (sm is NodeModel node && node.Group != null && !node.Group.AutoSize) + continue; + + var position = movable.Position; + if (Diagram.Options.GridSnapToCenter && movable is NodeModel n) + { + position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2, + movable.Position.Y + (n.Size?.Height ?? 0) / 2); + } + + _initialPositions.Add(movable, position); + } + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + _moved = false; + } + + public void OnPointerMove(Model? model, PointerEventArgs e) + { + if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) + return; + + _moved = true; + + var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; + var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; + + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + moveNodes(model, _totalMovedX, _totalMovedY); + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + } + + public void OnPointerMove(WheelEventArgs e) + { + if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) + return; + + _moved = true; + + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; + + moveNodes(null, _totalMovedX, _totalMovedY); + } + + private void moveNodes(Model? model, double deltaX, double deltaY) + { + foreach (var (movable, initialPosition) in _initialPositions) + { + var ndx = ApplyGridSize(deltaX + initialPosition.X); + var ndy = ApplyGridSize(deltaY + initialPosition.Y); + + if (Diagram.Options.GridSnapToCenter && movable is NodeModel node) + { + node.SetPosition(ndx - (node.Size?.Width ?? 0) / 2, ndy - (node.Size?.Height ?? 0) / 2); + } + else + { + movable.SetPosition(ndx, ndy); + } + } + } + + private void OnPointerUp(Model? model, PointerEventArgs e) + { + if (_initialPositions.Count == 0) + return; + + if (_moved) + { + foreach (var (movable, _) in _initialPositions) + { + movable.TriggerMoved(); + } + } + + _initialPositions.Clear(); + _totalMovedX = 0; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; + } + + private double ApplyGridSize(double n) + { + if (Diagram.Options.GridSize == null) + return n; + + var gridSize = Diagram.Options.GridSize.Value; + + return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); + } + + public override void Dispose() + { + _initialPositions.Clear(); + + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + Diagram.Wheel -= OnPointerMove; + } } From 346fbf4ec7ae66234d39c7dac64deed2293dcb2d Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Mon, 18 Dec 2023 14:08:40 +1100 Subject: [PATCH 11/27] Fixed all spacing --- .../Behaviors/DragNewLinkBehavior.cs | 348 +++---- .../Behaviors/ScrollBehavior.cs | 30 +- .../Controls/Default/ResizeControl.cs | 50 +- src/Blazor.Diagrams.Core/Diagram.cs | 748 +++++++------- .../Positions/Resizing/IResizerProvider.cs | 16 +- .../Behaviors/DragMovablesBehaviorTests.cs | 296 +++--- .../Behaviors/DragNewLinkBehaviorTests.cs | 926 +++++++++--------- .../Behaviors/ScrollBehaviorTests.cs | 60 +- 8 files changed, 1237 insertions(+), 1237 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 2f0d92ac..cca9fd98 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -11,178 +11,178 @@ namespace Blazor.Diagrams.Core.Behaviors; public class DragNewLinkBehavior : Behavior { - private PositionAnchor? _targetPositionAnchor; - - public BaseLinkModel? OngoingLink { get; private set; } - - public DragNewLinkBehavior(Diagram diagram) : base(diagram) - { - Diagram.PointerDown += OnPointerDown; - Diagram.PointerMove += OnPointerMove; - Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnPointerMove; - } - - public void StartFrom(ILinkable source, double clientX, double clientY) - { - if (OngoingLink != null) - return; - - _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); - OngoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor); - if (OngoingLink == null) - return; - - Diagram.Links.Add(OngoingLink); - } - - public void StartFrom(BaseLinkModel link, double clientX, double clientY) - { - if (OngoingLink != null) - return; - - _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); - OngoingLink = link; - OngoingLink.SetTarget(_targetPositionAnchor); - OngoingLink.Refresh(); - OngoingLink.RefreshLinks(); - } - - private void OnPointerDown(Model? model, MouseEventArgs e) - { - if (e.Button != (int)MouseEventButton.Left) - return; - - OngoingLink = null; - _targetPositionAnchor = null; - - if (model is PortModel port) - { - if (port.Locked) - return; - - _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(e.ClientX, e.ClientY)); - OngoingLink = Diagram.Options.Links.Factory(Diagram, port, _targetPositionAnchor); - if (OngoingLink == null) - return; - - OngoingLink.SetTarget(_targetPositionAnchor); - Diagram.Links.Add(OngoingLink); - } - } - - private void OnPointerMove(Model? model, MouseEventArgs e) - { - if (OngoingLink == null || model != null) - return; - - UpdateLinkPosition(e.ClientX, e.ClientY); - } - - private void OnPointerMove(WheelEventArgs e) - { - if (OngoingLink == null) - return; - - UpdateLinkPosition(e.ClientX + e.DeltaX, e.ClientY + e.DeltaY); - } - - private void UpdateLinkPosition(double clientX, double clientY) - { - _targetPositionAnchor!.SetPosition(CalculateTargetPosition(clientX, clientY)); - - if (Diagram.Options.Links.EnableSnapping) - { - var nearPort = FindNearPortToAttachTo(); - if (nearPort != null || OngoingLink!.Target is not PositionAnchor) - { - OngoingLink!.SetTarget(nearPort is null ? _targetPositionAnchor : new SinglePortAnchor(nearPort)); - } - } - - OngoingLink!.Refresh(); - OngoingLink!.RefreshLinks(); - } - - private void OnPointerUp(Model? model, MouseEventArgs e) - { - if (OngoingLink == null) - return; - - if (OngoingLink.IsAttached) // Snapped already - { - OngoingLink.TriggerTargetAttached(); - OngoingLink = null; - return; - } - - if (model is ILinkable linkable && (OngoingLink.Source.Model == null || OngoingLink.Source.Model.CanAttachTo(linkable))) - { - var targetAnchor = Diagram.Options.Links.TargetAnchorFactory(Diagram, OngoingLink, linkable); - OngoingLink.SetTarget(targetAnchor); - OngoingLink.TriggerTargetAttached(); - OngoingLink.Refresh(); - OngoingLink.RefreshLinks(); - } - else if (Diagram.Options.Links.RequireTarget) - { - Diagram.Links.Remove(OngoingLink); - } - else if (!Diagram.Options.Links.RequireTarget) - { - OngoingLink.Refresh(); - } - - OngoingLink = null; - } - - private Point CalculateTargetPosition(double clientX, double clientY) - { - var target = Diagram.GetRelativeMousePoint(clientX, clientY); - - if (OngoingLink == null) - { - return target; - } - - var source = OngoingLink.Source.GetPlainPosition()!; - var dirVector = target.Subtract(source).Normalize(); - var change = dirVector.Multiply(5); - return target.Subtract(change); - } - - private PortModel? FindNearPortToAttachTo() - { - if (OngoingLink is null || _targetPositionAnchor is null) - return null; - - PortModel? nearestSnapPort = null; - var nearestSnapPortDistance = double.PositiveInfinity; - - var position = _targetPositionAnchor!.GetPosition(OngoingLink)!; - - foreach (var port in Diagram.Nodes.SelectMany((NodeModel n) => n.Ports)) - { - var distance = position.DistanceTo(port.Position); - - if (distance <= Diagram.Options.Links.SnappingRadius && (OngoingLink.Source.Model?.CanAttachTo(port) != false)) - { - if (distance < nearestSnapPortDistance) - { - nearestSnapPortDistance = distance; - nearestSnapPort = port; - } - } - } - - return nearestSnapPort; - } - - public override void Dispose() - { - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnPointerMove; - } + private PositionAnchor? _targetPositionAnchor; + + public BaseLinkModel? OngoingLink { get; private set; } + + public DragNewLinkBehavior(Diagram diagram) : base(diagram) + { + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + Diagram.Wheel += OnPointerMove; + } + + public void StartFrom(ILinkable source, double clientX, double clientY) + { + if (OngoingLink != null) + return; + + _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); + OngoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor); + if (OngoingLink == null) + return; + + Diagram.Links.Add(OngoingLink); + } + + public void StartFrom(BaseLinkModel link, double clientX, double clientY) + { + if (OngoingLink != null) + return; + + _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); + OngoingLink = link; + OngoingLink.SetTarget(_targetPositionAnchor); + OngoingLink.Refresh(); + OngoingLink.RefreshLinks(); + } + + private void OnPointerDown(Model? model, MouseEventArgs e) + { + if (e.Button != (int)MouseEventButton.Left) + return; + + OngoingLink = null; + _targetPositionAnchor = null; + + if (model is PortModel port) + { + if (port.Locked) + return; + + _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(e.ClientX, e.ClientY)); + OngoingLink = Diagram.Options.Links.Factory(Diagram, port, _targetPositionAnchor); + if (OngoingLink == null) + return; + + OngoingLink.SetTarget(_targetPositionAnchor); + Diagram.Links.Add(OngoingLink); + } + } + + private void OnPointerMove(Model? model, MouseEventArgs e) + { + if (OngoingLink == null || model != null) + return; + + UpdateLinkPosition(e.ClientX, e.ClientY); + } + + private void OnPointerMove(WheelEventArgs e) + { + if (OngoingLink == null) + return; + + UpdateLinkPosition(e.ClientX + e.DeltaX, e.ClientY + e.DeltaY); + } + + private void UpdateLinkPosition(double clientX, double clientY) + { + _targetPositionAnchor!.SetPosition(CalculateTargetPosition(clientX, clientY)); + + if (Diagram.Options.Links.EnableSnapping) + { + var nearPort = FindNearPortToAttachTo(); + if (nearPort != null || OngoingLink!.Target is not PositionAnchor) + { + OngoingLink!.SetTarget(nearPort is null ? _targetPositionAnchor : new SinglePortAnchor(nearPort)); + } + } + + OngoingLink!.Refresh(); + OngoingLink!.RefreshLinks(); + } + + private void OnPointerUp(Model? model, MouseEventArgs e) + { + if (OngoingLink == null) + return; + + if (OngoingLink.IsAttached) // Snapped already + { + OngoingLink.TriggerTargetAttached(); + OngoingLink = null; + return; + } + + if (model is ILinkable linkable && (OngoingLink.Source.Model == null || OngoingLink.Source.Model.CanAttachTo(linkable))) + { + var targetAnchor = Diagram.Options.Links.TargetAnchorFactory(Diagram, OngoingLink, linkable); + OngoingLink.SetTarget(targetAnchor); + OngoingLink.TriggerTargetAttached(); + OngoingLink.Refresh(); + OngoingLink.RefreshLinks(); + } + else if (Diagram.Options.Links.RequireTarget) + { + Diagram.Links.Remove(OngoingLink); + } + else if (!Diagram.Options.Links.RequireTarget) + { + OngoingLink.Refresh(); + } + + OngoingLink = null; + } + + private Point CalculateTargetPosition(double clientX, double clientY) + { + var target = Diagram.GetRelativeMousePoint(clientX, clientY); + + if (OngoingLink == null) + { + return target; + } + + var source = OngoingLink.Source.GetPlainPosition()!; + var dirVector = target.Subtract(source).Normalize(); + var change = dirVector.Multiply(5); + return target.Subtract(change); + } + + private PortModel? FindNearPortToAttachTo() + { + if (OngoingLink is null || _targetPositionAnchor is null) + return null; + + PortModel? nearestSnapPort = null; + var nearestSnapPortDistance = double.PositiveInfinity; + + var position = _targetPositionAnchor!.GetPosition(OngoingLink)!; + + foreach (var port in Diagram.Nodes.SelectMany((NodeModel n) => n.Ports)) + { + var distance = position.DistanceTo(port.Position); + + if (distance <= Diagram.Options.Links.SnappingRadius && (OngoingLink.Source.Model?.CanAttachTo(port) != false)) + { + if (distance < nearestSnapPortDistance) + { + nearestSnapPortDistance = distance; + nearestSnapPort = port; + } + } + } + + return nearestSnapPort; + } + + public override void Dispose() + { + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + Diagram.Wheel -= OnPointerMove; + } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs index 9b563428..fa3ef63e 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs @@ -4,22 +4,22 @@ namespace Blazor.Diagrams.Core.Behaviors { - public class ScrollBehavior : WheelBehavior - { - public ScrollBehavior(Diagram diagram) - : base(diagram) - { - } + public class ScrollBehavior : WheelBehavior + { + public ScrollBehavior(Diagram diagram) + : base(diagram) + { + } - protected override void OnDiagramWheel(WheelEventArgs e) - { - if (Diagram.Container == null || !IsBehaviorEnabled(e)) - return; + protected override void OnDiagramWheel(WheelEventArgs e) + { + if (Diagram.Container == null || !IsBehaviorEnabled(e)) + return; - var x = Diagram.Pan.X - (e.DeltaX / Diagram.Options.Zoom.ScaleFactor); - var y = Diagram.Pan.Y - (e.DeltaY / Diagram.Options.Zoom.ScaleFactor); + var x = Diagram.Pan.X - (e.DeltaX / Diagram.Options.Zoom.ScaleFactor); + var y = Diagram.Pan.Y - (e.DeltaY / Diagram.Options.Zoom.ScaleFactor); - Diagram.SetPan(x, y); - } - } + Diagram.SetPan(x, y); + } + } } diff --git a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs index fa02cac2..3457605f 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs @@ -6,35 +6,35 @@ namespace Blazor.Diagrams.Core.Controls.Default { - public class ResizeControl : ExecutableControl - { - private readonly IResizerProvider _resizeProvider; + public class ResizeControl : ExecutableControl + { + private readonly IResizerProvider _resizeProvider; - public ResizeControl(IResizerProvider resizeProvider) - { - _resizeProvider = resizeProvider; - } + public ResizeControl(IResizerProvider resizeProvider) + { + _resizeProvider = resizeProvider; + } - public override Point? GetPosition(Model model) => _resizeProvider.GetPosition(model); + public override Point? GetPosition(Model model) => _resizeProvider.GetPosition(model); - public string? Class => _resizeProvider.Class; + public string? Class => _resizeProvider.Class; - public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) - { - _resizeProvider.OnResizeStart(diagram, model, e); - diagram.PointerMove += _resizeProvider.OnPointerMove; - diagram.Wheel += _resizeProvider.OnPointerMove; - diagram.PointerUp += _resizeProvider.OnResizeEnd; - diagram.PointerUp += (_, _) => OnResizeEnd(diagram); + public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) + { + _resizeProvider.OnResizeStart(diagram, model, e); + diagram.PointerMove += _resizeProvider.OnPointerMove; + diagram.Wheel += _resizeProvider.OnPointerMove; + diagram.PointerUp += _resizeProvider.OnResizeEnd; + diagram.PointerUp += (_, _) => OnResizeEnd(diagram); - return ValueTask.CompletedTask; - } + return ValueTask.CompletedTask; + } - void OnResizeEnd(Diagram diagram) - { - diagram.PointerMove -= _resizeProvider.OnPointerMove; - diagram.Wheel -= _resizeProvider.OnPointerMove; - diagram.PointerUp -= _resizeProvider.OnResizeEnd; - } - } + void OnResizeEnd(Diagram diagram) + { + diagram.PointerMove -= _resizeProvider.OnPointerMove; + diagram.Wheel -= _resizeProvider.OnPointerMove; + diagram.PointerUp -= _resizeProvider.OnResizeEnd; + } + } } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index fb5ab2ed..c80f64d2 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -20,400 +20,400 @@ namespace Blazor.Diagrams.Core; public abstract class Diagram { - private readonly Dictionary _behaviors; - private readonly List _orderedSelectables; - - public event Action? PointerDown; - public event Action? PointerMove; - public event Action? PointerUp; - public event Action? PointerEnter; - public event Action? PointerLeave; - public event Action? KeyDown; - public event Action? Wheel; - public event Action? PointerClick; - public event Action? PointerDoubleClick; - - public event Action? SelectionChanged; - public event Action? PanChanged; - public event Action? ZoomChanged; - public event Action? ContainerChanged; - public event Action? Changed; - - protected Diagram() - { - _behaviors = new Dictionary(); - _orderedSelectables = new List(); - - Nodes = new NodeLayer(this); - Links = new LinkLayer(this); - Groups = new GroupLayer(this); - Controls = new ControlsLayer(); - BehaviorOptions = new DiagramBehaviorOptions(); - - Nodes.Added += OnSelectableAdded; - Links.Added += OnSelectableAdded; - Groups.Added += OnSelectableAdded; - - Nodes.Removed += OnSelectableRemoved; - Links.Removed += OnSelectableRemoved; - Groups.Removed += OnSelectableRemoved; - - RegisterDefaultBehaviors(); - - BehaviorOptions.DiagramDragBehavior ??= GetBehavior(); - BehaviorOptions.DiagramShiftDragBehavior ??= GetBehavior(); - BehaviorOptions.DiagramWheelBehavior ??= GetBehavior(); - } - - public abstract DiagramOptions Options { get; } - public DiagramBehaviorOptions BehaviorOptions { get; } - public NodeLayer Nodes { get; } - public LinkLayer Links { get; } - public GroupLayer Groups { get; } - public ControlsLayer Controls { get; } - public Rectangle? Container { get; private set; } - public Point Pan { get; private set; } = Point.Zero; - public double Zoom { get; private set; } = 1; - public bool SuspendRefresh { get; set; } - public bool SuspendSorting { get; set; } - public IReadOnlyList OrderedSelectables => _orderedSelectables; - - public void Refresh() - { - if (SuspendRefresh) - return; - - Changed?.Invoke(); - } - - public void Batch(Action action) - { - if (SuspendRefresh) - { - // If it's already suspended, just execute the action and leave it suspended - // It's probably handled by an outer batch - action(); - return; - } - - SuspendRefresh = true; - action(); - SuspendRefresh = false; - Refresh(); - } - - #region Selection - - public IEnumerable GetSelectedModels() - { - foreach (var node in Nodes) - { - if (node.Selected) - yield return node; - } - - foreach (var link in Links) - { - if (link.Selected) - yield return link; - - foreach (var vertex in link.Vertices) - { - if (vertex.Selected) - yield return vertex; - } - } - - foreach (var group in Groups) - { - if (group.Selected) - yield return group; - } - } - - public void SelectModel(SelectableModel model, bool unselectOthers) - { - if (model.Selected) - return; - - if (unselectOthers) - UnselectAll(); - - model.Selected = true; - model.Refresh(); - SelectionChanged?.Invoke(model); - } - - public void UnselectModel(SelectableModel model) - { - if (!model.Selected) - return; - - model.Selected = false; - model.Refresh(); - SelectionChanged?.Invoke(model); - } - - public void UnselectAll() - { - foreach (var model in GetSelectedModels()) - { - model.Selected = false; - model.Refresh(); - // Todo: will result in many events, maybe one event for all of them? - SelectionChanged?.Invoke(model); - } - } - - #endregion - - #region Behaviors - void RegisterDefaultBehaviors() - { - RegisterBehavior(new SelectionBehavior(this)); - RegisterBehavior(new DragMovablesBehavior(this)); - RegisterBehavior(new DragNewLinkBehavior(this)); - RegisterBehavior(new PanBehavior(this)); - RegisterBehavior(new ZoomBehavior(this)); - RegisterBehavior(new EventsBehavior(this)); - RegisterBehavior(new KeyboardShortcutsBehavior(this)); - RegisterBehavior(new ControlsBehavior(this)); - RegisterBehavior(new VirtualizationBehavior(this)); - RegisterBehavior(new ScrollBehavior(this)); - RegisterBehavior(new SelectionBoxBehavior(this)); - } - - public void RegisterBehavior(Behavior behavior) - { - var type = behavior.GetType(); - if (_behaviors.ContainsKey(type)) - throw new Exception($"Behavior '{type.Name}' already registered"); - - _behaviors.Add(type, behavior); - } - - public T? GetBehavior() where T : Behavior - { - var type = typeof(T); - return (T?)(_behaviors.ContainsKey(type) ? _behaviors[type] : null); - } - - public void UnregisterBehavior() where T : Behavior - { - var type = typeof(T); - if (!_behaviors.ContainsKey(type)) - return; - - _behaviors[type].Dispose(); - _behaviors.Remove(type); - } - - #endregion - - public void ZoomToFit(double margin = 10) - { - if (Container == null || Nodes.Count == 0) - return; - - var selectedNodes = Nodes.Where(s => s.Selected); - var nodesToUse = selectedNodes.Any() ? selectedNodes : Nodes; - var bounds = nodesToUse.GetBounds(); - var width = bounds.Width + 2 * margin; - var height = bounds.Height + 2 * margin; - var minX = bounds.Left - margin; - var minY = bounds.Top - margin; - - SuspendRefresh = true; - - var xf = Container.Width / width; - var yf = Container.Height / height; - SetZoom(Math.Min(xf, yf)); - - var nx = Container.Left + Pan.X + minX * Zoom; - var ny = Container.Top + Pan.Y + minY * Zoom; - UpdatePan(Container.Left - nx, Container.Top - ny); - - SuspendRefresh = false; - Refresh(); - } - - public void SetPan(double x, double y) - { - Pan = new Point(x, y); - PanChanged?.Invoke(); - Refresh(); - } - - public void UpdatePan(double deltaX, double deltaY) - { - Pan = Pan.Add(deltaX, deltaY); - PanChanged?.Invoke(); - Refresh(); - } - - public void SetZoom(double newZoom) - { - if (newZoom <= 0) - throw new ArgumentException($"{nameof(newZoom)} cannot be equal or lower than 0"); - - if (newZoom < Options.Zoom.Minimum) - newZoom = Options.Zoom.Minimum; - - Zoom = newZoom; - ZoomChanged?.Invoke(); - Refresh(); - } - - public void SetContainer(Rectangle? newRect) - { - if (Equals(newRect, Container)) - return; - - Container = newRect; - ContainerChanged?.Invoke(); - Refresh(); - } - - public Point GetRelativeMousePoint(double clientX, double clientY) - { - if (Container == null) - throw new Exception( - "Container not available. Make sure you're not using this method before the diagram is fully loaded"); - - return new Point((clientX - Container.Left - Pan.X) / Zoom, (clientY - Container.Top - Pan.Y) / Zoom); - } - - public Point GetRelativePoint(double clientX, double clientY) - { - if (Container == null) - throw new Exception( - "Container not available. Make sure you're not using this method before the diagram is fully loaded"); - - return new Point(clientX - Container.Left, clientY - Container.Top); - } - - public Point GetScreenPoint(double clientX, double clientY) - { - if (Container == null) - throw new Exception( - "Container not available. Make sure you're not using this method before the diagram is fully loaded"); - - return new Point(Zoom * clientX + Container.Left + Pan.X, Zoom * clientY + Container.Top + Pan.Y); - } - - #region Ordering - - public void SendToBack(SelectableModel model) - { - var minOrder = GetMinOrder(); - if (model.Order == minOrder) - return; - - if (!_orderedSelectables.Remove(model)) - return; - - _orderedSelectables.Insert(0, model); - - // Todo: can optimize this by only updating the order of items before model - Batch(() => - { - SuspendSorting = true; - for (var i = 0; i < _orderedSelectables.Count; i++) - { - _orderedSelectables[i].Order = i + 1; - } - SuspendSorting = false; - }); - } - - public void SendToFront(SelectableModel model) - { - var maxOrder = GetMaxOrder(); - if (model.Order == maxOrder) - return; - - if (!_orderedSelectables.Remove(model)) - return; - - _orderedSelectables.Add(model); - - SuspendSorting = true; - model.Order = maxOrder + 1; - SuspendSorting = false; - Refresh(); - } + private readonly Dictionary _behaviors; + private readonly List _orderedSelectables; + + public event Action? PointerDown; + public event Action? PointerMove; + public event Action? PointerUp; + public event Action? PointerEnter; + public event Action? PointerLeave; + public event Action? KeyDown; + public event Action? Wheel; + public event Action? PointerClick; + public event Action? PointerDoubleClick; + + public event Action? SelectionChanged; + public event Action? PanChanged; + public event Action? ZoomChanged; + public event Action? ContainerChanged; + public event Action? Changed; + + protected Diagram() + { + _behaviors = new Dictionary(); + _orderedSelectables = new List(); + + Nodes = new NodeLayer(this); + Links = new LinkLayer(this); + Groups = new GroupLayer(this); + Controls = new ControlsLayer(); + BehaviorOptions = new DiagramBehaviorOptions(); + + Nodes.Added += OnSelectableAdded; + Links.Added += OnSelectableAdded; + Groups.Added += OnSelectableAdded; + + Nodes.Removed += OnSelectableRemoved; + Links.Removed += OnSelectableRemoved; + Groups.Removed += OnSelectableRemoved; + + RegisterDefaultBehaviors(); + + BehaviorOptions.DiagramDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramShiftDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramWheelBehavior ??= GetBehavior(); + } + + public abstract DiagramOptions Options { get; } + public DiagramBehaviorOptions BehaviorOptions { get; } + public NodeLayer Nodes { get; } + public LinkLayer Links { get; } + public GroupLayer Groups { get; } + public ControlsLayer Controls { get; } + public Rectangle? Container { get; private set; } + public Point Pan { get; private set; } = Point.Zero; + public double Zoom { get; private set; } = 1; + public bool SuspendRefresh { get; set; } + public bool SuspendSorting { get; set; } + public IReadOnlyList OrderedSelectables => _orderedSelectables; + + public void Refresh() + { + if (SuspendRefresh) + return; + + Changed?.Invoke(); + } + + public void Batch(Action action) + { + if (SuspendRefresh) + { + // If it's already suspended, just execute the action and leave it suspended + // It's probably handled by an outer batch + action(); + return; + } + + SuspendRefresh = true; + action(); + SuspendRefresh = false; + Refresh(); + } + + #region Selection + + public IEnumerable GetSelectedModels() + { + foreach (var node in Nodes) + { + if (node.Selected) + yield return node; + } + + foreach (var link in Links) + { + if (link.Selected) + yield return link; + + foreach (var vertex in link.Vertices) + { + if (vertex.Selected) + yield return vertex; + } + } + + foreach (var group in Groups) + { + if (group.Selected) + yield return group; + } + } + + public void SelectModel(SelectableModel model, bool unselectOthers) + { + if (model.Selected) + return; + + if (unselectOthers) + UnselectAll(); + + model.Selected = true; + model.Refresh(); + SelectionChanged?.Invoke(model); + } + + public void UnselectModel(SelectableModel model) + { + if (!model.Selected) + return; + + model.Selected = false; + model.Refresh(); + SelectionChanged?.Invoke(model); + } + + public void UnselectAll() + { + foreach (var model in GetSelectedModels()) + { + model.Selected = false; + model.Refresh(); + // Todo: will result in many events, maybe one event for all of them? + SelectionChanged?.Invoke(model); + } + } + + #endregion + + #region Behaviors + void RegisterDefaultBehaviors() + { + RegisterBehavior(new SelectionBehavior(this)); + RegisterBehavior(new DragMovablesBehavior(this)); + RegisterBehavior(new DragNewLinkBehavior(this)); + RegisterBehavior(new PanBehavior(this)); + RegisterBehavior(new ZoomBehavior(this)); + RegisterBehavior(new EventsBehavior(this)); + RegisterBehavior(new KeyboardShortcutsBehavior(this)); + RegisterBehavior(new ControlsBehavior(this)); + RegisterBehavior(new VirtualizationBehavior(this)); + RegisterBehavior(new ScrollBehavior(this)); + RegisterBehavior(new SelectionBoxBehavior(this)); + } + + public void RegisterBehavior(Behavior behavior) + { + var type = behavior.GetType(); + if (_behaviors.ContainsKey(type)) + throw new Exception($"Behavior '{type.Name}' already registered"); + + _behaviors.Add(type, behavior); + } + + public T? GetBehavior() where T : Behavior + { + var type = typeof(T); + return (T?)(_behaviors.ContainsKey(type) ? _behaviors[type] : null); + } + + public void UnregisterBehavior() where T : Behavior + { + var type = typeof(T); + if (!_behaviors.ContainsKey(type)) + return; + + _behaviors[type].Dispose(); + _behaviors.Remove(type); + } + + #endregion + + public void ZoomToFit(double margin = 10) + { + if (Container == null || Nodes.Count == 0) + return; + + var selectedNodes = Nodes.Where(s => s.Selected); + var nodesToUse = selectedNodes.Any() ? selectedNodes : Nodes; + var bounds = nodesToUse.GetBounds(); + var width = bounds.Width + 2 * margin; + var height = bounds.Height + 2 * margin; + var minX = bounds.Left - margin; + var minY = bounds.Top - margin; + + SuspendRefresh = true; + + var xf = Container.Width / width; + var yf = Container.Height / height; + SetZoom(Math.Min(xf, yf)); + + var nx = Container.Left + Pan.X + minX * Zoom; + var ny = Container.Top + Pan.Y + minY * Zoom; + UpdatePan(Container.Left - nx, Container.Top - ny); + + SuspendRefresh = false; + Refresh(); + } + + public void SetPan(double x, double y) + { + Pan = new Point(x, y); + PanChanged?.Invoke(); + Refresh(); + } + + public void UpdatePan(double deltaX, double deltaY) + { + Pan = Pan.Add(deltaX, deltaY); + PanChanged?.Invoke(); + Refresh(); + } + + public void SetZoom(double newZoom) + { + if (newZoom <= 0) + throw new ArgumentException($"{nameof(newZoom)} cannot be equal or lower than 0"); + + if (newZoom < Options.Zoom.Minimum) + newZoom = Options.Zoom.Minimum; + + Zoom = newZoom; + ZoomChanged?.Invoke(); + Refresh(); + } + + public void SetContainer(Rectangle? newRect) + { + if (Equals(newRect, Container)) + return; + + Container = newRect; + ContainerChanged?.Invoke(); + Refresh(); + } + + public Point GetRelativeMousePoint(double clientX, double clientY) + { + if (Container == null) + throw new Exception( + "Container not available. Make sure you're not using this method before the diagram is fully loaded"); + + return new Point((clientX - Container.Left - Pan.X) / Zoom, (clientY - Container.Top - Pan.Y) / Zoom); + } + + public Point GetRelativePoint(double clientX, double clientY) + { + if (Container == null) + throw new Exception( + "Container not available. Make sure you're not using this method before the diagram is fully loaded"); + + return new Point(clientX - Container.Left, clientY - Container.Top); + } + + public Point GetScreenPoint(double clientX, double clientY) + { + if (Container == null) + throw new Exception( + "Container not available. Make sure you're not using this method before the diagram is fully loaded"); + + return new Point(Zoom * clientX + Container.Left + Pan.X, Zoom * clientY + Container.Top + Pan.Y); + } + + #region Ordering + + public void SendToBack(SelectableModel model) + { + var minOrder = GetMinOrder(); + if (model.Order == minOrder) + return; + + if (!_orderedSelectables.Remove(model)) + return; + + _orderedSelectables.Insert(0, model); + + // Todo: can optimize this by only updating the order of items before model + Batch(() => + { + SuspendSorting = true; + for (var i = 0; i < _orderedSelectables.Count; i++) + { + _orderedSelectables[i].Order = i + 1; + } + SuspendSorting = false; + }); + } + + public void SendToFront(SelectableModel model) + { + var maxOrder = GetMaxOrder(); + if (model.Order == maxOrder) + return; + + if (!_orderedSelectables.Remove(model)) + return; + + _orderedSelectables.Add(model); + + SuspendSorting = true; + model.Order = maxOrder + 1; + SuspendSorting = false; + Refresh(); + } - public int GetMinOrder() - { - return _orderedSelectables.Count > 0 ? _orderedSelectables[0].Order : 0; - } + public int GetMinOrder() + { + return _orderedSelectables.Count > 0 ? _orderedSelectables[0].Order : 0; + } - public int GetMaxOrder() - { - return _orderedSelectables.Count > 0 ? _orderedSelectables[^1].Order : 0; - } + public int GetMaxOrder() + { + return _orderedSelectables.Count > 0 ? _orderedSelectables[^1].Order : 0; + } - /// - /// Sorts the list of selectables based on their order - /// - public void RefreshOrders(bool refresh = true) - { - _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); + /// + /// Sorts the list of selectables based on their order + /// + public void RefreshOrders(bool refresh = true) + { + _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); - if (refresh) - { - Refresh(); - } - } + if (refresh) + { + Refresh(); + } + } - private void OnSelectableAdded(SelectableModel model) - { - var maxOrder = GetMaxOrder(); - _orderedSelectables.Add(model); + private void OnSelectableAdded(SelectableModel model) + { + var maxOrder = GetMaxOrder(); + _orderedSelectables.Add(model); - if (model.Order == 0) - { - model.Order = maxOrder + 1; - } + if (model.Order == 0) + { + model.Order = maxOrder + 1; + } - model.OrderChanged += OnModelOrderChanged; - } + model.OrderChanged += OnModelOrderChanged; + } - private void OnSelectableRemoved(SelectableModel model) - { - model.OrderChanged -= OnModelOrderChanged; - _orderedSelectables.Remove(model); - } + private void OnSelectableRemoved(SelectableModel model) + { + model.OrderChanged -= OnModelOrderChanged; + _orderedSelectables.Remove(model); + } - private void OnModelOrderChanged(Model model) - { - if (SuspendSorting) - return; + private void OnModelOrderChanged(Model model) + { + if (SuspendSorting) + return; - RefreshOrders(); - } + RefreshOrders(); + } - #endregion + #endregion - #region Events + #region Events - public void TriggerPointerDown(Model? model, PointerEventArgs e) => PointerDown?.Invoke(model, e); + public void TriggerPointerDown(Model? model, PointerEventArgs e) => PointerDown?.Invoke(model, e); - public void TriggerPointerMove(Model? model, PointerEventArgs e) => PointerMove?.Invoke(model, e); + public void TriggerPointerMove(Model? model, PointerEventArgs e) => PointerMove?.Invoke(model, e); - public void TriggerPointerUp(Model? model, PointerEventArgs e) => PointerUp?.Invoke(model, e); + public void TriggerPointerUp(Model? model, PointerEventArgs e) => PointerUp?.Invoke(model, e); - public void TriggerPointerEnter(Model? model, PointerEventArgs e) => PointerEnter?.Invoke(model, e); + public void TriggerPointerEnter(Model? model, PointerEventArgs e) => PointerEnter?.Invoke(model, e); - public void TriggerPointerLeave(Model? model, PointerEventArgs e) => PointerLeave?.Invoke(model, e); + public void TriggerPointerLeave(Model? model, PointerEventArgs e) => PointerLeave?.Invoke(model, e); - public void TriggerKeyDown(KeyboardEventArgs e) => KeyDown?.Invoke(e); + public void TriggerKeyDown(KeyboardEventArgs e) => KeyDown?.Invoke(e); - public void TriggerWheel(WheelEventArgs e) => Wheel?.Invoke(e); + public void TriggerWheel(WheelEventArgs e) => Wheel?.Invoke(e); - public void TriggerPointerClick(Model? model, PointerEventArgs e) => PointerClick?.Invoke(model, e); + public void TriggerPointerClick(Model? model, PointerEventArgs e) => PointerClick?.Invoke(model, e); - public void TriggerPointerDoubleClick(Model? model, PointerEventArgs e) => PointerDoubleClick?.Invoke(model, e); + public void TriggerPointerDoubleClick(Model? model, PointerEventArgs e) => PointerDoubleClick?.Invoke(model, e); - #endregion + #endregion } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs index c15331e4..91e7d8ce 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs @@ -4,12 +4,12 @@ namespace Blazor.Diagrams.Core.Positions.Resizing { - public interface IResizerProvider : IPositionProvider - { - public string? Class { get; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); - public void OnPointerMove(Model? model, PointerEventArgs args); - public void OnPointerMove(WheelEventArgs args); - public void OnResizeEnd(Model? model, PointerEventArgs args); - } + public interface IResizerProvider : IPositionProvider + { + public string? Class { get; } + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); + public void OnPointerMove(Model? model, PointerEventArgs args); + public void OnPointerMove(WheelEventArgs args); + public void OnResizeEnd(Model? model, PointerEventArgs args); + } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs index 657b08b2..d4fd868b 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -10,152 +10,152 @@ namespace Blazor.Diagrams.Core.Tests.Behaviors; public class DragMovablesBehaviorTests { - [Fact] - public void Behavior_ShouldCallSetPosition() - { - // Arrange - var diagram = new TestDiagram(); - var nodeMock = new Mock(Point.Zero); - var node = diagram.Nodes.Add(nodeMock.Object); - diagram.SelectModel(node, false); - - // Act - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - nodeMock.Verify(n => n.SetPosition(50, 50), Times.Once); - } - - [Theory] - [InlineData(false, 0, 0, 45, 45)] - [InlineData(true, 0, 0, 35, 35)] - [InlineData(false, 3, 3, 45, 45)] - [InlineData(true, 3, 3, 50, 50)] - public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, double initialX, double initialY, double deltaX, double deltaY) - { - // Arrange - var diagram = new TestDiagram(new DiagramOptions - { - GridSize = 15, - GridSnapToCenter = gridSnapToCenter - }); - var nodeMock = new Mock(Point.Zero); - var node = diagram.Nodes.Add(nodeMock.Object); - node.Size = new Size(20, 20); - node.Position = new Point(initialX, initialY); - diagram.SelectModel(node, false); - - // Act - //Move 40px in X and Y - diagram.TriggerPointerDown(node, - new PointerEventArgs(20, 20, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(60, 60, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - nodeMock.Verify(n => n.SetPosition(deltaX, deltaY), Times.Once); - } - - [Fact] - public void Behavior_ShouldTriggerMoved() - { - // Arrange - var diagram = new TestDiagram(); - var node = diagram.Nodes.Add(new NodeModel(Point.Zero)); - var movedTrigger = false; - node.Moved += m => movedTrigger = true; - diagram.SelectModel(node, false); - - // Act - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerUp(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - movedTrigger.Should().BeTrue(); - } - - [Fact] - public void Behavior_ShouldNotTriggerMoved_WhenMovableDidntMove() - { - // Arrange - var diagram = new TestDiagram(); - var node = diagram.Nodes.Add(new NodeModel(Point.Zero)); - var movedTrigger = false; - node.Moved += m => movedTrigger = true; - diagram.SelectModel(node, false); - - // Act - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerUp(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - movedTrigger.Should().BeFalse(); - } - - [Fact] - public void Behavior_ShouldNotCallSetPosition_WhenGroupHasNoAutoSize() - { - // Arrange - var diagram = new TestDiagram(); - var nodeMock = new Mock(Point.Zero); - var group = new GroupModel(new[] { nodeMock.Object }, autoSize: false); - var node = diagram.Nodes.Add(nodeMock.Object); - diagram.SelectModel(node, false); - - // Act - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - nodeMock.Verify(n => n.SetPosition(50, 50), Times.Never); - } - - [Fact] - public void Behavior_ShouldCallSetPosition_WhenGroupHasAutoSize() - { - // Arrange - var diagram = new TestDiagram(); - var nodeMock = new Mock(Point.Zero); - var group = new GroupModel(new[] { nodeMock.Object }, autoSize: true); - var node = diagram.Nodes.Add(nodeMock.Object); - diagram.SelectModel(node, false); - - // Act - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - nodeMock.Verify(n => n.SetPosition(50, 50), Times.Once); - } - - [Fact] - public void Behavior_ShouldCallSetPosition_WhenWheelIsTriggered() - { - // Arrange - var diagram = new TestDiagram(); - var nodeMock = new Mock(Point.Zero); - var node = diagram.Nodes.Add(nodeMock.Object); - diagram.SelectModel(node, false); - - // Act - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 100, 0, 0)); - - // Assert - nodeMock.Verify(n => n.SetPosition(100, 100), Times.Once); - } + [Fact] + public void Behavior_ShouldCallSetPosition() + { + // Arrange + var diagram = new TestDiagram(); + var nodeMock = new Mock(Point.Zero); + var node = diagram.Nodes.Add(nodeMock.Object); + diagram.SelectModel(node, false); + + // Act + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + nodeMock.Verify(n => n.SetPosition(50, 50), Times.Once); + } + + [Theory] + [InlineData(false, 0, 0, 45, 45)] + [InlineData(true, 0, 0, 35, 35)] + [InlineData(false, 3, 3, 45, 45)] + [InlineData(true, 3, 3, 50, 50)] + public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, double initialX, double initialY, double deltaX, double deltaY) + { + // Arrange + var diagram = new TestDiagram(new DiagramOptions + { + GridSize = 15, + GridSnapToCenter = gridSnapToCenter + }); + var nodeMock = new Mock(Point.Zero); + var node = diagram.Nodes.Add(nodeMock.Object); + node.Size = new Size(20, 20); + node.Position = new Point(initialX, initialY); + diagram.SelectModel(node, false); + + // Act + //Move 40px in X and Y + diagram.TriggerPointerDown(node, + new PointerEventArgs(20, 20, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(60, 60, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + nodeMock.Verify(n => n.SetPosition(deltaX, deltaY), Times.Once); + } + + [Fact] + public void Behavior_ShouldTriggerMoved() + { + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel(Point.Zero)); + var movedTrigger = false; + node.Moved += m => movedTrigger = true; + diagram.SelectModel(node, false); + + // Act + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerUp(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + movedTrigger.Should().BeTrue(); + } + + [Fact] + public void Behavior_ShouldNotTriggerMoved_WhenMovableDidntMove() + { + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel(Point.Zero)); + var movedTrigger = false; + node.Moved += m => movedTrigger = true; + diagram.SelectModel(node, false); + + // Act + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerUp(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + movedTrigger.Should().BeFalse(); + } + + [Fact] + public void Behavior_ShouldNotCallSetPosition_WhenGroupHasNoAutoSize() + { + // Arrange + var diagram = new TestDiagram(); + var nodeMock = new Mock(Point.Zero); + var group = new GroupModel(new[] { nodeMock.Object }, autoSize: false); + var node = diagram.Nodes.Add(nodeMock.Object); + diagram.SelectModel(node, false); + + // Act + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + nodeMock.Verify(n => n.SetPosition(50, 50), Times.Never); + } + + [Fact] + public void Behavior_ShouldCallSetPosition_WhenGroupHasAutoSize() + { + // Arrange + var diagram = new TestDiagram(); + var nodeMock = new Mock(Point.Zero); + var group = new GroupModel(new[] { nodeMock.Object }, autoSize: true); + var node = diagram.Nodes.Add(nodeMock.Object); + diagram.SelectModel(node, false); + + // Act + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + nodeMock.Verify(n => n.SetPosition(50, 50), Times.Once); + } + + [Fact] + public void Behavior_ShouldCallSetPosition_WhenWheelIsTriggered() + { + // Arrange + var diagram = new TestDiagram(); + var nodeMock = new Mock(Point.Zero); + var node = diagram.Nodes.Add(nodeMock.Object); + diagram.SelectModel(node, false); + + // Act + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 100, 0, 0)); + + // Assert + nodeMock.Verify(n => n.SetPosition(100, 100), Times.Once); + } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index 73fa046e..85a988b6 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -10,467 +10,467 @@ namespace Blazor.Diagrams.Core.Tests.Behaviors; public class DragNewLinkBehaviorTests { - [Fact] - public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnPort() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(100, 50)); - var port = node.AddPort(new PortModel(node) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - var link = diagram.Links.Single(); - var source = link.Source as SinglePortAnchor; - source.Should().NotBeNull(); - source!.Port.Should().BeSameAs(port); - var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().Be(100); - ongoingPosition.Y.Should().Be(100); - } - - [Fact] - public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var factoryCalled = false; - diagram.Options.Links.Factory = (d, s, ta) => - { - factoryCalled = true; - return new LinkModel(new SinglePortAnchor((s as PortModel)!), ta); - }; - var node = new NodeModel(position: new Point(100, 50)); - var port = node.AddPort(new PortModel(node) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - factoryCalled.Should().BeTrue(); - var link = diagram.Links.Single(); - var source = link.Source as SinglePortAnchor; - source.Should().NotBeNull(); - source!.Port.Should().BeSameAs(port); - var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().Be(100); - ongoingPosition.Y.Should().Be(100); - } - - [Fact] - public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(100, 50)); - var linkRefreshed = false; - var port = node.AddPort(new PortModel(node) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - var link = diagram.Links.Single(); - link.Changed += _ => linkRefreshed = true; - diagram.TriggerPointerMove(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - var source = link.Source as SinglePortAnchor; - var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().BeGreaterThan(145); - ongoingPosition.Y.Should().BeGreaterThan(145); - linkRefreshed.Should().BeTrue(); - } - - [Fact] - public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoomIsChanged() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - diagram.SetZoom(1.5); - var node = new NodeModel(position: new Point(100, 50)); - var linkRefreshed = false; - var port = node.AddPort(new PortModel(node) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - var link = diagram.Links.Single(); - link.Changed += _ => linkRefreshed = true; - diagram.TriggerPointerMove(null, - new PointerEventArgs(160, 160, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - var source = link.Source as SinglePortAnchor; - var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().BeApproximately(107.7, 0.1); - ongoingPosition.Y.Should().BeApproximately(101.7, 0.1); - linkRefreshed.Should().BeTrue(); - } - - [Fact] - public void Behavior_ShouldSnapToClosestPortAndRefreshPort_WhenSnappingIsEnabledAndPortIsInRadius() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - diagram.Options.Links.EnableSnapping = true; - diagram.Options.Links.SnappingRadius = 60; - var node1 = new NodeModel(position: new Point(100, 50)); - var node2 = new NodeModel(position: new Point(160, 50)); - diagram.Nodes.Add(new[] { node1, node2 }); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - var port2 = node2.AddPort(new PortModel(node2) - { - Initialized = true, - Position = new Point(170, 60), - Size = new Size(10, 20) - }); - var port2Refreshed = false; - port2.Changed += _ => port2Refreshed = true; - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - var link = diagram.Links.Single(); - var target = link.Target as SinglePortAnchor; - target.Should().NotBeNull(); - target!.Port.Should().BeSameAs(port2); - port2Refreshed.Should().BeTrue(); - } - - [Fact] - public void Behavior_ShouldNotSnapToPort_WhenSnappingIsEnabledAndPortIsNotInRadius() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - diagram.Options.Links.EnableSnapping = true; - diagram.Options.Links.SnappingRadius = 50; - var node1 = new NodeModel(position: new Point(100, 50)); - var node2 = new NodeModel(position: new Point(160, 50)); - diagram.Nodes.Add(new[] { node1, node2 }); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - var port2 = node2.AddPort(new PortModel(node2) - { - Initialized = true, - Position = new Point(170, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - var link = diagram.Links.Single(); - link.Target.Should().BeOfType(); - } - - [Fact] - public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNotInRadiusAnymore() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - diagram.Options.Links.EnableSnapping = true; - diagram.Options.Links.SnappingRadius = 56; - var node1 = new NodeModel(position: new Point(100, 50)); - var node2 = new NodeModel(position: new Point(160, 50)); - diagram.Nodes.Add(new[] { node1, node2 }); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - var port2 = node2.AddPort(new PortModel(node2) - { - Initialized = true, - Position = new Point(170, 60), - Size = new Size(10, 20) - }); - var port2Refreshes = 0; - port2.Changed += _ => port2Refreshes++; - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, - true)); // Move towards the other port - diagram.TriggerPointerMove(null, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, - true)); // Move back to unsnap - - // Assert - var link = diagram.Links.Single(); - var target = link.Target as SinglePortAnchor; - target.Should().BeNull(); - port2Refreshes.Should().Be(2); - } - - [Fact] - public void Behavior_ShouldRemoveLink_WhenMouseUpOnCanvasAndRequireTargetIsTrue() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(100, 50)); - var port = node.AddPort(new PortModel(node) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port, - new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerUp(null, - new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - diagram.Links.Should().BeEmpty(); - } - - [Fact] - public void Behavior_ShouldRemoveLink_WhenMouseUpOnSamePort() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(100, 50)); - var port = node.AddPort(new PortModel(node) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port, - new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerUp(port, - new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - diagram.Links.Should().BeEmpty(); - } - - [Fact] - public void Behavior_ShouldSetTarget_WhenMouseUp() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node1 = new NodeModel(position: new Point(100, 50)); - var node2 = new NodeModel(position: new Point(160, 50)); - diagram.Nodes.Add(new[] { node1, node2 }); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - var port2 = node2.AddPort(new PortModel(node2) - { - Initialized = true, - Position = new Point(170, 60), - Size = new Size(10, 20) - }); - var port2Refreshes = 0; - port2.Changed += _ => port2Refreshes++; - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerUp(port2, - new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - var link = diagram.Links.Single(); - var target = link.Target as SinglePortAnchor; - target.Should().NotBeNull(); - target!.Port.Should().BeSameAs(port2); - port2Refreshes.Should().Be(1); - } - - [Fact] - public void Behavior_ShouldNotCreateOngoingLink_WhenFactoryReturnsNull() - { - // Arrange - var diagram = new TestDiagram(); - diagram.Options.Links.Factory = (d, s, ta) => null; - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - - var node1 = new NodeModel(position: new Point(100, 50)); - diagram.Nodes.Add(node1); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - diagram.Links.Should().HaveCount(0); - } - - [Fact] - public void Behavior_ShouldTriggerLinkTargetAttached_WhenMouseUpOnOtherPort() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node1 = new NodeModel(position: new Point(100, 50)); - var node2 = new NodeModel(position: new Point(160, 50)); - diagram.Nodes.Add(new[] { node1, node2 }); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - var port2 = node2.AddPort(new PortModel(node2) - { - Initialized = true, - Position = new Point(170, 60), - Size = new Size(10, 20) - }); - var targetAttachedTriggers = 0; - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - diagram.Links.Single().TargetAttached += _ => targetAttachedTriggers++; - - diagram.TriggerPointerUp(port2, - new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - targetAttachedTriggers.Should().Be(1); - } - - [Fact] - public void Behavior_ShouldTriggerLinkTargetAttached_WhenLinkSnappedToPortAndMouseUp() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - diagram.Options.Links.EnableSnapping = true; - diagram.Options.Links.SnappingRadius = 60; - var node1 = new NodeModel(position: new Point(100, 50)); - var node2 = new NodeModel(position: new Point(160, 50)); - diagram.Nodes.Add(new[] { node1, node2 }); - var port1 = node1.AddPort(new PortModel(node1) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - var port2 = node2.AddPort(new PortModel(node2) - { - Initialized = true, - Position = new Point(170, 60), - Size = new Size(10, 20) - }); - var targetAttachedTriggers = 0; - - // Act - diagram.TriggerPointerDown(port1, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - diagram.TriggerPointerMove(null, - new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - diagram.Links.Single().TargetAttached += _ => targetAttachedTriggers++; - - diagram.TriggerPointerUp(null, - new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - targetAttachedTriggers.Should().Be(1); - } - - [Fact] - public void Behavior_ShouldUpdateOngoingPosition_WhenWheelIsTriggered() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(100, 50)); - var linkRefreshed = false; - var port = node.AddPort(new PortModel(node) - { - Initialized = true, - Position = new Point(110, 60), - Size = new Size(10, 20) - }); - - // Act - diagram.TriggerPointerDown(port, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - var link = diagram.Links.Single(); - link.Changed += _ => linkRefreshed = true; - diagram.TriggerPointerMove(null, - new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerWheel(new WheelEventArgs(150, 150, 0, 0, false, false, false, 100, 100, 0, 0)); - - // Assert - var source = link.Source as SinglePortAnchor; - var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().BeGreaterThan(245); - ongoingPosition.Y.Should().BeGreaterThan(245); - linkRefreshed.Should().BeTrue(); - } + [Fact] + public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnPort() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(100, 50)); + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + var link = diagram.Links.Single(); + var source = link.Source as SinglePortAnchor; + source.Should().NotBeNull(); + source!.Port.Should().BeSameAs(port); + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().Be(100); + ongoingPosition.Y.Should().Be(100); + } + + [Fact] + public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var factoryCalled = false; + diagram.Options.Links.Factory = (d, s, ta) => + { + factoryCalled = true; + return new LinkModel(new SinglePortAnchor((s as PortModel)!), ta); + }; + var node = new NodeModel(position: new Point(100, 50)); + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + factoryCalled.Should().BeTrue(); + var link = diagram.Links.Single(); + var source = link.Source as SinglePortAnchor; + source.Should().NotBeNull(); + source!.Port.Should().BeSameAs(port); + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().Be(100); + ongoingPosition.Y.Should().Be(100); + } + + [Fact] + public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(100, 50)); + var linkRefreshed = false; + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + var link = diagram.Links.Single(); + link.Changed += _ => linkRefreshed = true; + diagram.TriggerPointerMove(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + var source = link.Source as SinglePortAnchor; + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().BeGreaterThan(145); + ongoingPosition.Y.Should().BeGreaterThan(145); + linkRefreshed.Should().BeTrue(); + } + + [Fact] + public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoomIsChanged() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + diagram.SetZoom(1.5); + var node = new NodeModel(position: new Point(100, 50)); + var linkRefreshed = false; + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + var link = diagram.Links.Single(); + link.Changed += _ => linkRefreshed = true; + diagram.TriggerPointerMove(null, + new PointerEventArgs(160, 160, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + var source = link.Source as SinglePortAnchor; + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().BeApproximately(107.7, 0.1); + ongoingPosition.Y.Should().BeApproximately(101.7, 0.1); + linkRefreshed.Should().BeTrue(); + } + + [Fact] + public void Behavior_ShouldSnapToClosestPortAndRefreshPort_WhenSnappingIsEnabledAndPortIsInRadius() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + diagram.Options.Links.EnableSnapping = true; + diagram.Options.Links.SnappingRadius = 60; + var node1 = new NodeModel(position: new Point(100, 50)); + var node2 = new NodeModel(position: new Point(160, 50)); + diagram.Nodes.Add(new[] { node1, node2 }); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) + { + Initialized = true, + Position = new Point(170, 60), + Size = new Size(10, 20) + }); + var port2Refreshed = false; + port2.Changed += _ => port2Refreshed = true; + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + var link = diagram.Links.Single(); + var target = link.Target as SinglePortAnchor; + target.Should().NotBeNull(); + target!.Port.Should().BeSameAs(port2); + port2Refreshed.Should().BeTrue(); + } + + [Fact] + public void Behavior_ShouldNotSnapToPort_WhenSnappingIsEnabledAndPortIsNotInRadius() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + diagram.Options.Links.EnableSnapping = true; + diagram.Options.Links.SnappingRadius = 50; + var node1 = new NodeModel(position: new Point(100, 50)); + var node2 = new NodeModel(position: new Point(160, 50)); + diagram.Nodes.Add(new[] { node1, node2 }); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) + { + Initialized = true, + Position = new Point(170, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + var link = diagram.Links.Single(); + link.Target.Should().BeOfType(); + } + + [Fact] + public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNotInRadiusAnymore() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + diagram.Options.Links.EnableSnapping = true; + diagram.Options.Links.SnappingRadius = 56; + var node1 = new NodeModel(position: new Point(100, 50)); + var node2 = new NodeModel(position: new Point(160, 50)); + diagram.Nodes.Add(new[] { node1, node2 }); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) + { + Initialized = true, + Position = new Point(170, 60), + Size = new Size(10, 20) + }); + var port2Refreshes = 0; + port2.Changed += _ => port2Refreshes++; + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, + true)); // Move towards the other port + diagram.TriggerPointerMove(null, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, + true)); // Move back to unsnap + + // Assert + var link = diagram.Links.Single(); + var target = link.Target as SinglePortAnchor; + target.Should().BeNull(); + port2Refreshes.Should().Be(2); + } + + [Fact] + public void Behavior_ShouldRemoveLink_WhenMouseUpOnCanvasAndRequireTargetIsTrue() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(100, 50)); + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerUp(null, + new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + diagram.Links.Should().BeEmpty(); + } + + [Fact] + public void Behavior_ShouldRemoveLink_WhenMouseUpOnSamePort() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(100, 50)); + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerUp(port, + new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + diagram.Links.Should().BeEmpty(); + } + + [Fact] + public void Behavior_ShouldSetTarget_WhenMouseUp() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node1 = new NodeModel(position: new Point(100, 50)); + var node2 = new NodeModel(position: new Point(160, 50)); + diagram.Nodes.Add(new[] { node1, node2 }); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) + { + Initialized = true, + Position = new Point(170, 60), + Size = new Size(10, 20) + }); + var port2Refreshes = 0; + port2.Changed += _ => port2Refreshes++; + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerUp(port2, + new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + var link = diagram.Links.Single(); + var target = link.Target as SinglePortAnchor; + target.Should().NotBeNull(); + target!.Port.Should().BeSameAs(port2); + port2Refreshes.Should().Be(1); + } + + [Fact] + public void Behavior_ShouldNotCreateOngoingLink_WhenFactoryReturnsNull() + { + // Arrange + var diagram = new TestDiagram(); + diagram.Options.Links.Factory = (d, s, ta) => null; + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + + var node1 = new NodeModel(position: new Point(100, 50)); + diagram.Nodes.Add(node1); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + diagram.Links.Should().HaveCount(0); + } + + [Fact] + public void Behavior_ShouldTriggerLinkTargetAttached_WhenMouseUpOnOtherPort() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node1 = new NodeModel(position: new Point(100, 50)); + var node2 = new NodeModel(position: new Point(160, 50)); + diagram.Nodes.Add(new[] { node1, node2 }); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) + { + Initialized = true, + Position = new Point(170, 60), + Size = new Size(10, 20) + }); + var targetAttachedTriggers = 0; + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + diagram.Links.Single().TargetAttached += _ => targetAttachedTriggers++; + + diagram.TriggerPointerUp(port2, + new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + targetAttachedTriggers.Should().Be(1); + } + + [Fact] + public void Behavior_ShouldTriggerLinkTargetAttached_WhenLinkSnappedToPortAndMouseUp() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + diagram.Options.Links.EnableSnapping = true; + diagram.Options.Links.SnappingRadius = 60; + var node1 = new NodeModel(position: new Point(100, 50)); + var node2 = new NodeModel(position: new Point(160, 50)); + diagram.Nodes.Add(new[] { node1, node2 }); + var port1 = node1.AddPort(new PortModel(node1) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) + { + Initialized = true, + Position = new Point(170, 60), + Size = new Size(10, 20) + }); + var targetAttachedTriggers = 0; + + // Act + diagram.TriggerPointerDown(port1, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + diagram.TriggerPointerMove(null, + new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + diagram.Links.Single().TargetAttached += _ => targetAttachedTriggers++; + + diagram.TriggerPointerUp(null, + new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + targetAttachedTriggers.Should().Be(1); + } + + [Fact] + public void Behavior_ShouldUpdateOngoingPosition_WhenWheelIsTriggered() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(100, 50)); + var linkRefreshed = false; + var port = node.AddPort(new PortModel(node) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + var link = diagram.Links.Single(); + link.Changed += _ => linkRefreshed = true; + diagram.TriggerPointerMove(null, + new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerWheel(new WheelEventArgs(150, 150, 0, 0, false, false, false, 100, 100, 0, 0)); + + // Assert + var source = link.Source as SinglePortAnchor; + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().BeGreaterThan(245); + ongoingPosition.Y.Should().BeGreaterThan(245); + linkRefreshed.Should().BeTrue(); + } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs index a1f09a89..4181ce3d 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs @@ -6,39 +6,39 @@ namespace Blazor.Diagrams.Core.Tests.Behaviors { - public class ScrollBehaviorTests - { - [Fact] - public void Behavior_WhenBehaviorEnabled_ShouldScroll() - { - // Arrange - var diagram = new TestDiagram(); - diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); - diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); - diagram.Options.Zoom.ScaleFactor = 1.05; + public class ScrollBehaviorTests + { + [Fact] + public void Behavior_WhenBehaviorEnabled_ShouldScroll() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + diagram.Options.Zoom.ScaleFactor = 1.05; - // Act - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + // Act + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); - // Assert - Assert.Equal(-95, diagram.Pan.X, 0); - Assert.Equal(-190, diagram.Pan.Y, 0); - } + // Assert + Assert.Equal(-95, diagram.Pan.X, 0); + Assert.Equal(-190, diagram.Pan.Y, 0); + } - [Fact] - public void Behavior_WhenBehaviorDisabled_ShouldNotScroll() - { - // Arrange - var diagram = new TestDiagram(); - diagram.BehaviorOptions.DiagramWheelBehavior = null; - diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + [Fact] + public void Behavior_WhenBehaviorDisabled_ShouldNotScroll() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = null; + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); - // Act - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + // Act + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); - // Assert - Assert.Equal(0, diagram.Pan.X); - Assert.Equal(0, diagram.Pan.Y); - } - } + // Assert + Assert.Equal(0, diagram.Pan.X); + Assert.Equal(0, diagram.Pan.Y); + } + } } From ca6b4099a5ffeee969e7c130950716089a4a49ce Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Mon, 18 Dec 2023 14:14:30 +1100 Subject: [PATCH 12/27] Removed some blank lines --- src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 8ffeff84..38bf7fdf 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -56,13 +56,12 @@ private void OnPointerDown(Model? model, PointerEventArgs e) _moved = false; } - public void OnPointerMove(Model? model, PointerEventArgs e) + private void OnPointerMove(Model? model, PointerEventArgs e) { if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) return; _moved = true; - var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; @@ -95,7 +94,6 @@ private void moveNodes(Model? model, double deltaX, double deltaY) { var ndx = ApplyGridSize(deltaX + initialPosition.X); var ndy = ApplyGridSize(deltaY + initialPosition.Y); - if (Diagram.Options.GridSnapToCenter && movable is NodeModel node) { node.SetPosition(ndx - (node.Size?.Width ?? 0) / 2, ndy - (node.Size?.Height ?? 0) / 2); @@ -119,7 +117,6 @@ private void OnPointerUp(Model? model, PointerEventArgs e) movable.TriggerMoved(); } } - _initialPositions.Clear(); _totalMovedX = 0; _totalMovedY = 0; @@ -133,14 +130,12 @@ private double ApplyGridSize(double n) return n; var gridSize = Diagram.Options.GridSize.Value; - return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); } public override void Dispose() { _initialPositions.Clear(); - Diagram.PointerDown -= OnPointerDown; Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; From f4a3d3e859742da72a33efccd18fecb5ee511d0e Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Tue, 19 Dec 2023 11:54:36 +1100 Subject: [PATCH 13/27] Addressed PR comments --- .../Behaviors/DragMovablesBehavior.cs | 12 ++--- .../Behaviors/DragNewLinkBehavior.cs | 8 +-- .../Controls/Default/ResizeControl.cs | 4 +- .../Resizing/BottomLeftResizerProvider.cs | 49 +++++-------------- .../Resizing/BottomRightResizerProvider.cs | 43 +++++----------- .../Positions/Resizing/IResizerProvider.cs | 2 +- .../Resizing/TopLeftResizerProvider.cs | 49 +++++-------------- .../Resizing/TopRightResizerProvider.cs | 48 +++++------------- 8 files changed, 65 insertions(+), 150 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 38bf7fdf..729d531f 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -23,7 +23,7 @@ public DragMovablesBehavior(Diagram diagram) : base(diagram) Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnPointerMove; + Diagram.Wheel += OnWheel; } private void OnPointerDown(Model? model, PointerEventArgs e) @@ -68,14 +68,14 @@ private void OnPointerMove(Model? model, PointerEventArgs e) _totalMovedX += deltaX; _totalMovedY += deltaY; - moveNodes(model, _totalMovedX, _totalMovedY); + MoveNodes(model, _totalMovedX, _totalMovedY); _lastClientX = e.ClientX; _lastClientY = e.ClientY; } - public void OnPointerMove(WheelEventArgs e) + public void OnWheel(WheelEventArgs e) { if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) return; @@ -85,10 +85,10 @@ public void OnPointerMove(WheelEventArgs e) _totalMovedX += e.DeltaX; _totalMovedY += e.DeltaY; - moveNodes(null, _totalMovedX, _totalMovedY); + MoveNodes(null, _totalMovedX, _totalMovedY); } - private void moveNodes(Model? model, double deltaX, double deltaY) + private void MoveNodes(Model? model, double deltaX, double deltaY) { foreach (var (movable, initialPosition) in _initialPositions) { @@ -139,6 +139,6 @@ public override void Dispose() Diagram.PointerDown -= OnPointerDown; Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnPointerMove; + Diagram.Wheel -= OnWheel; } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index cca9fd98..bddca19e 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -20,7 +20,7 @@ public DragNewLinkBehavior(Diagram diagram) : base(diagram) Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnPointerMove; + Diagram.Wheel += OnWheel; } public void StartFrom(ILinkable source, double clientX, double clientY) @@ -79,7 +79,7 @@ private void OnPointerMove(Model? model, MouseEventArgs e) UpdateLinkPosition(e.ClientX, e.ClientY); } - private void OnPointerMove(WheelEventArgs e) + private void OnWheel(WheelEventArgs e) { if (OngoingLink == null) return; @@ -101,7 +101,7 @@ private void UpdateLinkPosition(double clientX, double clientY) } OngoingLink!.Refresh(); - OngoingLink!.RefreshLinks(); + OngoingLink.RefreshLinks(); } private void OnPointerUp(Model? model, MouseEventArgs e) @@ -183,6 +183,6 @@ public override void Dispose() Diagram.PointerDown -= OnPointerDown; Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnPointerMove; + Diagram.Wheel -= OnWheel; } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs index 3457605f..1c4da450 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs @@ -23,7 +23,7 @@ public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEve { _resizeProvider.OnResizeStart(diagram, model, e); diagram.PointerMove += _resizeProvider.OnPointerMove; - diagram.Wheel += _resizeProvider.OnPointerMove; + diagram.Wheel += _resizeProvider.OnWheel; diagram.PointerUp += _resizeProvider.OnResizeEnd; diagram.PointerUp += (_, _) => OnResizeEnd(diagram); @@ -33,7 +33,7 @@ public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEve void OnResizeEnd(Diagram diagram) { diagram.PointerMove -= _resizeProvider.OnPointerMove; - diagram.Wheel -= _resizeProvider.OnPointerMove; + diagram.Wheel -= _resizeProvider.OnWheel; diagram.PointerUp -= _resizeProvider.OnResizeEnd; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs index 66f34fe7..9969b047 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -40,51 +40,28 @@ public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) public void OnPointerMove(Model? model, PointerEventArgs e) { - if (_nodeModel is null || _lastClientX == null || _lastClientY == null) - { - return; - } - - var deltaX = (e.ClientX - _lastClientX.Value); - var deltaY = (e.ClientY - _lastClientY.Value); - - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - var height = _originalSize.Height + _totalMovedY; - var width = _originalSize.Width - _totalMovedX; - - var positionX = _originalPosition.X + _totalMovedX; - - var positionY = _originalPosition.Y; + if (_nodeModel is null) return; - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - positionX = _nodeModel.Position.X; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - positionY = _nodeModel.Position.Y; - } + var deltaX = (e.ClientX - _lastClientX!.Value); + var deltaY = (e.ClientY - _lastClientY!.Value); _lastClientX = e.ClientX; _lastClientY = e.ClientY; - _nodeModel.SetPosition(positionX, positionY); - _nodeModel.SetSize(width, height); + ResizeNode(deltaX, deltaY); } - public void OnPointerMove(WheelEventArgs e) + public void OnWheel(WheelEventArgs e) { - if (_nodeModel is null) - { - return; - } + if (_nodeModel is null) return; + + ResizeNode(e.DeltaX, e.DeltaY); + } - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; + public void ResizeNode(double deltaX, double deltaY) + { + _totalMovedX += deltaX; + _totalMovedY += deltaY; var height = _originalSize.Height + _totalMovedY; var width = _originalSize.Width - _totalMovedX; diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs index 843b1741..69ae3ed4 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -38,45 +38,28 @@ public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) public void OnPointerMove(Model? model, PointerEventArgs e) { - if (_nodeModel is null || _lastClientX == null || _lastClientY == null) - { - return; - } - - - var deltaX = (e.ClientX - _lastClientX.Value); - var deltaY = (e.ClientY - _lastClientY.Value); - - _totalMovedX += deltaX; - _totalMovedY += deltaY; + if (_nodeModel is null) return; - var height = _originalSize.Height + _totalMovedY; - var width = _originalSize.Width + _totalMovedX; - - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - } + var deltaX = (e.ClientX - _lastClientX!.Value); + var deltaY = (e.ClientY - _lastClientY!.Value); _lastClientX = e.ClientX; _lastClientY = e.ClientY; - _nodeModel.SetSize(width, height); + ResizeNode(deltaX, deltaY); } - public void OnPointerMove(WheelEventArgs e) + public void OnWheel(WheelEventArgs e) { - if (_nodeModel is null) - { - return; - } + if (_nodeModel is null) return; + + ResizeNode(e.DeltaX, e.DeltaY); + } - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; + public void ResizeNode(double deltaX, double deltaY) + { + _totalMovedX += deltaX; + _totalMovedY += deltaY; var height = _originalSize.Height + _totalMovedY; var width = _originalSize.Width + _totalMovedX; diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs index 91e7d8ce..e36f9119 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs @@ -9,7 +9,7 @@ public interface IResizerProvider : IPositionProvider public string? Class { get; } public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); public void OnPointerMove(Model? model, PointerEventArgs args); - public void OnPointerMove(WheelEventArgs args); + public void OnWheel(WheelEventArgs args); public void OnResizeEnd(Model? model, PointerEventArgs args); } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs index 6a0525c8..4184473f 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -40,51 +40,28 @@ public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) public void OnPointerMove(Model? model, PointerEventArgs e) { - if (_nodeModel is null || _lastClientX == null || _lastClientY == null) - { - return; - } - - var deltaX = (e.ClientX - _lastClientX.Value); - var deltaY = (e.ClientY - _lastClientY.Value); - - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - - var height = _originalSize.Height - _totalMovedY; - var width = _originalSize.Width - _totalMovedX; - - var positionX = _originalPosition.X + _totalMovedX; - var positionY = _originalPosition.Y + _totalMovedY; + if (_nodeModel is null) return; - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - positionX = _nodeModel.Position.X; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - positionY = _nodeModel.Position.Y; - } + var deltaX = (e.ClientX - _lastClientX!.Value); + var deltaY = (e.ClientY - _lastClientY!.Value); _lastClientX = e.ClientX; _lastClientY = e.ClientY; - _nodeModel.SetPosition(positionX, positionY); - _nodeModel.SetSize(width, height); + ResizeNode(deltaX, deltaY); } - public void OnPointerMove(WheelEventArgs e) + public void OnWheel(WheelEventArgs e) { - if (_nodeModel is null || _lastClientX == null || _lastClientY == null) - { - return; - } + if (_nodeModel is null) return; + + ResizeNode(e.DeltaX, e.DeltaY); + } - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; + public void ResizeNode(double deltaX, double deltaY) + { + _totalMovedX += deltaX; + _totalMovedY += deltaY; var height = _originalSize.Height - _totalMovedY; var width = _originalSize.Width - _totalMovedX; diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs index 987b149b..d0c7d1db 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -40,50 +40,28 @@ public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) public void OnPointerMove(Model? model, PointerEventArgs e) { - if (_nodeModel is null || _lastClientX == null || _lastClientY == null) - { - return; - } - - var deltaX = (e.ClientX - _lastClientX.Value); - var deltaY = (e.ClientY - _lastClientY.Value); - - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - var height = _originalSize.Height - _totalMovedY; - var width = _originalSize.Width + _totalMovedX; + if (_nodeModel is null) return; - var positionX = _originalPosition.X; - var positionY = _originalPosition.Y + _totalMovedY; - - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - positionX = _nodeModel.Position.X; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - positionY = _nodeModel.Position.Y; - } + var deltaX = (e.ClientX - _lastClientX!.Value); + var deltaY = (e.ClientY - _lastClientY!.Value); _lastClientX = e.ClientX; _lastClientY = e.ClientY; - _nodeModel.SetPosition(positionX, positionY); - _nodeModel.SetSize(width, height); + ResizeNode(deltaX, deltaY); } - public void OnPointerMove(WheelEventArgs e) + public void OnWheel(WheelEventArgs e) { - if (_nodeModel is null) - { - return; - } + if (_nodeModel is null) return; + + ResizeNode(e.DeltaX, e.DeltaY); + } - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; + public void ResizeNode(double deltaX, double deltaY) + { + _totalMovedX += deltaX; + _totalMovedY += deltaY; var height = _originalSize.Height - _totalMovedY; var width = _originalSize.Width + _totalMovedX; From d204bb92319c60aa5e454917c69ac8c5d415e5cb Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Tue, 19 Dec 2023 14:23:32 +1100 Subject: [PATCH 14/27] Added tests --- .../BottomLeftResizerProviderTests.cs | 31 +++++++++++++++++++ .../BottomRightResizerProviderTests.cs | 31 +++++++++++++++++++ .../Resizing/TopLeftResizerProviderTests.cs | 31 +++++++++++++++++++ .../Resizing/TopRightResizerProviderTests.cs | 30 ++++++++++++++++++ 4 files changed, 123 insertions(+) diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs index 79ef6c17..0a57183b 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs @@ -46,6 +46,37 @@ public void DragResizer_ShouldResizeNode() node.Size.Height.Should().Be(215); } + [Fact] + public void ScrollingWheel_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(300); + } + [Fact] public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() { diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs index 20ab2777..68476136 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs @@ -46,6 +46,37 @@ public void DragResizer_ShouldResizeNode() node.Size.Height.Should().Be(215); } + [Fact] + public void ScrollingWheel_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(300); + } + [Fact] public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() { diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs index 1cc715d5..9e239f13 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs @@ -46,6 +46,37 @@ public void DragResizer_ShouldResizeNode() node.Size.Height.Should().Be(185); } + [Fact] + public void ScrollingWheel_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(100); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(100); + } + [Fact] public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() { diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs index a138d524..bd4712a7 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs @@ -46,6 +46,36 @@ public void DragResizer_ShouldResizeNode() node.Size.Height.Should().Be(185); } + [Fact] + public void ScrollingWheel_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(100); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(100); + } + [Fact] public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() { From 80178747178588f0f007b323a3590076208006b0 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 4 Jan 2024 11:02:47 +1100 Subject: [PATCH 15/27] Fixed implementation for DragMovablesBehavior --- .../Behaviors/DebugEventsBehavior.cs | 2 +- .../Behaviors/DragMovablesBehavior.cs | 11 +++-- .../Behaviors/ScrollBehavior.cs | 9 ++++- .../Behaviors/VirtualizationBehavior.cs | 11 +++-- .../Behaviors/ZoomBehavior.cs | 40 +++++++++---------- src/Blazor.Diagrams.Core/Diagram.cs | 8 ++-- .../Components/Widgets/GridWidget.razor.cs | 13 ++++-- .../DiagramTests.cs | 4 +- 8 files changed, 57 insertions(+), 41 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs index c5f71078..cd801847 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs @@ -52,7 +52,7 @@ private void Nodes_Added(NodeModel obj) Console.WriteLine($"Nodes.Added, Nodes=[{obj}]"); } - private void Diagram_PanChanged() + private void Diagram_PanChanged(double deltaX, double deltaY) { Console.WriteLine($"PanChanged, Pan={Diagram.Pan}"); } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 729d531f..3a01eed7 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -23,7 +23,7 @@ public DragMovablesBehavior(Diagram diagram) : base(diagram) Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnWheel; + Diagram.PanChanged += OnPanChanged; } private void OnPointerDown(Model? model, PointerEventArgs e) @@ -74,16 +74,15 @@ private void OnPointerMove(Model? model, PointerEventArgs e) _lastClientY = e.ClientY; } - - public void OnWheel(WheelEventArgs e) + public void OnPanChanged(double deltaX, double deltaY) { if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) return; _moved = true; - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; + _totalMovedX += deltaX; + _totalMovedY += deltaY; MoveNodes(null, _totalMovedX, _totalMovedY); } @@ -139,6 +138,6 @@ public override void Dispose() Diagram.PointerDown -= OnPointerDown; Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnWheel; + Diagram.PanChanged -= OnPanChanged; } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs index fa3ef63e..cde088e7 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs @@ -18,8 +18,15 @@ protected override void OnDiagramWheel(WheelEventArgs e) var x = Diagram.Pan.X - (e.DeltaX / Diagram.Options.Zoom.ScaleFactor); var y = Diagram.Pan.Y - (e.DeltaY / Diagram.Options.Zoom.ScaleFactor); + Diagram.GetScreenPoint(x, y); - Diagram.SetPan(x, y); + var _lastClientX = e.ClientX - e.DeltaX; + var _lastClientY = e.ClientY - e.DeltaY; + + var deltaX = e.ClientX - _lastClientX + (Diagram.Pan.X - x); + var deltaY = e.ClientY - _lastClientY + (Diagram.Pan.Y - y); + + Diagram.SetPan(x, y, deltaX, deltaY); } } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs index 7d0573b0..c9d03bfb 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs @@ -12,11 +12,16 @@ public VirtualizationBehavior(Diagram diagram) : base(diagram) Diagram.ContainerChanged += CheckVisibility; } + private void CheckVisibility(double deltaX, double deltaY) + { + CheckVisibility(); + } + private void CheckVisibility() { if (!Diagram.Options.Virtualization.Enabled) return; - + if (Diagram.Container == null) return; @@ -49,11 +54,11 @@ private void CheckVisibility(Model model) { if (model is not IHasBounds ihb) return; - + var bounds = ihb.GetBounds(); if (bounds == null) return; - + var left = bounds.Left * Diagram.Zoom + Diagram.Pan.X; var top = bounds.Top * Diagram.Zoom + Diagram.Pan.Y; var right = left + bounds.Width * Diagram.Zoom; diff --git a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs index 1c2a4f5c..ebf44e58 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs @@ -15,31 +15,31 @@ protected override void OnDiagramWheel(WheelEventArgs e) if (Diagram.Container == null || e.DeltaY == 0 || !Diagram.Options.Zoom.Enabled || !IsBehaviorEnabled(e)) return; - var scale = Diagram.Options.Zoom.ScaleFactor; - var oldZoom = Diagram.Zoom; - var deltaY = Diagram.Options.Zoom.Inverse ? e.DeltaY * -1 : e.DeltaY; - var newZoom = deltaY > 0 ? oldZoom * scale : oldZoom / scale; - newZoom = Math.Clamp(newZoom, Diagram.Options.Zoom.Minimum, Diagram.Options.Zoom.Maximum); + var scale = Diagram.Options.Zoom.ScaleFactor; + var oldZoom = Diagram.Zoom; + var deltaY = Diagram.Options.Zoom.Inverse ? e.DeltaY * -1 : e.DeltaY; + var newZoom = deltaY > 0 ? oldZoom * scale : oldZoom / scale; + newZoom = Math.Clamp(newZoom, Diagram.Options.Zoom.Minimum, Diagram.Options.Zoom.Maximum); - if (newZoom < 0 || newZoom == Diagram.Zoom) - return; + if (newZoom < 0 || newZoom == Diagram.Zoom) + return; - // Other algorithms (based only on the changes in the zoom) don't work for our case - // This solution is taken as is from react-diagrams (ZoomCanvasAction) - var clientWidth = Diagram.Container.Width; - var clientHeight = Diagram.Container.Height; - var widthDiff = clientWidth * newZoom - clientWidth * oldZoom; - var heightDiff = clientHeight * newZoom - clientHeight * oldZoom; - var clientX = e.ClientX - Diagram.Container.Left; - var clientY = e.ClientY - Diagram.Container.Top; - var xFactor = (clientX - Diagram.Pan.X) / oldZoom / clientWidth; - var yFactor = (clientY - Diagram.Pan.Y) / oldZoom / clientHeight; - var newPanX = Diagram.Pan.X - widthDiff * xFactor; - var newPanY = Diagram.Pan.Y - heightDiff * yFactor; + // Other algorithms (based only on the changes in the zoom) don't work for our case + // This solution is taken as is from react-diagrams (ZoomCanvasAction) + var clientWidth = Diagram.Container.Width; + var clientHeight = Diagram.Container.Height; + var widthDiff = clientWidth * newZoom - clientWidth * oldZoom; + var heightDiff = clientHeight * newZoom - clientHeight * oldZoom; + var clientX = e.ClientX - Diagram.Container.Left; + var clientY = e.ClientY - Diagram.Container.Top; + var xFactor = (clientX - Diagram.Pan.X) / oldZoom / clientWidth; + var yFactor = (clientY - Diagram.Pan.Y) / oldZoom / clientHeight; + var newPanX = Diagram.Pan.X - widthDiff * xFactor; + var newPanY = Diagram.Pan.Y - heightDiff * yFactor; Diagram.Batch(() => { - Diagram.SetPan(newPanX, newPanY); + Diagram.SetPan(newPanX, newPanY, 0, 0); Diagram.SetZoom(newZoom); }); } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index c80f64d2..7f1bc740 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -34,7 +34,7 @@ public abstract class Diagram public event Action? PointerDoubleClick; public event Action? SelectionChanged; - public event Action? PanChanged; + public event Action? PanChanged; public event Action? ZoomChanged; public event Action? ContainerChanged; public event Action? Changed; @@ -237,17 +237,17 @@ public void ZoomToFit(double margin = 10) Refresh(); } - public void SetPan(double x, double y) + public void SetPan(double x, double y, double deltaX, double deltaY) { Pan = new Point(x, y); - PanChanged?.Invoke(); + PanChanged?.Invoke(deltaX, deltaY); Refresh(); } public void UpdatePan(double deltaX, double deltaY) { Pan = Pan.Add(deltaX, deltaY); - PanChanged?.Invoke(); + PanChanged?.Invoke(deltaX, deltaY); Refresh(); } diff --git a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs index 596f985f..ba3ac954 100644 --- a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs @@ -1,7 +1,7 @@ -using System; -using System.Text; using Blazor.Diagrams.Core.Extensions; using Microsoft.AspNetCore.Components; +using System; +using System.Text; namespace Blazor.Diagrams.Components.Widgets; @@ -11,7 +11,7 @@ public partial class GridWidget : IDisposable private double _scaledSize; private double _posX; private double _posY; - + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; [Parameter] public double Size { get; set; } = 20; [Parameter] public double ZoomThreshold { get; set; } = 0; @@ -38,6 +38,11 @@ protected override void OnParametersSet() _visible = BlazorDiagram.Zoom > ZoomThreshold; } + private void RefreshPosition(double deltaX, double deltaY) + { + RefreshPosition(); + } + private void RefreshPosition() { _posX = BlazorDiagram.Pan.X; @@ -67,7 +72,7 @@ private string GenerateStyle() default: throw new ArgumentOutOfRangeException(); } - + return sb.ToString(); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs index b596c9ac..34654be4 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs @@ -84,7 +84,7 @@ public void ZoomToFit_ShouldTriggerAppropriateEvents() // Act diagram.Changed += () => refreshes++; diagram.ZoomChanged += () => zoomChanges++; - diagram.PanChanged += () => panChanges++; + diagram.PanChanged += (x, y) => panChanges++; diagram.ZoomToFit(10); // Assert @@ -122,7 +122,7 @@ public void ZoomOptions_ThrowExceptionWhenLessThan0(double zoomValue) var diagram = new TestDiagram(); Assert.Throws(() => diagram.Options.Zoom.Minimum = zoomValue); } - + [Fact] public void SetContainer_ShouldAcceptNullGracefully() { From b6ac41be26230ab078c4bd582f8ed92b303146bb Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 4 Jan 2024 11:16:56 +1100 Subject: [PATCH 16/27] Started using pan changed for resize controls --- src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs | 4 ++-- .../Positions/Resizing/BottomLeftResizerProvider.cs | 4 ++-- .../Positions/Resizing/BottomRightResizerProvider.cs | 4 ++-- .../Positions/Resizing/IResizerProvider.cs | 2 +- .../Positions/Resizing/TopLeftResizerProvider.cs | 4 ++-- .../Positions/Resizing/TopRightResizerProvider.cs | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs index 1c4da450..9a01aafc 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs @@ -23,7 +23,7 @@ public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEve { _resizeProvider.OnResizeStart(diagram, model, e); diagram.PointerMove += _resizeProvider.OnPointerMove; - diagram.Wheel += _resizeProvider.OnWheel; + diagram.PanChanged += _resizeProvider.OnPanChanged; diagram.PointerUp += _resizeProvider.OnResizeEnd; diagram.PointerUp += (_, _) => OnResizeEnd(diagram); @@ -33,7 +33,7 @@ public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEve void OnResizeEnd(Diagram diagram) { diagram.PointerMove -= _resizeProvider.OnPointerMove; - diagram.Wheel -= _resizeProvider.OnWheel; + diagram.PanChanged -= _resizeProvider.OnPanChanged; diagram.PointerUp -= _resizeProvider.OnResizeEnd; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs index 9969b047..4725fc5a 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -51,11 +51,11 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnWheel(WheelEventArgs e) + public void OnPanChanged(double deltaX, double deltaY) { if (_nodeModel is null) return; - ResizeNode(e.DeltaX, e.DeltaY); + ResizeNode(deltaX, deltaY); } public void ResizeNode(double deltaX, double deltaY) diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs index 69ae3ed4..5730a834 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -49,11 +49,11 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnWheel(WheelEventArgs e) + public void OnPanChanged(double deltaX, double deltaY) { if (_nodeModel is null) return; - ResizeNode(e.DeltaX, e.DeltaY); + ResizeNode(deltaX, deltaY); } public void ResizeNode(double deltaX, double deltaY) diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs index e36f9119..96e16e18 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs @@ -9,7 +9,7 @@ public interface IResizerProvider : IPositionProvider public string? Class { get; } public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); public void OnPointerMove(Model? model, PointerEventArgs args); - public void OnWheel(WheelEventArgs args); + public void OnPanChanged(double deltaX, double deltaY); public void OnResizeEnd(Model? model, PointerEventArgs args); } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs index 4184473f..cbe7d3d3 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -51,11 +51,11 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnWheel(WheelEventArgs e) + public void OnPanChanged(double deltaX, double deltaY) { if (_nodeModel is null) return; - ResizeNode(e.DeltaX, e.DeltaY); + ResizeNode(deltaX, deltaY); } public void ResizeNode(double deltaX, double deltaY) diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs index d0c7d1db..0babc8fc 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -51,11 +51,11 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnWheel(WheelEventArgs e) + public void OnPanChanged(double deltaX, double deltaY) { if (_nodeModel is null) return; - ResizeNode(e.DeltaX, e.DeltaY); + ResizeNode(deltaX, deltaY); } public void ResizeNode(double deltaX, double deltaY) From 0e8e39aa0a2935760170a1b643b0c2681ed252e6 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 4 Jan 2024 12:06:59 +1100 Subject: [PATCH 17/27] Started using Pan Changed for DragNewLinkBehavior --- src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs | 2 +- .../Behaviors/DragMovablesBehavior.cs | 2 +- src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs | 8 ++++---- src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs | 2 +- .../Behaviors/VirtualizationBehavior.cs | 2 +- src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs | 2 +- src/Blazor.Diagrams.Core/Diagram.cs | 8 ++++---- .../Positions/Resizing/BottomLeftResizerProvider.cs | 2 +- .../Positions/Resizing/BottomRightResizerProvider.cs | 2 +- .../Positions/Resizing/IResizerProvider.cs | 2 +- .../Positions/Resizing/TopLeftResizerProvider.cs | 2 +- .../Positions/Resizing/TopRightResizerProvider.cs | 2 +- .../Components/Widgets/GridWidget.razor.cs | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs index cd801847..afd1f0c7 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs @@ -52,7 +52,7 @@ private void Nodes_Added(NodeModel obj) Console.WriteLine($"Nodes.Added, Nodes=[{obj}]"); } - private void Diagram_PanChanged(double deltaX, double deltaY) + private void Diagram_PanChanged(double deltaX, double deltaY, double clientX, double clientY) { Console.WriteLine($"PanChanged, Pan={Diagram.Pan}"); } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 3a01eed7..0ea3dc66 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -74,7 +74,7 @@ private void OnPointerMove(Model? model, PointerEventArgs e) _lastClientY = e.ClientY; } - public void OnPanChanged(double deltaX, double deltaY) + public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) { if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) return; diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index bddca19e..a0b07404 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -20,7 +20,7 @@ public DragNewLinkBehavior(Diagram diagram) : base(diagram) Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnWheel; + Diagram.PanChanged += OnPanChanged; } public void StartFrom(ILinkable source, double clientX, double clientY) @@ -79,12 +79,12 @@ private void OnPointerMove(Model? model, MouseEventArgs e) UpdateLinkPosition(e.ClientX, e.ClientY); } - private void OnWheel(WheelEventArgs e) + private void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) { if (OngoingLink == null) return; - UpdateLinkPosition(e.ClientX + e.DeltaX, e.ClientY + e.DeltaY); + UpdateLinkPosition(clientX + deltaX, clientY + deltaY); } private void UpdateLinkPosition(double clientX, double clientY) @@ -183,6 +183,6 @@ public override void Dispose() Diagram.PointerDown -= OnPointerDown; Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnWheel; + Diagram.PanChanged -= OnPanChanged; } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs index cde088e7..bb745489 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs @@ -26,7 +26,7 @@ protected override void OnDiagramWheel(WheelEventArgs e) var deltaX = e.ClientX - _lastClientX + (Diagram.Pan.X - x); var deltaY = e.ClientY - _lastClientY + (Diagram.Pan.Y - y); - Diagram.SetPan(x, y, deltaX, deltaY); + Diagram.SetPan(x, y, deltaX, deltaY, _lastClientX, _lastClientY); } } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs index c9d03bfb..8674366d 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs @@ -12,7 +12,7 @@ public VirtualizationBehavior(Diagram diagram) : base(diagram) Diagram.ContainerChanged += CheckVisibility; } - private void CheckVisibility(double deltaX, double deltaY) + private void CheckVisibility(double deltaX, double deltaY, double clientX, double clientY) { CheckVisibility(); } diff --git a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs index ebf44e58..3489abe7 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs @@ -39,7 +39,7 @@ protected override void OnDiagramWheel(WheelEventArgs e) Diagram.Batch(() => { - Diagram.SetPan(newPanX, newPanY, 0, 0); + Diagram.SetPan(newPanX, newPanY, 0, 0, 0, 0); Diagram.SetZoom(newZoom); }); } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 7f1bc740..6160ff2b 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -34,7 +34,7 @@ public abstract class Diagram public event Action? PointerDoubleClick; public event Action? SelectionChanged; - public event Action? PanChanged; + public event Action? PanChanged; public event Action? ZoomChanged; public event Action? ContainerChanged; public event Action? Changed; @@ -237,17 +237,17 @@ public void ZoomToFit(double margin = 10) Refresh(); } - public void SetPan(double x, double y, double deltaX, double deltaY) + public void SetPan(double x, double y, double deltaX, double deltaY, double clientX, double clientY) { Pan = new Point(x, y); - PanChanged?.Invoke(deltaX, deltaY); + PanChanged?.Invoke(deltaX, deltaY, clientX, clientY); Refresh(); } public void UpdatePan(double deltaX, double deltaY) { Pan = Pan.Add(deltaX, deltaY); - PanChanged?.Invoke(deltaX, deltaY); + PanChanged?.Invoke(deltaX, deltaY, 0, 0); Refresh(); } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs index 4725fc5a..80fb7f00 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -51,7 +51,7 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnPanChanged(double deltaX, double deltaY) + public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) { if (_nodeModel is null) return; diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs index 5730a834..56e6fe9c 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -49,7 +49,7 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnPanChanged(double deltaX, double deltaY) + public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) { if (_nodeModel is null) return; diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs index 96e16e18..a271faf1 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs @@ -9,7 +9,7 @@ public interface IResizerProvider : IPositionProvider public string? Class { get; } public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); public void OnPointerMove(Model? model, PointerEventArgs args); - public void OnPanChanged(double deltaX, double deltaY); + public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY); public void OnResizeEnd(Model? model, PointerEventArgs args); } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs index cbe7d3d3..779107f5 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -51,7 +51,7 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnPanChanged(double deltaX, double deltaY) + public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) { if (_nodeModel is null) return; diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs index 0babc8fc..6c8220c9 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -51,7 +51,7 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnPanChanged(double deltaX, double deltaY) + public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) { if (_nodeModel is null) return; diff --git a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs index ba3ac954..ec1914de 100644 --- a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs @@ -38,7 +38,7 @@ protected override void OnParametersSet() _visible = BlazorDiagram.Zoom > ZoomThreshold; } - private void RefreshPosition(double deltaX, double deltaY) + private void RefreshPosition(double deltaX, double deltaY, double clientX, double clientY) { RefreshPosition(); } From 735834227e48ea4dcdd5a59d8d880fcfc098e3f2 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 4 Jan 2024 13:51:10 +1100 Subject: [PATCH 18/27] Fixed error in test --- tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs index 34654be4..43bf9a84 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs @@ -84,7 +84,7 @@ public void ZoomToFit_ShouldTriggerAppropriateEvents() // Act diagram.Changed += () => refreshes++; diagram.ZoomChanged += () => zoomChanges++; - diagram.PanChanged += (x, y) => panChanges++; + diagram.PanChanged += (deltaX, deltaY, clientX, clientY) => panChanges++; diagram.ZoomToFit(10); // Assert From d0deab77874f93f1fa59a0b592ca068a11140f82 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 4 Jan 2024 16:29:30 +1100 Subject: [PATCH 19/27] Refactored ResizeProviders for better reusability --- .../Controls/Default/ResizeControl.cs | 4 +- .../Resizing/BottomLeftResizerProvider.cs | 106 ++--------------- .../Resizing/BottomRightResizerProvider.cs | 97 ++-------------- .../Positions/Resizing/IResizerProvider.cs | 15 --- .../Positions/Resizing/ResizerProvider.cs | 108 ++++++++++++++++++ .../Resizing/TopLeftResizerProvider.cs | 107 ++--------------- .../Resizing/TopRightResizerProvider.cs | 106 ++--------------- .../Controls/ResizeControlTests.cs | 10 +- .../Controls/ResizeControlWidgetTests.cs | 2 +- 9 files changed, 162 insertions(+), 393 deletions(-) delete mode 100644 src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs create mode 100644 src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs diff --git a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs index 9a01aafc..1818f996 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs @@ -8,9 +8,9 @@ namespace Blazor.Diagrams.Core.Controls.Default { public class ResizeControl : ExecutableControl { - private readonly IResizerProvider _resizeProvider; + private readonly ResizerProvider _resizeProvider; - public ResizeControl(IResizerProvider resizeProvider) + public ResizeControl(ResizerProvider resizeProvider) { _resizeProvider = resizeProvider; } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs index 80fb7f00..e5d4f48a 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -1,99 +1,17 @@ -using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; - -namespace Blazor.Diagrams.Core.Positions.Resizing +namespace Blazor.Diagrams.Core.Positions.Resizing { - public class BottomLeftResizerProvider : IResizerProvider + public class BottomLeftResizerProvider : ResizerProvider { - public string? Class => "bottomleft"; - - private Size _originalSize = null!; - private Point _originalPosition = null!; - private NodeModel _nodeModel = null!; - private double? _lastClientX; - private double? _lastClientY; - private double _totalMovedX = 0; - private double _totalMovedY = 0; - - public Point? GetPosition(Model model) - { - if (model is NodeModel nodeModel && nodeModel.Size is not null) - { - return new Point(nodeModel.Position.X - 5, nodeModel.Position.Y + nodeModel.Size.Height + 5); - } - return null; - } - - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) - { - if (model is NodeModel nodeModel) - { - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalSize = nodeModel.Size!; - _nodeModel = nodeModel; - } - } - - public void OnPointerMove(Model? model, PointerEventArgs e) - { - if (_nodeModel is null) return; - - var deltaX = (e.ClientX - _lastClientX!.Value); - var deltaY = (e.ClientY - _lastClientY!.Value); - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - - ResizeNode(deltaX, deltaY); - } - - public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) - { - if (_nodeModel is null) return; - - ResizeNode(deltaX, deltaY); - } - - public void ResizeNode(double deltaX, double deltaY) - { - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - var height = _originalSize.Height + _totalMovedY; - var width = _originalSize.Width - _totalMovedX; - - var positionX = _originalPosition.X + _totalMovedX; - var positionY = _originalPosition.Y; - - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - positionX = _nodeModel.Position.X; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - positionY = _nodeModel.Position.Y; - } - - _nodeModel.SetPosition(positionX, positionY); - _nodeModel.SetSize(width, height); - } - - public void OnResizeEnd(Model? model, PointerEventArgs args) - { - _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _originalPosition = null!; - _totalMovedY = 0; - _lastClientX = null; - _lastClientY = null; - _nodeModel = null!; - } + override public string? Class => "bottomleft"; + + override public double HeightOffset => 5; + override public double WidthOffset => -5; + override public bool ShouldUseWidth => false; + override public bool ShouldUseHeight => true; + override public bool ShouldChangeXPositionOnResize => true; + override public bool ShouldChangeYPositionOnResize => false; + override public bool ShouldAddTotalMovedX => false; + override public bool ShouldAddTotalMovedY => true; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs index 56e6fe9c..05a98562 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -1,91 +1,16 @@ -using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; - -namespace Blazor.Diagrams.Core.Positions.Resizing +namespace Blazor.Diagrams.Core.Positions.Resizing { - public class BottomRightResizerProvider : IResizerProvider + public class BottomRightResizerProvider : ResizerProvider { - public string? Class => "bottomright"; - - private Size _originalSize = null!; - private double? _lastClientX; - private double? _lastClientY; - private NodeModel _nodeModel = null!; - private double _totalMovedX = 0; - private double _totalMovedY = 0; - - public Point? GetPosition(Model model) - { - if (model is NodeModel nodeModel && nodeModel.Size is not null) - { - return new Point(nodeModel.Position.X + nodeModel.Size.Width + 5, nodeModel.Position.Y + nodeModel.Size.Height + 5); - } - return null; - } - - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) - { - if (model is NodeModel nodeModel) - { - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _originalSize = nodeModel.Size!; - _nodeModel = nodeModel; - } - } - - public void OnPointerMove(Model? model, PointerEventArgs e) - { - if (_nodeModel is null) return; - - var deltaX = (e.ClientX - _lastClientX!.Value); - var deltaY = (e.ClientY - _lastClientY!.Value); - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - - ResizeNode(deltaX, deltaY); - } - - public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) - { - if (_nodeModel is null) return; - - ResizeNode(deltaX, deltaY); - } - - public void ResizeNode(double deltaX, double deltaY) - { - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - var height = _originalSize.Height + _totalMovedY; - var width = _originalSize.Width + _totalMovedX; - - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - } - - _nodeModel.SetSize(width, height); - } - - public void OnResizeEnd(Model? model, PointerEventArgs args) - { - _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _nodeModel = null!; - _totalMovedX = 0; - _totalMovedY = 0; - _lastClientX = null; - _lastClientY = null; - } + override public string? Class => "bottomright"; + override public double HeightOffset => 5; + override public double WidthOffset => 5; + override public bool ShouldUseWidth => true; + override public bool ShouldUseHeight => true; + override public bool ShouldChangeXPositionOnResize => false; + override public bool ShouldChangeYPositionOnResize => false; + override public bool ShouldAddTotalMovedX => true; + override public bool ShouldAddTotalMovedY => true; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs deleted file mode 100644 index a271faf1..00000000 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; - -namespace Blazor.Diagrams.Core.Positions.Resizing -{ - public interface IResizerProvider : IPositionProvider - { - public string? Class { get; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); - public void OnPointerMove(Model? model, PointerEventArgs args); - public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY); - public void OnResizeEnd(Model? model, PointerEventArgs args); - } -} diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs new file mode 100644 index 00000000..6de309e4 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs @@ -0,0 +1,108 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions.Resizing +{ + public abstract class ResizerProvider : IPositionProvider + { + abstract public string? Class { get; } + + private Size _originalSize = null!; + private Point _originalPosition = null!; + private double? _lastClientX; + private double? _lastClientY; + private NodeModel _nodeModel = null!; + private double _totalMovedX = 0; + private double _totalMovedY = 0; + + abstract public double WidthOffset { get; } + abstract public double HeightOffset { get; } + abstract public bool ShouldUseWidth { get; } + abstract public bool ShouldUseHeight { get; } + abstract public bool ShouldChangeXPositionOnResize { get; } + abstract public bool ShouldChangeYPositionOnResize { get; } + abstract public bool ShouldAddTotalMovedX { get; } + abstract public bool ShouldAddTotalMovedY { get; } + + virtual public Point? GetPosition(Model model) + { + if (model is NodeModel nodeModel && nodeModel.Size is not null) + { + return new Point(nodeModel.Position.X + (ShouldUseWidth ? nodeModel.Size.Width : 0) + WidthOffset, nodeModel.Position.Y + (ShouldUseHeight ? nodeModel.Size.Height : 0) + HeightOffset); + } + return null; + } + + virtual public void ResizeNode(double deltaX, double deltaY) + { + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + var width = _originalSize.Width + (ShouldAddTotalMovedX ? _totalMovedX : -_totalMovedX); + var height = _originalSize.Height + (ShouldAddTotalMovedY ? _totalMovedY : -_totalMovedY); + + var positionX = _originalPosition.X + (ShouldChangeXPositionOnResize ? _totalMovedX : 0); + var positionY = _originalPosition.Y + (ShouldChangeYPositionOnResize ? _totalMovedY : 0); + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + positionX = _nodeModel.Position.X; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + positionY = _nodeModel.Position.Y; + } + + _nodeModel.SetPosition(positionX, positionY); + _nodeModel.SetSize(width, height); + } + + virtual public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) + { + if (model is NodeModel nodeModel) + { + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); + _originalSize = nodeModel.Size!; + _nodeModel = nodeModel; + } + } + + virtual public void OnPointerMove(Model? model, PointerEventArgs e) + { + if (_nodeModel is null) return; + + var deltaX = (e.ClientX - _lastClientX!.Value); + var deltaY = (e.ClientY - _lastClientY!.Value); + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + ResizeNode(deltaX, deltaY); + } + + virtual public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) + { + if (_nodeModel is null) return; + + ResizeNode(deltaX, deltaY); + } + + virtual public void OnResizeEnd(Model? model, PointerEventArgs args) + { + _nodeModel?.TriggerSizeChanged(); + _originalSize = null!; + _originalPosition = null!; + _nodeModel = null!; + _totalMovedX = 0; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; + } + } +} diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs index 779107f5..eb787029 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -1,100 +1,17 @@ -using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; - -namespace Blazor.Diagrams.Core.Positions.Resizing +namespace Blazor.Diagrams.Core.Positions.Resizing { - public class TopLeftResizerProvider : IResizerProvider + public class TopLeftResizerProvider : ResizerProvider { - public string? Class => "topleft"; - - private Size _originalSize = null!; - private Point _originalPosition = null!; - private NodeModel _nodeModel = null!; - private double? _lastClientX; - private double? _lastClientY; - private double _totalMovedX = 0; - private double _totalMovedY = 0; - - public Point? GetPosition(Model model) - { - if (model is NodeModel nodeModel && nodeModel.Size is not null) - { - return new Point(nodeModel.Position.X - 5, nodeModel.Position.Y - 5); - } - return null; - } - - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) - { - if (model is NodeModel nodeModel) - { - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalSize = nodeModel.Size!; - _nodeModel = nodeModel; - } - } - - public void OnPointerMove(Model? model, PointerEventArgs e) - { - if (_nodeModel is null) return; - - var deltaX = (e.ClientX - _lastClientX!.Value); - var deltaY = (e.ClientY - _lastClientY!.Value); - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - - ResizeNode(deltaX, deltaY); - } - - public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) - { - if (_nodeModel is null) return; - - ResizeNode(deltaX, deltaY); - } - - public void ResizeNode(double deltaX, double deltaY) - { - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - var height = _originalSize.Height - _totalMovedY; - var width = _originalSize.Width - _totalMovedX; - - var positionX = _originalPosition.X + _totalMovedX; - var positionY = _originalPosition.Y + _totalMovedY; - - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - positionX = _nodeModel.Position.X; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - positionY = _nodeModel.Position.Y; - } - - _nodeModel.SetPosition(positionX, positionY); - _nodeModel.SetSize(width, height); - } - - public void OnResizeEnd(Model? model, PointerEventArgs args) - { - _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _originalPosition = null!; - _totalMovedX = 0; - _totalMovedY = 0; - _lastClientX = null; - _lastClientY = null; - _nodeModel = null!; - } + override public string? Class => "topleft"; + + override public double HeightOffset => -5; + override public double WidthOffset => -5; + override public bool ShouldUseWidth => false; + override public bool ShouldUseHeight => false; + override public bool ShouldChangeXPositionOnResize => true; + override public bool ShouldChangeYPositionOnResize => true; + override public bool ShouldAddTotalMovedX => false; + override public bool ShouldAddTotalMovedY => false; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs index 6c8220c9..175b4af4 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -1,100 +1,16 @@ -using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; - -namespace Blazor.Diagrams.Core.Positions.Resizing +namespace Blazor.Diagrams.Core.Positions.Resizing { - public class TopRightResizerProvider : IResizerProvider + public class TopRightResizerProvider : ResizerProvider { - public string? Class => "topright"; - - private Size _originalSize = null!; - private Point _originalPosition = null!; - private NodeModel _nodeModel = null!; - private double? _lastClientX; - private double? _lastClientY; - private double _totalMovedX = 0; - private double _totalMovedY = 0; - - public Point? GetPosition(Model model) - { - if (model is NodeModel nodeModel && nodeModel.Size is not null) - { - return new Point(nodeModel.Position.X + nodeModel.Size.Width + 5, nodeModel.Position.Y - 5); - } - return null; - } - - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) - { - if (model is NodeModel nodeModel) - { - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalSize = nodeModel.Size!; - _nodeModel = nodeModel; - } - } - - public void OnPointerMove(Model? model, PointerEventArgs e) - { - if (_nodeModel is null) return; - - var deltaX = (e.ClientX - _lastClientX!.Value); - var deltaY = (e.ClientY - _lastClientY!.Value); - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - - ResizeNode(deltaX, deltaY); - } - - public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) - { - if (_nodeModel is null) return; - - ResizeNode(deltaX, deltaY); - } - - public void ResizeNode(double deltaX, double deltaY) - { - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - var height = _originalSize.Height - _totalMovedY; - var width = _originalSize.Width + _totalMovedX; - - var positionX = _originalPosition.X; - var positionY = _originalPosition.Y + _totalMovedY; - - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - positionX = _nodeModel.Position.X; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - positionY = _nodeModel.Position.Y; - } - - _nodeModel.SetPosition(positionX, positionY); - _nodeModel.SetSize(width, height); - } - - public void OnResizeEnd(Model? model, PointerEventArgs args) - { - _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _originalPosition = null!; - _totalMovedX = 0; - _totalMovedY = 0; - _lastClientX = null; - _lastClientY = null; - _nodeModel = null!; - } + override public string? Class => "topright"; + override public double HeightOffset => -5; + override public double WidthOffset => 5; + override public bool ShouldUseWidth => true; + override public bool ShouldUseHeight => false; + override public bool ShouldChangeXPositionOnResize => false; + override public bool ShouldChangeYPositionOnResize => true; + override public bool ShouldAddTotalMovedX => true; + override public bool ShouldAddTotalMovedY => false; } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs index dcd9bab1..83df648a 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs @@ -12,7 +12,7 @@ public class ResizeControlTests [Fact] public void GetPosition_ShouldUseResizeProviderGetPosition() { - var resizeProvider = new Mock(); + var resizeProvider = new Mock(); var control = new ResizeControl(resizeProvider.Object); var model = new Mock(); @@ -24,7 +24,7 @@ public void GetPosition_ShouldUseResizeProviderGetPosition() [Fact] public void OnPointerDown_ShouldInvokeResizeStart() { - var resizeProvider = new Mock(); + var resizeProvider = new Mock(); var control = new ResizeControl(resizeProvider.Object); var diagram = Mock.Of(); var model = Mock.Of(); @@ -38,14 +38,14 @@ public void OnPointerDown_ShouldInvokeResizeStart() [Fact] public void OnPointerDown_ShouldAddEventHandlers() { - var resizeProvider = new Mock(); + var resizeProvider = new Mock(); var control = new ResizeControl(resizeProvider.Object); var diagram = new TestDiagram(); var model = Mock.Of(); var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); control.OnPointerDown(diagram, model, eventArgs); - + diagram.TriggerPointerMove(model, eventArgs); resizeProvider.Verify(m => m.OnPointerMove(model, eventArgs), Times.Once); @@ -56,7 +56,7 @@ public void OnPointerDown_ShouldAddEventHandlers() [Fact] public void OnPointerUp_ShouldRemoveEventHandlers() { - var resizeProvider = new Mock(); + var resizeProvider = new Mock(); var control = new ResizeControl(resizeProvider.Object); var diagram = new TestDiagram(); var model = Mock.Of(); diff --git a/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs index bf51e541..7e7b3419 100644 --- a/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs +++ b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs @@ -13,7 +13,7 @@ public class ResizeControlWidgetTests public void ShouldRenderDiv() { using var ctx = new TestContext(); - var providerMock = Mock.Of(); + var providerMock = Mock.Of(); var cut = ctx.RenderComponent(parameters => parameters.Add(w => w.Control, new ResizeControl(providerMock)) From 9f43a85a38a3b29a32d65b228f3a2f327e403077 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Fri, 5 Jan 2024 16:59:17 +1100 Subject: [PATCH 20/27] Finished fixing tests --- .../Behaviors/ZoomBehavior.cs | 2 +- src/Blazor.Diagrams.Core/Diagram.cs | 2 +- .../Behaviors/DragMovablesBehaviorTests.cs | 7 ++++-- .../Behaviors/DragNewLinkBehaviorTests.cs | 8 ++++--- .../BottomLeftResizerProviderTests.cs | 17 ++++++-------- .../BottomRightResizerProviderTests.cs | 16 ++++++-------- .../Resizing/TopLeftResizerProviderTests.cs | 22 +++++++++---------- .../Resizing/TopRightResizerProviderTests.cs | 19 +++++++--------- 8 files changed, 44 insertions(+), 49 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs index 3489abe7..830b6d69 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs @@ -39,7 +39,7 @@ protected override void OnDiagramWheel(WheelEventArgs e) Diagram.Batch(() => { - Diagram.SetPan(newPanX, newPanY, 0, 0, 0, 0); + Diagram.SetPan(newPanX, newPanY); Diagram.SetZoom(newZoom); }); } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 6160ff2b..401492d4 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -237,7 +237,7 @@ public void ZoomToFit(double margin = 10) Refresh(); } - public void SetPan(double x, double y, double deltaX, double deltaY, double clientX, double clientY) + public void SetPan(double x, double y, double deltaX = 0, double deltaY = 0, double clientX = 0, double clientY = 0) { Pan = new Point(x, y); PanChanged?.Invoke(deltaX, deltaY, clientX, clientY); diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs index d4fd868b..bcd467a1 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -1,3 +1,4 @@ +using Blazor.Diagrams.Core.Behaviors; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; @@ -142,13 +143,15 @@ public void Behavior_ShouldCallSetPosition_WhenGroupHasAutoSize() } [Fact] - public void Behavior_ShouldCallSetPosition_WhenWheelIsTriggered() + public void Behavior_ShouldCallSetPosition_WhenPanChanges() { // Arrange var diagram = new TestDiagram(); var nodeMock = new Mock(Point.Zero); var node = diagram.Nodes.Add(nodeMock.Object); diagram.SelectModel(node, false); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(0, 0, 100, 100)); // Act diagram.TriggerPointerDown(node, @@ -156,6 +159,6 @@ public void Behavior_ShouldCallSetPosition_WhenWheelIsTriggered() diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 100, 0, 0)); // Assert - nodeMock.Verify(n => n.SetPosition(100, 100), Times.Once); + nodeMock.Verify(n => n.SetPosition(It.IsInRange(194, 196, Range.Exclusive), It.IsInRange(194, 196, Range.Exclusive)), Times.Once); } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index 85a988b6..afe93f6d 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -1,4 +1,5 @@ using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Behaviors; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; @@ -443,7 +444,7 @@ public void Behavior_ShouldTriggerLinkTargetAttached_WhenLinkSnappedToPortAndMou } [Fact] - public void Behavior_ShouldUpdateOngoingPosition_WhenWheelIsTriggered() + public void Behavior_ShouldUpdateOngoingPosition_WhenPanChanges() { // Arrange var diagram = new TestDiagram(); @@ -456,6 +457,7 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenWheelIsTriggered() Position = new Point(110, 60), Size = new Size(10, 20) }); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); // Act diagram.TriggerPointerDown(port, @@ -469,8 +471,8 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenWheelIsTriggered() // Assert var source = link.Source as SinglePortAnchor; var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().BeGreaterThan(245); - ongoingPosition.Y.Should().BeGreaterThan(245); + ongoingPosition.X.Should().BeApproximately(337, 1); + ongoingPosition.Y.Should().BeApproximately(337, 1); linkRefreshed.Should().BeTrue(); } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs index 0a57183b..15544125 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs @@ -1,14 +1,10 @@ -using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Controls.Default; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Positions.Resizing; using FluentAssertions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Blazor.Diagrams.Core.Tests.Positions.Resizing @@ -47,7 +43,7 @@ public void DragResizer_ShouldResizeNode() } [Fact] - public void ScrollingWheel_ShouldResizeNode() + public void PanChanged_ShouldResizeNode() { // setup var diagram = new TestDiagram(); @@ -57,6 +53,7 @@ public void ScrollingWheel_ShouldResizeNode() var control = new ResizeControl(new BottomLeftResizerProvider()); diagram.Controls.AddFor(node).Add(control); diagram.SelectModel(node, false); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); // before resize node.Position.X.Should().Be(0); @@ -71,10 +68,10 @@ public void ScrollingWheel_ShouldResizeNode() // after resize - node.Position.X.Should().Be(10); + node.Position.X.Should().BeApproximately(19, 1); node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(300); + node.Size.Width.Should().BeApproximately(80, 1); + node.Size.Height.Should().BeApproximately(395, 1); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs index 68476136..4954bacc 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs @@ -1,14 +1,10 @@ -using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Controls.Default; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Positions.Resizing; using FluentAssertions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Blazor.Diagrams.Core.Tests.Positions.Resizing @@ -47,7 +43,7 @@ public void DragResizer_ShouldResizeNode() } [Fact] - public void ScrollingWheel_ShouldResizeNode() + public void PanChanged_ShouldResizeNode() { // setup var diagram = new TestDiagram(); @@ -57,6 +53,8 @@ public void ScrollingWheel_ShouldResizeNode() var control = new ResizeControl(new BottomRightResizerProvider()); diagram.Controls.AddFor(node).Add(control); diagram.SelectModel(node, false); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + // before resize node.Position.X.Should().Be(0); @@ -73,8 +71,8 @@ public void ScrollingWheel_ShouldResizeNode() // after resize node.Position.X.Should().Be(0); node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(110); - node.Size.Height.Should().Be(300); + node.Size.Width.Should().BeApproximately(119, 1); + node.Size.Height.Should().BeApproximately(395, 1); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs index 9e239f13..fb263cbb 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs @@ -1,14 +1,10 @@ -using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Controls.Default; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Positions.Resizing; using FluentAssertions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Blazor.Diagrams.Core.Tests.Positions.Resizing @@ -47,7 +43,7 @@ public void DragResizer_ShouldResizeNode() } [Fact] - public void ScrollingWheel_ShouldResizeNode() + public void PanChanged_ShouldResizeNode() { // setup var diagram = new TestDiagram(); @@ -57,6 +53,8 @@ public void ScrollingWheel_ShouldResizeNode() var control = new ResizeControl(new TopLeftResizerProvider()); diagram.Controls.AddFor(node).Add(control); diagram.SelectModel(node, false); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + // before resize node.Position.X.Should().Be(0); @@ -67,14 +65,14 @@ public void ScrollingWheel_ShouldResizeNode() // resize var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); control.OnPointerDown(diagram, node, eventArgs); - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, -100, 0, 0)); // after resize - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(100); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(100); + node.Position.X.Should().BeApproximately(19, 1); + node.Position.Y.Should().BeApproximately(-195, 1); + node.Size.Width.Should().BeApproximately(80, 1); + node.Size.Height.Should().BeApproximately(395, 1); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs index bd4712a7..96ef9e7f 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs @@ -1,14 +1,10 @@ -using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Controls.Default; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Positions.Resizing; using FluentAssertions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Blazor.Diagrams.Core.Tests.Positions.Resizing @@ -47,7 +43,7 @@ public void DragResizer_ShouldResizeNode() } [Fact] - public void ScrollingWheel_ShouldResizeNode() + public void PanChanged_ShouldResizeNode() { // setup var diagram = new TestDiagram(); @@ -57,6 +53,7 @@ public void ScrollingWheel_ShouldResizeNode() var control = new ResizeControl(new TopRightResizerProvider()); diagram.Controls.AddFor(node).Add(control); diagram.SelectModel(node, false); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); // before resize node.Position.X.Should().Be(0); @@ -67,13 +64,13 @@ public void ScrollingWheel_ShouldResizeNode() // resize var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); control.OnPointerDown(diagram, node, eventArgs); - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, -100, 0, 0)); // after resize node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(100); - node.Size.Width.Should().Be(110); - node.Size.Height.Should().Be(100); + node.Position.Y.Should().BeApproximately(-195, 1); + node.Size.Width.Should().BeApproximately(119, 1); + node.Size.Height.Should().BeApproximately(395, 1); } [Fact] From f6e831bbd862e8ca3fb699760964840988f1bfd3 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Tue, 9 Jan 2024 14:31:35 +1100 Subject: [PATCH 21/27] Addressed PR comments --- .../Behaviors/DebugEventsBehavior.cs | 2 +- .../Behaviors/DragMovablesBehavior.cs | 2 +- .../Behaviors/DragNewLinkBehavior.cs | 18 +++++++++++++----- .../Behaviors/PanBehavior.cs | 6 +++--- .../Behaviors/ScrollBehavior.cs | 13 +------------ .../Behaviors/VirtualizationBehavior.cs | 2 +- src/Blazor.Diagrams.Core/Diagram.cs | 8 ++++---- .../Positions/Resizing/ResizerProvider.cs | 2 +- .../Components/Widgets/GridWidget.razor.cs | 2 +- .../Behaviors/DragMovablesBehaviorTests.cs | 2 +- .../Behaviors/DragNewLinkBehaviorTests.cs | 6 +++--- .../Behaviors/ScrollBehaviorTests.cs | 5 ++--- .../Blazor.Diagrams.Core.Tests/DiagramTests.cs | 2 +- .../Resizing/BottomLeftResizerProviderTests.cs | 6 +++--- .../BottomRightResizerProviderTests.cs | 4 ++-- .../Resizing/TopLeftResizerProviderTests.cs | 8 ++++---- .../Resizing/TopRightResizerProviderTests.cs | 6 +++--- 17 files changed, 45 insertions(+), 49 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs index afd1f0c7..cd801847 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs @@ -52,7 +52,7 @@ private void Nodes_Added(NodeModel obj) Console.WriteLine($"Nodes.Added, Nodes=[{obj}]"); } - private void Diagram_PanChanged(double deltaX, double deltaY, double clientX, double clientY) + private void Diagram_PanChanged(double deltaX, double deltaY) { Console.WriteLine($"PanChanged, Pan={Diagram.Pan}"); } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 0ea3dc66..3a01eed7 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -74,7 +74,7 @@ private void OnPointerMove(Model? model, PointerEventArgs e) _lastClientY = e.ClientY; } - public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) + public void OnPanChanged(double deltaX, double deltaY) { if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) return; diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index a0b07404..262a692e 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -4,7 +4,6 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -using System; using System.Linq; namespace Blazor.Diagrams.Core.Behaviors; @@ -14,6 +13,8 @@ public class DragNewLinkBehavior : Behavior private PositionAnchor? _targetPositionAnchor; public BaseLinkModel? OngoingLink { get; private set; } + private double? _lastClientX; + private double? _lastClientY; public DragNewLinkBehavior(Diagram diagram) : base(diagram) { @@ -69,22 +70,27 @@ private void OnPointerDown(Model? model, MouseEventArgs e) OngoingLink.SetTarget(_targetPositionAnchor); Diagram.Links.Add(OngoingLink); } + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; } private void OnPointerMove(Model? model, MouseEventArgs e) { - if (OngoingLink == null || model != null) + if (OngoingLink == null || model != null || _lastClientX == null || _lastClientY == null) return; - UpdateLinkPosition(e.ClientX, e.ClientY); + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + UpdateLinkPosition((double)_lastClientX, (double)_lastClientY); } - private void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) + private void OnPanChanged(double deltaX, double deltaY) { if (OngoingLink == null) return; - UpdateLinkPosition(clientX + deltaX, clientY + deltaY); + UpdateLinkPosition((double)_lastClientX!, (double)_lastClientY!); } private void UpdateLinkPosition(double clientX, double clientY) @@ -134,6 +140,8 @@ private void OnPointerUp(Model? model, MouseEventArgs e) } OngoingLink = null; + _lastClientX = null; + _lastClientY = null; } private Point CalculateTargetPosition(double clientX, double clientY) diff --git a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs index 7856bc26..4dab58e9 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs @@ -1,7 +1,7 @@ -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Behaviors.Base; using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Behaviors.Base; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Core.Behaviors; diff --git a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs index bb745489..36e5e6d5 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs @@ -1,6 +1,5 @@ using Blazor.Diagrams.Core.Behaviors.Base; using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Options; namespace Blazor.Diagrams.Core.Behaviors { @@ -16,17 +15,7 @@ protected override void OnDiagramWheel(WheelEventArgs e) if (Diagram.Container == null || !IsBehaviorEnabled(e)) return; - var x = Diagram.Pan.X - (e.DeltaX / Diagram.Options.Zoom.ScaleFactor); - var y = Diagram.Pan.Y - (e.DeltaY / Diagram.Options.Zoom.ScaleFactor); - Diagram.GetScreenPoint(x, y); - - var _lastClientX = e.ClientX - e.DeltaX; - var _lastClientY = e.ClientY - e.DeltaY; - - var deltaX = e.ClientX - _lastClientX + (Diagram.Pan.X - x); - var deltaY = e.ClientY - _lastClientY + (Diagram.Pan.Y - y); - - Diagram.SetPan(x, y, deltaX, deltaY, _lastClientX, _lastClientY); + Diagram.UpdatePan(-e.DeltaX / Diagram.Zoom, -e.DeltaY / Diagram.Zoom); } } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs index 8674366d..c9d03bfb 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs @@ -12,7 +12,7 @@ public VirtualizationBehavior(Diagram diagram) : base(diagram) Diagram.ContainerChanged += CheckVisibility; } - private void CheckVisibility(double deltaX, double deltaY, double clientX, double clientY) + private void CheckVisibility(double deltaX, double deltaY) { CheckVisibility(); } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 401492d4..b1cc4eec 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -34,7 +34,7 @@ public abstract class Diagram public event Action? PointerDoubleClick; public event Action? SelectionChanged; - public event Action? PanChanged; + public event Action? PanChanged; public event Action? ZoomChanged; public event Action? ContainerChanged; public event Action? Changed; @@ -237,17 +237,17 @@ public void ZoomToFit(double margin = 10) Refresh(); } - public void SetPan(double x, double y, double deltaX = 0, double deltaY = 0, double clientX = 0, double clientY = 0) + public void SetPan(double x, double y) { Pan = new Point(x, y); - PanChanged?.Invoke(deltaX, deltaY, clientX, clientY); + PanChanged?.Invoke(x, y); Refresh(); } public void UpdatePan(double deltaX, double deltaY) { Pan = Pan.Add(deltaX, deltaY); - PanChanged?.Invoke(deltaX, deltaY, 0, 0); + PanChanged?.Invoke(-deltaX, -deltaY); Refresh(); } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs index 6de309e4..76259cee 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs @@ -86,7 +86,7 @@ virtual public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - virtual public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) + virtual public void OnPanChanged(double deltaX, double deltaY) { if (_nodeModel is null) return; diff --git a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs index ec1914de..ba3ac954 100644 --- a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs @@ -38,7 +38,7 @@ protected override void OnParametersSet() _visible = BlazorDiagram.Zoom > ZoomThreshold; } - private void RefreshPosition(double deltaX, double deltaY, double clientX, double clientY) + private void RefreshPosition(double deltaX, double deltaY) { RefreshPosition(); } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs index bcd467a1..813322dc 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -159,6 +159,6 @@ public void Behavior_ShouldCallSetPosition_WhenPanChanges() diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 100, 0, 0)); // Assert - nodeMock.Verify(n => n.SetPosition(It.IsInRange(194, 196, Range.Exclusive), It.IsInRange(194, 196, Range.Exclusive)), Times.Once); + nodeMock.Verify(n => n.SetPosition(100, 100), Times.Once); } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index afe93f6d..ec6f2846 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -454,7 +454,7 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenPanChanges() var port = node.AddPort(new PortModel(node) { Initialized = true, - Position = new Point(110, 60), + Position = new Point(100, 50), Size = new Size(10, 20) }); diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); @@ -471,8 +471,8 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenPanChanges() // Assert var source = link.Source as SinglePortAnchor; var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().BeApproximately(337, 1); - ongoingPosition.Y.Should().BeApproximately(337, 1); + ongoingPosition.X.Should().BeApproximately(expectedValue: 246, 1); + ongoingPosition.Y.Should().BeApproximately(expectedValue: 246, 1); linkRefreshed.Should().BeTrue(); } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs index 4181ce3d..d18be5ed 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs @@ -1,7 +1,6 @@ using Blazor.Diagrams.Core.Behaviors; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; using Xunit; namespace Blazor.Diagrams.Core.Tests.Behaviors @@ -21,8 +20,8 @@ public void Behavior_WhenBehaviorEnabled_ShouldScroll() diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); // Assert - Assert.Equal(-95, diagram.Pan.X, 0); - Assert.Equal(-190, diagram.Pan.Y, 0); + Assert.Equal(-100, diagram.Pan.X); + Assert.Equal(-200, diagram.Pan.Y); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs index 43bf9a84..c129c8bd 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs @@ -84,7 +84,7 @@ public void ZoomToFit_ShouldTriggerAppropriateEvents() // Act diagram.Changed += () => refreshes++; diagram.ZoomChanged += () => zoomChanges++; - diagram.PanChanged += (deltaX, deltaY, clientX, clientY) => panChanges++; + diagram.PanChanged += (deltaX, deltaY) => panChanges++; diagram.ZoomToFit(10); // Assert diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs index 15544125..596b856e 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs @@ -68,10 +68,10 @@ public void PanChanged_ShouldResizeNode() // after resize - node.Position.X.Should().BeApproximately(19, 1); + node.Position.X.Should().Be(10); node.Position.Y.Should().Be(0); - node.Size.Width.Should().BeApproximately(80, 1); - node.Size.Height.Should().BeApproximately(395, 1); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(300); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs index 4954bacc..c375036b 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs @@ -71,8 +71,8 @@ public void PanChanged_ShouldResizeNode() // after resize node.Position.X.Should().Be(0); node.Position.Y.Should().Be(0); - node.Size.Width.Should().BeApproximately(119, 1); - node.Size.Height.Should().BeApproximately(395, 1); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(300); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs index fb263cbb..ecca1e65 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs @@ -69,10 +69,10 @@ public void PanChanged_ShouldResizeNode() // after resize - node.Position.X.Should().BeApproximately(19, 1); - node.Position.Y.Should().BeApproximately(-195, 1); - node.Size.Width.Should().BeApproximately(80, 1); - node.Size.Height.Should().BeApproximately(395, 1); + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(-100); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(300); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs index 96ef9e7f..920518fc 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs @@ -68,9 +68,9 @@ public void PanChanged_ShouldResizeNode() // after resize node.Position.X.Should().Be(0); - node.Position.Y.Should().BeApproximately(-195, 1); - node.Size.Width.Should().BeApproximately(119, 1); - node.Size.Height.Should().BeApproximately(395, 1); + node.Position.Y.Should().Be(-100); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(300); } [Fact] From bc943bc9f669008a781fb1c258a503b6e9957b41 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Mon, 15 Jan 2024 16:41:31 +1100 Subject: [PATCH 22/27] Addressed new PR comments --- .../Behaviors/DragNewLinkBehavior.cs | 13 ++++++++----- src/Blazor.Diagrams.Core/Diagram.cs | 5 ++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 262a692e..6d742c5e 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -87,26 +87,29 @@ private void OnPointerMove(Model? model, MouseEventArgs e) private void OnPanChanged(double deltaX, double deltaY) { - if (OngoingLink == null) + if (OngoingLink == null || _lastClientX == null || _lastClientY == null) return; - UpdateLinkPosition((double)_lastClientX!, (double)_lastClientY!); + UpdateLinkPosition((double)_lastClientX, (double)_lastClientY); } private void UpdateLinkPosition(double clientX, double clientY) { + if (OngoingLink == null) + return; + _targetPositionAnchor!.SetPosition(CalculateTargetPosition(clientX, clientY)); if (Diagram.Options.Links.EnableSnapping) { var nearPort = FindNearPortToAttachTo(); - if (nearPort != null || OngoingLink!.Target is not PositionAnchor) + if (nearPort != null || OngoingLink.Target is not PositionAnchor) { - OngoingLink!.SetTarget(nearPort is null ? _targetPositionAnchor : new SinglePortAnchor(nearPort)); + OngoingLink.SetTarget(nearPort is null ? _targetPositionAnchor : new SinglePortAnchor(nearPort)); } } - OngoingLink!.Refresh(); + OngoingLink.Refresh(); OngoingLink.RefreshLinks(); } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index b1cc4eec..28cca0a5 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -239,8 +239,11 @@ public void ZoomToFit(double margin = 10) public void SetPan(double x, double y) { + var oldPanX = Pan.X; + var oldPanY = Pan.Y; + Pan = new Point(x, y); - PanChanged?.Invoke(x, y); + PanChanged?.Invoke(oldPanX - Pan.X, oldPanY - Pan.Y); Refresh(); } From c2a1cb2c9cd0764e8a837409bf7a04514cd06737 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Tue, 23 Jan 2024 10:45:44 +1100 Subject: [PATCH 23/27] Fixed null --- .../Positions/Resizing/ResizerProvider.cs | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs index 76259cee..b63588b1 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs @@ -9,13 +9,14 @@ public abstract class ResizerProvider : IPositionProvider { abstract public string? Class { get; } - private Size _originalSize = null!; - private Point _originalPosition = null!; + private Size? _originalSize = null; + private Point? _originalPosition = null; private double? _lastClientX; private double? _lastClientY; - private NodeModel _nodeModel = null!; + private NodeModel? _nodeModel = null; private double _totalMovedX = 0; private double _totalMovedY = 0; + private Diagram? _diagram; abstract public double WidthOffset { get; } abstract public double HeightOffset { get; } @@ -40,13 +41,13 @@ virtual public void ResizeNode(double deltaX, double deltaY) _totalMovedX += deltaX; _totalMovedY += deltaY; - var width = _originalSize.Width + (ShouldAddTotalMovedX ? _totalMovedX : -_totalMovedX); - var height = _originalSize.Height + (ShouldAddTotalMovedY ? _totalMovedY : -_totalMovedY); + var width = _originalSize!.Width + (ShouldAddTotalMovedX ? _totalMovedX : -_totalMovedX) / _diagram!.Zoom; + var height = _originalSize.Height + (ShouldAddTotalMovedY ? _totalMovedY : -_totalMovedY) / _diagram!.Zoom; - var positionX = _originalPosition.X + (ShouldChangeXPositionOnResize ? _totalMovedX : 0); - var positionY = _originalPosition.Y + (ShouldChangeYPositionOnResize ? _totalMovedY : 0); + var positionX = _originalPosition!.X + (ShouldChangeXPositionOnResize ? _totalMovedX : 0) / _diagram!.Zoom; + var positionY = _originalPosition.Y + (ShouldChangeYPositionOnResize ? _totalMovedY : 0) / _diagram!.Zoom; - if (width < _nodeModel.MinimumDimensions.Width) + if (width < _nodeModel!.MinimumDimensions.Width) { width = _nodeModel.MinimumDimensions.Width; positionX = _nodeModel.Position.X; @@ -68,14 +69,18 @@ virtual public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs _lastClientX = e.ClientX; _lastClientY = e.ClientY; _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalSize = nodeModel.Size!; + _originalSize = nodeModel.Size; _nodeModel = nodeModel; + _diagram = diagram; } } virtual public void OnPointerMove(Model? model, PointerEventArgs e) { - if (_nodeModel is null) return; + if (_originalSize is null || _originalPosition is null || _nodeModel is null || _diagram is null) + { + return; + } var deltaX = (e.ClientX - _lastClientX!.Value); var deltaY = (e.ClientY - _lastClientY!.Value); @@ -96,13 +101,14 @@ virtual public void OnPanChanged(double deltaX, double deltaY) virtual public void OnResizeEnd(Model? model, PointerEventArgs args) { _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _originalPosition = null!; - _nodeModel = null!; + _originalSize = null; + _originalPosition = null; + _nodeModel = null; _totalMovedX = 0; _totalMovedY = 0; _lastClientX = null; _lastClientY = null; + _diagram = null; } } } From 0086686531cd6dc8ee47433acb932c9027618b29 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Tue, 23 Jan 2024 13:41:42 +1100 Subject: [PATCH 24/27] Addressed PR comments --- site/Site/Pages/Documentation/Controls/Overview.razor | 2 +- site/Site/Pages/Documentation/Diagram/Api.razor | 2 +- .../Positions/Resizing/ResizerProvider.cs | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/site/Site/Pages/Documentation/Controls/Overview.razor b/site/Site/Pages/Documentation/Controls/Overview.razor index 335838ae..96ad07b2 100644 --- a/site/Site/Pages/Documentation/Controls/Overview.razor +++ b/site/Site/Pages/Documentation/Controls/Overview.razor @@ -140,7 +140,7 @@ Diagram.Controls.AddFor(SomeModel)

The ResizeControl adds a resizer which is a box that when dragged, can resize the node. The resizer position and movement of the node is controlled using a Resizer Provider.
- There are four ResizerProviders, one for each corner. Custom resizing behavior can be created by implementing IResizerProvider. + There are four ResizerProviders, one for each corner. Custom resizing behavior can be created by inheriting and overriding ResizerProvider.


diff --git a/site/Site/Pages/Documentation/Diagram/Api.razor b/site/Site/Pages/Documentation/Diagram/Api.razor
index af548a11..a03a6a4d 100644
--- a/site/Site/Pages/Documentation/Diagram/Api.razor
+++ b/site/Site/Pages/Documentation/Diagram/Api.razor
@@ -148,7 +148,7 @@
         
         
             PanChanged
-            Action?
+            Action<double, double>?
             
         
         
diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
index b63588b1..0ed47ee3 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
@@ -18,13 +18,19 @@ public abstract class ResizerProvider : IPositionProvider
         private double _totalMovedY = 0;
         private Diagram? _diagram;
 
+        /// Controls the X-Axis position of the resizer control in relation to the node outline 
         abstract public double WidthOffset { get; }
+        /// Controls the Y-Axis position of the resizer control in relation to the node outline 
         abstract public double HeightOffset { get; }
+        /// Factors in the width of the node when calculating the position of the resizer control 
         abstract public bool ShouldUseWidth { get; }
+        /// Factors in the height of the node when calculating the position of the resizer control 
         abstract public bool ShouldUseHeight { get; }
         abstract public bool ShouldChangeXPositionOnResize { get; }
         abstract public bool ShouldChangeYPositionOnResize { get; }
+        /// Controls whether the width should be modified on Node resizing 
         abstract public bool ShouldAddTotalMovedX { get; }
+        /// Controls whether the height should be modified on Node resizing 
         abstract public bool ShouldAddTotalMovedY { get; }
 
         virtual public Point? GetPosition(Model model)

From fc7f05934f50eac4675dfdc8ead35654b7d81367 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Tue, 23 Jan 2024 14:08:05 +1100
Subject: [PATCH 25/27] Fixed using statements

---
 .../Resizing/BottomLeftResizerProvider.cs     | 20 +++++++++++++------
 .../Resizing/BottomRightResizerProvider.cs    | 19 +++++++++++++-----
 .../Positions/Resizing/ResizerProvider.cs     | 17 +---------------
 .../Resizing/TopLeftResizerProvider.cs        | 20 +++++++++++++------
 .../Resizing/TopRightResizerProvider.cs       | 19 +++++++++++++-----
 .../Behaviors/DragNewLinkBehaviorTests.cs     |  9 ++++++++-
 .../Controls/ResizeControlTests.cs            | 15 ++++++++++----
 .../BottomLeftResizerProviderTests.cs         | 11 +++++++++-
 .../BottomRightResizerProviderTests.cs        | 11 +++++++++-
 .../Resizing/TopLeftResizerProviderTests.cs   | 11 +++++++++-
 .../Resizing/TopRightResizerProviderTests.cs  | 11 +++++++++-
 11 files changed, 116 insertions(+), 47 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs
index e5d4f48a..6b6cc3dc 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs
@@ -1,17 +1,25 @@
-namespace Blazor.Diagrams.Core.Positions.Resizing
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Models.Base;
+
+namespace Blazor.Diagrams.Core.Positions.Resizing
 {
     public class BottomLeftResizerProvider : ResizerProvider
     {
         override public string? Class => "bottomleft";
-
-        override public double HeightOffset => 5;
-        override public double WidthOffset => -5;
-        override public bool ShouldUseWidth => false;
-        override public bool ShouldUseHeight => true;
         override public bool ShouldChangeXPositionOnResize => true;
         override public bool ShouldChangeYPositionOnResize => false;
         override public bool ShouldAddTotalMovedX => false;
         override public bool ShouldAddTotalMovedY => true;
 
+        public override Point? GetPosition(Model model)
+        {
+            if (model is NodeModel nodeModel && nodeModel.Size is not null)
+            {
+                return new Point(nodeModel.Position.X - 5, nodeModel.Position.Y + nodeModel.Size.Height + 5);
+            }
+            return null;
+        }
+
     }
 }
diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs
index 05a98562..394b882b 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs
@@ -1,16 +1,25 @@
-namespace Blazor.Diagrams.Core.Positions.Resizing
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Models.Base;
+
+namespace Blazor.Diagrams.Core.Positions.Resizing
 {
     public class BottomRightResizerProvider : ResizerProvider
     {
         override public string? Class => "bottomright";
-        override public double HeightOffset => 5;
-        override public double WidthOffset => 5;
-        override public bool ShouldUseWidth => true;
-        override public bool ShouldUseHeight => true;
         override public bool ShouldChangeXPositionOnResize => false;
         override public bool ShouldChangeYPositionOnResize => false;
         override public bool ShouldAddTotalMovedX => true;
         override public bool ShouldAddTotalMovedY => true;
 
+        public override Point? GetPosition(Model model)
+        {
+            if (model is NodeModel nodeModel && nodeModel.Size is not null)
+            {
+                return new Point(nodeModel.Position.X + nodeModel.Size.Width + 5, nodeModel.Position.Y + nodeModel.Size.Height + 5);
+            }
+            return null;
+        }
+
     }
 }
diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
index 0ed47ee3..589587b0 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
@@ -18,14 +18,6 @@ public abstract class ResizerProvider : IPositionProvider
         private double _totalMovedY = 0;
         private Diagram? _diagram;
 
-        /// Controls the X-Axis position of the resizer control in relation to the node outline 
-        abstract public double WidthOffset { get; }
-        /// Controls the Y-Axis position of the resizer control in relation to the node outline 
-        abstract public double HeightOffset { get; }
-        /// Factors in the width of the node when calculating the position of the resizer control 
-        abstract public bool ShouldUseWidth { get; }
-        /// Factors in the height of the node when calculating the position of the resizer control 
-        abstract public bool ShouldUseHeight { get; }
         abstract public bool ShouldChangeXPositionOnResize { get; }
         abstract public bool ShouldChangeYPositionOnResize { get; }
         /// Controls whether the width should be modified on Node resizing 
@@ -33,14 +25,7 @@ public abstract class ResizerProvider : IPositionProvider
         /// Controls whether the height should be modified on Node resizing 
         abstract public bool ShouldAddTotalMovedY { get; }
 
-        virtual public Point? GetPosition(Model model)
-        {
-            if (model is NodeModel nodeModel && nodeModel.Size is not null)
-            {
-                return new Point(nodeModel.Position.X + (ShouldUseWidth ? nodeModel.Size.Width : 0) + WidthOffset, nodeModel.Position.Y + (ShouldUseHeight ? nodeModel.Size.Height : 0) + HeightOffset);
-            }
-            return null;
-        }
+        abstract public Point? GetPosition(Model model);
 
         virtual public void ResizeNode(double deltaX, double deltaY)
         {
diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs
index eb787029..24ccaa13 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs
@@ -1,17 +1,25 @@
-namespace Blazor.Diagrams.Core.Positions.Resizing
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Models.Base;
+
+namespace Blazor.Diagrams.Core.Positions.Resizing
 {
     public class TopLeftResizerProvider : ResizerProvider
     {
         override public string? Class => "topleft";
-
-        override public double HeightOffset => -5;
-        override public double WidthOffset => -5;
-        override public bool ShouldUseWidth => false;
-        override public bool ShouldUseHeight => false;
         override public bool ShouldChangeXPositionOnResize => true;
         override public bool ShouldChangeYPositionOnResize => true;
         override public bool ShouldAddTotalMovedX => false;
         override public bool ShouldAddTotalMovedY => false;
 
+        public override Point? GetPosition(Model model)
+        {
+            if (model is NodeModel nodeModel && nodeModel.Size is not null)
+            {
+                return new Point(nodeModel.Position.X - 5, nodeModel.Position.Y - 5);
+            }
+            return null;
+        }
+
     }
 }
diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs
index b3be45dd..72fd2a52 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs
@@ -1,16 +1,25 @@
-namespace Blazor.Diagrams.Core.Positions.Resizing
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Models.Base;
+
+namespace Blazor.Diagrams.Core.Positions.Resizing
 {
     public class TopRightResizerProvider : ResizerProvider
     {
         override public string? Class => "topright";
-        override public double HeightOffset => -5;
-        override public double WidthOffset => 5;
-        override public bool ShouldUseWidth => true;
-        override public bool ShouldUseHeight => false;
         override public bool ShouldChangeXPositionOnResize => false;
         override public bool ShouldChangeYPositionOnResize => true;
         override public bool ShouldAddTotalMovedX => true;
         override public bool ShouldAddTotalMovedY => false;
 
+        public override Point? GetPosition(Model model)
+        {
+            if (model is NodeModel nodeModel && nodeModel.Size is not null)
+            {
+                return new Point(nodeModel.Position.X + nodeModel.Size.Width + 5, nodeModel.Position.Y - 5);
+            }
+            return null;
+        }
+
     }
 }
\ No newline at end of file
diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
index ad40df21..d545c864 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
@@ -1,4 +1,11 @@
-using System.Linq;
+using Blazor.Diagrams.Core.Anchors;
+using Blazor.Diagrams.Core.Behaviors;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using FluentAssertions;
+using System.Linq;
+using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests.Behaviors;
 
diff --git a/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs
index c9f1b427..543ecb6f 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs
@@ -1,4 +1,11 @@
-namespace Blazor.Diagrams.Core.Tests.Controls;
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Models.Base;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using Moq;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Controls;
 
 public class ResizeControlTests
 {
@@ -17,7 +24,7 @@ public void GetPosition_ShouldUseResizeProviderGetPosition()
     [Fact]
     public void OnPointerDown_ShouldInvokeResizeStart()
     {
-        var resizeProvider = new Mock();
+        var resizeProvider = new Mock();
         var control = new ResizeControl(resizeProvider.Object);
         var diagram = Mock.Of();
         var model = Mock.Of();
@@ -31,7 +38,7 @@ public void OnPointerDown_ShouldInvokeResizeStart()
     [Fact]
     public void OnPointerDown_ShouldAddEventHandlers()
     {
-        var resizeProvider = new Mock();
+        var resizeProvider = new Mock();
         var control = new ResizeControl(resizeProvider.Object);
         var diagram = new TestDiagram();
         var model = Mock.Of();
@@ -49,7 +56,7 @@ public void OnPointerDown_ShouldAddEventHandlers()
     [Fact]
     public void OnPointerUp_ShouldRemoveEventHandlers()
     {
-        var resizeProvider = new Mock();
+        var resizeProvider = new Mock();
         var control = new ResizeControl(resizeProvider.Object);
         var diagram = new TestDiagram();
         var model = Mock.Of();
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
index 992bd152..25179552 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
@@ -1,4 +1,13 @@
-namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
+using Blazor.Diagrams.Core.Behaviors;
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using FluentAssertions;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
 
 public class BottomLeftResizerProviderTests
 {
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs
index 0c2bc25f..62ad0270 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs
@@ -1,4 +1,13 @@
-namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
+using Blazor.Diagrams.Core.Behaviors;
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using FluentAssertions;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
 
 public class BottomRightResizerProviderTests
 {
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
index 0c33d9dc..75de6d32 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
@@ -1,4 +1,13 @@
-namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
+using Blazor.Diagrams.Core.Behaviors;
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using FluentAssertions;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
 
 public class TopLeftResizerProviderTests
 {
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
index 45c92ca1..5071a423 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
@@ -1,4 +1,13 @@
-namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
+using Blazor.Diagrams.Core.Behaviors;
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using FluentAssertions;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
 
 public class TopRightResizerProviderTests
 {

From 7e33922d26c87c8c4e7c57b0810be5be7f38a460 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Tue, 23 Jan 2024 14:10:51 +1100
Subject: [PATCH 26/27] Fixed more usings

---
 .../Components/Controls/ResizeControlWidgetTests.cs      | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs
index 5dbd0457..ee4aa80a 100644
--- a/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs
+++ b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs
@@ -1,4 +1,11 @@
-namespace Blazor.Diagrams.Tests.Components.Controls;
+using Blazor.Diagrams.Components.Controls;
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using Bunit;
+using Moq;
+using Xunit;
+
+namespace Blazor.Diagrams.Tests.Components.Controls;
 
 public class ResizeControlWidgetTests
 {

From 965a28b9dca01e25b02d3fd70a9055d053339746 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Tue, 20 Feb 2024 10:34:18 +1100
Subject: [PATCH 27/27] reverted

---
 .../Positions/Resizing/ResizerProvider.cs                     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
index 589587b0..e720715d 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
@@ -20,9 +20,9 @@ public abstract class ResizerProvider : IPositionProvider
 
         abstract public bool ShouldChangeXPositionOnResize { get; }
         abstract public bool ShouldChangeYPositionOnResize { get; }
-        /// Controls whether the width should be modified on Node resizing 
+        /// Controls whether the totalMovedX should be added or subtracted 
         abstract public bool ShouldAddTotalMovedX { get; }
-        /// Controls whether the height should be modified on Node resizing 
+        /// Controls whether the totalMovedY should be added or subtracted 
         abstract public bool ShouldAddTotalMovedY { get; }
 
         abstract public Point? GetPosition(Model model);