diff --git a/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs index 3364becd..98a67974 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs @@ -12,6 +12,9 @@ public class SelectionBoxBehavior : DragBehavior private Point? _initialClientPoint; public event EventHandler? SelectionBoundsChanged; + private double? _lastClientX; + private double? _lastClientY; + private Point? _initialPan; public SelectionBoxBehavior(Diagram diagram) : base(diagram) @@ -19,6 +22,7 @@ public SelectionBoxBehavior(Diagram diagram) Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; Diagram.PointerUp += OnPointerUp; + Diagram.PanChanged += OnPanChanged; } public override void Dispose() @@ -26,6 +30,7 @@ public override void Dispose() Diagram.PointerDown -= OnPointerDown; Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; + Diagram.PanChanged -= OnPanChanged; } protected override void OnPointerDown(Model? model, PointerEventArgs e) @@ -34,6 +39,9 @@ protected override void OnPointerDown(Model? model, PointerEventArgs e) return; _initialClientPoint = new Point(e.ClientX, e.ClientY); + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + _initialPan = Diagram.Pan; } protected override void OnPointerMove(Model? model, PointerEventArgs e) @@ -41,30 +49,22 @@ protected override void OnPointerMove(Model? model, PointerEventArgs e) if (_initialClientPoint == null) return; - UpdateSelectionBox(e); + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; - var start = Diagram.GetRelativeMousePoint(_initialClientPoint.X, _initialClientPoint.Y); - var end = Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY); - var (sX, sY) = (Math.Min(start.X, end.X), Math.Min(start.Y, end.Y)); - var (eX, eY) = (Math.Max(start.X, end.X), Math.Max(start.Y, end.Y)); - var bounds = new Rectangle(sX, sY, eX, eY); + UpdateSelectionBox(e.ClientX, e.ClientY); + SelectNodesInBounds(e.ClientX, e.ClientY); + } - foreach (var node in Diagram.Nodes) + void UpdateSelectionBox(double clientX, double clientY) + { + if(_initialClientPoint == null || _initialPan == null) { - var nodeBounds = node.GetBounds(); - if (nodeBounds == null) - continue; - - if (bounds.Overlap(nodeBounds)) - Diagram.SelectModel(node, false); - else if (node.Selected) Diagram.UnselectModel(node); + return; } - } - void UpdateSelectionBox(MouseEventArgs e) - { - var start = Diagram.GetRelativePoint(_initialClientPoint!.X, _initialClientPoint.Y); - var end = Diagram.GetRelativePoint(e.ClientX, e.ClientY); + var start = Diagram.GetRelativePoint(_initialClientPoint.X + Diagram.Pan.X - _initialPan.X, _initialClientPoint.Y + Diagram.Pan.Y - _initialPan.Y); + var end = Diagram.GetRelativePoint(clientX, clientY); var (sX, sY) = (Math.Min(start.X, end.X), Math.Min(start.Y, end.Y)); var (eX, eY) = (Math.Max(start.X, end.X), Math.Max(start.Y, end.Y)); SelectionBoundsChanged?.Invoke(this, new Rectangle(sX, sY, eX, eY)); @@ -74,6 +74,42 @@ protected override void OnPointerUp(Model? model, PointerEventArgs e) { _initialClientPoint = null; SelectionBoundsChanged?.Invoke(this, null); + _lastClientX = null; + _lastClientY = null; + _initialPan = null; + } + + public void OnPanChanged(double deltaX, double deltaY) + { + if (_initialClientPoint == null || _lastClientX == null || _lastClientY == null) + return; + + UpdateSelectionBox((double) _lastClientX, (double) _lastClientY); + SelectNodesInBounds((double) _lastClientX, (double) _lastClientY); + } + + void SelectNodesInBounds(double clientX, double clientY) + { + if(_initialClientPoint == null || _initialPan == null) + { + return; + } + + var start = Diagram.GetRelativeMousePoint(_initialClientPoint.X + Diagram.Pan.X - _initialPan.X, _initialClientPoint.Y + Diagram.Pan.Y - _initialPan.Y); + var end = Diagram.GetRelativeMousePoint(clientX, clientY); + var (sX, sY) = (Math.Min(start.X, end.X), Math.Min(start.Y, end.Y)); + var (eX, eY) = (Math.Max(start.X, end.X), Math.Max(start.Y, end.Y)); + var bounds = new Rectangle(sX, sY, eX, eY); + + foreach (var node in Diagram.Nodes) + { + var nodeBounds = node.GetBounds(); + if (nodeBounds == null) + continue; + if (bounds.Overlap(nodeBounds)) + Diagram.SelectModel(node, false); + else if (node.Selected) Diagram.UnselectModel(node); + } } } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs index ba0d94d6..a394e033 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs @@ -39,6 +39,38 @@ public void Behavior_WhenBehaviorEnabled_ShouldUpdateSelectionBounds() Assert.Equal(100, lastBounds.Left); } + [Fact] + public void Behavior_WhenBehaviorEnabled_ShouldUpdateSelectionBoundsOnScroll() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior(); + diagram.BehaviorOptions.DiagramShiftDragBehavior = diagram.GetBehavior(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + + var selectionBoxBehavior = diagram.GetBehavior()!; + bool boundsChangedEventInvoked = false; + Rectangle? lastBounds = null; + selectionBoxBehavior.SelectionBoundsChanged += (_, newBounds) => + { + boundsChangedEventInvoked = true; + lastBounds = newBounds; + }; + + // Act + diagram.TriggerPointerDown(null, + new PointerEventArgs(100, 100, 0, 0, false, true, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, true, false, 200, 150, 0, 0)); + + // Assert + Assert.True(boundsChangedEventInvoked); + Assert.Equal(200, lastBounds!.Width); + Assert.Equal(150, lastBounds.Height); + Assert.Equal(-50, lastBounds.Top); + Assert.Equal(-100, lastBounds.Left); + } + [Fact] public void Behavior_WhenBehaviorDisabled_ShouldNotUpdateSelectionBounds() { @@ -99,6 +131,39 @@ public void Behavior_WithBoundsChangedDelegate_ShouldSelectNodesInsideArea() Assert.False(node.Selected); } + [Fact] + public void Behavior_WithBoundsChangedDelegate_ShouldSelectNodesInsideAreaWhenScrolling() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior(); + diagram.BehaviorOptions.DiagramShiftDragBehavior = diagram.GetBehavior(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + + var selectionBoxBehavior = diagram.GetBehavior()!; + selectionBoxBehavior.SelectionBoundsChanged += (_, _) => { }; + + var node = new NodeModel() + { + Size = new Size(100, 100), + Position = new Point(150, 150) + }; + diagram.Nodes.Add(node); + + // Act + diagram.TriggerPointerDown(null, + new PointerEventArgs(100, 100, 0, 0, false, true, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, true, false, 200, 200, 0, 0)); + + // Assert + Assert.True(node.Selected); + + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, true, false, -200, -200, 0, 0)); + + Assert.False(node.Selected); + } + [Fact] public void Behavior_WithoutBoundsChangedDelegate_ShouldNotSelectNodesInsideArea() { @@ -127,5 +192,36 @@ public void Behavior_WithoutBoundsChangedDelegate_ShouldNotSelectNodesInsideArea new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); Assert.False(node.Selected); } + + [Fact] + public void Behavior_WithoutBoundsChangedDelegate_ShouldNotSelectNodesInsideAreaWhenScrolling() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior(); + diagram.BehaviorOptions.DiagramShiftDragBehavior = diagram.GetBehavior(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + + var node = new NodeModel() + { + Size = new Size(100, 100), + Position = new Point(150, 150) + }; + diagram.Nodes.Add(node); + + // Act + diagram.TriggerPointerDown(null, + new PointerEventArgs(100, 100, 0, 0, false, true, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, true, false, 200, 200, 0, 0)); + + + // Assert + Assert.False(node.Selected); + + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, true, false, -200, -200, 0, 0)); + + Assert.False(node.Selected); + } } }