Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WI00737678 #38

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 147 additions & 41 deletions src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,35 @@
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.Geometry;
using Blazor.Diagrams.Core.Models.Base;
using Blazor.Diagrams.Core.Events;
using System;
using System.Collections.Generic;
using Blazor.Diagrams.Core.Models;
using DiagramPoint = Blazor.Diagrams.Core.Geometry.Point;
using System.Linq;
using Blazor.Diagrams.Core.Behaviors.Base;

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 +38,19 @@ public DragMovablesBehavior(Diagram diagram) : base(diagram)

private void OnPointerDown(Model? model, PointerEventArgs e)
{
if (model is not MovableModel)
Console.WriteLine("On Pointer Down...");
if (model is not NodeModel)
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)
if (sm is not NodeModel movable || movable.Locked)
{
position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2,
movable.Position.Y + (n.Size?.Height ?? 0) / 2);
continue;
}

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

_lastClientX = e.ClientX;
Expand All @@ -58,22 +60,61 @@ 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 jobNode)
{
var parent = jobNode.ParentNode;
while (parent != null)
{
if (_initialPositions.ContainsKey(parent))
{
_initialPositions.Remove(jobNode);
break;
}

parent = parent.ParentNode;
}
}
}

foreach (var (node, positions) in _initialPositions)
{
if (node is NodeModel jobNode)
{
foreach (var child in jobNode.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;

_totalMovedX += deltaX;
_totalMovedY += deltaY;
var dx = (e.ClientX - _lastClientX.Value) / Diagram.Zoom;
var dy = (e.ClientY - _lastClientY.Value) / Diagram.Zoom;

_totalMovedX += dx;
_totalMovedY += dy;

MoveNodes(model, _totalMovedX, _totalMovedY);

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

}

public void OnPanChanged(double deltaX, double deltaY)
{
if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null)
Expand All @@ -84,15 +125,15 @@ public void OnPanChanged(double deltaX, double deltaY)
_totalMovedX += deltaX;
_totalMovedY += deltaY;

MoveNodes(null, _totalMovedX, _totalMovedY);
MoveNodesOnPan(null, _totalMovedX, _totalMovedY);
}

private void MoveNodes(Model? model, double deltaX, double deltaY)
private void MoveNodesOnPan(Model? model, double deltaX, double deltaY)
{
foreach (var (movable, initialPosition) in _initialPositions)
{
var ndx = ApplyGridSize(deltaX + initialPosition.X);
var ndy = ApplyGridSize(deltaY + initialPosition.Y);
var ndx = ApplyGridSize(deltaX + initialPosition.position.X);
var ndy = ApplyGridSize(deltaY + initialPosition.position.Y);
if (Diagram.Options.GridSnapToCenter && movable is NodeModel node)
{
node.SetPosition(ndx - (node.Size?.Width ?? 0) / 2, ndy - (node.Size?.Height ?? 0) / 2);
Expand All @@ -104,40 +145,105 @@ private void MoveNodes(Model? model, double deltaX, double deltaY)
}
}

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);
}

void MoveNodes(Model? model, double deltaX, double deltaY)
{
foreach (var (node, positions) in _initialPositions)
{
SetPosition(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)
{
SetPosition(childNode, childPosition.X + deltaX, childPosition.Y + deltaY);
}

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

void SetPosition(NodeModel node, double x, double y)
{
if (node is NodeModel nodeModel && nodeModel.ParentNode != null)
{
x = Clamp(x, nodeModel.Size?.Width, nodeModel.ParentNode.Position.X, nodeModel.ParentNode.Size?.Width);
var parentY = nodeModel.ParentNode.Position.Y + CHILD_NODE_MIN_OFFSET_TOP;
var parentH = nodeModel.ParentNode.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 (movable, childMovableNodes) in _initialPositions)
{
movable.TriggerMoved();
if (movable is NodeModel movableNode)
{
foreach (var child in movableNode.GetAllChildNodes())
{
if (childMovableNodes.ChildPositions.ContainsKey(child))
{
child.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.PanChanged -= OnPanChanged;
}
}
}
32 changes: 31 additions & 1 deletion src/Blazor.Diagrams.Core/Models/NodeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public class NodeModel : MovableModel, IHasBounds, IHasShape, ILinkable
public event Action<NodeModel>? SizeChanged;
public event Action<NodeModel>? Moving;

internal NodeModel? ParentNode;

public NodeModel(Point? position = null) : base(position)
{
}
Expand Down Expand Up @@ -190,12 +192,40 @@ private void UpdatePortPositions(Size oldSize, Size newSize)
}
}

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

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

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

internal List<NodeModel> GetAllChildNodes()
{
var allChildren = new List<NodeModel>();
foreach (var child in childMovableNodes)
{
allChildren.Add(child);
if (child is NodeModel jobChild)
{
allChildren.AddRange(jobChild.GetAllChildNodes());
}
}
return allChildren;
}

internal void AddChildNode(NodeModel child)
{
child.ParentNode = this;
childMovableNodes.Add(child);
}

internal void RemoveChildNode(NodeModel child)
{
childMovableNodes.Remove(child);
child.ParentNode = null;
}

internal List<NodeModel> childMovableNodes = new List<NodeModel>();
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,36 +30,6 @@ public void Behavior_ShouldCallSetPosition()
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<NodeModel>(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()
{
Expand Down Expand Up @@ -102,26 +72,6 @@ public void Behavior_ShouldNotTriggerMoved_WhenMovableDidntMove()
movedTrigger.Should().BeFalse();
}

[Fact]
public void Behavior_ShouldNotCallSetPosition_WhenGroupHasNoAutoSize()
{
// Arrange
var diagram = new TestDiagram();
var nodeMock = new Mock<NodeModel>(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()
{
Expand Down
Loading