diff --git a/site/Site/Pages/Documentation/Controls/Overview.razor b/site/Site/Pages/Documentation/Controls/Overview.razor index bd87fa9a1..e732cfd2c 100644 --- a/site/Site/Pages/Documentation/Controls/Overview.razor +++ b/site/Site/Pages/Documentation/Controls/Overview.razor @@ -3,6 +3,7 @@ @using Blazor.Diagrams.Core.Controls; @using Blazor.Diagrams.Core.PathGenerators; @using Blazor.Diagrams.Core.Positions; +@using Blazor.Diagrams.Core.Positions.Resizing; @using Blazor.Diagrams.Core.Routers; @layout DocumentationLayout @inherits DocumentationPage @@ -135,6 +136,26 @@ Diagram.Controls.AddFor(SomeModel) +

Resize Control

+ +

+ 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 inheriting and overriding ResizerProvider. +

+ +

+Diagram.Controls.AddFor(SomeModel).Add(new ResizeControl(new BottomRightResizerProvider()));
+Diagram.Controls.AddFor(SomeModel).Add(new ResizeControl(new BottomLeftResizerProvider()));
+Diagram.Controls.AddFor(SomeModel).Add(new ResizeControl(new TopRightResizerProvider()));
+Diagram.Controls.AddFor(SomeModel).Add(new ResizeControl(new TopLeftResizerProvider()));
+
+ +
+ + + +
+ @@ -143,6 +164,7 @@ Diagram.Controls.AddFor(SomeModel) private BlazorDiagram _rDiagram = new(); private BlazorDiagram _ahDiagram = new(); private BlazorDiagram _dnlDiagram = new(); + private BlazorDiagram _resizerDiagram = new(); protected override void OnInitialized() { @@ -198,5 +220,21 @@ Diagram.Controls.AddFor(SomeModel) _dnlDiagram.Controls.AddFor(dnlNode2).Add(new DragNewLinkControl(0, 0.5, offsetX: -20)); _dnlDiagram.SelectModel(dnlNode1, false); _dnlDiagram.SelectModel(dnlNode2, false); + + // Resize Control + var resizeNode1 = _resizerDiagram.Nodes.Add(new NodeModel(new Point(100, 100))); + var resizeNode2 = _resizerDiagram.Nodes.Add(new NodeModel(new Point(500, 150))); + var resizeNode1Port = resizeNode1.AddPort(PortAlignment.Right); + var resizeNode2Port = resizeNode2.AddPort(PortAlignment.Left); + _resizerDiagram.Links.Add(new LinkModel(resizeNode1Port, resizeNode2Port)); + resizeNode1.Title = "Title"; + resizeNode2.Title = "Title"; + _resizerDiagram.Controls.AddFor(resizeNode1, ControlsType.OnSelection).Add(new ResizeControl(new BottomRightResizerProvider())); + _resizerDiagram.Controls.AddFor(resizeNode1, ControlsType.OnSelection).Add(new ResizeControl(new BottomLeftResizerProvider())); + _resizerDiagram.Controls.AddFor(resizeNode1, ControlsType.OnSelection).Add(new ResizeControl(new TopRightResizerProvider())); + _resizerDiagram.Controls.AddFor(resizeNode1, ControlsType.OnSelection).Add(new ResizeControl(new TopLeftResizerProvider())); + _resizerDiagram.Controls.AddFor(resizeNode2, ControlsType.OnSelection).Add(new ResizeControl(new BottomRightResizerProvider())); + _resizerDiagram.SelectModel(resizeNode1, false); + _resizerDiagram.SelectModel(resizeNode2, false); } } \ 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 new file mode 100644 index 000000000..04d0e02a5 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs @@ -0,0 +1,38 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Positions.Resizing; +using System.Threading.Tasks; + +namespace Blazor.Diagrams.Core.Controls.Default +{ + public class ResizeControl : ExecutableControl + { + private readonly ResizerProvider _resizeProvider; + + public ResizeControl(ResizerProvider resizeProvider) + { + _resizeProvider = resizeProvider; + } + + public override Point? GetPosition(Model model) => _resizeProvider.GetPosition(model); + + 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); + + return ValueTask.CompletedTask; + } + + void OnResizeEnd(Diagram diagram) + { + diagram.PointerMove -= _resizeProvider.OnPointerMove; + diagram.PointerUp -= _resizeProvider.OnResizeEnd; + } + } +} diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index 041d872ec..4a450be1b 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -27,13 +27,10 @@ public void AddChild(NodeModel child) { _children.Add(child); child.Group = this; - child.SizeChanged += OnNodeChanged; + child.SizeChanging += OnNodeChanged; child.Moving += OnNodeChanged; - if (UpdateDimensions()) - { - Refresh(); - } + UpdateDimensions(); } public void RemoveChild(NodeModel child) @@ -42,14 +39,10 @@ public void RemoveChild(NodeModel child) return; child.Group = null; - child.SizeChanged -= OnNodeChanged; + child.SizeChanging -= OnNodeChanged; child.Moving -= OnNodeChanged; - if (UpdateDimensions()) - { - Refresh(); - RefreshLinks(); - } + UpdateDimensions(); } public override void SetPosition(double x, double y) @@ -83,7 +76,7 @@ public void Ungroup() foreach (var child in Children) { child.Group = null; - child.SizeChanged -= OnNodeChanged; + child.SizeChanging -= OnNodeChanged; child.Moving -= OnNodeChanged; } @@ -96,7 +89,7 @@ private void Initialize(IEnumerable children) { _children.Add(child); child.Group = this; - child.SizeChanged += OnNodeChanged; + child.SizeChanging += OnNodeChanged; child.Moving += OnNodeChanged; } @@ -105,10 +98,7 @@ private void Initialize(IEnumerable children) private void OnNodeChanged(NodeModel node) { - if (UpdateDimensions()) - { - Refresh(); - } + UpdateDimensions(); } private bool UpdateDimensions() @@ -128,7 +118,7 @@ private bool UpdateDimensions() TriggerMoving(); } - Size = new Size(bounds.Width + Padding * 2, bounds.Height + Padding * 2); + SetSize(bounds.Width + Padding * 2, bounds.Height + Padding * 2); return true; } } diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs index 053c5b3d8..102a9fed6 100644 --- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs +++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs @@ -11,7 +11,9 @@ public class NodeModel : MovableModel, IHasBounds, IHasShape, ILinkable private readonly List _ports = new(); private readonly List _links = new(); private Size? _size; + public Size MinimumDimensions { get; set; } = new Size(0, 0); + public event Action? SizeChanging; public event Action? SizeChanged; public event Action? Moving; @@ -28,11 +30,7 @@ public Size? Size get => _size; set { - if (value?.Equals(_size) == true) - return; - _size = value; - SizeChanged?.Invoke(this); } } public bool ControlledSize { get; init; } @@ -103,6 +101,29 @@ public override void SetPosition(double x, double y) Moving?.Invoke(this); } + public void SetSize(double width, double height) + { + var newSize = new Size(width, height); + if (newSize.Equals(_size) == true) + return; + + Size? oldSize = Size != null ? new Size(Size.Width, Size.Height) : null; + + Size = newSize; + if (oldSize != null) + { + UpdatePortPositions(oldSize, newSize); + } + Refresh(); + RefreshLinks(); + SizeChanging?.Invoke(this); + } + + public void TriggerSizeChanged() + { + SizeChanged?.Invoke(this); + } + public virtual void UpdatePositionSilently(double deltaX, double deltaY) { base.SetPosition(Position.X + deltaX, Position.Y + deltaY); @@ -141,6 +162,9 @@ public virtual void UpdatePositionSilently(double deltaX, double deltaY) public virtual bool CanAttachTo(ILinkable other) => other is not PortModel && other is not BaseLinkModel; + /// + /// Updates port positions when node position changes. + /// private void UpdatePortPositions(double deltaX, double deltaY) { // Save some JS calls and update ports directly here @@ -151,6 +175,21 @@ private void UpdatePortPositions(double deltaX, double deltaY) } } + /// + /// Updates port positions when node size changes. + /// + private void UpdatePortPositions(Size oldSize, Size newSize) + { + var deltaWidth = newSize.Width - oldSize.Width; + var deltaHeight = newSize.Height - oldSize.Height; + + foreach (var port in _ports) + { + port.SetPortPositionOnNodeSizeChanged(deltaWidth, deltaHeight); + port.RefreshLinks(); + } + } + protected void TriggerMoving() { Moving?.Invoke(this); diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs index a0c850816..ef2068567 100644 --- a/src/Blazor.Diagrams.Core/Models/PortModel.cs +++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs @@ -67,4 +67,38 @@ public virtual bool CanAttachTo(ILinkable other) void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link); + + public virtual void SetPortPositionOnNodeSizeChanged(double deltaWidth, double deltaHeight) + { + switch (Alignment) + { + case PortAlignment.Top: + Position = new Point(Position.X + deltaWidth / 2, Position.Y); + break; + case PortAlignment.TopRight: + Position = new Point(Position.X + deltaWidth, Position.Y); + break; + case PortAlignment.TopLeft: + Position = new Point(Position.X, Position.Y); + break; + case PortAlignment.Right: + Position = new Point(Position.X + deltaWidth, Position.Y + deltaHeight / 2); + break; + case PortAlignment.Left: + Position = new Point(Position.X, Position.Y + deltaHeight / 2); + break; + case PortAlignment.Bottom: + Position = new Point(Position.X + deltaWidth / 2, Position.Y + deltaHeight); + break; + case PortAlignment.BottomRight: + Position = new Point(Position.X + deltaWidth, Position.Y + deltaHeight); + break; + case PortAlignment.BottomLeft: + Position = new Point(Position.X, Position.Y + deltaHeight); + break; + default: + Position = new Point(Position.X, Position.Y); + break; + } + } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs new file mode 100644 index 000000000..6b6cc3dc5 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -0,0 +1,25 @@ +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 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 new file mode 100644 index 000000000..394b882bb --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -0,0 +1,25 @@ +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 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 new file mode 100644 index 000000000..f44626c19 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs @@ -0,0 +1,104 @@ +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; } + + protected Size? OriginalSize { get; set; } + protected Point? OriginalPosition { get; set; } + protected double? LastClientX { get; set; } + protected double? LastClientY { get; set; } + protected NodeModel? NodeModel { get; set; } + protected Diagram? Diagram { get; set; } + private double _totalMovedX = 0; + private double _totalMovedY = 0; + + abstract public bool ShouldChangeXPositionOnResize { get; } + abstract public bool ShouldChangeYPositionOnResize { get; } + /// Controls whether the totalMovedX should be added or subtracted + abstract public bool ShouldAddTotalMovedX { get; } + /// Controls whether the totalMovedY should be added or subtracted + abstract public bool ShouldAddTotalMovedY { get; } + + abstract public Point? GetPosition(Model model); + + virtual public (Size size, Point position) CalculateNewSizeAndPosition(double deltaX, double deltaY) + { + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + 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) / Diagram!.Zoom; + var positionY = OriginalPosition.Y + (ShouldChangeYPositionOnResize ? _totalMovedY : 0) / Diagram!.Zoom; + + 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; + } + + return (new Size(width, height), new Point(positionX, positionY)); + } + + virtual public void SetSizeAndPosition(Size size, Point position) + { + NodeModel!.SetPosition(position.X, position.Y); + NodeModel.SetSize(size.Width, size.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; + this.NodeModel = nodeModel; + Diagram = diagram; + } + } + + virtual public void OnPointerMove(Model? model, PointerEventArgs e) + { + 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); + + LastClientX = e.ClientX; + LastClientY = e.ClientY; + + var result = CalculateNewSizeAndPosition(deltaX, deltaY); + SetSizeAndPosition(result.size, result.position); + } + + virtual public void OnResizeEnd(Model? model, PointerEventArgs args) + { + NodeModel?.TriggerSizeChanged(); + OriginalSize = null; + OriginalPosition = null; + NodeModel = null; + _totalMovedX = 0; + _totalMovedY = 0; + LastClientX = null; + LastClientY = null; + Diagram = null; + } + } +} diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs new file mode 100644 index 000000000..24ccaa132 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -0,0 +1,25 @@ +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 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 new file mode 100644 index 000000000..72fd2a525 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -0,0 +1,25 @@ +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 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/src/Blazor.Diagrams/BlazorDiagram.cs b/src/Blazor.Diagrams/BlazorDiagram.cs index ab61d98a8..b18eab9dc 100644 --- a/src/Blazor.Diagrams/BlazorDiagram.cs +++ b/src/Blazor.Diagrams/BlazorDiagram.cs @@ -19,7 +19,8 @@ public BlazorDiagram(BlazorDiagramOptions? options = null) [typeof(RemoveControl)] = typeof(RemoveControlWidget), [typeof(BoundaryControl)] = typeof(BoundaryControlWidget), [typeof(DragNewLinkControl)] = typeof(DragNewLinkControlWidget), - [typeof(ArrowHeadControl)] = typeof(ArrowHeadControlWidget) + [typeof(ArrowHeadControl)] = typeof(ArrowHeadControlWidget), + [typeof(ResizeControl)] = typeof(ResizeControlWidget) }; Options = options ?? new BlazorDiagramOptions(); diff --git a/src/Blazor.Diagrams/Components/Controls/ResizeControlWidget.razor b/src/Blazor.Diagrams/Components/Controls/ResizeControlWidget.razor new file mode 100644 index 000000000..cfd15bd7b --- /dev/null +++ b/src/Blazor.Diagrams/Components/Controls/ResizeControlWidget.razor @@ -0,0 +1,10 @@ +
+ +@code +{ + [Parameter] + public ResizeControl Control { get; set; } = null!; + + [Parameter] + public Model Model { get; set; } = null!; +} diff --git a/src/Blazor.Diagrams/Components/NodeWidget.razor b/src/Blazor.Diagrams/Components/NodeWidget.razor index 37ff3da8d..726e51d2c 100644 --- a/src/Blazor.Diagrams/Components/NodeWidget.razor +++ b/src/Blazor.Diagrams/Components/NodeWidget.razor @@ -1,4 +1,4 @@ -
+
@(Node.Title ?? "Title") @foreach (var port in Node.Ports) { @@ -11,4 +11,12 @@ [Parameter] public NodeModel Node { get; set; } = null!; + private string GenerateStyle() + { + if (Node.Size is not null) + { + return $"width: {Node.Size.Width}px; height: {Node.Size.Height}px"; + } + return string.Empty; + } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.css b/src/Blazor.Diagrams/wwwroot/default.styles.css index b095bbe4e..0e37330c2 100644 --- a/src/Blazor.Diagrams/wwwroot/default.styles.css +++ b/src/Blazor.Diagrams/wwwroot/default.styles.css @@ -127,4 +127,29 @@ g.diagram-group.default.selected > rect { transform: translate(-50%, -50%); } +.default-node-resizer { + width: 5px; + height: 5px; + background-color: #f5f5f5; + border: 1px solid #6e9fd4; + position: absolute; + transform: translate(-2.5px, -2.5px); +} + +.default-node-resizer.bottomright { + cursor: nwse-resize; +} + +.default-node-resizer.topright { + cursor: nesw-resize; +} + +.default-node-resizer.bottomleft { + cursor: nesw-resize; +} + +.default-node-resizer.topleft { + cursor: nwse-resize; +} + /*# sourceMappingURL=wwwroot\default.styles.css.map */ diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css b/src/Blazor.Diagrams/wwwroot/default.styles.min.css index c44adedc3..781fb0aed 100644 --- a/src/Blazor.Diagrams/wwwroot/default.styles.min.css +++ b/src/Blazor.Diagrams/wwwroot/default.styles.min.css @@ -1 +1 @@ -.default-node{width:100px;height:80px;border-radius:10px;background-color:#f5f5f5;border:1px solid #e8e8e8;-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.default-node.selected{border:1px solid #6e9fd4;}.default-node.selected .diagram-port{border:1px solid #6e9fd4;}.default-node .diagram-port,.default.diagram-group .diagram-port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .diagram-port:hover,.default-node .diagram-port.has-links,.default.diagram-group .diagram-port.has-links{background-color:#000;}.default-node .diagram-port.bottom,.default.diagram-group .diagram-port.bottom{bottom:0;left:50%;}.default-node .diagram-port.bottomleft,.default.diagram-group .diagram-port.bottomleft{bottom:0;left:0;}.default-node .diagram-port.bottomright,.default.diagram-group .diagram-port.bottomright{bottom:0;right:0;}.default-node .diagram-port.top,.default.diagram-group .diagram-port.top{top:0;left:50%;}.default-node .diagram-port.topleft,.default.diagram-group .diagram-port.topleft{top:0;left:0;}.default-node .diagram-port.topright,.default.diagram-group .diagram-port.topright{top:0;right:0;}.default-node .diagram-port.left,.default.diagram-group .diagram-port.left{left:0;top:50%;}.default-node .diagram-port.right,.default.diagram-group .diagram-port.right{right:0;top:50%;}.diagram-navigator.default{position:absolute;bottom:10px;right:10px;border:3px solid #9ba8b0;border-radius:15px;padding:20px;background-color:#fff;}div.diagram-group.default{outline:2px solid #000;background:#c6c6c6;}div.diagram-group.default.selected{outline:2px solid #6e9fd4;}g.diagram-group.default rect{outline:2px solid #000;fill:#c6c632;}g.diagram-group.default.selected>rect{outline:2px solid #008000;}.diagram-link div.default-link-label{display:inline-block;color:#fff;background-color:#6e9fd4;border-radius:.25rem;padding:.25rem;text-align:center;font-size:.875rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:3rem;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);} \ No newline at end of file +.default-node{width:100px;height:80px;border-radius:10px;background-color:#f5f5f5;border:1px solid #e8e8e8;-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.default-node.selected{border:1px solid #6e9fd4;}.default-node.selected .diagram-port{border:1px solid #6e9fd4;}.default-node .diagram-port,.default.diagram-group .diagram-port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .diagram-port:hover,.default-node .diagram-port.has-links,.default.diagram-group .diagram-port.has-links{background-color:#000;}.default-node .diagram-port.bottom,.default.diagram-group .diagram-port.bottom{bottom:0;left:50%;}.default-node .diagram-port.bottomleft,.default.diagram-group .diagram-port.bottomleft{bottom:0;left:0;}.default-node .diagram-port.bottomright,.default.diagram-group .diagram-port.bottomright{bottom:0;right:0;}.default-node .diagram-port.top,.default.diagram-group .diagram-port.top{top:0;left:50%;}.default-node .diagram-port.topleft,.default.diagram-group .diagram-port.topleft{top:0;left:0;}.default-node .diagram-port.topright,.default.diagram-group .diagram-port.topright{top:0;right:0;}.default-node .diagram-port.left,.default.diagram-group .diagram-port.left{left:0;top:50%;}.default-node .diagram-port.right,.default.diagram-group .diagram-port.right{right:0;top:50%;}.diagram-navigator.default{position:absolute;bottom:10px;right:10px;border:3px solid #9ba8b0;border-radius:15px;padding:20px;background-color:#fff;}div.diagram-group.default{outline:2px solid #000;background:#c6c6c6;}div.diagram-group.default.selected{outline:2px solid #6e9fd4;}g.diagram-group.default rect{outline:2px solid #000;fill:#c6c632;}g.diagram-group.default.selected>rect{outline:2px solid #008000;}.diagram-link div.default-link-label{display:inline-block;color:#fff;background-color:#6e9fd4;border-radius:.25rem;padding:.25rem;text-align:center;font-size:.875rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:3rem;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);}.default-node-resizer{width:5px;height:5px;background-color:#f5f5f5;border:1px solid #6e9fd4;position:absolute;transform:translate(-2.5px,-2.5px);}.default-node-resizer.bottomright{cursor:nwse-resize;}.default-node-resizer.topright{cursor:nesw-resize;}.default-node-resizer.bottomleft{cursor:nesw-resize;}.default-node-resizer.topleft{cursor:nwse-resize;} \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz b/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz index 905f20cd3..3c43a8a10 100644 Binary files a/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz and b/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz differ diff --git a/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs new file mode 100644 index 000000000..543ecb6f0 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs @@ -0,0 +1,74 @@ +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 +{ + [Fact] + public void GetPosition_ShouldUseResizeProviderGetPosition() + { + var resizeProvider = new Mock(); + var control = new ResizeControl(resizeProvider.Object); + var model = new Mock(); + + control.GetPosition(model.Object); + + resizeProvider.Verify(m => m.GetPosition(model.Object), Times.Once); + } + + [Fact] + public void OnPointerDown_ShouldInvokeResizeStart() + { + var resizeProvider = new Mock(); + var control = new ResizeControl(resizeProvider.Object); + var diagram = Mock.Of(); + 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); + + resizeProvider.Verify(m => m.OnResizeStart(diagram, model, eventArgs), Times.Once); + } + + [Fact] + public void OnPointerDown_ShouldAddEventHandlers() + { + 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); + + diagram.TriggerPointerUp(model, eventArgs); + resizeProvider.Verify(m => m.OnResizeEnd(model, eventArgs), Times.Once); + } + + [Fact] + public void OnPointerUp_ShouldRemoveEventHandlers() + { + 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.TriggerPointerUp(model, eventArgs); + + diagram.TriggerPointerMove(model, eventArgs); + resizeProvider.Verify(m => m.OnPointerMove(model, eventArgs), Times.Never); + + diagram.TriggerPointerUp(model, eventArgs); + resizeProvider.Verify(m => m.OnResizeEnd(model, eventArgs), Times.Once); + } +} \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs new file mode 100644 index 000000000..b27edc990 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs @@ -0,0 +1,53 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Moq; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Models +{ + public class NodeModelTest + { + [Fact] + public void UpdatePortOnSetPosition() + { + var node = new NodeModel(position: new Point(100, 100)); + node.Size = new Size(100, 100); + + var port = new PortModel(node, PortAlignment.BottomLeft, new Point(50, 50)); + node.AddPort(port); + + var newX = 200; + var newY = 300; + + //Act + node.SetPosition(newX, newY); + + //Assert + Assert.Equal(150, port.Position.X); + Assert.Equal(250, port.Position.Y); + } + + [Fact] + public void SetPortPositionOnNodeSizeChangedIsCalledOnSetSize() + { + // Arrange + var oldWidth = 100.0; + var oldHeight = 100.0; + var newWidth = 500.0; + var newHeight = 700.0; + var deltaX = newWidth - oldWidth; + var deltaY = newHeight - oldHeight; + + var node = new NodeModel(new Point(100, 100)) { Size = new Size(oldWidth, oldHeight) }; + var portMock = new Mock(node, PortAlignment.BottomLeft, null, null); + + node.AddPort(portMock.Object); + + // Act + node.SetSize(newWidth, newHeight); + + // Assert + portMock.Verify(m => m.SetPortPositionOnNodeSizeChanged(deltaX, deltaY), Times.Once); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs new file mode 100644 index 000000000..d4dc57bf5 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs @@ -0,0 +1,33 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Models +{ + public class PortModelTest + { + [Theory] + [InlineData(PortAlignment.Top, 50, 0)] + [InlineData(PortAlignment.TopLeft, 0, 0)] + [InlineData(PortAlignment.TopRight, 100, 0)] + [InlineData(PortAlignment.Bottom, 50, 100)] + [InlineData(PortAlignment.BottomLeft, 0, 100)] + [InlineData(PortAlignment.BottomRight, 100, 100)] + [InlineData(PortAlignment.Left, 0, 50)] + [InlineData(PortAlignment.Right, 100, 50)] + public void SetPortPositionOnNodeSizeChangedCalculatesCorrectPosition(PortAlignment alignment, double expectedXPosition, double expectedYPosition) + { + // Arrange + var node = new NodeModel(); + var port = new PortModel(node, alignment, new Point(0, 0)); + node.Size = new Size(100, 100); + + // Act + port.SetPortPositionOnNodeSizeChanged(100, 100); + + // Assert + Assert.Equal(expectedXPosition, port.Position.X); + Assert.Equal(expectedYPosition, port.Position.Y); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs new file mode 100644 index 000000000..1605cb14f --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs @@ -0,0 +1,145 @@ +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; + +public class BottomLeftResizerProviderTests +{ + [Fact] + public void DragResizer_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); + 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 DragResizer_SmallerThanMinSize_SetsNodeToMinSize() + { + // 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); + eventArgs = new PointerEventArgs(99, -199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, 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(99); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(0); + node.Size.Height.Should().Be(0); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut() + { + // 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); + diagram.SetZoom(0.5); + + // 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); + 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(20); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(80); + node.Size.Height.Should().Be(230); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn() + { + // 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); + diagram.SetZoom(2); + + // 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); + 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(5); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(95); + node.Size.Height.Should().Be(207.5); + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs new file mode 100644 index 000000000..959724e6d --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs @@ -0,0 +1,143 @@ +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; + +public class BottomRightResizerProviderTests +{ + [Fact] + public void DragResizer_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); + 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 DragResizer_SmallerThanMinSize_SetsNodeToMinSize() + { + // 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); + 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(0); + node.Size.Height.Should().Be(0); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut() + { + // 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); + diagram.SetZoom(0.5); + + // 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); + 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(120); + node.Size.Height.Should().Be(230); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn() + { + // 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); + diagram.SetZoom(2); + + // 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); + 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(105); + node.Size.Height.Should().Be(207.5); + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs new file mode 100644 index 000000000..13d241d08 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs @@ -0,0 +1,145 @@ +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; + +public class TopLeftResizerProviderTests +{ + [Fact] + public void DragResizer_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); + 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 DragResizer_SmallerThanMinSize_SetsNodeToMinSize() + { + // 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); + eventArgs = new PointerEventArgs(99, 199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, 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(99); + node.Position.Y.Should().Be(199); + node.Size.Width.Should().Be(0); + node.Size.Height.Should().Be(0); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut() + { + // 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); + diagram.SetZoom(0.5); + + // 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); + 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(20); + node.Position.Y.Should().Be(30); + node.Size.Width.Should().Be(80); + node.Size.Height.Should().Be(170); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn() + { + // 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); + diagram.SetZoom(2); + + // 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); + 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(5); + node.Position.Y.Should().Be(7.5); + node.Size.Width.Should().Be(95); + node.Size.Height.Should().Be(192.5); + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs new file mode 100644 index 000000000..5fcd3b821 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs @@ -0,0 +1,145 @@ +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; + +public class TopRightResizerProviderTests +{ + [Fact] + public void DragResizer_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); + 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 DragResizer_SmallerThanMinSize_SetsNodeToMinSize() + { + // 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); + eventArgs = new PointerEventArgs(-99, 199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, 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(199); + node.Size.Width.Should().Be(0); + node.Size.Height.Should().Be(0); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut() + { + // 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); + diagram.SetZoom(0.5); + + // 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); + 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(30); + node.Size.Width.Should().Be(120); + node.Size.Height.Should().Be(170); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn() + { + // 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); + diagram.SetZoom(2); + + // 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); + 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(7.5); + node.Size.Width.Should().Be(105); + node.Size.Height.Should().Be(192.5); + } +} diff --git a/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs new file mode 100644 index 000000000..ee4aa80a1 --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs @@ -0,0 +1,24 @@ +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 +{ + [Fact] + public void ShouldRenderDiv() + { + using var ctx = new TestContext(); + var providerMock = Mock.Of(); + + var cut = ctx.RenderComponent(parameters => + parameters.Add(w => w.Control, new ResizeControl(providerMock)) + ); + + cut.MarkupMatches("
"); + } +}