Skip to content

Commit

Permalink
WI00737678---RemoveDragNodesBehavior
Browse files Browse the repository at this point in the history
  • Loading branch information
gnawisetech committed Jun 4, 2024
1 parent 6478de3 commit 4b39aff
Show file tree
Hide file tree
Showing 6 changed files with 256 additions and 34 deletions.
156 changes: 132 additions & 24 deletions src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,30 @@
using Blazor.Diagrams.Core.Geometry;
using Blazor.Diagrams.Core.Models;
using Blazor.Diagrams.Core.Models.Base;
using System;
using System.Collections.Generic;
using DiagramPoint = Blazor.Diagrams.Core.Geometry.Point;

namespace Blazor.Diagrams.Core.Behaviors;

public class DragMovablesBehavior : Behavior
{
private readonly Dictionary<MovableModel, Point> _initialPositions;
record NodeMoveablePositions(Point position)
{
public Dictionary<NodeModel, DiagramPoint> ChildPositions { get; } = new();
}

private readonly Dictionary<NodeModel, NodeMoveablePositions> _initialPositions;
private double? _lastClientX;
private double? _lastClientY;
private bool _moved;
private double _totalMovedX = 0;
private double _totalMovedY = 0;
double _totalMovedX = 0;
double _totalMovedY = 0;

public const double CHILD_NODE_MIN_OFFSET_TOP = 40;
public const double CHILD_NODE_MIN_OFFSET_BOTTOM = 5;

public DragMovablesBehavior(Diagram diagram) : base(diagram)
{
_initialPositions = new Dictionary<MovableModel, Point>();
_initialPositions = new Dictionary<NodeModel, NodeMoveablePositions>();
Diagram.PointerDown += OnPointerDown;
Diagram.PointerMove += OnPointerMove;
Diagram.PointerUp += OnPointerUp;
Expand All @@ -28,27 +35,31 @@ public DragMovablesBehavior(Diagram diagram) : base(diagram)

private void OnPointerDown(Model? model, PointerEventArgs e)
{
if (model is not MovableModel)
if (model is not NodeModel)
return;

_initialPositions.Clear();
foreach (var sm in Diagram.GetSelectedModels())
{
if (sm is not MovableModel movable || movable.Locked)
if (sm is not NodeModel node || node.Locked)
{
continue;
}

// Special case: groups without auto size on
if (sm is NodeModel node && node.Group != null && !node.Group.AutoSize)
if (node.Group != null && !node.Group.AutoSize)
{
continue;
}

var position = node.Position;

var position = movable.Position;
if (Diagram.Options.GridSnapToCenter && movable is NodeModel n)
if (Diagram.Options.GridSnapToCenter && node is NodeModel nodeModel)
{
position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2,
movable.Position.Y + (n.Size?.Height ?? 0) / 2);
position = new Point(node.Position.X + (nodeModel?.Size?.Width ?? 0) / 2,
node.Position.Y + (nodeModel?.Size?.Height ?? 0) / 2);
}

_initialPositions.Add(movable, position);
_initialPositions.Add(node, new NodeMoveablePositions(position));
}

_lastClientX = e.ClientX;
Expand All @@ -58,10 +69,49 @@ private void OnPointerDown(Model? model, PointerEventArgs e)

private void OnPointerMove(Model? model, PointerEventArgs e)
{
if (!_moved)
{
foreach (var node in _initialPositions.Keys.ToArray())
{
if (node is NodeModel nodeModel)
{
var parent = nodeModel.GetParentNode();
while (parent != null)
{
if (_initialPositions.ContainsKey(parent))
{
_initialPositions.Remove(nodeModel);
break;
}

parent = parent.GetParentNode();
}
}
}

foreach (var (node, positions) in _initialPositions)
{
if (node is NodeModel nodeModel)
{
foreach (var child in nodeModel.GetAllChildNodes())
{
if (!child.Selected)
{
Diagram.SelectModel(child, false);
}

positions.ChildPositions[child] = child.Position;
}
}
}
}
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;

Expand All @@ -72,8 +122,8 @@ private void OnPointerMove(Model? model, PointerEventArgs e)

_lastClientX = e.ClientX;
_lastClientY = e.ClientY;

}

public void OnPanChanged(double deltaX, double deltaY)
{
if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null)
Expand All @@ -89,33 +139,90 @@ public void OnPanChanged(double deltaX, double deltaY)

private void MoveNodes(Model? model, double deltaX, double deltaY)
{
foreach (var (movable, initialPosition) in _initialPositions)
foreach (var (node, positions) in _initialPositions)
{
var ndx = ApplyGridSize(deltaX + initialPosition.X);
var ndy = ApplyGridSize(deltaY + initialPosition.Y);
if (Diagram.Options.GridSnapToCenter && movable is NodeModel node)
if (Diagram.Options.GridSnapToCenter && node is NodeModel nodeModel)
{
var ndx = ApplyGridSize(deltaX + positions.position.X);
var ndy = ApplyGridSize(deltaY - positions.position.Y);

node.SetPosition(ndx - (node.Size?.Width ?? 0) / 2, ndy - (node.Size?.Height ?? 0) / 2);
}
else
{
movable.SetPosition(ndx, ndy);
SetNodePosition(node, positions.position.X + deltaX, positions.position.Y + deltaY);
deltaX = node.Position.X - positions.position.X;
deltaY = node.Position.Y - positions.position.Y;

foreach (var (childNode, childPosition) in positions.ChildPositions)
{
SetNodePosition(childNode, childPosition.X + deltaX, childPosition.Y + deltaY);
}

if (node is NodeModel node1Model)
{
node1Model.TriggerMoving();
}
}
}
}

void SetNodePosition(NodeModel node, double x, double y)
{
if (node is NodeModel nodeModel && nodeModel.GetParentNode() != null)
{
x = Clamp(x, nodeModel.Size?.Width, nodeModel.GetParentNode().Position.X, nodeModel.GetParentNode().Size?.Width);
var parentY = nodeModel.GetParentNode().Position.Y + CHILD_NODE_MIN_OFFSET_TOP;
var parentH = nodeModel.GetParentNode().Size?.Height - CHILD_NODE_MIN_OFFSET_TOP - CHILD_NODE_MIN_OFFSET_BOTTOM;
y = Clamp(y, nodeModel.Size?.Height, parentY, parentH);

}

node.SetPosition(x, y);
}

double Clamp(double position, double? size, double? parentPosition, double? parentSize)
{
var clamped = position;

if (size != null && parentPosition != null && parentSize != null)
{
if (position < parentPosition)
{
clamped = (double)parentPosition;
}
else if (position + size > parentPosition + parentSize)
{
clamped = (double)(parentPosition + parentSize - size);
}
}

return clamped;
}

private void OnPointerUp(Model? model, PointerEventArgs e)
{
if (_initialPositions.Count == 0)
return;

if (_moved)
{
foreach (var (movable, _) in _initialPositions)
foreach (var (node, positions) in _initialPositions)
{
movable.TriggerMoved();
node.TriggerMoved();
if (node is NodeModel nodeModel)
{
foreach (var child in nodeModel.GetAllChildNodes())
{
if (positions.ChildPositions.ContainsKey(child))
{
child.TriggerMoved();
}
}
}
}
}

_initialPositions.Clear();
_totalMovedX = 0;
_totalMovedY = 0;
Expand All @@ -135,9 +242,10 @@ private double ApplyGridSize(double n)
public override void Dispose()
{
_initialPositions.Clear();

Diagram.PointerDown -= OnPointerDown;
Diagram.PointerMove -= OnPointerMove;
Diagram.PointerUp -= OnPointerUp;
Diagram.PanChanged -= OnPanChanged;
}
}
}
12 changes: 12 additions & 0 deletions src/Blazor.Diagrams.Core/Models/Base/IHasChild.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Blazor.Diagrams.Core.Models.Base;

public interface IHasChild
{
public List<NodeModel> GetAllChildNodes();

internal void AddChildNode(NodeModel child);

internal void RemoveChildNode(NodeModel child);

internal void ClearChildNodes();
}
6 changes: 6 additions & 0 deletions src/Blazor.Diagrams.Core/Models/Base/IHasParent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Blazor.Diagrams.Core.Models.Base;

public interface IHasParent
{
public NodeModel GetParentNode();
}
47 changes: 42 additions & 5 deletions src/Blazor.Diagrams.Core/Models/NodeModel.cs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
using Blazor.Diagrams.Core.Geometry;
using Blazor.Diagrams.Core.Models.Base;
using System;
using System.Collections.Generic;
using System.Linq;

namespace Blazor.Diagrams.Core.Models;

public class NodeModel : MovableModel, IHasBounds, IHasShape, ILinkable
public class NodeModel : MovableModel, IHasBounds, IHasShape, ILinkable, IHasChild, IHasParent
{
private readonly List<PortModel> _ports = new();
private readonly List<BaseLinkModel> _links = new();
private readonly List<NodeModel> _childNodes = new();

private Size? _size;
public Size MinimumDimensions { get; set; } = new Size(0, 0);

private NodeModel? _parent;

public event Action<NodeModel>? SizeChanging;
public event Action<NodeModel>? SizeChanged;
public event Action<NodeModel>? Moving;
Expand Down Expand Up @@ -190,12 +191,48 @@ private void UpdatePortPositions(Size oldSize, Size newSize)
}
}

protected void TriggerMoving()
public void TriggerMoving()
{
Moving?.Invoke(this);
}

void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link);

void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link);

public List<NodeModel> GetAllChildNodes()
{
var allChildren = new List<NodeModel>();
foreach (var child in _childNodes)
{
allChildren.Add(child);
if (child is NodeModel childNodeModel)
{
allChildren.AddRange(childNodeModel.GetAllChildNodes());
}
}
return allChildren;
}

public void AddChildNode(NodeModel child)
{
child._parent = this;
_childNodes.Add(child);
}

public void RemoveChildNode(NodeModel child)
{
child._parent = null;
_childNodes.Remove(child);
}

public void ClearChildNodes()
{
_childNodes.Clear();
}

public NodeModel GetParentNode()
{
return _parent!;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ public void Behavior_ShouldCallSetPosition()
}

[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)]
[InlineData(false, 0, 0, 40, 40)]
[InlineData(true, 0, 0, 35, 20)]
[InlineData(false, 3, 3, 43, 43)]
[InlineData(true, 3, 3, 50, 20)]
public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, double initialX, double initialY, double deltaX, double deltaY)
{
// Arrange
Expand Down Expand Up @@ -161,4 +161,31 @@ public void Behavior_ShouldCallSetPosition_WhenPanChanges()
// Assert
nodeMock.Verify(n => n.SetPosition(100, 100), Times.Once);
}

[Fact]
public void Behavior_ShouldCallSetPosition_WhenNodeWithChildDragged()
{
// Arrange
var diagram = new TestDiagram();
var parentNodeMock = new Mock<NodeModel>(Point.Zero);
var childNodeMock = new Mock<NodeModel>(new Point(50, 50));
var parentNode = diagram.Nodes.Add(parentNodeMock.Object);
parentNode.Size = new Size(300, 300);
var childNode = diagram.Nodes.Add(childNodeMock.Object);
childNode.Size = new Size(150, 150);

parentNode.AddChildNode(childNode);

diagram.SelectModel(parentNode, false);

// Act
diagram.TriggerPointerDown(parentNode,
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
parentNodeMock.Verify(n => n.SetPosition(50, 50), Times.Once);
childNodeMock.Verify(n => n.SetPosition(50, 50), Times.Once);
}
}
Loading

0 comments on commit 4b39aff

Please sign in to comment.