From 88ab362ea3c3c789e74dd456d80824054b21220b Mon Sep 17 00:00:00 2001 From: Chun Liu Date: Wed, 17 Nov 2021 14:25:46 +0800 Subject: [PATCH 001/193] don't update the group's dimensions if the last child is remove. --- src/Blazor.Diagrams.Core/Models/GroupModel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index 14931d84e..e4f711f1a 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -107,6 +107,9 @@ private void OnNodeChanged(NodeModel node) private bool UpdateDimensions() { + if (Children.Count == 0) + return true; + if (Children.Any(n => n.Size == null)) return false; From 89d1efbb0f60cdc00a31689032f7331ed7325c20 Mon Sep 17 00:00:00 2001 From: Chun Liu Date: Tue, 23 Nov 2021 14:56:13 +0800 Subject: [PATCH 002/193] Updates to supoort groups in a group. --- src/Blazor.Diagrams.Core/Diagram.cs | 1 + src/Blazor.Diagrams.Core/Models/GroupModel.cs | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 3a51a765b..e1ac1af66 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -166,6 +166,7 @@ public void RemoveGroup(GroupModel group) Batch(() => { + _groups.RemoveAll(g => ((NodeModel)g).Group?.Equals(group) ?? false); Nodes.Remove(group.Children.ToArray()); Links.Remove(group.AllLinks.ToArray()); group.Ungroup(); diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index e4f711f1a..75a7ca23a 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -101,6 +101,12 @@ private void OnNodeChanged(NodeModel node) { if (UpdateDimensions()) { + if (Group?.UpdateDimensions() ?? false) + { + // Update the parent group's dimensions + Group.Refresh(); + } + Refresh(); } } From 9026312f01ae890627dfb4bae16320e8c0b3a2e9 Mon Sep 17 00:00:00 2001 From: Chun Liu Date: Wed, 24 Nov 2021 14:21:02 +0800 Subject: [PATCH 003/193] Revert "Updates to supoort groups in a group." This reverts commit 89d1efbb0f60cdc00a31689032f7331ed7325c20. --- src/Blazor.Diagrams.Core/Diagram.cs | 1 - src/Blazor.Diagrams.Core/Models/GroupModel.cs | 6 ------ 2 files changed, 7 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index e1ac1af66..3a51a765b 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -166,7 +166,6 @@ public void RemoveGroup(GroupModel group) Batch(() => { - _groups.RemoveAll(g => ((NodeModel)g).Group?.Equals(group) ?? false); Nodes.Remove(group.Children.ToArray()); Links.Remove(group.AllLinks.ToArray()); group.Ungroup(); diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index 75a7ca23a..e4f711f1a 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -101,12 +101,6 @@ private void OnNodeChanged(NodeModel node) { if (UpdateDimensions()) { - if (Group?.UpdateDimensions() ?? false) - { - // Update the parent group's dimensions - Group.Refresh(); - } - Refresh(); } } From af6eb4da61874ede2a4604186c642e1ef5ca086d Mon Sep 17 00:00:00 2001 From: zHaytam Date: Sat, 15 Jan 2022 11:17:22 +0100 Subject: [PATCH 004/193] Add Blazor.Diagrams tests project --- Blazor.Diagrams.sln | 17 ++++++++++---- src/Blazor.Diagrams.Core/Diagram.cs | 1 + .../Blazor.Diagrams.Tests.csproj | 23 +++++++++++++++++++ 3 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj diff --git a/Blazor.Diagrams.sln b/Blazor.Diagrams.sln index 7f2699817..76f6b479c 100644 --- a/Blazor.Diagrams.sln +++ b/Blazor.Diagrams.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30114.128 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32014.148 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EE32E278-A887-454E-987D-FFE9E37169FE}" EndProject @@ -31,11 +31,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Diagrams.Core.Tests" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{A9FC9B20-A9F1-4066-8B59-83BD26D3B1C8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Diagram-Demo", "docs\Diagram-Demo\Diagram-Demo.csproj", "{5F423724-5319-4DCE-B9F2-8B2D7E1FDC17}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Diagram-Demo", "docs\Diagram-Demo\Diagram-Demo.csproj", "{5F423724-5319-4DCE-B9F2-8B2D7E1FDC17}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomNodesLinks", "docs\CustomNodesLinks\CustomNodesLinks.csproj", "{3D104DB4-C7F0-42CA-9D78-AB2C8A8AE3D5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomNodesLinks", "docs\CustomNodesLinks\CustomNodesLinks.csproj", "{3D104DB4-C7F0-42CA-9D78-AB2C8A8AE3D5}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Layouts", "docs\Layouts\Layouts.csproj", "{78C85C89-B464-4083-8829-78BA52BB4780}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Layouts", "docs\Layouts\Layouts.csproj", "{78C85C89-B464-4083-8829-78BA52BB4780}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blazor.Diagrams.Tests", "tests\Blazor.Diagrams.Tests\Blazor.Diagrams.Tests.csproj", "{ED3B0D8F-F29A-4C66-A167-C36824A76902}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -83,6 +85,10 @@ Global {78C85C89-B464-4083-8829-78BA52BB4780}.Debug|Any CPU.Build.0 = Debug|Any CPU {78C85C89-B464-4083-8829-78BA52BB4780}.Release|Any CPU.ActiveCfg = Release|Any CPU {78C85C89-B464-4083-8829-78BA52BB4780}.Release|Any CPU.Build.0 = Release|Any CPU + {ED3B0D8F-F29A-4C66-A167-C36824A76902}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ED3B0D8F-F29A-4C66-A167-C36824A76902}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ED3B0D8F-F29A-4C66-A167-C36824A76902}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ED3B0D8F-F29A-4C66-A167-C36824A76902}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -98,6 +104,7 @@ Global {5F423724-5319-4DCE-B9F2-8B2D7E1FDC17} = {A9FC9B20-A9F1-4066-8B59-83BD26D3B1C8} {3D104DB4-C7F0-42CA-9D78-AB2C8A8AE3D5} = {A9FC9B20-A9F1-4066-8B59-83BD26D3B1C8} {78C85C89-B464-4083-8829-78BA52BB4780} = {A9FC9B20-A9F1-4066-8B59-83BD26D3B1C8} + {ED3B0D8F-F29A-4C66-A167-C36824A76902} = {CEEAE4C2-CE68-4FC3-9E0F-D4781B91F7F4} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {969540A2-8162-4063-A4E3-B488F69BD582} diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 3a51a765b..f091f84fb 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -12,6 +12,7 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Blazor.Diagrams")] +[assembly: InternalsVisibleTo("Blazor.Diagrams.Tests")] [assembly: InternalsVisibleTo("Blazor.Diagrams.Core.Tests")] namespace Blazor.Diagrams.Core { diff --git a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj new file mode 100644 index 000000000..1f59fe075 --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj @@ -0,0 +1,23 @@ + + + + netcoreapp3.1 + enable + false + + + + + + + + + + + + + + + + + From 2790dd0ee88e818f3b7c44c37ab6316ba71bc7c9 Mon Sep 17 00:00:00 2001 From: zHaytam Date: Sat, 15 Jan 2022 11:17:47 +0100 Subject: [PATCH 005/193] Add tests for LinkVertex,Node and SvgNode widgets --- .../Components/LinkVertexWidgetTests.cs | 112 ++++++++++++++++++ .../Components/NodeWidgetTests.cs | 37 ++++++ .../Components/SvgNodeWidgetTests.cs | 23 ++++ 3 files changed, 172 insertions(+) create mode 100644 tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs create mode 100644 tests/Blazor.Diagrams.Tests/Components/NodeWidgetTests.cs create mode 100644 tests/Blazor.Diagrams.Tests/Components/SvgNodeWidgetTests.cs diff --git a/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs new file mode 100644 index 000000000..fcca0b773 --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs @@ -0,0 +1,112 @@ + +using Blazor.Diagrams.Components; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; + +using Bunit; + +using FluentAssertions; + +using Microsoft.AspNetCore.Components.Web; + +using System.Threading.Tasks; + +using Xunit; + +namespace Blazor.Diagrams.Tests.Components +{ + public class LinkVertexWidgetTests + { + [Fact] + public void ShouldRenderCircle() + { + // Arrange + using var ctx = new TestContext(); + var node1 = new NodeModel(); + var node2 = new NodeModel(); + var link = new LinkModel(node1, node2); + var vertex = new LinkVertexModel(link, new Point(10.5, 20)); + link.Vertices.Add(vertex); + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.Vertex, vertex) + .Add(n => n.Color, "red") + .Add(n => n.SelectedColor, "blue")); + + // Assert + cut.MarkupMatches(""); + } + + [Fact] + public void ShouldRenderCircleWithSelectedColor_WhenVertexIsSelected() + { + // Arrange + using var ctx = new TestContext(); + var node1 = new NodeModel(); + var node2 = new NodeModel(); + var link = new LinkModel(node1, node2); + var vertex = new LinkVertexModel(link, new Point(10.5, 20)); + link.Vertices.Add(vertex); + vertex.Selected = true; + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.Vertex, vertex) + .Add(n => n.Color, "red") + .Add(n => n.SelectedColor, "blue")); + + // Assert + cut.MarkupMatches(""); + } + + [Fact] + public void ShouldRerender_WhenVertexIsRefreshed() + { + // Arrange + using var ctx = new TestContext(); + var node1 = new NodeModel(); + var node2 = new NodeModel(); + var link = new LinkModel(node1, node2); + var vertex = new LinkVertexModel(link, new Point(10.5, 20)); + link.Vertices.Add(vertex); + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.Vertex, vertex) + .Add(n => n.Color, "red") + .Add(n => n.SelectedColor, "blue")); + + // Assert + cut.RenderCount.Should().Be(1); + vertex.Refresh(); + cut.RenderCount.Should().Be(2); + } + + [Fact] + public async Task ShouldDeleteItselfAndRefreshParent_WhenDoubleClicked() + { + // Arrange + using var ctx = new TestContext(); + var node1 = new NodeModel(); + var node2 = new NodeModel(); + var link = new LinkModel(node1, node2); + int linkRefreshes = 0; + var vertex = new LinkVertexModel(link, new Point(10.5, 20)); + link.Vertices.Add(vertex); + link.Changed += () => linkRefreshes++; + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.Vertex, vertex) + .Add(n => n.Color, "red") + .Add(n => n.SelectedColor, "blue")); + + await cut.Find("circle").DoubleClickAsync(new MouseEventArgs()); + + // Assert + link.Vertices.Should().BeEmpty(); + linkRefreshes.Should().Be(1); + } + } +} diff --git a/tests/Blazor.Diagrams.Tests/Components/NodeWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/NodeWidgetTests.cs new file mode 100644 index 000000000..9e8ba3239 --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/Components/NodeWidgetTests.cs @@ -0,0 +1,37 @@ +using Blazor.Diagrams.Components; +using Blazor.Diagrams.Components.Renderers; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; + +using Bunit; + +using FluentAssertions; + +using Xunit; + +namespace Blazor.Diagrams.Tests.Components +{ + public class NodeWidgetTests + { + [Fact] + public void DefaultNodeWidget_ShouldHaveSingleClassAndNoPorts_WhenItHasNoPortsAndNoSelectionNorGroup() + { + // Arrange + using var ctx = new TestContext(); + var node = new NodeModel(Point.Zero); + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.Node, node)); + + // Assert + var content = cut.Find("div.default-node"); + content.ClassList.Should().ContainSingle(); + content.ClassList[0].Should().Be("default-node"); + content.TextContent.Trim().Should().Be("Title"); + + var ports = cut.FindComponents(); + ports.Should().BeEmpty(); + } + } +} diff --git a/tests/Blazor.Diagrams.Tests/Components/SvgNodeWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/SvgNodeWidgetTests.cs new file mode 100644 index 000000000..e13500b4a --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/Components/SvgNodeWidgetTests.cs @@ -0,0 +1,23 @@ +using Blazor.Diagrams.Components; + +using Bunit; +using Xunit; + +namespace Blazor.Diagrams.Tests.Components +{ + public class SvgNodeWidgetTests + { + [Fact] + public void ShouldRenderSimpleRect() + { + // Arrange + using var ctx = new TestContext(); + + // Act + var cut = ctx.RenderComponent(); + + // Assert + cut.MarkupMatches(""); + } + } +} From c18b9cc7cf25e871425d5839ae175e221e5221fd Mon Sep 17 00:00:00 2001 From: zHaytam Date: Sun, 23 Jan 2022 12:48:21 +0100 Subject: [PATCH 006/193] Use InvokeAsync(SHC) --- src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs | 10 +++++----- .../Components/Groups/GroupContainer.razor.cs | 2 +- .../Components/LinkVertexWidget.razor.cs | 4 ++-- .../Components/NavigatorWidget.razor.cs | 2 +- .../Components/Renderers/LinkLabelRenderer.cs | 2 +- .../Components/Renderers/LinkRenderer.cs | 8 ++++---- .../Components/Renderers/NodeRenderer.cs | 2 +- .../Components/Renderers/PortRenderer.cs | 2 +- .../Components/SelectionBoxWidget.razor.cs | 4 ++-- 9 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index f6b6c1384..9aa9c2eb3 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -25,7 +25,7 @@ public partial class DiagramCanvas : IDisposable protected ElementReference elementReference; private DotNetObjectReference _reference; - private bool _shouldReRender; + private bool _shouldRender; private string LayerStyle => FormattableString.Invariant($"transform: translate({Diagram.Pan.X}px, {Diagram.Pan.Y}px) scale({Diagram.Zoom});"); @@ -54,9 +54,9 @@ protected override async Task OnAfterRenderAsync(bool firstRender) protected override bool ShouldRender() { - if (_shouldReRender) + if (_shouldRender) { - _shouldReRender = false; + _shouldRender = false; return true; } @@ -81,8 +81,8 @@ protected override bool ShouldRender() private void OnDiagramChanged() { - _shouldReRender = true; - StateHasChanged(); + _shouldRender = true; + InvokeAsync(StateHasChanged); } public void Dispose() diff --git a/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor.cs b/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor.cs index e02e02c4e..2bd0749e6 100644 --- a/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor.cs +++ b/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor.cs @@ -64,7 +64,7 @@ protected override void OnAfterRender(bool firstRender) private void OnGroupChanged() { _shouldRender = true; - StateHasChanged(); + InvokeAsync(StateHasChanged); } private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Group, e); diff --git a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs index 6eada93e6..c3101cbbe 100644 --- a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs @@ -10,7 +10,7 @@ public partial class LinkVertexWidget : IDisposable { private bool _shouldRender = true; - [CascadingParameter] public Diagram Diagram { get; set; } + [CascadingParameter] public IDiagram Diagram { get; set; } [Parameter] public LinkVertexModel Vertex { get; set; } [Parameter] public string Color { get; set; } [Parameter] public string SelectedColor { get; set; } @@ -41,7 +41,7 @@ protected override bool ShouldRender() private void OnVertexChanged() { _shouldRender = true; - StateHasChanged(); + InvokeAsync(StateHasChanged); } private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Vertex, e); diff --git a/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs b/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs index e204046a8..744a83772 100644 --- a/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs @@ -74,7 +74,7 @@ private void Refresh() NodePositionAdjustment = new Point(nodesMinX < 0 ? Math.Abs(nodesMinX) : 0, nodesMinY < 0 ? Math.Abs(nodesMinY) : 0); XFactor = Width / fullSizeWidth; YFactor = Height / fullSizeHeight; - StateHasChanged(); + InvokeAsync(StateHasChanged); } private void AdjustFullSizeWithNodesRect(double nodesMinX, double nodesMinY, ref double fullSizeWidth, diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs index c0ca02a49..4bf4a8f47 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs @@ -41,7 +41,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.CloseComponent(); } - private void OnLabelChanged() => StateHasChanged(); + private void OnLabelChanged() => InvokeAsync(StateHasChanged); private Point FindPosition() { diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs index 708049193..47cbf903a 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs @@ -19,14 +19,14 @@ public class LinkRenderer : ComponentBase, IDisposable public void Dispose() { - Link.Changed -= Link_Changed; + Link.Changed -= OnLinkChanged; } protected override void OnInitialized() { base.OnInitialized(); - Link.Changed += Link_Changed; + Link.Changed += OnLinkChanged; } protected override bool ShouldRender() => _shouldRender; @@ -57,10 +57,10 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) protected override void OnAfterRender(bool firstRender) => _shouldRender = false; - private void Link_Changed() + private void OnLinkChanged() { _shouldRender = true; - StateHasChanged(); + InvokeAsync(StateHasChanged); } private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Link, e); diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index 85f37a253..5bd1822fd 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -164,7 +164,7 @@ private async void CheckVisibility() private void ReRender() { _shouldRender = true; - StateHasChanged(); + InvokeAsync(StateHasChanged); } private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Node, e); diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs index 61e00ab3d..e742e57c2 100644 --- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs @@ -142,7 +142,7 @@ private async void OnPortChanged() if (Port.Initialized) { _shouldRender = true; - StateHasChanged(); + await InvokeAsync(StateHasChanged); } else { diff --git a/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs b/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs index 787de7cf0..891d97a27 100644 --- a/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs @@ -62,7 +62,7 @@ private void OnMouseMove(Model model, MouseEventArgs e) } } - StateHasChanged(); + InvokeAsync(StateHasChanged); } private void SetSelectionBoxInformation(MouseEventArgs e) @@ -80,7 +80,7 @@ private void OnMouseUp(Model model, MouseEventArgs e) _initialClientPoint = null; _selectionBoxTopLeft = null; _selectionBoxSize = null; - StateHasChanged(); + InvokeAsync(StateHasChanged); } public void Dispose() From e3e8811479794f6cfea038b2e075f19b5cbe441e Mon Sep 17 00:00:00 2001 From: zHaytam Date: Sat, 29 Jan 2022 13:37:29 +0100 Subject: [PATCH 007/193] Update LinkVertexWidget.razor.cs --- src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs index c3101cbbe..418433afd 100644 --- a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs @@ -10,7 +10,7 @@ public partial class LinkVertexWidget : IDisposable { private bool _shouldRender = true; - [CascadingParameter] public IDiagram Diagram { get; set; } + [CascadingParameter] public Diagram Diagram { get; set; } [Parameter] public LinkVertexModel Vertex { get; set; } [Parameter] public string Color { get; set; } [Parameter] public string SelectedColor { get; set; } From 022149d90239e40898b162a0d58f4cde8cb236aa Mon Sep 17 00:00:00 2001 From: zHaytam Date: Sat, 29 Jan 2022 13:42:03 +0100 Subject: [PATCH 008/193] Upgrade all projects to .NET 6 --- docs/CustomNodesLinks/CustomNodesLinks.csproj | 2 +- docs/Diagram-Demo/Diagram-Demo.csproj | 2 +- docs/Layouts/Layouts.csproj | 2 +- samples/ServerSide/ServerSide.csproj | 2 +- samples/SharedDemo/SharedDemo.csproj | 7 +++---- samples/Wasm/Wasm.csproj | 13 ++++++------- .../Blazor.Diagrams.Algorithms.csproj | 2 +- .../Blazor.Diagrams.Core.csproj | 6 +++--- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 7 +++---- .../Blazor.Diagrams.Core.Tests.csproj | 4 ++-- .../Blazor.Diagrams.Tests.csproj | 2 +- 11 files changed, 23 insertions(+), 26 deletions(-) diff --git a/docs/CustomNodesLinks/CustomNodesLinks.csproj b/docs/CustomNodesLinks/CustomNodesLinks.csproj index 2f00b3a27..0b40cdb67 100644 --- a/docs/CustomNodesLinks/CustomNodesLinks.csproj +++ b/docs/CustomNodesLinks/CustomNodesLinks.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 diff --git a/docs/Diagram-Demo/Diagram-Demo.csproj b/docs/Diagram-Demo/Diagram-Demo.csproj index 4b8e0d0a5..7bc16f503 100644 --- a/docs/Diagram-Demo/Diagram-Demo.csproj +++ b/docs/Diagram-Demo/Diagram-Demo.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 Diagram_Demo diff --git a/docs/Layouts/Layouts.csproj b/docs/Layouts/Layouts.csproj index 762134e56..3350a9e9e 100644 --- a/docs/Layouts/Layouts.csproj +++ b/docs/Layouts/Layouts.csproj @@ -1,7 +1,7 @@ - net5.0 + net6.0 diff --git a/samples/ServerSide/ServerSide.csproj b/samples/ServerSide/ServerSide.csproj index 89612adab..7a5552fa1 100644 --- a/samples/ServerSide/ServerSide.csproj +++ b/samples/ServerSide/ServerSide.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 diff --git a/samples/SharedDemo/SharedDemo.csproj b/samples/SharedDemo/SharedDemo.csproj index e72d2894d..79272cab8 100644 --- a/samples/SharedDemo/SharedDemo.csproj +++ b/samples/SharedDemo/SharedDemo.csproj @@ -1,13 +1,12 @@  - netstandard2.1 - 3.0 + net6.0 - - + + diff --git a/samples/Wasm/Wasm.csproj b/samples/Wasm/Wasm.csproj index b4f7f1a06..f88bb08ef 100644 --- a/samples/Wasm/Wasm.csproj +++ b/samples/Wasm/Wasm.csproj @@ -1,15 +1,14 @@ - + - netstandard2.1 - 3.0 + net6.0 + enable - - - - + + + diff --git a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj index 2facce903..4bcfa56fa 100644 --- a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj +++ b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj @@ -1,7 +1,7 @@ - netstandard2.1 + net6.0 enable true MIT diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index 99cc8f32b..cf888e687 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + net6.0 enable true MIT @@ -17,9 +17,9 @@ ZBD.png https://blazor-diagrams.zhaytam.com/ - + - + diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index 12c245096..f2b544cfb 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -1,8 +1,7 @@  - netstandard2.1 - 3.0 + net6.0 zHaytam MIT 2.1.6 @@ -20,8 +19,8 @@ - - + + diff --git a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj index 4f95ed612..8d8622a35 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj +++ b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj @@ -1,8 +1,8 @@ - netcoreapp3.1 - + net6.0 + enable false diff --git a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj index 1f59fe075..ade3cbc69 100644 --- a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj +++ b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj @@ -1,7 +1,7 @@ - netcoreapp3.1 + net6.0 enable false From 8d573bef46197a8184f3a210e6ed1405e67facae Mon Sep 17 00:00:00 2001 From: William D Cossey Date: Tue, 29 Mar 2022 10:44:13 +0100 Subject: [PATCH 009/193] Fixes issue with `` dragging on FireFox. --- samples/SharedDemo/Demos/DragAndDrop.razor | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/samples/SharedDemo/Demos/DragAndDrop.razor b/samples/SharedDemo/Demos/DragAndDrop.razor index 0e1dd3776..fcead75a2 100644 --- a/samples/SharedDemo/Demos/DragAndDrop.razor +++ b/samples/SharedDemo/Demos/DragAndDrop.razor @@ -8,14 +8,18 @@
- - Default Node +
+ + Default Node +
- - Bot Answer Node +
+ + Bot Answer Node +
From 7d5141ec2da19e19df033a73c9f642e2fac39bbf Mon Sep 17 00:00:00 2001 From: Ricardo Barbosa Date: Wed, 30 Mar 2022 13:15:55 +0100 Subject: [PATCH 010/193] Fixed null argument exception when diagram is toggled on/off --- .../Components/NavigatorWidget.razor | 2 +- .../Components/NavigatorWidget.razor.cs | 23 +++++++++++-------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/Blazor.Diagrams/Components/NavigatorWidget.razor b/src/Blazor.Diagrams/Components/NavigatorWidget.razor index 7bfd1db13..e2608834d 100644 --- a/src/Blazor.Diagrams/Components/NavigatorWidget.razor +++ b/src/Blazor.Diagrams/Components/NavigatorWidget.razor @@ -1,4 +1,4 @@ -@if (Diagram.Container != null) +@if (Diagram?.Container != null) { var addedNodeX = Math.Max(0, Diagram.Pan.X) + NodePositionAdjustment.X; var addedNodeY = Math.Max(0, Diagram.Pan.Y) + NodePositionAdjustment.Y; diff --git a/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs b/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs index 744a83772..be5beb053 100644 --- a/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs @@ -23,18 +23,21 @@ public partial class NavigatorWidget : IDisposable protected override void OnParametersSet() { base.OnParametersSet(); + if(Diagram != null) + { + foreach (var node in Diagram.Nodes) + node.Changed += Refresh; - foreach (var node in Diagram.Nodes) - node.Changed += Refresh; - - foreach (var group in Diagram.Groups) - group.Changed += Refresh; + foreach (var group in Diagram.Groups) + group.Changed += Refresh; - Diagram.Changed += Diagram_Changed; - Diagram.Nodes.Added += Diagram_NodesAdded; - Diagram.Nodes.Removed += Diagram_NodesRemoved; - Diagram.GroupAdded += Diagram_GroupAdded; - Diagram.GroupRemoved += Diagram_GroupRemoved; + Diagram.Changed += Diagram_Changed; + Diagram.Nodes.Added += Diagram_NodesAdded; + Diagram.Nodes.Removed += Diagram_NodesRemoved; + Diagram.GroupAdded += Diagram_GroupAdded; + Diagram.GroupRemoved += Diagram_GroupRemoved; + } + } private void Diagram_Changed() => Refresh(); From 573fc339fcaad8e680c8eedee3e96906e5e54d40 Mon Sep 17 00:00:00 2001 From: Ricardo Barbosa Date: Wed, 30 Mar 2022 13:30:23 +0100 Subject: [PATCH 011/193] Update NavigatorWidget.razor.cs Added node and link event unsubscribed --- .../Components/NavigatorWidget.razor.cs | 62 +++++++++++-------- 1 file changed, 36 insertions(+), 26 deletions(-) diff --git a/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs b/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs index be5beb053..f818a6df4 100644 --- a/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs @@ -23,7 +23,7 @@ public partial class NavigatorWidget : IDisposable protected override void OnParametersSet() { base.OnParametersSet(); - if(Diagram != null) + if (Diagram != null) { foreach (var node in Diagram.Nodes) node.Changed += Refresh; @@ -37,7 +37,7 @@ protected override void OnParametersSet() Diagram.GroupAdded += Diagram_GroupAdded; Diagram.GroupRemoved += Diagram_GroupRemoved; } - + } private void Diagram_Changed() => Refresh(); @@ -58,26 +58,30 @@ private void Diagram_NodesRemoved(NodeModel node) private void Refresh() { - var nodes = Diagram.Nodes - .Union(Diagram.Groups) - .Where(n => n.Size?.Equals(Size.Zero) == false).ToList(); - - if (nodes.Count == 0) - return; - - var bounds = nodes.GetBounds(); - var nodesMinX = bounds.Left * Diagram.Zoom; - var nodesMaxX = bounds.Right * Diagram.Zoom; - var nodesMinY = bounds.Top * Diagram.Zoom; - var nodesMaxY = bounds.Bottom * Diagram.Zoom; - - (double fullSizeWidth, double fullSizeHeight) = GetFullSize(nodesMaxX, nodesMaxY); - AdjustFullSizeWithNodesRect(nodesMinX, nodesMinY, ref fullSizeWidth, ref fullSizeHeight); - - NodePositionAdjustment = new Point(nodesMinX < 0 ? Math.Abs(nodesMinX) : 0, nodesMinY < 0 ? Math.Abs(nodesMinY) : 0); - XFactor = Width / fullSizeWidth; - YFactor = Height / fullSizeHeight; - InvokeAsync(StateHasChanged); + if (Diagram != null) + { + var nodes = Diagram.Nodes + .Union(Diagram.Groups) + .Where(n => n.Size?.Equals(Size.Zero) == false).ToList(); + + if (nodes.Count == 0) + return; + + var bounds = nodes.GetBounds(); + var nodesMinX = bounds.Left * Diagram.Zoom; + var nodesMaxX = bounds.Right * Diagram.Zoom; + var nodesMinY = bounds.Top * Diagram.Zoom; + var nodesMaxY = bounds.Bottom * Diagram.Zoom; + + (double fullSizeWidth, double fullSizeHeight) = GetFullSize(nodesMaxX, nodesMaxY); + AdjustFullSizeWithNodesRect(nodesMinX, nodesMinY, ref fullSizeWidth, ref fullSizeHeight); + + NodePositionAdjustment = new Point(nodesMinX < 0 ? Math.Abs(nodesMinX) : 0, nodesMinY < 0 ? Math.Abs(nodesMinY) : 0); + XFactor = Width / fullSizeWidth; + YFactor = Height / fullSizeHeight; + InvokeAsync(StateHasChanged); + } + } private void AdjustFullSizeWithNodesRect(double nodesMinX, double nodesMinY, ref double fullSizeWidth, @@ -188,11 +192,17 @@ private void AdjustFullSizeWithNodesRect(double nodesMinX, double nodesMinY, ref public void Dispose() { - Diagram.Changed -= Diagram_Changed; - Diagram.Nodes.Added -= Diagram_NodesAdded; - Diagram.Nodes.Removed -= Diagram_NodesRemoved; + if (Diagram != null) + { + Diagram.Changed -= Diagram_Changed; + Diagram.Nodes.Added -= Diagram_NodesAdded; + Diagram.Nodes.Removed -= Diagram_NodesRemoved; + foreach (var node in Diagram.Nodes) + node.Changed -= Refresh; - // Todo: unregister node/group changed events + foreach (var group in Diagram.Groups) + group.Changed -= Refresh; + } } } } \ No newline at end of file From d0425998f35b5dbe6dddf8a148896ea0cc1cf968 Mon Sep 17 00:00:00 2001 From: Ricardo Barbosa Date: Thu, 31 Mar 2022 17:55:23 +0100 Subject: [PATCH 012/193] Added clamp value if zoom is less or equal to 0 --- src/Blazor.Diagrams.Core/Diagram.cs | 5 ++++ .../DiagramTests.cs | 24 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 3a51a765b..2ff18e36d 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -40,6 +40,7 @@ public class Diagram : Model public event Action? PanChanged; public event Action? ZoomChanged; public event Action? ContainerChanged; + public const double MIN_ZOOM_VALUE = 0.01; public Diagram(DiagramOptions? options = null) { @@ -323,6 +324,10 @@ public void UpdatePan(double deltaX, double deltaY) public void SetZoom(double newZoom) { + if (newZoom <= 0) + { + newZoom = MIN_ZOOM_VALUE; + } Zoom = newZoom; ZoomChanged?.Invoke(); Refresh(); diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs index 577bb1a69..660a5a719 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs @@ -91,5 +91,29 @@ public void ZoomToFit_ShouldTriggerAppropriateEvents() zoomChanges.Should().Be(1); panChanges.Should().Be(1); } + + [Theory] + [InlineData(-0.5)] + [InlineData(-0.00001)] + [InlineData(0)] + [InlineData(-3)] + public void Zoom_ShoulClampToMinimumValue(double zoomValue) + { + var diagram = new Diagram(); + diagram.SetZoom(zoomValue); + Assert.Equal(diagram.Zoom, Diagram.MIN_ZOOM_VALUE); + } + + [Theory] + [InlineData(0.000001)] + [InlineData(1)] + [InlineData(1.5)] + [InlineData(0.1)] + public void Zoom_SetZoom(double zoomValue) + { + var diagram = new Diagram(); + diagram.SetZoom(zoomValue); + Assert.Equal(diagram.Zoom, zoomValue); + } } } From 7f7e148326da6f5c5149c422234c544a7ddffcca Mon Sep 17 00:00:00 2001 From: Ricardo Barbosa Date: Thu, 31 Mar 2022 18:00:24 +0100 Subject: [PATCH 013/193] Changed min zoom value 0.1 seems to be minimum acceptable value --- src/Blazor.Diagrams.Core/Diagram.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 2ff18e36d..cc489bafb 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -40,7 +40,7 @@ public class Diagram : Model public event Action? PanChanged; public event Action? ZoomChanged; public event Action? ContainerChanged; - public const double MIN_ZOOM_VALUE = 0.01; + public const double MIN_ZOOM_VALUE = 0.1; public Diagram(DiagramOptions? options = null) { From 92788d50de940021b1be6a3a2fea90a5211847c6 Mon Sep 17 00:00:00 2001 From: Ricardo Barbosa Date: Thu, 31 Mar 2022 18:09:15 +0100 Subject: [PATCH 014/193] Replaced const with values from zoom options --- src/Blazor.Diagrams.Core/Diagram.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index cc489bafb..ffb720ce4 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -40,7 +40,6 @@ public class Diagram : Model public event Action? PanChanged; public event Action? ZoomChanged; public event Action? ContainerChanged; - public const double MIN_ZOOM_VALUE = 0.1; public Diagram(DiagramOptions? options = null) { @@ -325,9 +324,10 @@ public void UpdatePan(double deltaX, double deltaY) public void SetZoom(double newZoom) { if (newZoom <= 0) - { - newZoom = MIN_ZOOM_VALUE; - } + newZoom = Options.Zoom.Minimum; + else if (newZoom > Options.Zoom.Maximum) + newZoom = Options.Zoom.Maximum; + Zoom = newZoom; ZoomChanged?.Invoke(); Refresh(); From 5d2dcc972ace22c280d43d7b87dc5aca30636a13 Mon Sep 17 00:00:00 2001 From: Ricardo Barbosa Date: Thu, 31 Mar 2022 18:27:46 +0100 Subject: [PATCH 015/193] Added checks for zoom value --- src/Blazor.Diagrams.Core/Diagram.cs | 10 +++++++-- .../DiagramTests.cs | 21 ++++++++----------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index ffb720ce4..291bb6fd1 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -324,9 +324,15 @@ public void UpdatePan(double deltaX, double deltaY) public void SetZoom(double newZoom) { if (newZoom <= 0) + throw new ArgumentException($"{nameof(newZoom)} cannot be equal or lower than 0"); + + if (Options.Zoom.Minimum <= 0) + throw new ArgumentException($"(Zoom Options) =>{nameof(Options.Zoom.Minimum)} cannot be equal or lower than 0"); + + if (newZoom < Options.Zoom.Minimum) newZoom = Options.Zoom.Minimum; - else if (newZoom > Options.Zoom.Maximum) - newZoom = Options.Zoom.Maximum; + + Zoom = newZoom; ZoomChanged?.Invoke(); diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs index 660a5a719..cff5b8505 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs @@ -1,6 +1,7 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using FluentAssertions; +using System; using Xunit; namespace Blazor.Diagrams.Core.Tests @@ -93,27 +94,23 @@ public void ZoomToFit_ShouldTriggerAppropriateEvents() } [Theory] - [InlineData(-0.5)] - [InlineData(-0.00001)] - [InlineData(0)] - [InlineData(-3)] + [InlineData(0.001)] + [InlineData(0.1)] public void Zoom_ShoulClampToMinimumValue(double zoomValue) { var diagram = new Diagram(); diagram.SetZoom(zoomValue); - Assert.Equal(diagram.Zoom, Diagram.MIN_ZOOM_VALUE); + Assert.Equal(diagram.Zoom, diagram.Options.Zoom.Minimum); } [Theory] - [InlineData(0.000001)] - [InlineData(1)] - [InlineData(1.5)] - [InlineData(0.1)] - public void Zoom_SetZoom(double zoomValue) + [InlineData(0)] + [InlineData(-0.1)] + [InlineData(-0.00001)] + public void Zoom_ThrowExceptionWehnLessThan0(double zoomValue) { var diagram = new Diagram(); - diagram.SetZoom(zoomValue); - Assert.Equal(diagram.Zoom, zoomValue); + Assert.Throws(()=>diagram.SetZoom(zoomValue)); } } } From e7de07116f35e7114b18d0cf3610f43a9eee6ec0 Mon Sep 17 00:00:00 2001 From: Ricardo Barbosa Date: Tue, 5 Apr 2022 11:37:34 +0100 Subject: [PATCH 016/193] Added Zoom Options minimum check Added test for minimum zoom options --- src/Blazor.Diagrams.Core/Diagram.cs | 3 +-- src/Blazor.Diagrams.Core/DiagramOptions.cs | 10 +++++++++- tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs | 14 ++++++++++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 291bb6fd1..10fd12def 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -326,8 +326,7 @@ public void SetZoom(double newZoom) if (newZoom <= 0) throw new ArgumentException($"{nameof(newZoom)} cannot be equal or lower than 0"); - if (Options.Zoom.Minimum <= 0) - throw new ArgumentException($"(Zoom Options) =>{nameof(Options.Zoom.Minimum)} cannot be equal or lower than 0"); + if (newZoom < Options.Zoom.Minimum) newZoom = Options.Zoom.Minimum; diff --git a/src/Blazor.Diagrams.Core/DiagramOptions.cs b/src/Blazor.Diagrams.Core/DiagramOptions.cs index 56aefd38a..cfb5825bc 100644 --- a/src/Blazor.Diagrams.Core/DiagramOptions.cs +++ b/src/Blazor.Diagrams.Core/DiagramOptions.cs @@ -60,11 +60,19 @@ public class DiagramZoomOptions [Description("Whether to inverse the zoom direction or not")] public bool Inverse { get; set; } [Description("Minimum value allowed")] - public double Minimum { get; set; } = 0.1; + public double Minimum { get { return _minimum; } set { SetMinimum(value); } } + private double _minimum = 0.1; [Description("Maximum value allowed")] public double Maximum { get; set; } = 2; [Description("Zoom Scale Factor. Should be between 1.01 and 2. Default is 1.05.")] public double ScaleFactor { get; set; } = 1.05; + + private void SetMinimum(double minValue) + { + if (minValue <= 0) + throw new ArgumentException($"(Zoom Options) =>{nameof(Minimum)} cannot be equal or lower than 0"); + _minimum = minValue; + } } /// diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs index cff5b8505..643f31cae 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs @@ -107,10 +107,20 @@ public void Zoom_ShoulClampToMinimumValue(double zoomValue) [InlineData(0)] [InlineData(-0.1)] [InlineData(-0.00001)] - public void Zoom_ThrowExceptionWehnLessThan0(double zoomValue) + public void Zoom_ThrowExceptionWhenLessThan0(double zoomValue) { var diagram = new Diagram(); - Assert.Throws(()=>diagram.SetZoom(zoomValue)); + Assert.Throws(() => diagram.SetZoom(zoomValue)); + } + + [Theory] + [InlineData(0)] + [InlineData(-0.1)] + [InlineData(-0.00001)] + public void ZoomOptions_ThrowExceptionWhenLessThan0(double zoomValue) + { + var diagram = new Diagram(); + Assert.Throws(() => diagram.Options.Zoom.Minimum = zoomValue); } } } From 6adabf8ef36761d9ad60296dcf224722c269ce72 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 22 Jul 2022 12:08:04 +0100 Subject: [PATCH 017/193] Remove Web dependency from Core project --- docs/CustomNodesLinks/Pages/Index.razor | 1 + docs/Diagram-Demo/Pages/Diagrams.razor | 4 +- docs/Layouts/Pages/Index.razor | 4 +- .../ReconnectLinksToClosestPorts.razor.cs | 6 +- .../Demos/CustomGroup/Demo.razor.cs | 2 +- .../SharedDemo/Demos/CustomLink/Demo.razor.cs | 2 +- samples/SharedDemo/Demos/CustomNode.razor.cs | 4 +- .../SharedDemo/Demos/CustomPort/Demo.razor.cs | 4 +- samples/SharedDemo/Demos/DragAndDrop.razor.cs | 2 +- .../Demos/DynamicInsertions.razor.cs | 10 +- samples/SharedDemo/Demos/Events.razor.cs | 2 +- .../Demos/Groups/CustomShortcut.razor.cs | 4 +- .../SharedDemo/Demos/Groups/Dynamic.razor.cs | 4 +- .../SharedDemo/Demos/Groups/Factory.razor.cs | 4 +- .../SharedDemo/Demos/Groups/Grouping.razor.cs | 4 +- .../Demos/Links/LabelsDemo.razor.cs | 6 +- .../Demos/Links/MarkersDemo.razor.cs | 2 +- .../Demos/Links/PathGeneratorsDemo.razor.cs | 3 +- .../Demos/Links/RoutersDemo.razor.cs | 3 +- .../Demos/Links/SnappingDemo.razor.cs | 2 +- .../Demos/Links/VerticesDemo.razor.cs | 3 +- samples/SharedDemo/Demos/Locked.razor.cs | 2 +- .../Demos/Nodes/PortlessLinks.razor.cs | 5 +- .../SharedDemo/Demos/Nodes/SvgDemo.razor.cs | 2 +- samples/SharedDemo/Demos/Performance.razor.cs | 4 +- samples/SharedDemo/Demos/Simple.razor.cs | 3 +- samples/SharedDemo/Demos/SnapToGrid.razor.cs | 3 +- samples/SharedDemo/Demos/ZoomToFit.razor.cs | 6 +- .../LinksReconnectionAlgorithms.cs | 2 +- src/Blazor.Diagrams.Core/Behavior.cs | 4 +- .../Behaviors/DebugEventsBehavior.cs | 2 +- .../Behaviors/DeleteSelectionBehavior.cs | 4 +- .../Behaviors/DragMovablesBehavior.cs | 4 +- .../Behaviors/DragNewLinkBehavior.cs | 4 +- .../Behaviors/EventsBehavior.cs | 4 +- .../Behaviors/GroupingBehavior.cs | 4 +- .../Behaviors/PanBehavior.cs | 4 +- .../Behaviors/SelectionBehavior.cs | 4 +- .../Behaviors/ZoomBehavior.cs | 4 +- .../Blazor.Diagrams.Core.csproj | 50 +++++----- src/Blazor.Diagrams.Core/Delegates.cs | 8 +- .../{Diagram.cs => DiagramBase.cs} | 62 ++++--------- src/Blazor.Diagrams.Core/DiagramOptions.cs | 2 +- .../Events/KeyboardEventArgs.cs | 4 + .../Events/MouseEventArgs.cs | 4 + .../Events/TouchEventArgs.cs | 5 + .../Events/WheelEventArgs.cs | 15 +++ src/Blazor.Diagrams.Core/Layers/BaseLayer.cs | 4 +- src/Blazor.Diagrams.Core/Layers/LinkLayer.cs | 2 +- src/Blazor.Diagrams.Core/Layers/NodeLayer.cs | 2 +- .../PathGenerators/PathGenerators.Smooth.cs | 2 +- .../PathGenerators/PathGenerators.Straight.cs | 2 +- .../Routers/Routers.Normal.cs | 2 +- .../Routers/Routers.Orthogonal.cs | 2 +- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 91 ++++++++++--------- .../Components/DiagramCanvas.razor.cs | 19 ++-- .../Components/Groups/GroupContainer.razor.cs | 5 +- .../Components/LinkVertexWidget.razor.cs | 9 +- .../Components/LinkWidget.razor.cs | 5 +- .../Components/Renderers/LinkRenderer.cs | 9 +- .../Components/Renderers/NodeRenderer.cs | 8 +- .../Components/Renderers/PortRenderer.cs | 8 +- .../Components/SelectionBoxWidget.razor.cs | 2 +- src/Blazor.Diagrams/Diagram.cs | 35 +++++++ .../Extensions/EventsExtensions.cs | 34 +++++++ .../Behaviors/DeleteSelectionBehaviorTests.cs | 82 ++++------------- .../Behaviors/EventsBehaviorTests.cs | 34 +++---- .../DiagramTests.cs | 14 +-- 68 files changed, 343 insertions(+), 319 deletions(-) rename src/Blazor.Diagrams.Core/{Diagram.cs => DiagramBase.cs} (82%) create mode 100644 src/Blazor.Diagrams.Core/Events/KeyboardEventArgs.cs create mode 100644 src/Blazor.Diagrams.Core/Events/MouseEventArgs.cs create mode 100644 src/Blazor.Diagrams.Core/Events/TouchEventArgs.cs create mode 100644 src/Blazor.Diagrams.Core/Events/WheelEventArgs.cs create mode 100644 src/Blazor.Diagrams/Diagram.cs create mode 100644 src/Blazor.Diagrams/Extensions/EventsExtensions.cs diff --git a/docs/CustomNodesLinks/Pages/Index.razor b/docs/CustomNodesLinks/Pages/Index.razor index 6bcb54eec..969787da1 100644 --- a/docs/CustomNodesLinks/Pages/Index.razor +++ b/docs/CustomNodesLinks/Pages/Index.razor @@ -1,5 +1,6 @@ @page "/" +@using Blazor.Diagrams @using Blazor.Diagrams.Core @using Blazor.Diagrams.Core.Geometry @using Blazor.Diagrams.Core.Models diff --git a/docs/Diagram-Demo/Pages/Diagrams.razor b/docs/Diagram-Demo/Pages/Diagrams.razor index 9951ac8c1..8ecd60e4d 100644 --- a/docs/Diagram-Demo/Pages/Diagrams.razor +++ b/docs/Diagram-Demo/Pages/Diagrams.razor @@ -25,7 +25,7 @@ or it will not be rendered. @code { - private Diagram Diagram { get; set; } + private DiagramBase Diagram { get; set; } protected override void OnInitialized() { @@ -45,7 +45,7 @@ or it will not be rendered. Inverse = false, // Whether to inverse the direction of the zoom when using the wheel } }; - Diagram = new Diagram(options); + Diagram = new DiagramBase(options); Setup(); } diff --git a/docs/Layouts/Pages/Index.razor b/docs/Layouts/Pages/Index.razor index f21525988..0af284803 100644 --- a/docs/Layouts/Pages/Index.razor +++ b/docs/Layouts/Pages/Index.razor @@ -36,7 +36,7 @@ or it will not be rendered. @code { - private Diagram _diagram { get; set; } + private DiagramBase _diagram { get; set; } private string _layout; @@ -58,7 +58,7 @@ or it will not be rendered. Inverse = false, // Whether to inverse the direction of the zoom when using the wheel } }; - _diagram = new Diagram(options); + _diagram = new DiagramBase(options); Setup(); } diff --git a/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor.cs b/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor.cs index 12c43ee8f..491ec8b58 100644 --- a/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor.cs +++ b/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor.cs @@ -1,8 +1,8 @@ -using Blazor.Diagrams.Algorithms; -using Blazor.Diagrams.Core; +using Blazor.Diagrams; +using Blazor.Diagrams.Algorithms; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; -using Blazor.Diagrams.Core.Geometry; namespace SharedDemo.Demos.Algorithms { diff --git a/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs b/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs index 79addca7f..6016b89e5 100644 --- a/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs @@ -1,4 +1,4 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; diff --git a/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs b/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs index 9d506ff21..27e7cab73 100644 --- a/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs @@ -1,4 +1,4 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; diff --git a/samples/SharedDemo/Demos/CustomNode.razor.cs b/samples/SharedDemo/Demos/CustomNode.razor.cs index 55a4e4fca..add6bed08 100644 --- a/samples/SharedDemo/Demos/CustomNode.razor.cs +++ b/samples/SharedDemo/Demos/CustomNode.razor.cs @@ -1,7 +1,7 @@ -using Blazor.Diagrams.Core; -using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams; namespace SharedDemo.Demos { diff --git a/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs b/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs index 54adfc292..0849c4ee4 100644 --- a/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs @@ -1,6 +1,6 @@ -using Blazor.Diagrams.Core; -using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; namespace SharedDemo.Demos.CustomPort { diff --git a/samples/SharedDemo/Demos/DragAndDrop.razor.cs b/samples/SharedDemo/Demos/DragAndDrop.razor.cs index c5be42c4d..e56110123 100644 --- a/samples/SharedDemo/Demos/DragAndDrop.razor.cs +++ b/samples/SharedDemo/Demos/DragAndDrop.razor.cs @@ -1,4 +1,4 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components.Web; diff --git a/samples/SharedDemo/Demos/DynamicInsertions.razor.cs b/samples/SharedDemo/Demos/DynamicInsertions.razor.cs index 1f6874f4b..a44d15d20 100644 --- a/samples/SharedDemo/Demos/DynamicInsertions.razor.cs +++ b/samples/SharedDemo/Demos/DynamicInsertions.razor.cs @@ -1,7 +1,7 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; -using Blazor.Diagrams.Core.Geometry; using System; using System.Linq; @@ -40,15 +40,15 @@ protected void AddPort() if (node == null) return; - foreach(PortAlignment portAlignment in Enum.GetValues(typeof(PortAlignment))) + foreach (PortAlignment portAlignment in Enum.GetValues(typeof(PortAlignment))) { - if(node.GetPort(portAlignment) == null) + if (node.GetPort(portAlignment) == null) { node.AddPort(portAlignment); node.Refresh(); break; } - } + } } protected void RemovePort() diff --git a/samples/SharedDemo/Demos/Events.razor.cs b/samples/SharedDemo/Demos/Events.razor.cs index d83cfa26b..396846e9f 100644 --- a/samples/SharedDemo/Demos/Events.razor.cs +++ b/samples/SharedDemo/Demos/Events.razor.cs @@ -1,4 +1,4 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; diff --git a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs index c988ed75e..0c37a98db 100644 --- a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs +++ b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs @@ -1,6 +1,6 @@ -using Blazor.Diagrams.Core; -using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; namespace SharedDemo.Demos.Groups { diff --git a/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs b/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs index 25bfbb0cf..a6991d609 100644 --- a/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs @@ -1,6 +1,6 @@ -using Blazor.Diagrams.Core; -using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; using System; namespace SharedDemo.Demos.Groups diff --git a/samples/SharedDemo/Demos/Groups/Factory.razor.cs b/samples/SharedDemo/Demos/Groups/Factory.razor.cs index 289e9fbb0..28045d4f0 100644 --- a/samples/SharedDemo/Demos/Groups/Factory.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Factory.razor.cs @@ -1,6 +1,6 @@ -using Blazor.Diagrams.Core; -using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; namespace SharedDemo.Demos.Groups { diff --git a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs index 0f9b57828..44fb33a8b 100644 --- a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs @@ -1,7 +1,7 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; -using Blazor.Diagrams.Core.Geometry; namespace SharedDemo.Demos { diff --git a/samples/SharedDemo/Demos/Links/LabelsDemo.razor.cs b/samples/SharedDemo/Demos/Links/LabelsDemo.razor.cs index b824fc5e5..830c9864b 100644 --- a/samples/SharedDemo/Demos/Links/LabelsDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/LabelsDemo.razor.cs @@ -1,10 +1,6 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; -using System; -using System.Collections.Generic; -using System.Text; namespace SharedDemo.Demos.Links { diff --git a/samples/SharedDemo/Demos/Links/MarkersDemo.razor.cs b/samples/SharedDemo/Demos/Links/MarkersDemo.razor.cs index 2de943cdd..e4163be2c 100644 --- a/samples/SharedDemo/Demos/Links/MarkersDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/MarkersDemo.razor.cs @@ -1,4 +1,4 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; diff --git a/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs b/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs index 1e56a2b68..679ffa998 100644 --- a/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; +using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; diff --git a/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs b/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs index 8700d4983..6d660ff05 100644 --- a/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; +using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; diff --git a/samples/SharedDemo/Demos/Links/SnappingDemo.razor.cs b/samples/SharedDemo/Demos/Links/SnappingDemo.razor.cs index f8fb376de..0bf3b2b1d 100644 --- a/samples/SharedDemo/Demos/Links/SnappingDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/SnappingDemo.razor.cs @@ -1,4 +1,4 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; diff --git a/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs b/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs index d51baac5b..c346f95c7 100644 --- a/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; +using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; diff --git a/samples/SharedDemo/Demos/Locked.razor.cs b/samples/SharedDemo/Demos/Locked.razor.cs index a4e43536d..5baf498b4 100644 --- a/samples/SharedDemo/Demos/Locked.razor.cs +++ b/samples/SharedDemo/Demos/Locked.razor.cs @@ -1,4 +1,4 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; diff --git a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs index a0aaa84fc..432b80605 100644 --- a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs @@ -1,9 +1,6 @@ using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core; -using System; -using System.Collections.Generic; -using System.Text; using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams; namespace SharedDemo.Demos.Nodes { diff --git a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs index 44f49104e..c84c6328b 100644 --- a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs @@ -1,4 +1,4 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; diff --git a/samples/SharedDemo/Demos/Performance.razor.cs b/samples/SharedDemo/Demos/Performance.razor.cs index 3a04162d9..a8d61b6ca 100644 --- a/samples/SharedDemo/Demos/Performance.razor.cs +++ b/samples/SharedDemo/Demos/Performance.razor.cs @@ -1,7 +1,7 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; -using Blazor.Diagrams.Core.Geometry; namespace SharedDemo.Demos { diff --git a/samples/SharedDemo/Demos/Simple.razor.cs b/samples/SharedDemo/Demos/Simple.razor.cs index c725c00b9..18ab1de51 100644 --- a/samples/SharedDemo/Demos/Simple.razor.cs +++ b/samples/SharedDemo/Demos/Simple.razor.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; +using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; diff --git a/samples/SharedDemo/Demos/SnapToGrid.razor.cs b/samples/SharedDemo/Demos/SnapToGrid.razor.cs index 8e4902e7c..74c65fe7d 100644 --- a/samples/SharedDemo/Demos/SnapToGrid.razor.cs +++ b/samples/SharedDemo/Demos/SnapToGrid.razor.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core; +using Blazor.Diagrams; +using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; diff --git a/samples/SharedDemo/Demos/ZoomToFit.razor.cs b/samples/SharedDemo/Demos/ZoomToFit.razor.cs index 70871063b..69561b81e 100644 --- a/samples/SharedDemo/Demos/ZoomToFit.razor.cs +++ b/samples/SharedDemo/Demos/ZoomToFit.razor.cs @@ -1,7 +1,7 @@ -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core; -using Microsoft.AspNetCore.Components; +using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Microsoft.AspNetCore.Components; namespace SharedDemo.Demos { diff --git a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs index 3c9364354..f088fc939 100644 --- a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs +++ b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs @@ -8,7 +8,7 @@ namespace Blazor.Diagrams.Algorithms { public static class LinksReconnectionAlgorithms { - public static void ReconnectLinksToClosestPorts(this Diagram diagram) + public static void ReconnectLinksToClosestPorts(this DiagramBase diagram) { // Only refresh ports once var portsToRefresh = new HashSet(); diff --git a/src/Blazor.Diagrams.Core/Behavior.cs b/src/Blazor.Diagrams.Core/Behavior.cs index 2d81088f5..7cd683c7c 100644 --- a/src/Blazor.Diagrams.Core/Behavior.cs +++ b/src/Blazor.Diagrams.Core/Behavior.cs @@ -4,12 +4,12 @@ namespace Blazor.Diagrams.Core { public abstract class Behavior : IDisposable { - public Behavior(Diagram diagram) + public Behavior(DiagramBase diagram) { Diagram = diagram; } - protected Diagram Diagram { get; } + protected DiagramBase Diagram { get; } public abstract void Dispose(); } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs index 88aaafd41..8ecc8b931 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs @@ -7,7 +7,7 @@ namespace Blazor.Diagrams.Core.Behaviors { public class DebugEventsBehavior : Behavior { - public DebugEventsBehavior(Diagram diagram) : base(diagram) + public DebugEventsBehavior(DiagramBase diagram) : base(diagram) { Diagram.Changed += Diagram_Changed; Diagram.ContainerChanged += Diagram_ContainerChanged; diff --git a/src/Blazor.Diagrams.Core/Behaviors/DeleteSelectionBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DeleteSelectionBehavior.cs index 56f7cfbb9..3718713cc 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DeleteSelectionBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DeleteSelectionBehavior.cs @@ -1,13 +1,13 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -using Microsoft.AspNetCore.Components.Web; +using Blazor.Diagrams.Core.Events; using System.Linq; namespace Blazor.Diagrams.Core.Behaviors { public class DeleteSelectionBehavior : Behavior { - public DeleteSelectionBehavior(Diagram diagram) : base(diagram) + public DeleteSelectionBehavior(DiagramBase diagram) : base(diagram) { Diagram.KeyDown += Diagram_KeyDown; } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 3e67dcbdd..db085507d 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -1,6 +1,6 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; -using Microsoft.AspNetCore.Components.Web; +using Blazor.Diagrams.Core.Events; using System; using System.Linq; @@ -12,7 +12,7 @@ public class DragMovablesBehavior : Behavior private double? _lastClientX; private double? _lastClientY; - public DragMovablesBehavior(Diagram diagram) : base(diagram) + public DragMovablesBehavior(DiagramBase diagram) : base(diagram) { Diagram.MouseDown += OnMouseDown; Diagram.MouseMove += OnMouseMove; diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 33e8fdc64..49e3e00d2 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -1,7 +1,7 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -using Microsoft.AspNetCore.Components.Web; +using Blazor.Diagrams.Core.Events; using System.Linq; namespace Blazor.Diagrams.Core.Behaviors @@ -12,7 +12,7 @@ public class DragNewLinkBehavior : Behavior private double _initialY; private BaseLinkModel? _ongoingLink; - public DragNewLinkBehavior(Diagram diagram) : base(diagram) + public DragNewLinkBehavior(DiagramBase diagram) : base(diagram) { Diagram.MouseDown += OnMouseDown; Diagram.MouseMove += OnMouseMove; diff --git a/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs index 9a1c6fb56..36b06f336 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs @@ -1,5 +1,5 @@ using Blazor.Diagrams.Core.Models.Base; -using Microsoft.AspNetCore.Components.Web; +using Blazor.Diagrams.Core.Events; using System; using System.Diagnostics; @@ -11,7 +11,7 @@ public class EventsBehavior : Behavior private bool _captureMouseMove; private int _mouseMovedCount; - public EventsBehavior(Diagram diagram) : base(diagram) + public EventsBehavior(DiagramBase diagram) : base(diagram) { _mouseClickSw = new Stopwatch(); diff --git a/src/Blazor.Diagrams.Core/Behaviors/GroupingBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/GroupingBehavior.cs index 8b69f66ee..7c3dbcc9c 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/GroupingBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/GroupingBehavior.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Components.Web; +using Blazor.Diagrams.Core.Events; using System; using System.Linq; @@ -6,7 +6,7 @@ namespace Blazor.Diagrams.Core.Behaviors { public class GroupingBehavior : Behavior { - public GroupingBehavior(Diagram diagram) : base(diagram) + public GroupingBehavior(DiagramBase diagram) : base(diagram) { Diagram.KeyDown += Diagram_KeyDown; } diff --git a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs index 4cdbec417..186796769 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs @@ -1,6 +1,6 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; -using Microsoft.AspNetCore.Components.Web; +using Blazor.Diagrams.Core.Events; namespace Blazor.Diagrams.Core.Behaviors { @@ -10,7 +10,7 @@ public class PanBehavior : Behavior private double _lastClientX; private double _lastClientY; - public PanBehavior(Diagram diagram) : base(diagram) + public PanBehavior(DiagramBase diagram) : base(diagram) { Diagram.MouseDown += OnMouseDown; Diagram.MouseMove += OnMouseMove; diff --git a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs index 40caf1d57..db99ae7b4 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs @@ -1,11 +1,11 @@ using Blazor.Diagrams.Core.Models.Base; -using Microsoft.AspNetCore.Components.Web; +using Blazor.Diagrams.Core.Events; namespace Blazor.Diagrams.Core.Behaviors { public class SelectionBehavior : Behavior { - public SelectionBehavior(Diagram diagram) : base(diagram) + public SelectionBehavior(DiagramBase diagram) : base(diagram) { Diagram.MouseDown += OnMouseDown; Diagram.TouchStart += OnTouchStart; diff --git a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs index 3a8a3c5f0..34fe783a6 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs @@ -1,6 +1,6 @@ using Blazor.Diagrams.Core.Geometry; -using Microsoft.AspNetCore.Components.Web; +using Blazor.Diagrams.Core.Events; using System; @@ -8,7 +8,7 @@ namespace Blazor.Diagrams.Core.Behaviors { public class ZoomBehavior : Behavior { - public ZoomBehavior(Diagram diagram) : base(diagram) + public ZoomBehavior(DiagramBase diagram) : base(diagram) { Diagram.Wheel += Diagram_Wheel; } diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index cf888e687..f51e63c9b 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -1,32 +1,28 @@  - - net6.0 - enable - true - MIT - zHaytam - A fully customizable and extensible all-purpose diagrams library for Blazor - 2.1.6 - 2.1.6 - https://github.com/zHaytam/Blazor.Diagrams - 2.1.6 - Z.Blazor.Diagrams.Core - blazor diagrams diagramming svg drag - Z.Blazor.Diagrams.Core - ZBD.png - https://blazor-diagrams.zhaytam.com/ - - - - - + + net6.0 + enable + true + MIT + zHaytam + A fully customizable and extensible all-purpose diagrams library for Blazor + 2.1.6 + 2.1.6 + https://github.com/zHaytam/Blazor.Diagrams + 2.1.6 + Z.Blazor.Diagrams.Core + blazor diagrams diagramming svg drag + Z.Blazor.Diagrams.Core + ZBD.png + https://blazor-diagrams.zhaytam.com/ + - - - True - - - + + + True + + + diff --git a/src/Blazor.Diagrams.Core/Delegates.cs b/src/Blazor.Diagrams.Core/Delegates.cs index e1c2afd7a..8cb400281 100644 --- a/src/Blazor.Diagrams.Core/Delegates.cs +++ b/src/Blazor.Diagrams.Core/Delegates.cs @@ -4,11 +4,11 @@ namespace Blazor.Diagrams.Core { - public delegate Point[] Router(Diagram diagram, BaseLinkModel link); + public delegate Point[] Router(DiagramBase diagram, BaseLinkModel link); - public delegate PathGeneratorResult PathGenerator(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target); + public delegate PathGeneratorResult PathGenerator(DiagramBase diagram, BaseLinkModel link, Point[] route, Point source, Point target); - public delegate BaseLinkModel LinkFactory(Diagram diagram, PortModel sourcePort); + public delegate BaseLinkModel LinkFactory(DiagramBase diagram, PortModel sourcePort); - public delegate GroupModel GroupFactory(Diagram diagram, NodeModel[] children); + public delegate GroupModel GroupFactory(DiagramBase diagram, NodeModel[] children); } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/DiagramBase.cs similarity index 82% rename from src/Blazor.Diagrams.Core/Diagram.cs rename to src/Blazor.Diagrams.Core/DiagramBase.cs index 6fdbd184a..35dd4bfa3 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/DiagramBase.cs @@ -4,8 +4,7 @@ using Blazor.Diagrams.Core.Layers; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Web; +using Blazor.Diagrams.Core.Events; using System; using System.Collections.Generic; using System.Linq; @@ -16,22 +15,21 @@ [assembly: InternalsVisibleTo("Blazor.Diagrams.Core.Tests")] namespace Blazor.Diagrams.Core { - public class Diagram : Model + public class DiagramBase : Model { private readonly Dictionary _behaviors; - private readonly Dictionary _componentByModelMapping; private readonly List _groups; - public event Action? MouseDown; - public event Action? MouseMove; - public event Action? MouseUp; + public event Action? MouseDown; + public event Action? MouseMove; + public event Action? MouseUp; public event Action? KeyDown; public event Action? Wheel; - public event Action? MouseClick; - public event Action? MouseDoubleClick; - public event Action? TouchStart; - public event Action? TouchMove; - public event Action? TouchEnd; + public event Action? MouseClick; + public event Action? MouseDoubleClick; + public event Action? TouchStart; + public event Action? TouchMove; + public event Action? TouchEnd; public event Action? SelectionChanged; public event Action? GroupAdded; @@ -42,10 +40,9 @@ public class Diagram : Model public event Action? ZoomChanged; public event Action? ContainerChanged; - public Diagram(DiagramOptions? options = null) + public DiagramBase(DiagramOptions? options = null) { _behaviors = new Dictionary(); - _componentByModelMapping = new Dictionary(); _groups = new List(); Options = options ?? new DiagramOptions(); @@ -264,23 +261,6 @@ public void UnregisterBehavior() where T : Behavior #endregion - public void RegisterModelComponent() where M : Model where C : ComponentBase - => RegisterModelComponent(typeof(M), typeof(C)); - - public void RegisterModelComponent(Type modelType, Type componentType) - { - if (_componentByModelMapping.ContainsKey(modelType)) - throw new Exception($"Component already registered for model '{modelType.Name}'."); - - _componentByModelMapping.Add(modelType, componentType); - } - - public Type? GetComponentForModel(M model) where M : Model - { - var modelType = model.GetType(); - return _componentByModelMapping.ContainsKey(modelType) ? _componentByModelMapping[modelType] : null; - } - public void ZoomToFit(double margin = 10) { if (Container == null || Nodes.Count == 0) @@ -327,13 +307,9 @@ 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(); @@ -375,25 +351,25 @@ public Point GetScreenPoint(double clientX, double clientY) #region Events - internal void OnMouseDown(Model model, MouseEventArgs e) => MouseDown?.Invoke(model, e); + internal void OnMouseDown(Model? model, MouseEventArgs e) => MouseDown?.Invoke(model, e); - internal void OnMouseMove(Model model, MouseEventArgs e) => MouseMove?.Invoke(model, e); + internal void OnMouseMove(Model? model, MouseEventArgs e) => MouseMove?.Invoke(model, e); - internal void OnMouseUp(Model model, MouseEventArgs e) => MouseUp?.Invoke(model, e); + internal void OnMouseUp(Model? model, MouseEventArgs e) => MouseUp?.Invoke(model, e); internal void OnKeyDown(KeyboardEventArgs e) => KeyDown?.Invoke(e); internal void OnWheel(WheelEventArgs e) => Wheel?.Invoke(e); - internal void OnMouseClick(Model model, MouseEventArgs e) => MouseClick?.Invoke(model, e); + internal void OnMouseClick(Model? model, MouseEventArgs e) => MouseClick?.Invoke(model, e); - internal void OnMouseDoubleClick(Model model, MouseEventArgs e) => MouseDoubleClick?.Invoke(model, e); + internal void OnMouseDoubleClick(Model? model, MouseEventArgs e) => MouseDoubleClick?.Invoke(model, e); - internal void OnTouchStart(Model model, TouchEventArgs e) => TouchStart?.Invoke(model, e); + internal void OnTouchStart(Model? model, TouchEventArgs e) => TouchStart?.Invoke(model, e); - internal void OnTouchMove(Model model, TouchEventArgs e) => TouchMove?.Invoke(model, e); + internal void OnTouchMove(Model? model, TouchEventArgs e) => TouchMove?.Invoke(model, e); - internal void OnTouchEnd(Model model, TouchEventArgs e) => TouchEnd?.Invoke(model, e); + internal void OnTouchEnd(Model? model, TouchEventArgs e) => TouchEnd?.Invoke(model, e); #endregion } diff --git a/src/Blazor.Diagrams.Core/DiagramOptions.cs b/src/Blazor.Diagrams.Core/DiagramOptions.cs index cfb5825bc..5d0a2c5cc 100644 --- a/src/Blazor.Diagrams.Core/DiagramOptions.cs +++ b/src/Blazor.Diagrams.Core/DiagramOptions.cs @@ -1,6 +1,6 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -using Microsoft.AspNetCore.Components.Web; +using Blazor.Diagrams.Core.Events; using System; using System.ComponentModel; diff --git a/src/Blazor.Diagrams.Core/Events/KeyboardEventArgs.cs b/src/Blazor.Diagrams.Core/Events/KeyboardEventArgs.cs new file mode 100644 index 000000000..b5791ab5d --- /dev/null +++ b/src/Blazor.Diagrams.Core/Events/KeyboardEventArgs.cs @@ -0,0 +1,4 @@ +namespace Blazor.Diagrams.Core.Events +{ + public record KeyboardEventArgs(string Key, string Code, float Location, bool CtrlKey, bool ShiftKey, bool AltKey); +} diff --git a/src/Blazor.Diagrams.Core/Events/MouseEventArgs.cs b/src/Blazor.Diagrams.Core/Events/MouseEventArgs.cs new file mode 100644 index 000000000..e083db90b --- /dev/null +++ b/src/Blazor.Diagrams.Core/Events/MouseEventArgs.cs @@ -0,0 +1,4 @@ +namespace Blazor.Diagrams.Core.Events +{ + public record MouseEventArgs(double ClientX, double ClientY, long Button, long Buttons, bool CtrlKey, bool ShiftKey, bool AltKey); +} diff --git a/src/Blazor.Diagrams.Core/Events/TouchEventArgs.cs b/src/Blazor.Diagrams.Core/Events/TouchEventArgs.cs new file mode 100644 index 000000000..9c20defe7 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Events/TouchEventArgs.cs @@ -0,0 +1,5 @@ +namespace Blazor.Diagrams.Core.Events +{ + public record TouchEventArgs(TouchPoint[] ChangedTouches, bool CtrlKey, bool ShiftKey, bool AltKey); + public record TouchPoint(long Identifier, double ClientX, double ClientY); +} diff --git a/src/Blazor.Diagrams.Core/Events/WheelEventArgs.cs b/src/Blazor.Diagrams.Core/Events/WheelEventArgs.cs new file mode 100644 index 000000000..d51b0687d --- /dev/null +++ b/src/Blazor.Diagrams.Core/Events/WheelEventArgs.cs @@ -0,0 +1,15 @@ +namespace Blazor.Diagrams.Core.Events +{ + public record WheelEventArgs( + double ClientX, + double ClientY, + long Button, + long Buttons, + bool CtrlKey, + bool ShiftKey, + bool AltKey, + double DeltaX, + double DeltaY, + double DeltaZ, + long DeltaMode) : MouseEventArgs(ClientX, ClientY, Button, Buttons, CtrlKey, ShiftKey, AltKey); +} diff --git a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs index bfdb6ff36..236235a17 100644 --- a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs @@ -12,7 +12,7 @@ public abstract class BaseLayer : IReadOnlyList where T : Model public event Action? Added; public event Action? Removed; - public BaseLayer(Diagram diagram) + public BaseLayer(DiagramBase diagram) { Diagram = diagram; } @@ -98,7 +98,7 @@ protected virtual void OnItemAdded(T item) { } protected virtual void OnItemRemoved(T item) { } - public Diagram Diagram { get; } + public DiagramBase Diagram { get; } public int Count => _items.Count; public T this[int index] => _items[index]; diff --git a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs index 83f9cff43..b483d7129 100644 --- a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs @@ -4,7 +4,7 @@ namespace Blazor.Diagrams.Core.Layers { public class LinkLayer : BaseLayer { - public LinkLayer(Diagram diagram) : base(diagram) { } + public LinkLayer(DiagramBase diagram) : base(diagram) { } protected override void OnItemAdded(BaseLinkModel link) { diff --git a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs index 68743a197..bd726f795 100644 --- a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs @@ -5,7 +5,7 @@ namespace Blazor.Diagrams.Core.Layers { public class NodeLayer : BaseLayer { - public NodeLayer(Diagram diagram) : base(diagram) { } + public NodeLayer(DiagramBase diagram) : base(diagram) { } public override void Remove(NodeModel node) { diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs index 8f4ff88af..004cf7225 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs @@ -9,7 +9,7 @@ public static partial class PathGenerators { private const double _margin = 125; - public static PathGeneratorResult Smooth(Diagram _, BaseLinkModel link, Point[] route, Point source, Point target) + public static PathGeneratorResult Smooth(DiagramBase _, BaseLinkModel link, Point[] route, Point source, Point target) { route = ConcatRouteAndSourceAndTarget(route, source, target); diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs index 4dd93a863..23fbbc1cd 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs @@ -6,7 +6,7 @@ namespace Blazor.Diagrams.Core { public static partial class PathGenerators { - public static PathGeneratorResult Straight(Diagram _, BaseLinkModel link, Point[] route, Point source, Point target) + public static PathGeneratorResult Straight(DiagramBase _, BaseLinkModel link, Point[] route, Point source, Point target) { route = ConcatRouteAndSourceAndTarget(route, source, target); double? sourceAngle = null; diff --git a/src/Blazor.Diagrams.Core/Routers/Routers.Normal.cs b/src/Blazor.Diagrams.Core/Routers/Routers.Normal.cs index e27f84312..6a0d826da 100644 --- a/src/Blazor.Diagrams.Core/Routers/Routers.Normal.cs +++ b/src/Blazor.Diagrams.Core/Routers/Routers.Normal.cs @@ -6,7 +6,7 @@ namespace Blazor.Diagrams.Core { public static partial class Routers { - public static Point[] Normal(Diagram _, BaseLinkModel link) + public static Point[] Normal(DiagramBase _, BaseLinkModel link) { return link.Vertices.Select(v => v.Position).ToArray(); } diff --git a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs index 3ccedbc48..0aff4a75d 100644 --- a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs +++ b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs @@ -13,7 +13,7 @@ namespace Blazor.Diagrams.Core { public static partial class Routers { - public static Point[] Orthogonal(Diagram _, BaseLinkModel link) + public static Point[] Orthogonal(DiagramBase _, BaseLinkModel link) { if (link.IsPortless) throw new Exception("Orthogonal router doesn't work with portless links yet"); diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index f2b544cfb..408d2cbc8 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -1,49 +1,50 @@  - - net6.0 - zHaytam - MIT - 2.1.6 - 2.1.6 - https://github.com/zHaytam/Blazor.Diagrams - A fully customizable and extensible all-purpose diagrams library for Blazor - 2.1.6 - true - blazor diagrams diagramming svg drag - Z.Blazor.Diagrams - https://blazor-diagrams.zhaytam.com/ - Z.Blazor.Diagrams - ZBD.png - - - - - - - - - - - - - - - - True - - - - - - - - - - - - - - + + net6.0 + enable + zHaytam + MIT + 2.1.6 + 2.1.6 + https://github.com/zHaytam/Blazor.Diagrams + A fully customizable and extensible all-purpose diagrams library for Blazor + 2.1.6 + true + blazor diagrams diagramming svg drag + Z.Blazor.Diagrams + https://blazor-diagrams.zhaytam.com/ + Z.Blazor.Diagrams + ZBD.png + + + + + + + + + + + + + + + + True + + + + + + + + + + + + + + diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index 9aa9c2eb3..c2d804c0e 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -1,5 +1,4 @@ -using Blazor.Diagrams.Core; -using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; @@ -63,21 +62,21 @@ protected override bool ShouldRender() return false; } - private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(null, e); + private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(null, e.ToCore()); - private void OnMouseMove(MouseEventArgs e) => Diagram.OnMouseMove(null, e); + private void OnMouseMove(MouseEventArgs e) => Diagram.OnMouseMove(null, e.ToCore()); - private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(null, e); + private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(null, e.ToCore()); - private void OnKeyDown(KeyboardEventArgs e) => Diagram.OnKeyDown(e); + private void OnKeyDown(KeyboardEventArgs e) => Diagram.OnKeyDown(e.ToCore()); - private void OnWheel(WheelEventArgs e) => Diagram.OnWheel(e); + private void OnWheel(WheelEventArgs e) => Diagram.OnWheel(e.ToCore()); - private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(null, e); + private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(null, e.ToCore()); - private void OnTouchMove(TouchEventArgs e) => Diagram.OnTouchMove(null, e); + private void OnTouchMove(TouchEventArgs e) => Diagram.OnTouchMove(null, e.ToCore()); - private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(null, e); + private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(null, e.ToCore()); private void OnDiagramChanged() { diff --git a/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor.cs b/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor.cs index 2bd0749e6..b83a242c2 100644 --- a/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor.cs +++ b/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor.cs @@ -1,6 +1,7 @@ using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using System; @@ -67,8 +68,8 @@ private void OnGroupChanged() InvokeAsync(StateHasChanged); } - private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Group, e); + private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Group, e.ToCore()); - private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Group, e); + private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Group, e.ToCore()); } } diff --git a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs index 418433afd..82d395813 100644 --- a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs @@ -1,5 +1,6 @@ using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using System; @@ -44,13 +45,13 @@ private void OnVertexChanged() InvokeAsync(StateHasChanged); } - private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Vertex, e); + private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Vertex, e.ToCore()); - private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Vertex, e); + private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Vertex, e.ToCore()); - private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Vertex, e); + private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Vertex, e.ToCore()); - private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(Vertex, e); + private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(Vertex, e.ToCore()); private void OnDoubleClick(MouseEventArgs e) { diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs index 248937afc..5fe781633 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Components.Web; using Blazor.Diagrams.Core.Geometry; using System.Collections.Generic; +using Blazor.Diagrams.Extensions; namespace Blazor.Diagrams.Components { @@ -21,7 +22,7 @@ private void OnMouseDown(MouseEventArgs e, int index) return; var vertex = CreateVertex(e.ClientX, e.ClientY, index); - Diagram.OnMouseDown(vertex, e); + Diagram.OnMouseDown(vertex, e.ToCore()); } private void OnTouchStart(TouchEventArgs e, int index) @@ -30,7 +31,7 @@ private void OnTouchStart(TouchEventArgs e, int index) return; var vertex = CreateVertex(e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY, index); - Diagram.OnTouchStart(vertex, e); + Diagram.OnTouchStart(vertex, e.ToCore()); } private LinkVertexModel CreateVertex(double clientX, double clientY, int index) diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs index 47cbf903a..1dbab252d 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs @@ -1,5 +1,6 @@ using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; @@ -63,12 +64,12 @@ private void OnLinkChanged() InvokeAsync(StateHasChanged); } - private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Link, e); + private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Link, e.ToCore()); - private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Link, e); + private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Link, e.ToCore()); - private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Link, e); + private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Link, e.ToCore()); - private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(Link, e); + private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(Link, e.ToCore()); } } diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index 5bd1822fd..a043b2dc1 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -167,12 +167,12 @@ private void ReRender() InvokeAsync(StateHasChanged); } - private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Node, e); + private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Node, e.ToCore()); - private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Node, e); + private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Node, e.ToCore()); - private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Node, e); + private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Node, e.ToCore()); - private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(Node, e); + private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(Node, e.ToCore()); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs index e742e57c2..8dd83a59b 100644 --- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs @@ -78,14 +78,14 @@ protected override async Task OnAfterRenderAsync(bool firstRender) } } - private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Port, e); + private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Port, e.ToCore()); - private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Port, e); + private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Port, e.ToCore()); - private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Port, e); + private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Port, e.ToCore()); private void OnTouchEnd(TouchEventArgs e) - => Diagram.OnTouchEnd(FindPortOn(e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY), e); + => Diagram.OnTouchEnd(FindPortOn(e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY), e.ToCore()); private PortModel FindPortOn(double clientX, double clientY) { diff --git a/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs b/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs index 891d97a27..66561d3b1 100644 --- a/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs @@ -1,8 +1,8 @@ using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Events; using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Web; using System; namespace Blazor.Diagrams.Components diff --git a/src/Blazor.Diagrams/Diagram.cs b/src/Blazor.Diagrams/Diagram.cs new file mode 100644 index 000000000..1927650a0 --- /dev/null +++ b/src/Blazor.Diagrams/Diagram.cs @@ -0,0 +1,35 @@ +using Blazor.Diagrams.Core; +using Blazor.Diagrams.Core.Models.Base; +using Microsoft.AspNetCore.Components; +using System; +using System.Collections.Generic; + +namespace Blazor.Diagrams +{ + public class Diagram : DiagramBase + { + private readonly Dictionary _componentByModelMapping; + + public Diagram(DiagramOptions? options = null) : base(options) + { + _componentByModelMapping = new Dictionary(); + } + + public void RegisterModelComponent() where M : Model where C : ComponentBase + => RegisterModelComponent(typeof(M), typeof(C)); + + public void RegisterModelComponent(Type modelType, Type componentType) + { + if (_componentByModelMapping.ContainsKey(modelType)) + throw new Exception($"Component already registered for model '{modelType.Name}'."); + + _componentByModelMapping.Add(modelType, componentType); + } + + public Type? GetComponentForModel(M model) where M : Model + { + var modelType = model.GetType(); + return _componentByModelMapping.ContainsKey(modelType) ? _componentByModelMapping[modelType] : null; + } + } +} diff --git a/src/Blazor.Diagrams/Extensions/EventsExtensions.cs b/src/Blazor.Diagrams/Extensions/EventsExtensions.cs new file mode 100644 index 000000000..c382523de --- /dev/null +++ b/src/Blazor.Diagrams/Extensions/EventsExtensions.cs @@ -0,0 +1,34 @@ +using Blazor.Diagrams.Core.Events; +using System.Linq; +using Web = Microsoft.AspNetCore.Components.Web; + +namespace Blazor.Diagrams.Extensions +{ + public static class EventsExtensions + { + public static MouseEventArgs ToCore(this Web.MouseEventArgs e) + { + return new MouseEventArgs(e.ClientX, e.ClientY, e.Button, e.Buttons, e.CtrlKey, e.ShiftKey, e.AltKey); + } + + public static KeyboardEventArgs ToCore(this Web.KeyboardEventArgs e) + { + return new KeyboardEventArgs(e.Key, e.Code, e.Location, e.CtrlKey, e.ShiftKey, e.AltKey); + } + + public static WheelEventArgs ToCore(this Web.WheelEventArgs e) + { + return new WheelEventArgs(e.ClientX, e.ClientY, e.Button, e.Buttons, e.CtrlKey, e.ShiftKey, e.AltKey, e.DeltaX, e.DeltaY, e.DeltaZ, e.DeltaMode); + } + + public static TouchEventArgs ToCore(this Web.TouchEventArgs e) + { + return new TouchEventArgs(e.ChangedTouches.Select(ToCore).ToArray(), e.CtrlKey, e.ShiftKey, e.AltKey); + } + + public static TouchPoint ToCore(this Web.TouchPoint e) + { + return new TouchPoint(e.Identifier, e.ClientX, e.ClientY); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DeleteSelectionBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DeleteSelectionBehaviorTests.cs index af3fc8219..fc2cc7499 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DeleteSelectionBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DeleteSelectionBehaviorTests.cs @@ -1,7 +1,7 @@ using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Models; using FluentAssertions; -using Microsoft.AspNetCore.Components.Web; using System; using Xunit; @@ -13,7 +13,7 @@ public class DeleteSelectionBehaviorTests public void Behavior_ShouldNotRun_WhenItsRemoved() { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); diagram.UnregisterBehavior(); diagram.Nodes.Add(new NodeModel { @@ -21,13 +21,7 @@ public void Behavior_ShouldNotRun_WhenItsRemoved() }); // Act - diagram.OnKeyDown(new KeyboardEventArgs - { - AltKey = false, - CtrlKey = false, - ShiftKey = false, - Code = "Delete", - }); + diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, false, false, false)); // Assert diagram.Nodes.Count.Should().Be(1); @@ -37,7 +31,7 @@ public void Behavior_ShouldNotRun_WhenItsRemoved() public void Behavior_ShouldTakeIntoAccountDeleteKeyOption() { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); diagram.Options.DeleteKey = "Test"; diagram.Nodes.Add(new NodeModel { @@ -45,13 +39,7 @@ public void Behavior_ShouldTakeIntoAccountDeleteKeyOption() }); // Act - diagram.OnKeyDown(new KeyboardEventArgs - { - AltKey = false, - CtrlKey = false, - ShiftKey = false, - Code = "Test", - }); + diagram.OnKeyDown(new KeyboardEventArgs("Test", "Test", 0, false, false ,false)); // Assert diagram.Nodes.Count.Should().Be(0); @@ -61,7 +49,7 @@ public void Behavior_ShouldTakeIntoAccountDeleteKeyOption() public void Behavior_ShouldNotDeleteModel_WhenItsLocked() { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); diagram.Nodes.Add(new NodeModel { Selected = true, @@ -69,13 +57,7 @@ public void Behavior_ShouldNotDeleteModel_WhenItsLocked() }); // Act - diagram.OnKeyDown(new KeyboardEventArgs - { - AltKey = false, - CtrlKey = false, - ShiftKey = false, - Code = "Delete", - }); + diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, false, false, false)); // Assert diagram.Nodes.Count.Should().Be(1); @@ -86,7 +68,7 @@ public void Behavior_ShouldTakeIntoAccountGroupConstraint() { // Arrange var funcCalled = false; - var diagram = new Diagram(); + var diagram = new DiagramBase(); diagram.Options.Constraints.ShouldDeleteGroup = _ => { funcCalled = true; @@ -98,13 +80,7 @@ public void Behavior_ShouldTakeIntoAccountGroupConstraint() }); // Act - diagram.OnKeyDown(new KeyboardEventArgs - { - AltKey = false, - CtrlKey = false, - ShiftKey = false, - Code = "Delete", - }); + diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, false, false, false)); // Assert funcCalled.Should().BeTrue(); @@ -116,7 +92,7 @@ public void Behavior_ShouldTakeIntoAccountNodeConstraint() { // Arrange var funcCalled = false; - var diagram = new Diagram(); + var diagram = new DiagramBase(); diagram.Options.Constraints.ShouldDeleteNode = _ => { funcCalled = true; @@ -128,13 +104,7 @@ public void Behavior_ShouldTakeIntoAccountNodeConstraint() }); // Act - diagram.OnKeyDown(new KeyboardEventArgs - { - AltKey = false, - CtrlKey = false, - ShiftKey = false, - Code = "Delete", - }); + diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, false, false, false)); // Assert funcCalled.Should().BeTrue(); @@ -146,7 +116,7 @@ public void Behavior_ShouldTakeIntoAccountLinkConstraint() { // Arrange var funcCalled = false; - var diagram = new Diagram(); + var diagram = new DiagramBase(); diagram.Options.Constraints.ShouldDeleteLink = _ => { funcCalled = true; @@ -163,13 +133,7 @@ public void Behavior_ShouldTakeIntoAccountLinkConstraint() }); // Act - diagram.OnKeyDown(new KeyboardEventArgs - { - AltKey = false, - CtrlKey = false, - ShiftKey = false, - Code = "Delete", - }); + diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, false, false, false)); // Assert funcCalled.Should().BeTrue(); @@ -180,7 +144,7 @@ public void Behavior_ShouldTakeIntoAccountLinkConstraint() public void Behavior_ShouldResultInSingleRefresh() { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); diagram.Nodes.Add(new NodeModel[] { new NodeModel { Selected = true }, @@ -191,13 +155,7 @@ public void Behavior_ShouldResultInSingleRefresh() diagram.Changed += () => refreshes++; // Act - diagram.OnKeyDown(new KeyboardEventArgs - { - AltKey = false, - CtrlKey = false, - ShiftKey = false, - Code = "Delete", - }); + diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, false, false, false)); // Assert diagram.Nodes.Count.Should().Be(0); @@ -211,20 +169,14 @@ public void Behavior_ShouldResultInSingleRefresh() public void Behavior_ShouldNotDeleteModel_WhenCtrlAltOrShiftIsPressed(bool ctrl, bool shift, bool alt) { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); diagram.Nodes.Add(new NodeModel { Selected = true }); // Act - diagram.OnKeyDown(new KeyboardEventArgs - { - AltKey = alt, - CtrlKey = ctrl, - ShiftKey = shift, - Code = "Delete", - }); + diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, ctrl, shift, alt)); // Assert diagram.Nodes.Count.Should().Be(1); diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs index 6a87456c2..ead3b44a6 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs @@ -1,6 +1,6 @@ using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Events; using FluentAssertions; -using Microsoft.AspNetCore.Components.Web; using System.Threading.Tasks; using Xunit; @@ -12,14 +12,14 @@ public class EventsBehaviorTests public void Behavior_ShouldNotTriggerMouseClick_WhenItsRemoved() { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); diagram.UnregisterBehavior(); var eventTriggered = false; // Act diagram.MouseClick += (m, e) => eventTriggered = true; - diagram.OnMouseDown(null, new MouseEventArgs()); - diagram.OnMouseUp(null, new MouseEventArgs()); + diagram.OnMouseDown(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.OnMouseUp(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); // Assert eventTriggered.Should().BeFalse(); @@ -29,13 +29,13 @@ public void Behavior_ShouldNotTriggerMouseClick_WhenItsRemoved() public void Behavior_ShouldTriggerMouseClick_WhenMouseDownThenUpWithoutMove() { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); var eventTriggered = false; // Act diagram.MouseClick += (m, e) => eventTriggered = true; - diagram.OnMouseDown(null, new MouseEventArgs()); - diagram.OnMouseUp(null, new MouseEventArgs()); + diagram.OnMouseDown(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.OnMouseUp(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); // Assert eventTriggered.Should().BeTrue(); @@ -45,14 +45,14 @@ public void Behavior_ShouldTriggerMouseClick_WhenMouseDownThenUpWithoutMove() public void Behavior_ShouldNotTriggerMouseClick_WhenMouseMoves() { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); var eventTriggered = false; // Act diagram.MouseClick += (m, e) => eventTriggered = true; - diagram.OnMouseDown(null, new MouseEventArgs()); - diagram.OnMouseMove(null, new MouseEventArgs()); - diagram.OnMouseUp(null, new MouseEventArgs()); + diagram.OnMouseDown(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.OnMouseMove(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.OnMouseUp(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); // Assert eventTriggered.Should().BeFalse(); @@ -62,13 +62,13 @@ public void Behavior_ShouldNotTriggerMouseClick_WhenMouseMoves() public void Behavior_ShouldTriggerMouseDoubleClick_WhenTwoMouseClicksHappenWithinTime() { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); var eventTriggered = false; // Act diagram.MouseDoubleClick += (m, e) => eventTriggered = true; - diagram.OnMouseClick(null, new MouseEventArgs()); - diagram.OnMouseClick(null, new MouseEventArgs()); + diagram.OnMouseClick(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.OnMouseClick(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); // Assert eventTriggered.Should().BeTrue(); @@ -78,14 +78,14 @@ public void Behavior_ShouldTriggerMouseDoubleClick_WhenTwoMouseClicksHappenWithi public async Task Behavior_ShouldNotTriggerMouseDoubleClick_WhenTimeExceeds500() { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); var eventTriggered = false; // Act diagram.MouseDoubleClick += (m, e) => eventTriggered = true; - diagram.OnMouseClick(null, new MouseEventArgs()); + diagram.OnMouseClick(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); await Task.Delay(520); - diagram.OnMouseClick(null, new MouseEventArgs()); + diagram.OnMouseClick(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); // Assert eventTriggered.Should().BeFalse(); diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs index 643f31cae..46d9d4b8f 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs @@ -12,7 +12,7 @@ public class DiagramTests public void GetScreenPoint_ShouldReturnCorrectPoint() { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); // Act diagram.SetZoom(1.234); @@ -29,7 +29,7 @@ public void GetScreenPoint_ShouldReturnCorrectPoint() public void ZoomToFit_ShouldUseSelectedNodesIfAny() { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); diagram.SetContainer(new Rectangle(new Point(0, 0), new Size(1080, 768))); diagram.Nodes.Add(new NodeModel(new Point(50, 50)) { @@ -50,7 +50,7 @@ public void ZoomToFit_ShouldUseSelectedNodesIfAny() public void ZoomToFit_ShouldUseNodesWhenNoneSelected() { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); diagram.SetContainer(new Rectangle(new Point(0, 0), new Size(1080, 768))); diagram.Nodes.Add(new NodeModel(new Point(50, 50)) { @@ -70,7 +70,7 @@ public void ZoomToFit_ShouldUseNodesWhenNoneSelected() public void ZoomToFit_ShouldTriggerAppropriateEvents() { // Arrange - var diagram = new Diagram(); + var diagram = new DiagramBase(); diagram.SetContainer(new Rectangle(new Point(0, 0), new Size(1080, 768))); diagram.Nodes.Add(new NodeModel(new Point(50, 50)) { @@ -98,7 +98,7 @@ public void ZoomToFit_ShouldTriggerAppropriateEvents() [InlineData(0.1)] public void Zoom_ShoulClampToMinimumValue(double zoomValue) { - var diagram = new Diagram(); + var diagram = new DiagramBase(); diagram.SetZoom(zoomValue); Assert.Equal(diagram.Zoom, diagram.Options.Zoom.Minimum); } @@ -109,7 +109,7 @@ public void Zoom_ShoulClampToMinimumValue(double zoomValue) [InlineData(-0.00001)] public void Zoom_ThrowExceptionWhenLessThan0(double zoomValue) { - var diagram = new Diagram(); + var diagram = new DiagramBase(); Assert.Throws(() => diagram.SetZoom(zoomValue)); } @@ -119,7 +119,7 @@ public void Zoom_ThrowExceptionWhenLessThan0(double zoomValue) [InlineData(-0.00001)] public void ZoomOptions_ThrowExceptionWhenLessThan0(double zoomValue) { - var diagram = new Diagram(); + var diagram = new DiagramBase(); Assert.Throws(() => diagram.Options.Zoom.Minimum = zoomValue); } } From f4fb158eae9618cf9cc885e43dffbc60af41f3c7 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 23 Jul 2022 17:56:51 +0100 Subject: [PATCH 018/193] Fix MouseClick events triggering without both Down/Up events --- .../Behaviors/EventsBehavior.cs | 24 ++++++++++++------- .../Behaviors/EventsBehaviorTests.cs | 15 ++++++++++++ 2 files changed, 30 insertions(+), 9 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs index 36b06f336..490dafb54 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs @@ -8,6 +8,7 @@ namespace Blazor.Diagrams.Core.Behaviors public class EventsBehavior : Behavior { private readonly Stopwatch _mouseClickSw; + private Model? _model; private bool _captureMouseMove; private int _mouseMovedCount; @@ -21,7 +22,7 @@ public EventsBehavior(DiagramBase diagram) : base(diagram) Diagram.MouseClick += OnMouseClick; } - private void OnMouseClick(Model model, MouseEventArgs e) + private void OnMouseClick(Model? model, MouseEventArgs e) { if (_mouseClickSw.IsRunning && _mouseClickSw.ElapsedMilliseconds <= 500) { @@ -31,12 +32,14 @@ private void OnMouseClick(Model model, MouseEventArgs e) _mouseClickSw.Restart(); } - private void OnMouseDown(Model model, MouseEventArgs e) + private void OnMouseDown(Model? model, MouseEventArgs e) { _captureMouseMove = true; + _mouseMovedCount = 0; + _model = model; } - private void OnMouseMove(Model model, MouseEventArgs e) + private void OnMouseMove(Model? model, MouseEventArgs e) { if (!_captureMouseMove) return; @@ -44,16 +47,17 @@ private void OnMouseMove(Model model, MouseEventArgs e) _mouseMovedCount++; } - private void OnMouseUp(Model model, MouseEventArgs e) + private void OnMouseUp(Model? model, MouseEventArgs e) { + if (!_captureMouseMove) return; // Only set by OnMouseDown _captureMouseMove = false; - if (_mouseMovedCount > 0) + if (_mouseMovedCount > 0) return; + + if (_model == model) { - _mouseMovedCount = 0; - return; + Diagram.OnMouseClick(model, e); + _model = null; } - - Diagram.OnMouseClick(model, e); } public override void Dispose() @@ -61,6 +65,8 @@ public override void Dispose() Diagram.MouseDown -= OnMouseDown; Diagram.MouseMove -= OnMouseMove; Diagram.MouseUp -= OnMouseUp; + Diagram.MouseClick -= OnMouseClick; + _model = null; } } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs index ead3b44a6..a15516d2a 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs @@ -90,5 +90,20 @@ public async Task Behavior_ShouldNotTriggerMouseDoubleClick_WhenTimeExceeds500() // Assert eventTriggered.Should().BeFalse(); } + + [Fact] + public void Behavior_ShouldTriggerMouseClick_OnlyWhenMouseDownWasAlsoTriggered_Issue204() + { + // Arrange + var diagram = new DiagramBase(); + var eventTriggered = false; + + // Act + diagram.MouseClick += (m, e) => eventTriggered = true; + diagram.OnMouseUp(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + + // Assert + eventTriggered.Should().BeFalse(); + } } } From c590e8ad4b65d127145eb0ae970f87de708d9295 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 23 Jul 2022 17:57:16 +0100 Subject: [PATCH 019/193] Turn Point and Size into records --- src/Blazor.Diagrams.Core/DiagramBase.cs | 2 +- src/Blazor.Diagrams.Core/Geometry/Point.cs | 27 ++++++------ .../Geometry/Rectangle.cs | 41 ++++++++----------- src/Blazor.Diagrams.Core/Geometry/Size.cs | 16 ++------ .../Components/DiagramCanvas.razor.cs | 2 +- 5 files changed, 36 insertions(+), 52 deletions(-) diff --git a/src/Blazor.Diagrams.Core/DiagramBase.cs b/src/Blazor.Diagrams.Core/DiagramBase.cs index 35dd4bfa3..69c9e8c6b 100644 --- a/src/Blazor.Diagrams.Core/DiagramBase.cs +++ b/src/Blazor.Diagrams.Core/DiagramBase.cs @@ -62,7 +62,7 @@ public DiagramBase(DiagramOptions? options = null) public NodeLayer Nodes { get; } public LinkLayer Links { get; } public IReadOnlyList Groups => _groups; - public Rectangle? Container { get; internal set; } + public Rectangle? Container { get; private set; } public Point Pan { get; private set; } = Point.Zero; public double Zoom { get; private set; } = 1; public DiagramOptions Options { get; } diff --git a/src/Blazor.Diagrams.Core/Geometry/Point.cs b/src/Blazor.Diagrams.Core/Geometry/Point.cs index 28edd1132..09e899059 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Point.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Point.cs @@ -2,9 +2,9 @@ namespace Blazor.Diagrams.Core.Geometry { - public class Point + public record Point { - public static Point Zero { get; } = new Point(0, 0); + public static Point Zero { get; } = new(0, 0); public Point(double x, double y) { @@ -12,27 +12,24 @@ public Point(double x, double y) Y = y; } - public double X { get; } - public double Y { get; } + public double X { get; init; } + public double Y { get; init; } public double Dot(Point other) => X * other.X + Y * other.Y; public Point Lerp(Point other, double t) - => new Point(X * (1.0 - t) + other.X * t, Y * (1.0 - t) + other.Y * t); + => new(X * (1.0 - t) + other.X * t, Y * (1.0 - t) + other.Y * t); // Maybe just make Points mutable? - public Point Add(double value) => new Point(X + value, Y + value); - public Point Add(double x, double y) => new Point(X + x, Y + y); + public Point Add(double value) => new(X + value, Y + value); + public Point Add(double x, double y) => new(X + x, Y + y); - public Point Substract(double value) => new Point(X - value, Y - value); - public Point Substract(double x, double y) => new Point(X - x, Y - y); + public Point Substract(double value) => new(X - value, Y - value); + public Point Substract(double x, double y) => new(X - x, Y - y); - public double DistanceTo(Point other) - => Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2)); + public double DistanceTo(Point other) => Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2)); - public override string ToString() => $"Point(x={X}, y={Y})"; - - public static Point operator -(Point a, Point b) => new Point(a.X - b.X, a.Y - b.Y); - public static Point operator +(Point a, Point b) => new Point(a.X + b.X, a.Y + b.Y); + public static Point operator -(Point a, Point b) => new(a.X - b.X, a.Y - b.Y); + public static Point operator +(Point a, Point b) => new(a.X + b.X, a.Y + b.Y); public void Deconstruct(out double x, out double y) { diff --git a/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs b/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs index 25068f4f7..258a28e8c 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs @@ -5,19 +5,14 @@ namespace Blazor.Diagrams.Core.Geometry { public class Rectangle : IShape { - public static Rectangle Zero { get; } = new Rectangle(0, 0, 0, 0); + public static Rectangle Zero { get; } = new(0, 0, 0, 0); - public double Width { get; set; } - public double Height { get; set; } - public double Top { get; set; } - public double Right { get; set; } - public double Bottom { get; set; } - public double Left { get; set; } - - public Rectangle() - { - - } + public double Width { get; } + public double Height { get; } + public double Top { get; } + public double Right { get; } + public double Bottom { get; } + public double Left { get; } public Rectangle(double left, double top, double right, double bottom) { @@ -56,7 +51,7 @@ public bool Intersects(Rectangle r) } public Rectangle Inflate(double horizontal, double vertical) - => new Rectangle(Left - horizontal, Top - vertical, Right + horizontal, Bottom + vertical); + => new(Left - horizontal, Top - vertical, Right + horizontal, Bottom + vertical); public Rectangle Union(Rectangle r) { @@ -64,7 +59,7 @@ public Rectangle Union(Rectangle r) var x2 = Math.Max(Left + Width, r.Left + r.Width); var y1 = Math.Min(Top, r.Top); var y2 = Math.Max(Top + Height, r.Top + r.Height); - return new Rectangle(x1, y1, x2, y2); + return new(x1, y1, x2, y2); } public bool ContainsPoint(Point point) => ContainsPoint(point.X, point.Y); @@ -89,15 +84,15 @@ public IEnumerable GetIntersectionsWithLine(Line line) } } - public Point Center => new Point(Left + Width / 2, Top + Height / 2); - public Point NorthEast => new Point(Right, Top); - public Point SouthEast => new Point(Right, Bottom); - public Point SouthWest => new Point(Left, Bottom); - public Point NorthWest => new Point(Left, Top); - public Point East => new Point(Right, Top + Height / 2); - public Point North => new Point(Left + Width / 2, Top); - public Point South => new Point(Left + Width / 2, Bottom); - public Point West => new Point(Left, Top + Height / 2); + public Point Center => new(Left + Width / 2, Top + Height / 2); + public Point NorthEast => new(Right, Top); + public Point SouthEast => new(Right, Bottom); + public Point SouthWest => new(Left, Bottom); + public Point NorthWest => new(Left, Top); + public Point East => new(Right, Top + Height / 2); + public Point North => new(Left + Width / 2, Top); + public Point South => new(Left + Width / 2, Bottom); + public Point West => new(Left, Top + Height / 2); public bool Equals(Rectangle? other) { diff --git a/src/Blazor.Diagrams.Core/Geometry/Size.cs b/src/Blazor.Diagrams.Core/Geometry/Size.cs index 487b8f196..4207884e8 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Size.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Size.cs @@ -1,10 +1,8 @@ namespace Blazor.Diagrams.Core.Geometry { - public class Size + public record Size { - public static Size Zero { get; } = new Size(0, 0); - - public Size() { } + public static Size Zero { get; } = new(0, 0); public Size(double width, double height) { @@ -12,13 +10,7 @@ public Size(double width, double height) Height = height; } - public double Width { get; set; } - public double Height { get; set; } - - public Size Add(double value) => new Size(Width + value, Height + value); - - public bool Equals(Size? size) => size != null && Width == size.Width && Height == size.Height; - - public override string ToString() => $"Size(width={Width}, height={Height})"; + public double Width { get; init; } + public double Height { get; init; } } } diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index c2d804c0e..3c2c98d6a 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -43,7 +43,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (firstRender) { - Diagram.Container = await JSRuntime.GetBoundingClientRect(elementReference); + Diagram.SetContainer(await JSRuntime.GetBoundingClientRect(elementReference)); await JSRuntime.ObserveResizes(elementReference, _reference); } } From f9fd4d99f71db964d0453cf776e78b6580630d4f Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 25 Jul 2022 18:09:41 +0100 Subject: [PATCH 020/193] Fix Virtualization throwing JSException --- src/Blazor.Diagrams.Core/Geometry/Rectangle.cs | 2 ++ src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs | 8 +------- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs b/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs index 258a28e8c..2824ff787 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text.Json.Serialization; namespace Blazor.Diagrams.Core.Geometry { @@ -14,6 +15,7 @@ public class Rectangle : IShape public double Bottom { get; } public double Left { get; } + [JsonConstructor] public Rectangle(double left, double top, double right, double bottom) { Left = left; diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index a043b2dc1..37efc4f82 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -130,7 +130,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) } } - private async void CheckVisibility() + private void CheckVisibility() { // _isVisible must be true in case virtualization gets disabled and some nodes are hidden if (!Diagram.Options.EnableVirtualization && _isVisible) @@ -151,12 +151,6 @@ private async void CheckVisibility() { _isVisible = isVisible; _becameVisible = isVisible; - - if (!_isVisible) - { - await JsRuntime.UnobserveResizes(_element); - } - ReRender(); } } From 3b03edaa95eaa24623a32a5d11be5494a74a498b Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 31 Jul 2022 20:31:07 +0100 Subject: [PATCH 021/193] Add inheritance support to GetComponentForModel --- docs/Diagram-Demo/Pages/Diagrams.razor | 1 - docs/Layouts/Pages/Index.razor | 1 - .../SharedDemo/Demos/CustomPort/Demo.razor.cs | 2 +- src/Blazor.Diagrams.Core/DiagramOptions.cs | 4 - .../Components/Renderers/LinkRenderer.cs | 4 +- .../Components/Renderers/NodeRenderer.cs | 1 - src/Blazor.Diagrams/Diagram.cs | 35 ++++++-- tests/Blazor.Diagrams.Tests/DiagramTests.cs | 85 +++++++++++++++++++ 8 files changed, 114 insertions(+), 19 deletions(-) create mode 100644 tests/Blazor.Diagrams.Tests/DiagramTests.cs diff --git a/docs/Diagram-Demo/Pages/Diagrams.razor b/docs/Diagram-Demo/Pages/Diagrams.razor index 8ecd60e4d..c8716868f 100644 --- a/docs/Diagram-Demo/Pages/Diagrams.razor +++ b/docs/Diagram-Demo/Pages/Diagrams.razor @@ -34,7 +34,6 @@ or it will not be rendered. var options = new DiagramOptions { DeleteKey = "Delete", // What key deletes the selected nodes/links - DefaultNodeComponent = null, // Default component for nodes AllowMultiSelection = true, // Whether to allow multi selection using CTRL Links = new DiagramLinkOptions { diff --git a/docs/Layouts/Pages/Index.razor b/docs/Layouts/Pages/Index.razor index 0af284803..a3ec81351 100644 --- a/docs/Layouts/Pages/Index.razor +++ b/docs/Layouts/Pages/Index.razor @@ -47,7 +47,6 @@ or it will not be rendered. var options = new DiagramOptions { DeleteKey = "Delete", // What key deletes the selected nodes/links - DefaultNodeComponent = null, // Default component for nodes AllowMultiSelection = true, // Whether to allow multi selection using CTRL Links = new DiagramLinkOptions { diff --git a/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs b/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs index 0849c4ee4..00d39cd46 100644 --- a/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs @@ -17,7 +17,7 @@ protected override void OnInitialized() "In this example, you can only attach links from/to ports with the same color."; LayoutData.DataChanged(); - _diagram.Options.DefaultNodeComponent = typeof(ColoredNodeWidget); + _diagram.RegisterModelComponent(replace: true); var node1 = NewNode(50, 50); var node2 = NewNode(300, 300); diff --git a/src/Blazor.Diagrams.Core/DiagramOptions.cs b/src/Blazor.Diagrams.Core/DiagramOptions.cs index 5d0a2c5cc..7b1d342b0 100644 --- a/src/Blazor.Diagrams.Core/DiagramOptions.cs +++ b/src/Blazor.Diagrams.Core/DiagramOptions.cs @@ -10,8 +10,6 @@ public class DiagramOptions { [Description("Key code for deleting entities")] public string DeleteKey { get; set; } = "Delete"; - [Description("The default component for nodes")] - public Type? DefaultNodeComponent { get; set; } [Description("The grid size (grid-based snaping")] public int? GridSize { get; set; } [Description("Whether to allow users to select multiple nodes at once using CTRL or not")] @@ -32,8 +30,6 @@ public class DiagramOptions /// public class DiagramLinkOptions { - [Description("The default component for links")] - public Type? DefaultLinkComponent { get; set; } [Description("The default color for links")] public string DefaultColor { get; set; } = "black"; [Description("The default color for selected links")] diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs index 1dbab252d..b800a3733 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs @@ -34,9 +34,7 @@ protected override void OnInitialized() protected override void BuildRenderTree(RenderTreeBuilder builder) { - var componentType = Diagram.GetComponentForModel(Link) ?? - Diagram.Options.Links.DefaultLinkComponent ?? - typeof(LinkWidget); + var componentType = Diagram.GetComponentForModel(Link) ?? typeof(LinkWidget); builder.OpenElement(0, "g"); builder.AddAttribute(1, "class", "link"); diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index 37efc4f82..b4344268a 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -87,7 +87,6 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) return; var componentType = Diagram.GetComponentForModel(Node) ?? - Diagram.Options.DefaultNodeComponent ?? (Node.Layer == RenderLayer.HTML ? typeof(NodeWidget) : typeof(SvgNodeWidget)); builder.OpenElement(0, Node.Layer == RenderLayer.HTML ? "div" : "g"); diff --git a/src/Blazor.Diagrams/Diagram.cs b/src/Blazor.Diagrams/Diagram.cs index 1927650a0..d787ef396 100644 --- a/src/Blazor.Diagrams/Diagram.cs +++ b/src/Blazor.Diagrams/Diagram.cs @@ -15,21 +15,40 @@ public Diagram(DiagramOptions? options = null) : base(options) _componentByModelMapping = new Dictionary(); } - public void RegisterModelComponent() where M : Model where C : ComponentBase - => RegisterModelComponent(typeof(M), typeof(C)); + public void RegisterModelComponent(bool replace = false) where M : Model where C : ComponentBase + => RegisterModelComponent(typeof(M), typeof(C), replace); - public void RegisterModelComponent(Type modelType, Type componentType) + public void RegisterModelComponent(Type modelType, Type componentType, bool replace = false) { - if (_componentByModelMapping.ContainsKey(modelType)) + if (!replace && _componentByModelMapping.ContainsKey(modelType)) throw new Exception($"Component already registered for model '{modelType.Name}'."); - _componentByModelMapping.Add(modelType, componentType); + _componentByModelMapping[modelType] = componentType; } - public Type? GetComponentForModel(M model) where M : Model + public Type? GetComponentForModel(Type modelType, bool checkSubclasses = true) { - var modelType = model.GetType(); - return _componentByModelMapping.ContainsKey(modelType) ? _componentByModelMapping[modelType] : null; + if (_componentByModelMapping.ContainsKey(modelType)) + { + return _componentByModelMapping[modelType]; + } + + if (checkSubclasses) + { + foreach (var rmt in _componentByModelMapping.Keys) + { + if (modelType.IsSubclassOf(rmt)) + return _componentByModelMapping[rmt]; + } + } + + return null; } + + public Type? GetComponentForModel(bool checkSubclasses = true) where M : Model + => GetComponentForModel(typeof(M), checkSubclasses); + + public Type? GetComponentForModel(Model model, bool checkSubclasses = true) + => GetComponentForModel(model.GetType(), checkSubclasses); } } diff --git a/tests/Blazor.Diagrams.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Tests/DiagramTests.cs new file mode 100644 index 000000000..21fce90ba --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/DiagramTests.cs @@ -0,0 +1,85 @@ +using Blazor.Diagrams.Components; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using FluentAssertions; +using Microsoft.AspNetCore.Components; +using Xunit; + +namespace Blazor.Diagrams.Tests +{ + public class DiagramTests + { + [Fact] + public void GetComponentForModel_ShouldReturnComponentType_WhenModelTypeWasRegistered() + { + // Arrange + var diagram = new Diagram(); + diagram.RegisterModelComponent(); + + // Act + var componentType = diagram.GetComponentForModel(); + + // Assert + componentType.Should().Be(typeof(NodeWidget)); + } + + [Fact] + public void GetComponentForModel_ShouldReturnNull_WhenModelTypeWasNotRegistered() + { + // Arrange + var diagram = new Diagram(); + + // Act + var componentType = diagram.GetComponentForModel(); + + // Assert + componentType.Should().BeNull(); + } + + [Fact] + public void GetComponentForModel_ShouldReturnComponentType_WhenInheritedModelTypeWasRegistered() + { + // Arrange + var diagram = new Diagram(); + diagram.RegisterModelComponent(); + + // Act + var componentType = diagram.GetComponentForModel(); + + // Assert + componentType.Should().Be(typeof(NodeWidget)); + } + + [Fact] + public void GetComponentForModel_ShouldReturnSpecificComponentType_WhenInheritedAndSpecificModelTypeWasRegistered() + { + // Arrange + var diagram = new Diagram(); + diagram.RegisterModelComponent(); + diagram.RegisterModelComponent(); + + // Act + var componentType = diagram.GetComponentForModel(); + + // Assert + componentType.Should().Be(typeof(CustomWidget)); + } + + [Fact] + public void GetComponentForModel_ShouldReturnNull_WhenCheckSubclassesIsFalse() + { + // Arrange + var diagram = new Diagram(); + diagram.RegisterModelComponent(); + + // Act + var componentType = diagram.GetComponentForModel(false); + + // Assert + componentType.Should().BeNull(); + } + + private class CustomModel : Model { } + private class CustomWidget : ComponentBase { } + } +} From c101f5c9f59da21359eaf0cbbd03b39647102efc Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 2 Aug 2022 13:30:46 +0100 Subject: [PATCH 022/193] Refactor constraints to be async (ValueTask) --- docs/CustomNodesLinks/Pages/Index.razor | 1 - .../SharedDemo/Demos/Nodes/SvgDemo.razor.cs | 2 +- .../Behaviors/DeleteSelectionBehavior.cs | 48 +++++++++++-------- src/Blazor.Diagrams.Core/DiagramOptions.cs | 7 +-- .../Behaviors/DeleteSelectionBehaviorTests.cs | 7 +-- 5 files changed, 36 insertions(+), 29 deletions(-) diff --git a/docs/CustomNodesLinks/Pages/Index.razor b/docs/CustomNodesLinks/Pages/Index.razor index 969787da1..eaaf8d074 100644 --- a/docs/CustomNodesLinks/Pages/Index.razor +++ b/docs/CustomNodesLinks/Pages/Index.razor @@ -35,7 +35,6 @@ or it will not be rendered. var options = new DiagramOptions { DeleteKey = "Delete", // What key deletes the selected nodes/links - DefaultNodeComponent = null, // Default component for nodes AllowMultiSelection = true, // Whether to allow multi selection using CTRL Links = new DiagramLinkOptions { diff --git a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs index c84c6328b..00d36904a 100644 --- a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs @@ -21,7 +21,7 @@ protected override void OnInitialized() private void InitializeDiagram() { - _diagram.Options.DefaultNodeComponent = typeof(SvgNodeWidget); + _diagram.RegisterModelComponent(); var node1 = NewNode(80, 80); var node2 = NewNode(280, 150); diff --git a/src/Blazor.Diagrams.Core/Behaviors/DeleteSelectionBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DeleteSelectionBehavior.cs index 3718713cc..011f528ba 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DeleteSelectionBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DeleteSelectionBehavior.cs @@ -9,40 +9,46 @@ public class DeleteSelectionBehavior : Behavior { public DeleteSelectionBehavior(DiagramBase diagram) : base(diagram) { - Diagram.KeyDown += Diagram_KeyDown; + Diagram.KeyDown += OnKeyDown; } - private void Diagram_KeyDown(KeyboardEventArgs e) + private async void OnKeyDown(KeyboardEventArgs e) { if (e.AltKey || e.CtrlKey || e.ShiftKey || e.Code != Diagram.Options.DeleteKey) return; - Diagram.Batch(() => + var wasSuspended = Diagram.SuspendRefresh; + if (!wasSuspended) Diagram.SuspendRefresh = true; + + foreach (var sm in Diagram.GetSelectedModels().ToList()) { - foreach (var sm in Diagram.GetSelectedModels().ToList()) - { - if (sm.Locked) - continue; + if (sm.Locked) + continue; - if (sm is GroupModel group && Diagram.Options.Constraints.ShouldDeleteGroup(group)) - { - Diagram.RemoveGroup(group); - } - else if (sm is NodeModel node && Diagram.Options.Constraints.ShouldDeleteNode(node)) - { - Diagram.Nodes.Remove(node); - } - else if (sm is BaseLinkModel link && Diagram.Options.Constraints.ShouldDeleteLink(link)) - { - Diagram.Links.Remove(link); - } + if (sm is GroupModel group && (await Diagram.Options.Constraints.ShouldDeleteGroup(group))) + { + Diagram.RemoveGroup(group); + } + else if (sm is NodeModel node && (await Diagram.Options.Constraints.ShouldDeleteNode(node))) + { + Diagram.Nodes.Remove(node); } - }); + else if (sm is BaseLinkModel link && (await Diagram.Options.Constraints.ShouldDeleteLink(link))) + { + Diagram.Links.Remove(link); + } + } + + if (!wasSuspended) + { + Diagram.SuspendRefresh = false; + Diagram.Refresh(); + } } public override void Dispose() { - Diagram.KeyDown -= Diagram_KeyDown; + Diagram.KeyDown -= OnKeyDown; } } } diff --git a/src/Blazor.Diagrams.Core/DiagramOptions.cs b/src/Blazor.Diagrams.Core/DiagramOptions.cs index 7b1d342b0..efb425376 100644 --- a/src/Blazor.Diagrams.Core/DiagramOptions.cs +++ b/src/Blazor.Diagrams.Core/DiagramOptions.cs @@ -3,6 +3,7 @@ using Blazor.Diagrams.Core.Events; using System; using System.ComponentModel; +using System.Threading.Tasks; namespace Blazor.Diagrams.Core { @@ -90,10 +91,10 @@ public class DiagramGroupOptions public class DiagramConstraintsOptions { [Description("Decide if a node can/should be deleted")] - public Func ShouldDeleteNode { get; set; } = _ => true; + public Func> ShouldDeleteNode { get; set; } = _ => ValueTask.FromResult(true); [Description("Decide if a link can/should be deleted")] - public Func ShouldDeleteLink { get; set; } = _ => true; + public Func> ShouldDeleteLink { get; set; } = _ => ValueTask.FromResult(true); [Description("Decide if a group can/should be deleted")] - public Func ShouldDeleteGroup { get; set; } = _ => true; + public Func> ShouldDeleteGroup { get; set; } = _ => ValueTask.FromResult(true); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DeleteSelectionBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DeleteSelectionBehaviorTests.cs index fc2cc7499..c55c093db 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DeleteSelectionBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DeleteSelectionBehaviorTests.cs @@ -3,6 +3,7 @@ using Blazor.Diagrams.Core.Models; using FluentAssertions; using System; +using System.Threading.Tasks; using Xunit; namespace Blazor.Diagrams.Core.Tests.Behaviors @@ -72,7 +73,7 @@ public void Behavior_ShouldTakeIntoAccountGroupConstraint() diagram.Options.Constraints.ShouldDeleteGroup = _ => { funcCalled = true; - return false; + return ValueTask.FromResult(false); }; diagram.AddGroup(new GroupModel(Array.Empty()) { @@ -96,7 +97,7 @@ public void Behavior_ShouldTakeIntoAccountNodeConstraint() diagram.Options.Constraints.ShouldDeleteNode = _ => { funcCalled = true; - return false; + return ValueTask.FromResult(false); }; diagram.Nodes.Add(new NodeModel { @@ -120,7 +121,7 @@ public void Behavior_ShouldTakeIntoAccountLinkConstraint() diagram.Options.Constraints.ShouldDeleteLink = _ => { funcCalled = true; - return false; + return ValueTask.FromResult(false); }; diagram.Nodes.Add(new NodeModel[] { From c6788ed4ca97b6b171c79577b10e8e9fa3cf11b9 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 5 Aug 2022 21:22:13 +0100 Subject: [PATCH 023/193] Remove RenderLayer and add SvgNodeModel --- .../Demos/Nodes/PortlessLinks.razor.cs | 2 +- .../SharedDemo/Demos/Nodes/SvgDemo.razor.cs | 3 +- .../Behaviors/GroupingBehavior.cs | 4 --- src/Blazor.Diagrams.Core/DiagramBase.cs | 10 ------ src/Blazor.Diagrams.Core/Models/NodeModel.cs | 15 +++------ .../Models/RenderLayer.cs | 8 ----- .../Components/DiagramCanvas.razor | 4 +-- .../Components/Renderers/NodeRenderer.cs | 31 ++++++++++++------- .../Components/Renderers/PortRenderer.cs | 24 +++++++++----- src/Blazor.Diagrams/Models/SvgNodeModel.cs | 16 ++++++++++ src/Blazor.Diagrams/_Imports.razor | 1 + 11 files changed, 62 insertions(+), 56 deletions(-) delete mode 100644 src/Blazor.Diagrams.Core/Models/RenderLayer.cs create mode 100644 src/Blazor.Diagrams/Models/SvgNodeModel.cs diff --git a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs index 432b80605..8d37a7018 100644 --- a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs @@ -38,6 +38,6 @@ private void InitializeDiagram() class RoundedNode : NodeModel { - public RoundedNode(Point position = null, ShapeDefiner shape = null) : base(position, RenderLayer.HTML, shape) { } + public RoundedNode(Point position = null, ShapeDefiner shape = null) : base(position, shape) { } } } diff --git a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs index 00d36904a..84c16fd72 100644 --- a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs @@ -1,6 +1,7 @@ using Blazor.Diagrams; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Models; namespace SharedDemo.Demos.Nodes { @@ -32,7 +33,7 @@ private void InitializeDiagram() private NodeModel NewNode(double x, double y) { - var node = new NodeModel(new Point(x, y), RenderLayer.SVG); + var node = new SvgNodeModel(new Point(x, y)); node.AddPort(PortAlignment.Left); node.AddPort(PortAlignment.Right); return node; diff --git a/src/Blazor.Diagrams.Core/Behaviors/GroupingBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/GroupingBehavior.cs index 7c3dbcc9c..48d4ceba0 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/GroupingBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/GroupingBehavior.cs @@ -1,5 +1,4 @@ using Blazor.Diagrams.Core.Events; -using System; using System.Linq; namespace Blazor.Diagrams.Core.Behaviors @@ -41,9 +40,6 @@ private void Diagram_KeyDown(KeyboardEventArgs e) if (selectedNodes.Any(n => n.Group != null)) return; - if (selectedNodes.Select(n => n.Layer).Distinct().Count() > 1) - return; - Diagram.Group(selectedNodes); } } diff --git a/src/Blazor.Diagrams.Core/DiagramBase.cs b/src/Blazor.Diagrams.Core/DiagramBase.cs index 69c9e8c6b..7883abbea 100644 --- a/src/Blazor.Diagrams.Core/DiagramBase.cs +++ b/src/Blazor.Diagrams.Core/DiagramBase.cs @@ -115,16 +115,6 @@ public GroupModel Group(params NodeModel[] children) /// A group instance. public void AddGroup(GroupModel group) { - if (group.Children.Count > 0) - { - var layers = group.Children.Select(n => n.Layer).Distinct(); - if (layers.Count() > 1) - throw new InvalidOperationException("Cannot group nodes with different layers"); - - if (layers.First() == RenderLayer.SVG) - throw new InvalidOperationException("SVG groups aren't implemented yet"); - } - foreach (var child in group.Children) { if (child is NodeModel node && !Nodes.Contains(node)) diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs index b01d8d0b0..9072cfd55 100644 --- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs +++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs @@ -15,21 +15,16 @@ public class NodeModel : MovableModel public event Action? SizeChanged; public event Action? Moving; - public NodeModel(Point? position = null, RenderLayer layer = RenderLayer.HTML, - ShapeDefiner? shape = null) : base(position) + public NodeModel(Point? position = null, ShapeDefiner? shape = null) : base(position) { - Layer = layer; ShapeDefiner = shape ?? Shapes.Rectangle; } - public NodeModel(string id, Point? position = null, RenderLayer layer = RenderLayer.HTML, - ShapeDefiner? shape = null) : base(id, position) + public NodeModel(string id, Point? position = null, ShapeDefiner? shape = null) : base(id, position) { - Layer = layer; ShapeDefiner = shape ?? Shapes.Rectangle; } - public RenderLayer Layer { get; } public ShapeDefiner ShapeDefiner { get; } public Size? Size { @@ -44,7 +39,7 @@ public Size? Size } } public GroupModel? Group { get; internal set; } - public string Title { get; set; } + public string? Title { get; set; } public IReadOnlyList Ports => _ports; public IReadOnlyList Links => _links; @@ -59,9 +54,9 @@ public PortModel AddPort(PortModel port) public PortModel AddPort(PortAlignment alignment = PortAlignment.Bottom) => AddPort(new PortModel(this, alignment, Position)); - public PortModel GetPort(PortAlignment alignment) => Ports.FirstOrDefault(p => p.Alignment == alignment); + public PortModel? GetPort(PortAlignment alignment) => Ports.FirstOrDefault(p => p.Alignment == alignment); - public T GetPort(PortAlignment alignment) where T : PortModel => (T)GetPort(alignment); + public T? GetPort(PortAlignment alignment) where T : PortModel => (T?)GetPort(alignment); public void RefreshAll() { diff --git a/src/Blazor.Diagrams.Core/Models/RenderLayer.cs b/src/Blazor.Diagrams.Core/Models/RenderLayer.cs deleted file mode 100644 index 7ca6201f9..000000000 --- a/src/Blazor.Diagrams.Core/Models/RenderLayer.cs +++ /dev/null @@ -1,8 +0,0 @@ -namespace Blazor.Diagrams.Core.Models -{ - public enum RenderLayer - { - HTML, - SVG - } -} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor b/src/Blazor.Diagrams/Components/DiagramCanvas.razor index 4ad38c1b0..7838c1db9 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor @@ -20,7 +20,7 @@ } - @foreach (var node in Diagram.Nodes.Where(n => n.Layer == RenderLayer.SVG)) + @foreach (var node in Diagram.Nodes.OfType()) { } @@ -33,7 +33,7 @@ } - @foreach (var node in Diagram.Nodes.Where(n => n.Layer == RenderLayer.HTML && n.Group == null)) + @foreach (var node in Diagram.Nodes.Where(n => n is not SvgNodeModel && n.Group == null)) { } diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index b4344268a..4acee4888 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -1,8 +1,8 @@ -using Blazor.Diagrams.Core; -using Blazor.Diagrams.Core.Extensions; +using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Extensions; +using Blazor.Diagrams.Models; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; @@ -18,16 +18,17 @@ public class NodeRenderer : ComponentBase, IDisposable private bool _isVisible = true; private bool _becameVisible; private ElementReference _element; - private DotNetObjectReference _reference; + private DotNetObjectReference? _reference; + private bool _isSvg; [CascadingParameter] - public Diagram Diagram { get; set; } + public Diagram Diagram { get; set; } = null!; [Parameter] - public NodeModel Node { get; set; } + public NodeModel Node { get; set; } = null!; [Inject] - private IJSRuntime JsRuntime { get; set; } + private IJSRuntime JsRuntime { get; set; } = null!; public void Dispose() { @@ -70,6 +71,13 @@ protected override void OnInitialized() Node.Changed += ReRender; } + protected override void OnParametersSet() + { + base.OnParametersSet(); + + _isSvg = Node is SvgNodeModel; + } + protected override bool ShouldRender() { if (_shouldRender) @@ -86,20 +94,19 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) if (!_isVisible) return; - var componentType = Diagram.GetComponentForModel(Node) ?? - (Node.Layer == RenderLayer.HTML ? typeof(NodeWidget) : typeof(SvgNodeWidget)); + var componentType = Diagram.GetComponentForModel(Node) ?? (_isSvg ? typeof(SvgNodeWidget) : typeof(NodeWidget)); - builder.OpenElement(0, Node.Layer == RenderLayer.HTML ? "div" : "g"); + builder.OpenElement(0, _isSvg ? "g" : "div"); builder.AddAttribute(1, "class", $"node{(Node.Locked ? " locked" : string.Empty)}"); builder.AddAttribute(2, "data-node-id", Node.Id); - if (Node.Layer == RenderLayer.HTML) + if (_isSvg) { - builder.AddAttribute(3, "style", $"top: {Node.Position.Y.ToInvariantString()}px; left: {Node.Position.X.ToInvariantString()}px"); + builder.AddAttribute(3, "transform", $"translate({Node.Position.X.ToInvariantString()} {Node.Position.Y.ToInvariantString()})"); } else { - builder.AddAttribute(3, "transform", $"translate({Node.Position.X.ToInvariantString()} {Node.Position.Y.ToInvariantString()})"); + builder.AddAttribute(3, "style", $"top: {Node.Position.Y.ToInvariantString()}px; left: {Node.Position.X.ToInvariantString()}px"); } builder.AddAttribute(4, "onmousedown", EventCallback.Factory.Create(this, OnMouseDown)); diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs index 8dd83a59b..3b7dffed2 100644 --- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs @@ -1,5 +1,4 @@ using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; @@ -9,6 +8,7 @@ using Blazor.Diagrams.Core.Geometry; using Microsoft.AspNetCore.Components.Rendering; using System.Linq; +using Blazor.Diagrams.Models; namespace Blazor.Diagrams.Components.Renderers { @@ -18,21 +18,22 @@ public class PortRenderer : ComponentBase, IDisposable private ElementReference _element; private bool _updatingDimensions; private bool _shouldRefreshPort; + private bool _isParentSvg; [CascadingParameter] - public Diagram Diagram { get; set; } + public Diagram Diagram { get; set; } = null!; [Inject] - private IJSRuntime JSRuntime { get; set; } + private IJSRuntime JSRuntime { get; set; } = null!; [Parameter] - public PortModel Port { get; set; } + public PortModel Port { get; set; } = null!; [Parameter] - public string Class { get; set; } + public string? Class { get; set; } [Parameter] - public RenderFragment ChildContent { get; set; } + public RenderFragment? ChildContent { get; set; } public void Dispose() { @@ -46,11 +47,18 @@ protected override void OnInitialized() Port.Changed += OnPortChanged; } + protected override void OnParametersSet() + { + base.OnParametersSet(); + + _isParentSvg = Port.Parent is SvgNodeModel; + } + protected override bool ShouldRender() => _shouldRender; protected override void BuildRenderTree(RenderTreeBuilder builder) { - builder.OpenElement(0, Port.Parent.Layer == RenderLayer.HTML ? "div" : "g"); + builder.OpenElement(0, _isParentSvg ? "g" : "div"); builder.AddAttribute(1, "class", "port" + " " + (Port.Alignment.ToString().ToLower()) + " " + (Port.Links.Count > 0 ? "has-links" : "") + " " + (Class)); builder.AddAttribute(2, "data-port-id", Port.Id); builder.AddAttribute(3, "onmousedown", EventCallback.Factory.Create(this, OnMouseDown)); @@ -87,7 +95,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(FindPortOn(e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY), e.ToCore()); - private PortModel FindPortOn(double clientX, double clientY) + private PortModel? FindPortOn(double clientX, double clientY) { var allPorts = Diagram.Nodes.SelectMany(n => n.Ports) .Union(Diagram.Groups.SelectMany(g => g.Ports)); diff --git a/src/Blazor.Diagrams/Models/SvgNodeModel.cs b/src/Blazor.Diagrams/Models/SvgNodeModel.cs new file mode 100644 index 000000000..1a5252ad5 --- /dev/null +++ b/src/Blazor.Diagrams/Models/SvgNodeModel.cs @@ -0,0 +1,16 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; + +namespace Blazor.Diagrams.Models +{ + public class SvgNodeModel : NodeModel + { + public SvgNodeModel(Point? position = null, ShapeDefiner? shape = null) : base(position, shape) + { + } + + public SvgNodeModel(string id, Point? position = null, ShapeDefiner? shape = null) : base(id, position, shape) + { + } + } +} diff --git a/src/Blazor.Diagrams/_Imports.razor b/src/Blazor.Diagrams/_Imports.razor index 71498dca2..76771ae75 100644 --- a/src/Blazor.Diagrams/_Imports.razor +++ b/src/Blazor.Diagrams/_Imports.razor @@ -4,3 +4,4 @@ @using Blazor.Diagrams.Core.Models; @using Blazor.Diagrams.Components.Renderers @using Blazor.Diagrams.Core.Geometry; +@using Blazor.Diagrams.Models; From 6b6e74e9127efa039875cf0df8939045a1a3b76b Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 5 Aug 2022 23:27:06 +0100 Subject: [PATCH 024/193] Add Keyboard Shortcuts behavior --- .../Demos/Groups/CustomShortcut.razor.cs | 7 +- .../Behaviors/DeleteSelectionBehavior.cs | 54 -------------- .../Behaviors/GroupingBehavior.cs | 52 ------------- .../Behaviors/KeyboardShortcutsBehavior.cs | 48 ++++++++++++ .../Behaviors/KeyboardShortcutsDefaults.cs | 74 +++++++++++++++++++ src/Blazor.Diagrams.Core/DiagramBase.cs | 9 ++- src/Blazor.Diagrams.Core/DiagramOptions.cs | 4 - src/Blazor.Diagrams.Core/Utils/KeysUtils.cs | 19 +++++ 8 files changed, 153 insertions(+), 114 deletions(-) delete mode 100644 src/Blazor.Diagrams.Core/Behaviors/DeleteSelectionBehavior.cs delete mode 100644 src/Blazor.Diagrams.Core/Behaviors/GroupingBehavior.cs create mode 100644 src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs create mode 100644 src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs create mode 100644 src/Blazor.Diagrams.Core/Utils/KeysUtils.cs diff --git a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs index 0c37a98db..a8c05df47 100644 --- a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs +++ b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs @@ -1,4 +1,5 @@ using Blazor.Diagrams; +using Blazor.Diagrams.Core.Behaviors; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; @@ -13,11 +14,13 @@ protected override void OnInitialized() base.OnInitialized(); LayoutData.Title = "Custom Shortcut"; - LayoutData.Info = "You can customize what needs to be pressed to group selected nodes. CTRL+SHIFT+K in this example."; + LayoutData.Info = "You can customize what needs to be pressed to group selected nodes. Ctrl+Shift+k in this example."; LayoutData.DataChanged(); _diagram.Options.Groups.Enabled = true; - _diagram.Options.Groups.KeyboardShortcut = e => e.CtrlKey && e.ShiftKey && e.Key.ToLower() == "k"; + var ksb = _diagram.GetBehavior(); + ksb.RemoveShortcut("G", true, false, true); + ksb.SetShortcut("K", true, true, false, KeyboardShortcutsDefaults.Grouping); var node1 = NewNode(50, 50); var node2 = NewNode(250, 250); diff --git a/src/Blazor.Diagrams.Core/Behaviors/DeleteSelectionBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DeleteSelectionBehavior.cs deleted file mode 100644 index 011f528ba..000000000 --- a/src/Blazor.Diagrams.Core/Behaviors/DeleteSelectionBehavior.cs +++ /dev/null @@ -1,54 +0,0 @@ -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; -using Blazor.Diagrams.Core.Events; -using System.Linq; - -namespace Blazor.Diagrams.Core.Behaviors -{ - public class DeleteSelectionBehavior : Behavior - { - public DeleteSelectionBehavior(DiagramBase diagram) : base(diagram) - { - Diagram.KeyDown += OnKeyDown; - } - - private async void OnKeyDown(KeyboardEventArgs e) - { - if (e.AltKey || e.CtrlKey || e.ShiftKey || e.Code != Diagram.Options.DeleteKey) - return; - - var wasSuspended = Diagram.SuspendRefresh; - if (!wasSuspended) Diagram.SuspendRefresh = true; - - foreach (var sm in Diagram.GetSelectedModels().ToList()) - { - if (sm.Locked) - continue; - - if (sm is GroupModel group && (await Diagram.Options.Constraints.ShouldDeleteGroup(group))) - { - Diagram.RemoveGroup(group); - } - else if (sm is NodeModel node && (await Diagram.Options.Constraints.ShouldDeleteNode(node))) - { - Diagram.Nodes.Remove(node); - } - else if (sm is BaseLinkModel link && (await Diagram.Options.Constraints.ShouldDeleteLink(link))) - { - Diagram.Links.Remove(link); - } - } - - if (!wasSuspended) - { - Diagram.SuspendRefresh = false; - Diagram.Refresh(); - } - } - - public override void Dispose() - { - Diagram.KeyDown -= OnKeyDown; - } - } -} diff --git a/src/Blazor.Diagrams.Core/Behaviors/GroupingBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/GroupingBehavior.cs deleted file mode 100644 index 48d4ceba0..000000000 --- a/src/Blazor.Diagrams.Core/Behaviors/GroupingBehavior.cs +++ /dev/null @@ -1,52 +0,0 @@ -using Blazor.Diagrams.Core.Events; -using System.Linq; - -namespace Blazor.Diagrams.Core.Behaviors -{ - public class GroupingBehavior : Behavior - { - public GroupingBehavior(DiagramBase diagram) : base(diagram) - { - Diagram.KeyDown += Diagram_KeyDown; - } - - private void Diagram_KeyDown(KeyboardEventArgs e) - { - if (!Diagram.Options.Groups.Enabled) - return; - - if (!Diagram.GetSelectedModels().Any()) - return; - - if (!Diagram.Options.Groups.KeyboardShortcut(e)) - return; - - var selectedNodes = Diagram.Nodes.Where(n => n.Selected).ToArray(); - var nodesWithGroup = selectedNodes.Where(n => n.Group != null).ToArray(); - if (nodesWithGroup.Length > 0) - { - // Ungroup - foreach (var group in nodesWithGroup.GroupBy(n => n.Group!).Select(g => g.Key)) - { - Diagram.Ungroup(group); - } - } - else - { - // Group - if (selectedNodes.Length < 2) - return; - - if (selectedNodes.Any(n => n.Group != null)) - return; - - Diagram.Group(selectedNodes); - } - } - - public override void Dispose() - { - Diagram.KeyDown -= Diagram_KeyDown; - } - } -} diff --git a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs new file mode 100644 index 000000000..d79f396ce --- /dev/null +++ b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs @@ -0,0 +1,48 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Utils; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Blazor.Diagrams.Core.Behaviors +{ + public class KeyboardShortcutsBehavior : Behavior + { + private readonly Dictionary> _shortcuts; + + public KeyboardShortcutsBehavior(DiagramBase diagram) : base(diagram) + { + _shortcuts = new Dictionary>(); + SetShortcut("Delete", false, false, false, KeyboardShortcutsDefaults.DeleteSelection); + SetShortcut("G", true, false, true, KeyboardShortcutsDefaults.Grouping); + + Diagram.KeyDown += OnDiagramKeyDown; + } + + public void SetShortcut(string key, bool ctrl, bool shift, bool alt, Func action) + { + var k = KeysUtils.GetStringRepresentation(ctrl, shift, alt, key); + _shortcuts[k] = action; + } + + public bool RemoveShortcut(string key, bool ctrl, bool shift, bool alt) + { + var k = KeysUtils.GetStringRepresentation(ctrl, shift, alt, key); + return _shortcuts.Remove(k); + } + + private async void OnDiagramKeyDown(KeyboardEventArgs e) + { + var k = KeysUtils.GetStringRepresentation(e.CtrlKey, e.ShiftKey, e.AltKey, e.Key); + if (_shortcuts.TryGetValue(k, out var action)) + { + await action(Diagram); + } + } + + public override void Dispose() + { + Diagram.KeyDown -= OnDiagramKeyDown; + } + } +} diff --git a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs new file mode 100644 index 000000000..4c486a073 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs @@ -0,0 +1,74 @@ +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using System.Linq; +using System.Threading.Tasks; + +namespace Blazor.Diagrams.Core.Behaviors +{ + public static class KeyboardShortcutsDefaults + { + public static async ValueTask DeleteSelection(DiagramBase diagram) + { + var wasSuspended = diagram.SuspendRefresh; + if (!wasSuspended) diagram.SuspendRefresh = true; + + foreach (var sm in diagram.GetSelectedModels().ToArray()) + { + if (sm.Locked) + continue; + + if (sm is GroupModel group && (await diagram.Options.Constraints.ShouldDeleteGroup(group))) + { + diagram.RemoveGroup(group); + } + else if (sm is NodeModel node && (await diagram.Options.Constraints.ShouldDeleteNode(node))) + { + diagram.Nodes.Remove(node); + } + else if (sm is BaseLinkModel link && (await diagram.Options.Constraints.ShouldDeleteLink(link))) + { + diagram.Links.Remove(link); + } + } + + if (!wasSuspended) + { + diagram.SuspendRefresh = false; + diagram.Refresh(); + } + } + + public static ValueTask Grouping(DiagramBase diagram) + { + if (!diagram.Options.Groups.Enabled) + return ValueTask.CompletedTask; + + if (!diagram.GetSelectedModels().Any()) + return ValueTask.CompletedTask; + + var selectedNodes = diagram.Nodes.Where(n => n.Selected).ToArray(); + var nodesWithGroup = selectedNodes.Where(n => n.Group != null).ToArray(); + if (nodesWithGroup.Length > 0) + { + // Ungroup + foreach (var group in nodesWithGroup.GroupBy(n => n.Group!).Select(g => g.Key)) + { + diagram.Ungroup(group); + } + } + else + { + // Group + if (selectedNodes.Length < 2) + return ValueTask.CompletedTask; + + if (selectedNodes.Any(n => n.Group != null)) + return ValueTask.CompletedTask; + + diagram.Group(selectedNodes); + } + + return ValueTask.CompletedTask; + } + } +} diff --git a/src/Blazor.Diagrams.Core/DiagramBase.cs b/src/Blazor.Diagrams.Core/DiagramBase.cs index 7883abbea..f356bc3e0 100644 --- a/src/Blazor.Diagrams.Core/DiagramBase.cs +++ b/src/Blazor.Diagrams.Core/DiagramBase.cs @@ -52,11 +52,10 @@ public DiagramBase(DiagramOptions? options = null) RegisterBehavior(new SelectionBehavior(this)); RegisterBehavior(new DragMovablesBehavior(this)); RegisterBehavior(new DragNewLinkBehavior(this)); - RegisterBehavior(new DeleteSelectionBehavior(this)); RegisterBehavior(new PanBehavior(this)); RegisterBehavior(new ZoomBehavior(this)); - RegisterBehavior(new GroupingBehavior(this)); RegisterBehavior(new EventsBehavior(this)); + RegisterBehavior(new KeyboardShortcutsBehavior(this)); } public NodeLayer Nodes { get; } @@ -239,6 +238,12 @@ public void RegisterBehavior(Behavior behavior) _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); diff --git a/src/Blazor.Diagrams.Core/DiagramOptions.cs b/src/Blazor.Diagrams.Core/DiagramOptions.cs index efb425376..635bb5d07 100644 --- a/src/Blazor.Diagrams.Core/DiagramOptions.cs +++ b/src/Blazor.Diagrams.Core/DiagramOptions.cs @@ -9,8 +9,6 @@ namespace Blazor.Diagrams.Core { public class DiagramOptions { - [Description("Key code for deleting entities")] - public string DeleteKey { get; set; } = "Delete"; [Description("The grid size (grid-based snaping")] public int? GridSize { get; set; } [Description("Whether to allow users to select multiple nodes at once using CTRL or not")] @@ -79,8 +77,6 @@ public class DiagramGroupOptions { [Description("Whether to allow users to group/ungroup nodes")] public bool Enabled { get; set; } - [Description("Keyboard shortcut (CTRL+ALT+G by default)")] - public Func KeyboardShortcut { get; set; } = e => e.CtrlKey && e.AltKey && e.Key == "g"; [Description("Group model factory")] public GroupFactory Factory { get; set; } = (diagram, children) => new GroupModel(children); } diff --git a/src/Blazor.Diagrams.Core/Utils/KeysUtils.cs b/src/Blazor.Diagrams.Core/Utils/KeysUtils.cs new file mode 100644 index 000000000..eefbab74c --- /dev/null +++ b/src/Blazor.Diagrams.Core/Utils/KeysUtils.cs @@ -0,0 +1,19 @@ +using System.Text; + +namespace Blazor.Diagrams.Core.Utils +{ + public static class KeysUtils + { + public static string GetStringRepresentation(bool ctrl, bool shift, bool alt, string key) + { + var sb = new StringBuilder(); + + if (ctrl) sb.Append("Ctrl+"); + if (shift) sb.Append("Shift+"); + if (alt) sb.Append("Alt+"); + sb.Append(key); + + return sb.ToString(); + } + } +} From 831303c165f14edca83eebeec1685f0d00ca5810 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 6 Aug 2022 11:04:20 +0100 Subject: [PATCH 025/193] Fix build errors --- docs/CustomNodesLinks/Pages/Index.razor | 1 - docs/Diagram-Demo/Pages/Diagrams.razor | 1 - docs/Layouts/Pages/Index.razor | 1 - ...s.cs => KeyboardShortcutsDefaultsTests.cs} | 84 ++++--------------- 4 files changed, 16 insertions(+), 71 deletions(-) rename tests/Blazor.Diagrams.Core.Tests/Behaviors/{DeleteSelectionBehaviorTests.cs => KeyboardShortcutsDefaultsTests.cs} (54%) diff --git a/docs/CustomNodesLinks/Pages/Index.razor b/docs/CustomNodesLinks/Pages/Index.razor index eaaf8d074..465443080 100644 --- a/docs/CustomNodesLinks/Pages/Index.razor +++ b/docs/CustomNodesLinks/Pages/Index.razor @@ -34,7 +34,6 @@ or it will not be rendered. var options = new DiagramOptions { - DeleteKey = "Delete", // What key deletes the selected nodes/links AllowMultiSelection = true, // Whether to allow multi selection using CTRL Links = new DiagramLinkOptions { diff --git a/docs/Diagram-Demo/Pages/Diagrams.razor b/docs/Diagram-Demo/Pages/Diagrams.razor index c8716868f..8f6e8bba7 100644 --- a/docs/Diagram-Demo/Pages/Diagrams.razor +++ b/docs/Diagram-Demo/Pages/Diagrams.razor @@ -33,7 +33,6 @@ or it will not be rendered. var options = new DiagramOptions { - DeleteKey = "Delete", // What key deletes the selected nodes/links AllowMultiSelection = true, // Whether to allow multi selection using CTRL Links = new DiagramLinkOptions { diff --git a/docs/Layouts/Pages/Index.razor b/docs/Layouts/Pages/Index.razor index a3ec81351..189efd5d8 100644 --- a/docs/Layouts/Pages/Index.razor +++ b/docs/Layouts/Pages/Index.razor @@ -46,7 +46,6 @@ or it will not be rendered. var options = new DiagramOptions { - DeleteKey = "Delete", // What key deletes the selected nodes/links AllowMultiSelection = true, // Whether to allow multi selection using CTRL Links = new DiagramLinkOptions { diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DeleteSelectionBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs similarity index 54% rename from tests/Blazor.Diagrams.Core.Tests/Behaviors/DeleteSelectionBehaviorTests.cs rename to tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs index c55c093db..d4a88104b 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DeleteSelectionBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs @@ -1,5 +1,4 @@ using Blazor.Diagrams.Core.Behaviors; -using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Models; using FluentAssertions; using System; @@ -8,46 +7,10 @@ namespace Blazor.Diagrams.Core.Tests.Behaviors { - public class DeleteSelectionBehaviorTests + public class KeyboardShortcutsDefaultsTests { [Fact] - public void Behavior_ShouldNotRun_WhenItsRemoved() - { - // Arrange - var diagram = new DiagramBase(); - diagram.UnregisterBehavior(); - diagram.Nodes.Add(new NodeModel - { - Selected = true - }); - - // Act - diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, false, false, false)); - - // Assert - diagram.Nodes.Count.Should().Be(1); - } - - [Fact] - public void Behavior_ShouldTakeIntoAccountDeleteKeyOption() - { - // Arrange - var diagram = new DiagramBase(); - diagram.Options.DeleteKey = "Test"; - diagram.Nodes.Add(new NodeModel - { - Selected = true - }); - - // Act - diagram.OnKeyDown(new KeyboardEventArgs("Test", "Test", 0, false, false ,false)); - - // Assert - diagram.Nodes.Count.Should().Be(0); - } - - [Fact] - public void Behavior_ShouldNotDeleteModel_WhenItsLocked() + public async Task DeleteSelection_ShouldNotDeleteModel_WhenItsLocked() { // Arrange var diagram = new DiagramBase(); @@ -58,14 +21,14 @@ public void Behavior_ShouldNotDeleteModel_WhenItsLocked() }); // Act - diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, false, false, false)); + await KeyboardShortcutsDefaults.DeleteSelection(diagram); // Assert diagram.Nodes.Count.Should().Be(1); } [Fact] - public void Behavior_ShouldTakeIntoAccountGroupConstraint() + public async Task DeleteSelection_ShouldTakeIntoAccountGroupConstraint() { // Arrange var funcCalled = false; @@ -81,7 +44,7 @@ public void Behavior_ShouldTakeIntoAccountGroupConstraint() }); // Act - diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, false, false, false)); + await KeyboardShortcutsDefaults.DeleteSelection(diagram); // Assert funcCalled.Should().BeTrue(); @@ -89,7 +52,7 @@ public void Behavior_ShouldTakeIntoAccountGroupConstraint() } [Fact] - public void Behavior_ShouldTakeIntoAccountNodeConstraint() + public async Task DeleteSelection_ShouldTakeIntoAccountNodeConstraint() { // Arrange var funcCalled = false; @@ -105,7 +68,7 @@ public void Behavior_ShouldTakeIntoAccountNodeConstraint() }); // Act - diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, false, false, false)); + await KeyboardShortcutsDefaults.DeleteSelection(diagram); // Assert funcCalled.Should().BeTrue(); @@ -113,7 +76,7 @@ public void Behavior_ShouldTakeIntoAccountNodeConstraint() } [Fact] - public void Behavior_ShouldTakeIntoAccountLinkConstraint() + public async Task DeleteSelection_ShouldTakeIntoAccountLinkConstraint() { // Arrange var funcCalled = false; @@ -134,7 +97,7 @@ public void Behavior_ShouldTakeIntoAccountLinkConstraint() }); // Act - diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, false, false, false)); + await KeyboardShortcutsDefaults.DeleteSelection(diagram); // Assert funcCalled.Should().BeTrue(); @@ -142,7 +105,7 @@ public void Behavior_ShouldTakeIntoAccountLinkConstraint() } [Fact] - public void Behavior_ShouldResultInSingleRefresh() + public async Task DeleteSelection_ShouldResultInSingleRefresh() { // Arrange var diagram = new DiagramBase(); @@ -151,36 +114,21 @@ public void Behavior_ShouldResultInSingleRefresh() new NodeModel { Selected = true }, new NodeModel { Selected = true } }); + diagram.Links.Add(new LinkModel(diagram.Nodes[0], diagram.Nodes[1]) + { + Selected = true + }); var refreshes = 0; diagram.Changed += () => refreshes++; // Act - diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, false, false, false)); + await KeyboardShortcutsDefaults.DeleteSelection(diagram); // Assert diagram.Nodes.Count.Should().Be(0); + diagram.Links.Count.Should().Be(0); refreshes.Should().Be(1); } - - [Theory] - [InlineData(true, false, false)] - [InlineData(false, true, false)] - [InlineData(false, false, true)] - public void Behavior_ShouldNotDeleteModel_WhenCtrlAltOrShiftIsPressed(bool ctrl, bool shift, bool alt) - { - // Arrange - var diagram = new DiagramBase(); - diagram.Nodes.Add(new NodeModel - { - Selected = true - }); - - // Act - diagram.OnKeyDown(new KeyboardEventArgs("Delete", "Delete", 0, ctrl, shift, alt)); - - // Assert - diagram.Nodes.Count.Should().Be(1); - } } } From 5c058c10fa07df99a4a502d3d9b31f4909174a7c Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 6 Aug 2022 12:47:45 +0100 Subject: [PATCH 026/193] Add KeyboardShortcutsBehaviorTests --- .github/workflows/build.yml | 47 ++++++++++ .../KeyboardShortcutsBehaviorTests.cs | 90 +++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..8b2c606c0 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,47 @@ +name: Build +on: + push: + branches: + - 3.0.0 + pull_request: + types: [opened, synchronize, reopened] +jobs: + build: + name: Build + runs-on: windows-latest + steps: + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 1.11 + - uses: actions/checkout@v2 + with: + fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis + - name: Cache SonarCloud packages + uses: actions/cache@v1 + with: + path: ~\sonar\cache + key: ${{ runner.os }}-sonar + restore-keys: ${{ runner.os }}-sonar + - name: Cache SonarCloud scanner + id: cache-sonar-scanner + uses: actions/cache@v1 + with: + path: .\.sonar\scanner + key: ${{ runner.os }}-sonar-scanner + restore-keys: ${{ runner.os }}-sonar-scanner + - name: Install SonarCloud scanner + if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' + shell: powershell + run: | + New-Item -Path .\.sonar\scanner -ItemType Directory + dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner + - name: Build and analyze + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + shell: powershell + run: | + .\.sonar\scanner\dotnet-sonarscanner begin /k:"Blazor-Diagrams_Blazor.Diagrams" /o:"blazor-diagrams" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" + + .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs new file mode 100644 index 000000000..626b19780 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs @@ -0,0 +1,90 @@ +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Events; +using FluentAssertions; +using System.Threading.Tasks; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Behaviors +{ + public class KeyboardShortcutsBehaviorTests + { + [Theory] + [InlineData("A", true, true, true)] + [InlineData("B", true, false, false)] + [InlineData("C", true, true, false)] + [InlineData("D", true, false, true)] + [InlineData("E", false, false, true)] + [InlineData("F", false, true, true)] + [InlineData("G", false, false, false)] + public void Behavior_ShouldExecuteAction_WhenCombinationIsPressed(string key, bool ctrl, bool shift, bool alt) + { + // Arrange + var diagram = new DiagramBase(); + var ksb = diagram.GetBehavior()!; + var executed = false; + + ksb.SetShortcut(key, ctrl, shift, alt, d => + { + executed = true; + return ValueTask.CompletedTask; + }); + + // Act + diagram.OnKeyDown(new KeyboardEventArgs(key, key, 0, ctrl, shift, alt)); + + // Assert + executed.Should().BeTrue(); + } + + [Fact] + public void Behavior_ShouldDoNothing_WhenRemoved() + { + // Arrange + var diagram = new DiagramBase(); + var ksb = diagram.GetBehavior()!; + diagram.UnregisterBehavior(); + var executed = false; + + ksb.SetShortcut("A", false, false, false, d => + { + executed = true; + return ValueTask.CompletedTask; + }); + + // Act + diagram.OnKeyDown(new KeyboardEventArgs("A", "A", 0, false, false, false)); + + // Assert + executed.Should().BeFalse(); + } + + [Fact] + public void SetShortcut_ShouldOverride() + { + // Arrange + var diagram = new DiagramBase(); + var ksb = diagram.GetBehavior()!; + var executed1 = false; + var executed2 = false; + + ksb.SetShortcut("A", false, false, false, d => + { + executed1 = true; + return ValueTask.CompletedTask; + }); + + ksb.SetShortcut("A", false, false, false, d => + { + executed2 = true; + return ValueTask.CompletedTask; + }); + + // Act + diagram.OnKeyDown(new KeyboardEventArgs("A", "A", 0, false, false, false)); + + // Assert + executed1.Should().BeFalse(); + executed2.Should().BeTrue(); + } + } +} From 61cc7a63cd1850e0313781cc606e4676362f8afe Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 6 Aug 2022 12:53:46 +0100 Subject: [PATCH 027/193] Update build.yml --- .github/workflows/build.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b2c606c0..7b36a41d8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,7 +8,7 @@ on: jobs: build: name: Build - runs-on: windows-latest + runs-on: ubuntu-latest steps: - name: Set up JDK 11 uses: actions/setup-java@v1 @@ -42,6 +42,6 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} shell: powershell run: | - .\.sonar\scanner\dotnet-sonarscanner begin /k:"Blazor-Diagrams_Blazor.Diagrams" /o:"blazor-diagrams" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" - - .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file + dotnet-sonarscanner begin /k:"Blazor-Diagrams_Blazor.Diagrams" /o:"blazor-diagrams" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" + dotnet build + dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file From 5ad22fc31e7fab7e457eff210507decaf8f5885f Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 6 Aug 2022 12:56:08 +0100 Subject: [PATCH 028/193] Update build.yml --- .github/workflows/build.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b36a41d8..521b35243 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,15 +32,12 @@ jobs: restore-keys: ${{ runner.os }}-sonar-scanner - name: Install SonarCloud scanner if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' - shell: powershell run: | - New-Item -Path .\.sonar\scanner -ItemType Directory - dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner + dotnet tool install --global dotnet-sonarscanner - name: Build and analyze env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - shell: powershell run: | dotnet-sonarscanner begin /k:"Blazor-Diagrams_Blazor.Diagrams" /o:"blazor-diagrams" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" dotnet build From ec3d977590230395ef74620de2006c04d604e246 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 6 Aug 2022 13:03:38 +0100 Subject: [PATCH 029/193] Update build.yml --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 521b35243..e19ffb7c5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -41,4 +41,5 @@ jobs: run: | dotnet-sonarscanner begin /k:"Blazor-Diagrams_Blazor.Diagrams" /o:"blazor-diagrams" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" dotnet build + dotnet test --no-build --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file From cafa1278b158b7e9a8b5d6e9ff84ff8580a5515b Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 6 Aug 2022 13:13:59 +0100 Subject: [PATCH 030/193] Update build.yml --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e19ffb7c5..da20c898a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -39,7 +39,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | - dotnet-sonarscanner begin /k:"Blazor-Diagrams_Blazor.Diagrams" /o:"blazor-diagrams" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" + dotnet-sonarscanner begin /k:"Blazor-Diagrams_Blazor.Diagrams" /o:"blazor-diagrams" /d:sonar.login="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.cs.opencover.reportsPaths="**\TestResults\*\*.xml" dotnet build dotnet test --no-build --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover dotnet-sonarscanner end /d:sonar.login="${{ secrets.SONAR_TOKEN }}" \ No newline at end of file From 31310033952c0ef3f2a35527bff95c0a5795351b Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 9 Aug 2022 22:06:53 +0100 Subject: [PATCH 031/193] Add Anchors functionality --- Blazor.Diagrams.sln | 4 +- .../Demos/CustomLink/ThickLinkWidget.razor | 18 ++- .../SharedDemo/Demos/Groups/Grouping.razor.cs | 8 +- .../Demos/Nodes/PortlessLinks.razor.cs | 13 +- samples/SharedDemo/_Imports.razor | 1 + .../Extensions/PointExtensions.cs | 15 --- .../LinksReconnectionAlgorithms.cs | 29 +++-- src/Blazor.Diagrams.Core/Anchors/Anchor.cs | 23 ++++ .../Anchors/ShapeIntersectionAnchor.cs | 68 +++++++++++ .../Anchors/SinglePortAnchor.cs | 46 +++++++ .../Behaviors/DragNewLinkBehavior.cs | 56 +++++---- src/Blazor.Diagrams.Core/DiagramBase.cs | 14 +-- src/Blazor.Diagrams.Core/DiagramsException.cs | 11 ++ .../Extensions/BaseLinkModelExtensions.cs | 114 ------------------ src/Blazor.Diagrams.Core/Layers/LinkLayer.cs | 79 ++++++++---- .../Models/Base/BaseLinkModel.cs | 78 ++++-------- src/Blazor.Diagrams.Core/Models/LinkModel.cs | 19 ++- src/Blazor.Diagrams.Core/Models/PortModel.cs | 11 +- .../PathGenerators/PathGenerators.Smooth.cs | 33 +++-- .../Routers/Routers.Orthogonal.cs | 27 +++-- .../Components/DiagramCanvas.razor.cs | 10 +- .../Components/LinkWidget.razor | 3 +- .../Components/LinkWidget.razor.cs | 86 +------------ .../Components/Renderers/GroupRenderer.cs | 1 - .../Blazor.Diagrams.Core.Tests.csproj | 4 + 25 files changed, 393 insertions(+), 378 deletions(-) delete mode 100644 src/Blazor.Diagrams.Algorithms/Extensions/PointExtensions.cs create mode 100644 src/Blazor.Diagrams.Core/Anchors/Anchor.cs create mode 100644 src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs create mode 100644 src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs create mode 100644 src/Blazor.Diagrams.Core/DiagramsException.cs delete mode 100644 src/Blazor.Diagrams.Core/Extensions/BaseLinkModelExtensions.cs diff --git a/Blazor.Diagrams.sln b/Blazor.Diagrams.sln index 76f6b479c..491396633 100644 --- a/Blazor.Diagrams.sln +++ b/Blazor.Diagrams.sln @@ -21,7 +21,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Diagrams.Algorithms" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{260DF53D-FF2B-4D1D-ACA7-431F6B62EE38}" ProjectSection(SolutionItems) = preProject + .github\workflows\build.yml = .github\workflows\build.yml CHANGELOG.md = CHANGELOG.md + .github\workflows\main.yml = .github\workflows\main.yml README.md = README.md EndProjectSection EndProject @@ -37,7 +39,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CustomNodesLinks", "docs\Cu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Layouts", "docs\Layouts\Layouts.csproj", "{78C85C89-B464-4083-8829-78BA52BB4780}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Blazor.Diagrams.Tests", "tests\Blazor.Diagrams.Tests\Blazor.Diagrams.Tests.csproj", "{ED3B0D8F-F29A-4C66-A167-C36824A76902}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Diagrams.Tests", "tests\Blazor.Diagrams.Tests\Blazor.Diagrams.Tests.csproj", "{ED3B0D8F-F29A-4C66-A167-C36824A76902}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/samples/SharedDemo/Demos/CustomLink/ThickLinkWidget.razor b/samples/SharedDemo/Demos/CustomLink/ThickLinkWidget.razor index e8902ec2a..2f79d579a 100644 --- a/samples/SharedDemo/Demos/CustomLink/ThickLinkWidget.razor +++ b/samples/SharedDemo/Demos/CustomLink/ThickLinkWidget.razor @@ -3,9 +3,19 @@ public LinkModel Link { get; set; } } - \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs index 44fb33a8b..be70a2e8e 100644 --- a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs @@ -17,9 +17,15 @@ protected override void OnInitialized() var node1 = NewNode(50, 50); var node2 = NewNode(250, 250); var node3 = NewNode(500, 100); - diagram.Nodes.Add(new[] { node1, node2, node3 }); + var node4 = NewNode(500, 250); + diagram.Nodes.Add(new[] { node1, node2, node3, node4 }); diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + diagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + + var group1 = diagram.Group(node1, node2); + diagram.Group(group1, node3); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs index 8d37a7018..0673edd04 100644 --- a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs @@ -1,6 +1,7 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams; +using Blazor.Diagrams.Core.Anchors; namespace SharedDemo.Demos.Nodes { @@ -26,12 +27,22 @@ private void InitializeDiagram() var node1 = new NodeModel(new Point(80, 80), shape: Shapes.Rectangle); var node2 = new RoundedNode(new Point(280, 150), shape: Shapes.Circle); + var node3 = new NodeModel(new Point(400, 300), shape: Shapes.Rectangle); + node3.AddPort(PortAlignment.Left); _diagram.Nodes.Add(node1); _diagram.Nodes.Add(node2); + _diagram.Nodes.Add(node3); _diagram.Links.Add(new LinkModel(node1, node2) { SourceMarker = LinkMarker.Arrow, - TargetMarker = LinkMarker.Arrow + TargetMarker = LinkMarker.Arrow, + Segmentable = true + }); + _diagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node2), new SinglePortAnchor(node3.GetPort(PortAlignment.Left))) + { + SourceMarker = LinkMarker.Arrow, + TargetMarker = LinkMarker.Arrow, + Segmentable = true }); } } diff --git a/samples/SharedDemo/_Imports.razor b/samples/SharedDemo/_Imports.razor index 2ac648706..41a9ddb92 100644 --- a/samples/SharedDemo/_Imports.razor +++ b/samples/SharedDemo/_Imports.razor @@ -6,6 +6,7 @@ @using Microsoft.AspNetCore.Components.Forms @using Microsoft.AspNetCore.Components.Routing @using Blazor.Diagrams.Core.Extensions; +@using Blazor.Diagrams.Core.Geometry; @using Blazor.Diagrams.Components.Renderers; @using Blazor.Diagrams.Components.Groups; @using SharedDemo.Components; \ No newline at end of file diff --git a/src/Blazor.Diagrams.Algorithms/Extensions/PointExtensions.cs b/src/Blazor.Diagrams.Algorithms/Extensions/PointExtensions.cs deleted file mode 100644 index 7471dbfa4..000000000 --- a/src/Blazor.Diagrams.Algorithms/Extensions/PointExtensions.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Blazor.Diagrams.Core.Geometry; -using System; - -namespace Blazor.Diagrams.Algorithms.Extensions -{ - public static class PointExtensions - { - public static double DistanceTo(this Point firstPoint, Point secondPoint) - { - var x = Math.Abs(firstPoint.X - secondPoint.X); - var y = Math.Abs(firstPoint.Y - secondPoint.Y); - return Math.Sqrt(x * x + y * y); - } - } -} diff --git a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs index f088fc939..8c57478b6 100644 --- a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs +++ b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs @@ -1,5 +1,5 @@ -using Blazor.Diagrams.Algorithms.Extensions; -using Blazor.Diagrams.Core; +using Blazor.Diagrams.Core; +using Blazor.Diagrams.Core.Anchors; using Blazor.Diagrams.Core.Models; using System.Collections.Generic; using System.Linq; @@ -15,16 +15,19 @@ public static void ReconnectLinksToClosestPorts(this DiagramBase diagram) foreach (var link in diagram.Links.ToArray()) { - if (link.TargetPort == null) + if (link.Target == null) continue; - var sourcePorts = link.SourcePort.Parent.Ports; - var targetPorts = link.TargetPort.Parent.Ports; + if (link.Source is not SinglePortAnchor spa1 || link.Target is not SinglePortAnchor spa2) + continue; + + var sourcePorts = spa1.Node.Ports; + var targetPorts = spa2.Node.Ports; // Find the ports with minimal distance var minDistance = double.MaxValue; - var minSourcePort = link.SourcePort; - var minTargetPort = link.TargetPort; + var minSourcePort = spa1.Port; + var minTargetPort = spa2.Port; foreach (var sourcePort in sourcePorts) { foreach (var targetPort in targetPorts) @@ -40,18 +43,18 @@ public static void ReconnectLinksToClosestPorts(this DiagramBase diagram) } // Reconnect - if (link.SourcePort != minSourcePort) + if (spa1.Port != minSourcePort) { - portsToRefresh.Add(link.SourcePort); + portsToRefresh.Add(spa1.Port); portsToRefresh.Add(minSourcePort); - link.SetSourcePort(minSourcePort); + link.SetSource(new SinglePortAnchor(minSourcePort)); } - if (link.TargetPort != minTargetPort) + if (spa2.Port != minTargetPort) { - portsToRefresh.Add(link.TargetPort); + portsToRefresh.Add(spa2.Port); portsToRefresh.Add(minTargetPort); - link.SetTargetPort(minTargetPort); + link.SetTarget(new SinglePortAnchor(minTargetPort)); } } diff --git a/src/Blazor.Diagrams.Core/Anchors/Anchor.cs b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs new file mode 100644 index 000000000..035c9dcbf --- /dev/null +++ b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs @@ -0,0 +1,23 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using System; + +namespace Blazor.Diagrams.Core.Anchors +{ + public abstract class Anchor + { + public Anchor(NodeModel node, Point? offset = null) + { + Node = node; + Offset = offset ?? Point.Zero; + } + + public NodeModel Node { get; } + public Point Offset { get; } + + public abstract Point? GetPosition(BaseLinkModel link, Point[] route); + + public Point? GetPosition(BaseLinkModel link) => GetPosition(link, Array.Empty()); + } +} diff --git a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs new file mode 100644 index 000000000..769ea19c7 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs @@ -0,0 +1,68 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using System.Collections.Generic; + +namespace Blazor.Diagrams.Core.Anchors +{ + public class ShapeIntersectionAnchor : Anchor + { + public ShapeIntersectionAnchor(NodeModel node, Point? offset = null) : base(node, offset) { } + + public override Point? GetPosition(BaseLinkModel link, Point[] route) + { + if (Node.Size == null) + return null; + + var isTarget = link.Target == this; + var nodeCenter = Node.GetBounds()!.Center; + Point? pt; + if (route.Length > 0) + { + pt = route[isTarget ? ^1 : 0]; + } + else + { + pt = GetOtherPosition(link, isTarget); + } + + if (pt is null) return null; + + var line = new Line(pt, nodeCenter); + var intersections = Node.GetShape().GetIntersectionsWithLine(line); + return GetClosestPointTo(intersections, pt); + } + + private static Point? GetOtherPosition(BaseLinkModel link, bool isTarget) + { + if (!isTarget && link.Target == null) + return link.OnGoingPosition; + + var anchor = isTarget ? link.Source : link.Target!; + return anchor switch + { + SinglePortAnchor spa => spa.Port.MiddlePosition, + ShapeIntersectionAnchor sia => sia.Node.GetBounds()?.Center ?? null, + _ => throw new DiagramsException($"Unhandled Anchor type {anchor.GetType().Name} when trying to find intersection") + }; + } + + private static Point GetClosestPointTo(IEnumerable points, Point point) + { + var minDist = double.MaxValue; + Point minPoint = null; + + foreach (var pt in points) + { + var dist = pt.DistanceTo(point); + if (dist < minDist) + { + minDist = dist; + minPoint = pt; + } + } + + return minPoint; + } + } +} diff --git a/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs new file mode 100644 index 000000000..35525892c --- /dev/null +++ b/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs @@ -0,0 +1,46 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Anchors +{ + public class SinglePortAnchor : Anchor + { + public SinglePortAnchor(PortModel port, Point? offset = null) : base(port.Parent, offset) + { + Port = port; + } + + public PortModel Port { get; } + + public override Point? GetPosition(BaseLinkModel link, Point[] route) + { + if (!Port.Initialized) + return null; + + if ((link.Source == this && link.SourceMarker is null) || (link.Target == this && link.TargetMarker is null)) + return Port.MiddlePosition; + + var pt = Port.Position; + switch (Port.Alignment) + { + case PortAlignment.Top: + return new Point(pt.X + Port.Size.Width / 2, pt.Y); + case PortAlignment.TopRight: + return new Point(pt.X + Port.Size.Width, pt.Y); + case PortAlignment.Right: + return new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height / 2); + case PortAlignment.BottomRight: + return new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height); + case PortAlignment.Bottom: + return new Point(pt.X + Port.Size.Width / 2, pt.Y + Port.Size.Height); + case PortAlignment.BottomLeft: + return new Point(pt.X, pt.Y + Port.Size.Height); + case PortAlignment.Left: + return new Point(pt.X, pt.Y + Port.Size.Height / 2); + default: + return pt; + } + } + } +} diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 49e3e00d2..614fceffe 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -3,6 +3,7 @@ using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Events; using System.Linq; +using Blazor.Diagrams.Core.Anchors; namespace Blazor.Diagrams.Core.Behaviors { @@ -22,15 +23,15 @@ public DragNewLinkBehavior(DiagramBase diagram) : base(diagram) Diagram.TouchEnd += OnTouchEnd; } - private void OnTouchStart(Model model, TouchEventArgs e) + private void OnTouchStart(Model? model, TouchEventArgs e) => Start(model, e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY); - private void OnTouchMove(Model model, TouchEventArgs e) + private void OnTouchMove(Model? model, TouchEventArgs e) => Move(model, e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY); - private void OnTouchEnd(Model model, TouchEventArgs e) => End(model); + private void OnTouchEnd(Model? model, TouchEventArgs e) => End(model); - private void OnMouseDown(Model model, MouseEventArgs e) + private void OnMouseDown(Model? model, MouseEventArgs e) { if (e.Button != (int)MouseEventButton.Left) return; @@ -38,42 +39,48 @@ private void OnMouseDown(Model model, MouseEventArgs e) Start(model, e.ClientX, e.ClientY); } - private void OnMouseMove(Model model, MouseEventArgs e) => Move(model, e.ClientX, e.ClientY); + private void OnMouseMove(Model? model, MouseEventArgs e) => Move(model, e.ClientX, e.ClientY); - private void OnMouseUp(Model model, MouseEventArgs e) => End(model); + private void OnMouseUp(Model? model, MouseEventArgs e) => End(model); - private void Start(Model model, double clientX, double clientY) + private void Start(Model? model, double clientX, double clientY) { - if (!(model is PortModel port) || port.Locked) + if (model is PortModel port) + { + if (port.Locked) return; + _ongoingLink = Diagram.Options.Links.Factory(Diagram, port); + _ongoingLink.OnGoingPosition = _ongoingLink.Source.GetPosition(_ongoingLink); + } + else + { return; + } _initialX = clientX; _initialY = clientY; - _ongoingLink = Diagram.Options.Links.Factory(Diagram, port); - _ongoingLink.OnGoingPosition = new Point(port.Position.X + port.Size.Width / 2, - port.Position.Y + port.Size.Height / 2); Diagram.Links.Add(_ongoingLink); } - private void Move(Model model, double clientX, double clientY) + private void Move(Model? model, double clientX, double clientY) { if (_ongoingLink == null || model != null) return; var deltaX = (clientX - _initialX) / Diagram.Zoom; var deltaY = (clientY - _initialY) / Diagram.Zoom; - var sX = _ongoingLink.SourcePort!.Position.X + _ongoingLink.SourcePort.Size.Width / 2; - var sY = _ongoingLink.SourcePort.Position.Y + _ongoingLink.SourcePort.Size.Height / 2; + var sourcePosition = _ongoingLink.Source.GetPosition(_ongoingLink)!; // Port should be initialized already, so no null here + var sX = sourcePosition.X; + var sY = sourcePosition.Y; _ongoingLink.OnGoingPosition = new Point(sX + deltaX, sY + deltaY); if (Diagram.Options.Links.EnableSnapping) { var nearPort = FindNearPortToAttachTo(); - if (nearPort != null || _ongoingLink.TargetPort != null) + if (nearPort != null || _ongoingLink.Target != null) { - var oldPort = _ongoingLink.TargetPort; - _ongoingLink.SetTargetPort(nearPort); + var oldPort = (_ongoingLink.Target as SinglePortAnchor)?.Port; // Assumption for now + _ongoingLink.SetTarget(nearPort is null ? null : new SinglePortAnchor(nearPort)); oldPort?.Refresh(); nearPort?.Refresh(); } @@ -82,7 +89,7 @@ private void Move(Model model, double clientX, double clientY) _ongoingLink.Refresh(); } - private void End(Model model) + private void End(Model? model) { if (_ongoingLink == null) return; @@ -93,7 +100,9 @@ private void End(Model model) return; } - if (!(model is PortModel port) || !_ongoingLink.SourcePort!.CanAttachTo(port)) + var sourcePort = (_ongoingLink.Source as SinglePortAnchor)!.Port; // Assumption for now + + if (model is not PortModel port || !sourcePort.CanAttachTo(port)) { Diagram.Links.Remove(_ongoingLink); _ongoingLink = null; @@ -101,20 +110,21 @@ private void End(Model model) } _ongoingLink.OnGoingPosition = null; - _ongoingLink.SetTargetPort(port); + _ongoingLink.SetTarget(new SinglePortAnchor(port)); _ongoingLink.Refresh(); port.Refresh(); - _ongoingLink.SourcePort.Parent.Group?.Refresh(); + sourcePort.Parent.Group?.Refresh(); port?.Parent.Group?.Refresh(); _ongoingLink = null; } private PortModel? FindNearPortToAttachTo() { + var sourcePort = (_ongoingLink!.Source as SinglePortAnchor)!.Port; // Assumption for now + foreach (var port in Diagram.Nodes.SelectMany(n => n.Ports)) { - if (_ongoingLink!.OnGoingPosition!.DistanceTo(port.Position) < Diagram.Options.Links.SnappingRadius && - _ongoingLink.SourcePort!.CanAttachTo(port)) + if (_ongoingLink!.OnGoingPosition!.DistanceTo(port.MiddlePosition) < Diagram.Options.Links.SnappingRadius && sourcePort.CanAttachTo(port)) return port; } diff --git a/src/Blazor.Diagrams.Core/DiagramBase.cs b/src/Blazor.Diagrams.Core/DiagramBase.cs index f356bc3e0..e5c41b1f7 100644 --- a/src/Blazor.Diagrams.Core/DiagramBase.cs +++ b/src/Blazor.Diagrams.Core/DiagramBase.cs @@ -100,8 +100,8 @@ public void Batch(Action action) /// The created group instance. public GroupModel Group(params NodeModel[] children) { - if (children.Any(n => n.Group != null)) - throw new InvalidOperationException("Cannot group nodes that already belong to another group"); + //if (children.Any(n => n.Group != null)) + // throw new InvalidOperationException("Cannot group nodes that already belong to another group"); var group = Options.Groups.Factory(this, children); AddGroup(group); @@ -114,11 +114,11 @@ public GroupModel Group(params NodeModel[] children) /// A group instance. public void AddGroup(GroupModel group) { - foreach (var child in group.Children) - { - if (child is NodeModel node && !Nodes.Contains(node)) - throw new Exception("One of the nodes isn't in the diagram. Make sure to add all the nodes before creating the group."); - } + //foreach (var child in group.Children) + //{ + // if (child is NodeModel node && !Nodes.Contains(node)) + // throw new Exception("One of the nodes isn't in the diagram. Make sure to add all the nodes before creating the group."); + //} _groups.Add(group); GroupAdded?.Invoke(group); diff --git a/src/Blazor.Diagrams.Core/DiagramsException.cs b/src/Blazor.Diagrams.Core/DiagramsException.cs new file mode 100644 index 000000000..ac16ec75b --- /dev/null +++ b/src/Blazor.Diagrams.Core/DiagramsException.cs @@ -0,0 +1,11 @@ +using System; + +namespace Blazor.Diagrams.Core +{ + public class DiagramsException : Exception + { + public DiagramsException(string? message) : base(message) + { + } + } +} diff --git a/src/Blazor.Diagrams.Core/Extensions/BaseLinkModelExtensions.cs b/src/Blazor.Diagrams.Core/Extensions/BaseLinkModelExtensions.cs deleted file mode 100644 index 4bd8351b7..000000000 --- a/src/Blazor.Diagrams.Core/Extensions/BaseLinkModelExtensions.cs +++ /dev/null @@ -1,114 +0,0 @@ -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; -using System; -using System.Globalization; - -namespace Blazor.Diagrams.Core.Extensions -{ - [Obsolete] - public static class BaseLinkModelExtensions - { - private const double _margin = 125; - - /// - /// If the link is attached, returns the same output as GetMiddleTargetX(). - /// Otherwise, returns the X value of link's ongoing position. - /// - /// The BaseLinkModel entity - public static double GetTargetX(this BaseLinkModel link) - { - if (!link.IsAttached) - return link.OnGoingPosition!.X; - - return link.GetMiddleTargetX(); - } - - /// - /// If the link is attached, returns the same output as GetMiddleTargetY(). - /// Otherwise, returns the Y value of link's ongoing position. - /// - /// The BaseLinkModel entity - public static double GetTargetY(this BaseLinkModel link) - { - if (!link.IsAttached) - return link.OnGoingPosition!.Y; - - return link.GetMiddleTargetY(); - } - - public static string GenerateCurvedPath(this BaseLinkModel link) - { - var sX = link.GetMiddleSourceX(); - var sY = link.GetMiddleSourceY(); - double tX, tY; - - if (link.IsAttached) - { - tX = link.GetMiddleTargetX(); - tY = link.GetMiddleTargetY(); - } - else - { - tX = link.OnGoingPosition!.X; - tY = link.OnGoingPosition.Y; - } - - var cX = (sX + tX) / 2; - var cY = (sY + tY) / 2; - - var curvePointA = GetCurvePoint(sX, sY, cX, cY, link.SourcePort.Alignment); - var curvePointB = GetCurvePoint(tX, tY, cX, cY, link.TargetPort?.Alignment); - return FormattableString.Invariant($"M {sX} {sY} C {curvePointA}, {curvePointB}, {tX} {tY}"); - } - - private static string GetCurvePoint(double pX, double pY, double cX, double cY, PortAlignment? alignment) - { - var margin = Math.Min(_margin, Math.Pow(Math.Pow(pX - cX, 2) + Math.Pow(pY - cY, 2), .5)); - return alignment switch - { - PortAlignment.Top => FormattableString.Invariant($"{pX} {Math.Min(pY - margin, cY)}"), - PortAlignment.Bottom => FormattableString.Invariant($"{pX} {Math.Max(pY + margin, cY)}"), - PortAlignment.TopRight => FormattableString.Invariant($"{Math.Max(pX + margin, cX)} {Math.Min(pY - margin, cY)}"), - PortAlignment.BottomRight => FormattableString.Invariant($"{Math.Max(pX + margin, cX)} {Math.Max(pY + margin, cY)}"), - PortAlignment.Right => FormattableString.Invariant($"{Math.Max(pX + margin, cX)} {pY}"), - PortAlignment.Left => FormattableString.Invariant($"{Math.Min(pX - margin, cX)} {pY}"), - PortAlignment.BottomLeft => FormattableString.Invariant($"{Math.Min(pX - margin, cX)} {Math.Max(pY + margin, cY)}"), - PortAlignment.TopLeft => FormattableString.Invariant($"{Math.Min(pX - margin, cX)} {Math.Min(pY - margin, cY)}"), - _ => FormattableString.Invariant($"{cX} {cY}"), - }; - } - - public static string CalculateAngleForTargetArrow(this BaseLinkModel link) - { - var sX = link.GetMiddleSourceX(); - var sY = link.GetMiddleSourceY(); - double tX, tY; - - if (link.IsAttached) - { - tX = link.GetMiddleTargetX(); - tY = link.GetMiddleTargetY(); - } - else - { - tX = link.OnGoingPosition!.X; - tY = link.OnGoingPosition.Y; - } - - var angle = 90 + Math.Atan2(tY - sY, tX - sX) * 180 / Math.PI; - return angle.ToString(CultureInfo.InvariantCulture); - } - - public static double GetMiddleSourceX(this BaseLinkModel link) - => link.SourcePort.Position.X + (link.SourcePort.Size.Width / 2); - - public static double GetMiddleSourceY(this BaseLinkModel link) - => link.SourcePort.Position.Y + (link.SourcePort.Size.Height / 2); - - public static double GetMiddleTargetX(this BaseLinkModel link) - => link.TargetPort!.Position.X + (link.TargetPort.Size.Width / 2); - - public static double GetMiddleTargetY(this BaseLinkModel link) - => link.TargetPort!.Position.Y + (link.TargetPort.Size.Height / 2); - } -} diff --git a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs index b483d7129..d719dd567 100644 --- a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Core.Layers { @@ -8,42 +9,70 @@ public LinkLayer(DiagramBase diagram) : base(diagram) { } protected override void OnItemAdded(BaseLinkModel link) { - if (!link.IsPortless) - { - link.SourcePort!.AddLink(link); - link.TargetPort?.AddLink(link); + HandleAnchor(link, link.Source, true); + if (link.Target != null) HandleAnchor(link, link.Target, true); - link.SourcePort.Refresh(); - link.TargetPort?.Refresh(); - } - else - { - link.SourceNode.AddLink(link); - link.TargetNode?.AddLink(link); - } + link.Source.Node.Group?.Refresh(); + link.Target?.Node.Group?.Refresh(); - link.SourceNode.Group?.Refresh(); - link.TargetNode?.Group?.Refresh(); + link.SourceChanged += OnLinkSourceChanged; + link.TargetChanged += OnLinkTargetChanged; } protected override void OnItemRemoved(BaseLinkModel link) { - if (!link.IsPortless) + HandleAnchor(link, link.Source, false); + if (link.Target != null) HandleAnchor(link, link.Target, false); + + link.Source.Node.Group?.Refresh(); + link.Target?.Node.Group?.Refresh(); + + link.SourceChanged -= OnLinkSourceChanged; + link.TargetChanged -= OnLinkTargetChanged; + } + + private void OnLinkSourceChanged(BaseLinkModel link, Anchor old, Anchor @new) + { + HandleAnchor(link, old, add: false); + HandleAnchor(link, @new, add: true); + } + + private void OnLinkTargetChanged(BaseLinkModel link, Anchor? old, Anchor? @new) + { + if (old != null) HandleAnchor(link, old, add: false); + if (@new != null) HandleAnchor(link, @new, add: true); + } + + private static void HandleAnchor(BaseLinkModel link, Anchor anchor, bool add) + { + if (anchor is SinglePortAnchor spa) { - link.SourcePort!.RemoveLink(link); - link.TargetPort?.RemoveLink(link); + if (add) + { + spa.Port.AddLink(link); + } + else + { + spa.Port.RemoveLink(link); + } - link.SourcePort.Refresh(); - link.TargetPort?.Refresh(); + spa.Port.Refresh(); + } + else if (anchor is ShapeIntersectionAnchor sia) + { + if (add) + { + sia.Node.AddLink(link); + } + else + { + sia.Node.RemoveLink(link); + } } else { - link.SourceNode.RemoveLink(link); - link.TargetNode?.RemoveLink(link); + throw new DiagramsException($"Unhandled Anchor type {anchor.GetType().Name}"); } - - link.SourceNode.Group?.Refresh(); - link.TargetNode?.Group?.Refresh(); } } } diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index 2a898a517..f2a98ee3a 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; using System; using System.Collections.Generic; @@ -6,49 +7,24 @@ namespace Blazor.Diagrams.Core.Models.Base { public abstract class BaseLinkModel : SelectableModel { - /// - /// An event that fires when the SourcePort changes. - /// - public event Action? SourcePortChanged; - /// - /// An event that fires when the TargetPort changes. - /// - public event Action? TargetPortChanged; + public event Action? SourceChanged; + public event Action? TargetChanged; - public BaseLinkModel(NodeModel sourceNode, NodeModel? targetNode) + protected BaseLinkModel(Anchor source, Anchor? target = null) { - SourceNode = sourceNode; - TargetNode = targetNode; + Source = source; + Target = target; } - public BaseLinkModel(string id, NodeModel sourceNode, NodeModel? targetNode) : base(id) + protected BaseLinkModel(string id, Anchor source, Anchor? target = null) : base(id) { - SourceNode = sourceNode; - TargetNode = targetNode; + Source = source; + Target = target; } - public BaseLinkModel(PortModel sourcePort, PortModel? targetPort = null) - { - SourcePort = sourcePort; - TargetPort = targetPort; - SourceNode = SourcePort.Parent; - TargetNode = targetPort?.Parent; - } - - public BaseLinkModel(string id, PortModel sourcePort, PortModel? targetPort = null) : base(id) - { - SourcePort = sourcePort; - TargetPort = targetPort; - SourceNode = SourcePort.Parent; - TargetNode = targetPort?.Parent; - } - - public NodeModel SourceNode { get; private set; } - public NodeModel? TargetNode { get; private set; } - public PortModel? SourcePort { get; private set; } - public PortModel? TargetPort { get; private set; } - public bool IsAttached => TargetNode != null || TargetPort != null; - public bool IsPortless => SourcePort == null; + public Anchor Source { get; private set; } + public Anchor? Target { get; private set; } + public bool IsAttached => Target != null; public Point? OnGoingPosition { get; set; } public Router? Router { get; set; } public PathGenerator? PathGenerator { get; set; } @@ -58,30 +34,26 @@ public BaseLinkModel(string id, PortModel sourcePort, PortModel? targetPort = nu public List Vertices { get; } = new List(); public List Labels { get; set; } = new List(); - public void SetSourcePort(PortModel port) + public void SetSource(Anchor anchor) { - if (SourcePort == port) + ArgumentNullException.ThrowIfNull(anchor, nameof(anchor)); + + if (Source == anchor) return; - var old = SourcePort; - SourcePort?.RemoveLink(this); - SourcePort = port; - SourcePort.AddLink(this); - SourceNode = SourcePort.Parent; - SourcePortChanged?.Invoke(this, old, SourcePort); + var old = Source; + Source = anchor; + SourceChanged?.Invoke(this, old, Source); } - public void SetTargetPort(PortModel? port) + public void SetTarget(Anchor? anchor) { - if (TargetPort == port) + if (Target == anchor) return; - var old = TargetPort; - TargetPort?.RemoveLink(this); - TargetPort = port; - TargetPort?.AddLink(this); - TargetNode = TargetPort?.Parent; - TargetPortChanged?.Invoke(this, old, TargetPort); + var old = Target; + Target = anchor; + TargetChanged?.Invoke(this, old, Target); } } } diff --git a/src/Blazor.Diagrams.Core/Models/LinkModel.cs b/src/Blazor.Diagrams.Core/Models/LinkModel.cs index d2b083735..9d134a72b 100644 --- a/src/Blazor.Diagrams.Core/Models/LinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/LinkModel.cs @@ -1,16 +1,25 @@ -using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Core.Models { public class LinkModel : BaseLinkModel { - public LinkModel(PortModel sourcePort, PortModel? targetPort = null) : base(sourcePort, targetPort) { } + public LinkModel(Anchor source, Anchor? target = null) : base(source, target) { } - public LinkModel(NodeModel sourceNode, NodeModel? targetNode) : base(sourceNode, targetNode) { } + public LinkModel(string id, Anchor source, Anchor? target = null) : base(id, source, target) { } - public LinkModel(string id, PortModel sourcePort, PortModel? targetPort = null) : base(id, sourcePort, targetPort) { } + public LinkModel(PortModel sourcePort, PortModel? targetPort = null) + : base(new SinglePortAnchor(sourcePort), targetPort is null ? null : new SinglePortAnchor(targetPort)) { } - public LinkModel(string id, NodeModel sourceNode, NodeModel? targetNode) : base(id, sourceNode, targetNode) { } + public LinkModel(NodeModel sourceNode, NodeModel? targetNode) + : base(new ShapeIntersectionAnchor(sourceNode), targetNode is null ? null : new ShapeIntersectionAnchor(targetNode)) { } + + public LinkModel(string id, PortModel sourcePort, PortModel? targetPort = null) + : base(id, new SinglePortAnchor(sourcePort), targetPort is null ? null : new SinglePortAnchor(targetPort)) { } + + public LinkModel(string id, NodeModel sourceNode, NodeModel? targetNode) + : base(id, new ShapeIntersectionAnchor(sourceNode), targetNode is null ? null : new ShapeIntersectionAnchor(targetNode)) { } public string? Color { get; set; } public string? SelectedColor { get; set; } diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs index d6ca55536..2f2f5759f 100644 --- a/src/Blazor.Diagrams.Core/Models/PortModel.cs +++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs @@ -1,13 +1,12 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; using System.Collections.Generic; -using System.Collections.ObjectModel; namespace Blazor.Diagrams.Core.Models { public class PortModel : Model { - private readonly List _links = new List(4); + private readonly List _links = new(4); public PortModel(NodeModel parent, PortAlignment alignment = PortAlignment.Bottom, Point? position = null, Size? size = null) @@ -30,9 +29,9 @@ public PortModel(string id, NodeModel parent, PortAlignment alignment = PortAlig public NodeModel Parent { get; } public PortAlignment Alignment { get; } public Point Position { get; set; } - public Point MiddlePosition => new Point(Position.X + Size.Width / 2, Position.Y + Size.Height / 2); + public Point MiddlePosition => new(Position.X + Size.Width / 2, Position.Y + Size.Height / 2); public Size Size { get; set; } - public ReadOnlyCollection Links => _links.AsReadOnly(); + public IReadOnlyList Links => _links; /// /// If set to false, a call to Refresh() will force the port to update its position/size using JS /// @@ -49,9 +48,9 @@ public void RefreshAll() public T GetParent() where T : NodeModel => (T)Parent; public virtual bool CanAttachTo(PortModel port) - => port != this && !port.Locked && Parent != port.Parent; + => port != this && !port.Locked && Parent != port.Parent; // Todo: remove in order to support same node links - public Rectangle GetBounds() => new Rectangle(Position, Size); + public Rectangle GetBounds() => new(Position, Size); internal void AddLink(BaseLinkModel link) => _links.Add(link); diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs index 004cf7225..00faa03f8 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using System; @@ -65,26 +66,36 @@ private static PathGeneratorResult CurveThroughPoints(Point[] route, BaseLinkMod private static Point[] GetRouteWithCurvePoints(BaseLinkModel link, Point[] route) { - if (link.IsPortless) + var cX = (route[0].X + route[1].X) / 2; + var cY = (route[0].Y + route[1].Y) / 2; + var curvePointA = GetCurvePoint(route, link.Source, route[0].X, route[0].Y, cX, cY, first: true); + var curvePointB = GetCurvePoint(route, link.Target, route[1].X, route[1].Y, cX, cY, first: false); + return new[] { route[0], curvePointA, curvePointB, route[1] }; + } + + private static Point GetCurvePoint(Point[] route, Anchor? anchor, double pX, double pY, double cX, double cY, bool first) + { + if (anchor is null) + return new Point(cX, cY); + + if (anchor is SinglePortAnchor spa) + { + return GetCurvePoint(pX, pY, cX, cY, spa.Port.Alignment); + } + else if (anchor is ShapeIntersectionAnchor) { if (Math.Abs(route[0].X - route[1].X) >= Math.Abs(route[0].Y - route[1].Y)) { - var cX = (route[0].X + route[1].X) / 2; - return new[] { route[0], new Point(cX, route[0].Y), new Point(cX, route[1].Y), route[1] }; + return first ? new Point(cX, route[0].Y) : new Point(cX, route[1].Y); } else { - var cY = (route[0].Y + route[1].Y) / 2; - return new[] { route[0], new Point(route[0].X, cY), new Point(route[1].X, cY), route[1] }; + return first ? new Point(route[0].X, cY) : new Point(route[1].X, cY); } } else { - var cX = (route[0].X + route[1].X) / 2; - var cY = (route[0].Y + route[1].Y) / 2; - var curvePointA = GetCurvePoint(route[0].X, route[0].Y, cX, cY, link.SourcePort?.Alignment); - var curvePointB = GetCurvePoint(route[1].X, route[1].Y, cX, cY, link.TargetPort?.Alignment); - return new[] { route[0], curvePointA, curvePointB, route[1] }; + throw new DiagramsException($"Unhandled Anchor type {anchor.GetType().Name} when trying to find curve point"); } } diff --git a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs index 0aff4a75d..23030b966 100644 --- a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs +++ b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core.Extensions; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; @@ -15,25 +16,33 @@ public static partial class Routers { public static Point[] Orthogonal(DiagramBase _, BaseLinkModel link) { - if (link.IsPortless) + if (link.Source is not SinglePortAnchor spa1) throw new Exception("Orthogonal router doesn't work with portless links yet"); - if (link.TargetPort == null || link.SourcePort!.Parent.Size == null || link.TargetPort.Parent.Size == null) + if (link.Target is not null && link.Target is not SinglePortAnchor) + throw new Exception("Orthogonal router doesn't work with portless links yet"); + + var sourcePort = spa1.Port; + var targetAnchor = (link.Target as SinglePortAnchor); + + if (targetAnchor == null || sourcePort.Parent.Size == null || targetAnchor.Port.Parent.Size == null) return Normal(_, link); + var targetPort = targetAnchor.Port; + var shapeMargin = 10; var globalBoundsMargin = 50; var spots = new List(); var verticals = new List(); var horizontals = new List(); - var sideA = link.SourcePort.Alignment; + var sideA = sourcePort.Alignment; var sideAVertical = IsVerticalSide(sideA); - var sideB = link.TargetPort.Alignment; + var sideB = targetPort.Alignment; var sideBVertical = IsVerticalSide(sideB); - var originA = GetPortPositionBasedOnAlignment(link.SourcePort); - var originB = GetPortPositionBasedOnAlignment(link.TargetPort); - var shapeA = link.SourcePort.Parent.GetBounds(includePorts: true)!; - var shapeB = link.TargetPort.Parent.GetBounds(includePorts: true)!; + var originA = GetPortPositionBasedOnAlignment(sourcePort); + var originB = GetPortPositionBasedOnAlignment(targetPort); + var shapeA = sourcePort.Parent.GetBounds(includePorts: true)!; + var shapeB = targetPort.Parent.GetBounds(includePorts: true)!; var inflatedA = shapeA.Inflate(shapeMargin, shapeMargin); var inflatedB = shapeB.Inflate(shapeMargin, shapeMargin); diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index 3c2c98d6a..143b1c459 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -11,19 +11,19 @@ namespace Blazor.Diagrams.Components public partial class DiagramCanvas : IDisposable { [CascadingParameter] - public Diagram Diagram { get; set; } + public Diagram Diagram { get; set; } = null!; [Parameter] - public RenderFragment Widgets { get; set; } + public RenderFragment? Widgets { get; set; } [Parameter] - public string Class { get; set; } + public string? Class { get; set; } [Inject] - public IJSRuntime JSRuntime { get; set; } + public IJSRuntime JSRuntime { get; set; } = null!; protected ElementReference elementReference; - private DotNetObjectReference _reference; + private DotNetObjectReference? _reference; private bool _shouldRender; private string LayerStyle diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor b/src/Blazor.Diagrams/Components/LinkWidget.razor index 808771b76..d5dc90653 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor @@ -4,7 +4,8 @@ var router = Link.Router ?? Diagram.Options.Links.DefaultRouter; var pathGenerator = Link.PathGenerator ?? Diagram.Options.Links.DefaultPathGenerator; var route = router(Diagram, Link); - (var source, var target) = FindConnectionPoints(route); + var source = Link.Source.GetPosition(Link, route); + var target = Link.Target is null ? Link.OnGoingPosition : Link.Target.GetPosition(Link, route); if (source == null || target == null) return; diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs index 5fe781633..f26ace83a 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs @@ -1,20 +1,18 @@ using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; -using Blazor.Diagrams.Core.Geometry; -using System.Collections.Generic; using Blazor.Diagrams.Extensions; +using Blazor.Diagrams.Core.Geometry; namespace Blazor.Diagrams.Components { public partial class LinkWidget { [CascadingParameter] - public Diagram Diagram { get; set; } + public Diagram Diagram { get; set; } = null!; [Parameter] - public LinkModel Link { get; set; } + public LinkModel Link { get; set; } = null!; private void OnMouseDown(MouseEventArgs e, int index) { @@ -41,83 +39,5 @@ private LinkVertexModel CreateVertex(double clientX, double clientY, int index) Link.Vertices.Insert(index, vertex); return vertex; } - - private (Point source, Point target) FindConnectionPoints(Point[] route) - { - if (Link.SourcePort == null) // Portless - { - if (Link.SourceNode.Size == null || Link.TargetNode?.Size == null) - return (null, null); - - var sourceCenter = Link.SourceNode.GetBounds().Center; - var targetCenter = Link.TargetNode?.GetBounds().Center ?? Link.OnGoingPosition; - var firstPt = route.Length > 0 ? route[0] : targetCenter; - var secondPt = route.Length > 0 ? route[0] : sourceCenter; - var sourceLine = new Line(firstPt, sourceCenter); - var targetLine = new Line(secondPt, targetCenter); - var sourceIntersections = Link.SourceNode.GetShape().GetIntersectionsWithLine(sourceLine); - var targetIntersections = Link.TargetNode.GetShape().GetIntersectionsWithLine(targetLine); - var sourceIntersection = GetClosestPointTo(sourceIntersections, firstPt); - var targetIntersection = GetClosestPointTo(targetIntersections, secondPt); - return (sourceIntersection ?? sourceCenter, targetIntersection ?? targetCenter); - } - else - { - if (!Link.SourcePort.Initialized || Link.TargetPort?.Initialized == false) - return (null, null); - - var source = GetPortPositionBasedOnAlignment(Link.SourcePort, Link.SourceMarker); - var target = GetPortPositionBasedOnAlignment(Link.TargetPort, Link.TargetMarker); - return (source, target ?? Link.OnGoingPosition); - } - } - - private Point GetPortPositionBasedOnAlignment(PortModel port, LinkMarker marker) - { - if (port == null) - return null; - - if (marker == null) - return port.MiddlePosition; - - var pt = port.Position; - switch (port.Alignment) - { - case PortAlignment.Top: - return new Point(pt.X + port.Size.Width / 2, pt.Y); - case PortAlignment.TopRight: - return new Point(pt.X + port.Size.Width, pt.Y); - case PortAlignment.Right: - return new Point(pt.X + port.Size.Width, pt.Y + port.Size.Height / 2); - case PortAlignment.BottomRight: - return new Point(pt.X + port.Size.Width, pt.Y + port.Size.Height); - case PortAlignment.Bottom: - return new Point(pt.X + port.Size.Width / 2, pt.Y + port.Size.Height); - case PortAlignment.BottomLeft: - return new Point(pt.X, pt.Y + port.Size.Height); - case PortAlignment.Left: - return new Point(pt.X, pt.Y + port.Size.Height / 2); - default: - return pt; - } - } - - private Point GetClosestPointTo(IEnumerable points, Point point) - { - var minDist = double.MaxValue; - Point minPoint = null; - - foreach (var pt in points) - { - var dist = pt.DistanceTo(point); - if (dist < minDist) - { - minDist = dist; - minPoint = pt; - } - } - - return minPoint; - } } } diff --git a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs index c6fc6166f..91f7c51d3 100644 --- a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs @@ -1,5 +1,4 @@ using Blazor.Diagrams.Components.Groups; -using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; diff --git a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj index 8d8622a35..251ad7c14 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj +++ b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj @@ -24,4 +24,8 @@
+ + + +
From 5506ad446dfabeb99664a218001059efdbf6550a Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 10 Aug 2022 00:12:40 +0100 Subject: [PATCH 032/193] Fix tests --- docs/Layouts/Pages/Index.razor | 4 +- .../Models/Base/BaseLinkModelTests.cs | 62 +++++++------------ 2 files changed, 24 insertions(+), 42 deletions(-) diff --git a/docs/Layouts/Pages/Index.razor b/docs/Layouts/Pages/Index.razor index 189efd5d8..9b9667787 100644 --- a/docs/Layouts/Pages/Index.razor +++ b/docs/Layouts/Pages/Index.razor @@ -91,8 +91,8 @@ or it will not be rendered. var edges = _diagram.Links.OfType() .Select(lm => { - var source = nodes.Single(dn => dn.Id == lm.SourceNode.Id); - var target = nodes.Single(dn => dn.Id == lm?.TargetNode?.Id); + var source = nodes.Single(dn => dn.Id == lm.Source.Node.Id); + var target = nodes.Single(dn => dn.Id == lm?.Target?.Node?.Id); return new QG.Edge(source, target); }) .ToList(); diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs index eedfe8d7b..b3879355c 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using FluentAssertions; using Xunit; @@ -8,19 +9,19 @@ namespace Blazor.Diagrams.Core.Tests.Models.Base public class BaseLinkModelTests { [Fact] - public void SetSourcePort_ShouldChangePropertiesAndTriggerEvent() + public void SetSource_ShouldChangePropertiesAndTriggerEvent() { // Arrange - var link = new TestLink(sourcePort: new PortModel(null), targetPort: null); + var link = new LinkModel(sourcePort: new PortModel(null), targetPort: null); var parent = new NodeModel(); - var sp = new PortModel(parent); + var sp = new SinglePortAnchor(new PortModel(parent)); var eventsTriggered = 0; - PortModel oldSp = null; - PortModel newSp = null; - BaseLinkModel linkInstance = null; + Anchor? oldSp = null; + Anchor? newSp = null; + BaseLinkModel? linkInstance = null; // Act - link.SourcePortChanged += (l, o, n) => + link.SourceChanged += (l, o, n) => { eventsTriggered++; linkInstance = l; @@ -28,31 +29,31 @@ public void SetSourcePort_ShouldChangePropertiesAndTriggerEvent() newSp = n; }; - link.SetSourcePort(sp); + link.SetSource(sp); // Assert eventsTriggered.Should().Be(1); - link.SourcePort.Should().BeSameAs(sp); + link.Source.Should().BeSameAs(sp); oldSp.Should().NotBeNull(); newSp.Should().BeSameAs(sp); linkInstance.Should().BeSameAs(link); - link.SourceNode.Should().BeSameAs(parent); + link.Source.Node.Should().BeSameAs(parent); } [Fact] - public void SetTargetPort_ShouldChangePropertiesAndTriggerEvent() + public void SetTarget_ShouldChangePropertiesAndTriggerEvent() { // Arrange - var link = new TestLink(sourcePort: new PortModel(null), targetPort: null); + var link = new LinkModel(sourcePort: new PortModel(null), targetPort: null); var parent = new NodeModel(); - var tp = new PortModel(parent); + var tp = new SinglePortAnchor(new PortModel(parent)); var eventsTriggered = 0; - PortModel oldTp = null; - PortModel newTp = null; - BaseLinkModel linkInstance = null; + Anchor? oldTp = null; + Anchor? newTp = null; + BaseLinkModel? linkInstance = null; // Act - link.TargetPortChanged += (l, o, n) => + link.TargetChanged += (l, o, n) => { eventsTriggered++; linkInstance = l; @@ -60,34 +61,15 @@ public void SetTargetPort_ShouldChangePropertiesAndTriggerEvent() newTp = n; }; - link.SetTargetPort(tp); + link.SetTarget(tp); // Assert eventsTriggered.Should().Be(1); - link.TargetPort.Should().BeSameAs(tp); + link.Target.Should().BeSameAs(tp); oldTp.Should().BeNull(); newTp.Should().BeSameAs(tp); linkInstance.Should().BeSameAs(link); - link.TargetNode.Should().BeSameAs(parent); - } - - private class TestLink : BaseLinkModel - { - public TestLink(NodeModel sourceNode, NodeModel targetNode) : base(sourceNode, targetNode) - { - } - - public TestLink(PortModel sourcePort, PortModel targetPort = null) : base(sourcePort, targetPort) - { - } - - public TestLink(string id, NodeModel sourceNode, NodeModel targetNode) : base(id, sourceNode, targetNode) - { - } - - public TestLink(string id, PortModel sourcePort, PortModel targetPort = null) : base(id, sourcePort, targetPort) - { - } + link.Target!.Node.Should().BeSameAs(parent); } } } From b862549183bb5cd817e6cba48d8ddd08fc3506e1 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 10 Aug 2022 17:37:43 +0100 Subject: [PATCH 033/193] Remove unnecessary port refreshes when dragging link ends/snapping --- src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs | 4 ---- src/Blazor.Diagrams.Core/DiagramOptions.cs | 2 +- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 614fceffe..5d76b7506 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -79,10 +79,7 @@ private void Move(Model? model, double clientX, double clientY) var nearPort = FindNearPortToAttachTo(); if (nearPort != null || _ongoingLink.Target != null) { - var oldPort = (_ongoingLink.Target as SinglePortAnchor)?.Port; // Assumption for now _ongoingLink.SetTarget(nearPort is null ? null : new SinglePortAnchor(nearPort)); - oldPort?.Refresh(); - nearPort?.Refresh(); } } @@ -112,7 +109,6 @@ private void End(Model? model) _ongoingLink.OnGoingPosition = null; _ongoingLink.SetTarget(new SinglePortAnchor(port)); _ongoingLink.Refresh(); - port.Refresh(); sourcePort.Parent.Group?.Refresh(); port?.Parent.Group?.Refresh(); _ongoingLink = null; diff --git a/src/Blazor.Diagrams.Core/DiagramOptions.cs b/src/Blazor.Diagrams.Core/DiagramOptions.cs index 635bb5d07..56b3b0d37 100644 --- a/src/Blazor.Diagrams.Core/DiagramOptions.cs +++ b/src/Blazor.Diagrams.Core/DiagramOptions.cs @@ -38,7 +38,7 @@ public class DiagramLinkOptions [Description("Default PathGenerator for links")] public PathGenerator DefaultPathGenerator { get; set; } = PathGenerators.Smooth; [Description("Whether to enable link snapping")] - public bool EnableSnapping { get; set; } + public bool EnableSnapping { get; set; } = false; [Description("Link snapping radius")] public double SnappingRadius { get; set; } = 50; [Description("Link model factory")] From 3e808b493ab42cd8d8507b166793f984e6a4817d Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 10 Aug 2022 17:37:53 +0100 Subject: [PATCH 034/193] Add unit tests for DragNewLinkBehavior --- .../Anchors/ShapeIntersectionAnchor.cs | 4 +- .../Behaviors/DragNewLinkBehaviorTests.cs | 317 ++++++++++++++++++ 2 files changed, 319 insertions(+), 2 deletions(-) create mode 100644 tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs diff --git a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs index 769ea19c7..27bcab9d8 100644 --- a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs @@ -47,10 +47,10 @@ public ShapeIntersectionAnchor(NodeModel node, Point? offset = null) : base(node }; } - private static Point GetClosestPointTo(IEnumerable points, Point point) + private static Point? GetClosestPointTo(IEnumerable points, Point point) { var minDist = double.MaxValue; - Point minPoint = null; + Point? minPoint = null; foreach (var pt in points) { diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs new file mode 100644 index 000000000..1f933c3ac --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -0,0 +1,317 @@ +using Blazor.Diagrams.Core.Anchors; +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 +{ + public class DragNewLinkBehaviorTests + { + [Fact] + public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnPort() + { + // Arrange + var diagram = new DiagramBase(); + 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.OnMouseDown(port, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + + // Assert + var link = diagram.Links.Single(); + var source = link.Source as SinglePortAnchor; + source.Should().NotBeNull(); + link.Target.Should().BeNull(); + source!.Port.Should().BeSameAs(port); + link.OnGoingPosition.Should().NotBeNull(); + var sourcePosition = source.GetPosition(link)!; + link.OnGoingPosition!.X.Should().Be(sourcePosition.X); + link.OnGoingPosition.Y.Should().Be(sourcePosition.Y); + } + + [Fact] + public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() + { + // Arrange + var diagram = new DiagramBase(); + var factoryCalled = false; + diagram.Options.Links.Factory = (d, sp) => + { + factoryCalled = true; + return new LinkModel(sp); + }; + 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.OnMouseDown(port, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + + // Assert + factoryCalled.Should().BeTrue(); + var link = diagram.Links.Single(); + var source = link.Source as SinglePortAnchor; + source.Should().NotBeNull(); + link.Target.Should().BeNull(); + source!.Port.Should().BeSameAs(port); + link.OnGoingPosition.Should().NotBeNull(); + var sourcePosition = source.GetPosition(link)!; + link.OnGoingPosition!.X.Should().Be(sourcePosition.X); + link.OnGoingPosition.Y.Should().Be(sourcePosition.Y); + } + + [Fact] + public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() + { + // Arrange + var diagram = new DiagramBase(); + 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.OnMouseDown(port, new MouseEventArgs(100, 100, 0, 0, false, false, false)); + var link = diagram.Links.Single(); + link.Changed += () => linkRefreshed = true; + diagram.OnMouseMove(null, new MouseEventArgs(150, 150, 0, 0, false, false, false)); + + // Assert + var source = link.Source as SinglePortAnchor; + var sourcePosition = source!.GetPosition(link)!; + link.OnGoingPosition!.X.Should().Be(sourcePosition.X + 50); + link.OnGoingPosition.Y.Should().Be(sourcePosition.Y + 50); + linkRefreshed.Should().BeTrue(); + } + + [Fact] + public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoomIsChanged() + { + // Arrange + var diagram = new DiagramBase(); + 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.OnMouseDown(port, new MouseEventArgs(100, 100, 0, 0, false, false, false)); + var link = diagram.Links.Single(); + link.Changed += () => linkRefreshed = true; + diagram.OnMouseMove(null, new MouseEventArgs(160, 160, 0, 0, false, false, false)); + + // Assert + var source = link.Source as SinglePortAnchor; + var sourcePosition = source!.GetPosition(link)!; + link.OnGoingPosition!.X.Should().Be(sourcePosition.X + 40); + link.OnGoingPosition.Y.Should().Be(sourcePosition.Y + 40); + linkRefreshed.Should().BeTrue(); + } + + [Fact] + public void Behavior_ShouldSnapToClosestPortAndRefreshPort_WhenSnappingIsEnabledAndPortIsInRadius() + { + // Arrange + var diagram = new DiagramBase(); + 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.OnMouseDown(port1, new MouseEventArgs(100, 100, 0, 0, false, false, false)); + diagram.OnMouseMove(null, new MouseEventArgs(105, 105, 0, 0, false, false, false)); + + // 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 DiagramBase(); + 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.OnMouseDown(port1, new MouseEventArgs(100, 100, 0, 0, false, false, false)); + diagram.OnMouseMove(null, new MouseEventArgs(105, 105, 0, 0, false, false, false)); + + // Assert + var link = diagram.Links.Single(); + link.Target.Should().BeNull(); + } + + [Fact] + public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNotInRadiusAnymore() + { + // Arrange + var diagram = new DiagramBase(); + 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.OnMouseDown(port1, new MouseEventArgs(100, 100, 0, 0, false, false, false)); + diagram.OnMouseMove(null, new MouseEventArgs(105, 105, 0, 0, false, false, false)); // Move towards the other port + diagram.OnMouseMove(null, new MouseEventArgs(100, 100, 0, 0, false, false, false)); // 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_WhenMouseUpOnCanvas() + { + // Arrange + var diagram = new DiagramBase(); + 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.OnMouseDown(port, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.OnMouseUp(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + + // Assert + diagram.Links.Should().BeEmpty(); + } + + [Fact] + public void Behavior_ShouldRemoveLink_WhenMouseUpOnSamePort() + { + // Arrange + var diagram = new DiagramBase(); + 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.OnMouseDown(port, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.OnMouseUp(port, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + + // Assert + diagram.Links.Should().BeEmpty(); + } + + [Fact] + public void Behavior_ShouldSetTarget_WhenMouseUp() + { + // Arrange + var diagram = new DiagramBase(); + 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.OnMouseDown(port1, new MouseEventArgs(100, 100, 0, 0, false, false, false)); + diagram.OnMouseUp(port2, new MouseEventArgs(105, 105, 0, 0, false, false, false)); + + // Assert + var link = diagram.Links.Single(); + link.OnGoingPosition.Should().BeNull(); + var target = link.Target as SinglePortAnchor; + target.Should().NotBeNull(); + target!.Port.Should().BeSameAs(port2); + port2Refreshes.Should().Be(1); + } + } +} From 767205bc2a354b711ce526ad227dfca43d066129 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 11 Aug 2022 17:46:36 +0100 Subject: [PATCH 035/193] Rename AllLinks to PortLinks --- CHANGELOG.md | 43 ++++++++++++++++++++ src/Blazor.Diagrams.Core/DiagramBase.cs | 4 +- src/Blazor.Diagrams.Core/Layers/NodeLayer.cs | 2 +- src/Blazor.Diagrams.Core/Models/NodeModel.cs | 2 +- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efcbd7449..0c342c683 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,49 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Diagrams (3.0.0) - 2022-X-X + +### Added + +- `Diagram` class (inherits `DiagramBase`) to the blazor package to replace the old Core one +- `Blazor.Diagrams.Models.SvgNodeModel` class to represent a node that needs to be rendered in the SVG layer +- `GetBehavior` method to `DiagramBase` in order to retrieve a registered behavior +- `KeyboardShortcutsBehavior` class which handles keyboard shortcuts/actions: + - `SetShortcut`: sets an action (`Func`) to be executed whenever the specified combination is pressed + - `RemoveShortcut`: removes a defined action (if it exists) +- `KeyboardShortcutsDefaults` containing the default shortcuts that were deleted (`DeleteSelection` and `Grouping`) +- Anchors functionality: + - An Anchor determines where on an element the link will connect + - Instead of links requiring the source and target to be either both nodes or both ports, there is now only one `Source` and `Target` of type `Anchor` + - This lets the link not worry about the details of from/to where its going, as long as the anchor provides it with its position when asked for + - Current implementations: + - `SinglePortAnchor`: Specifies that the connection point is a specific port (replaces a link) + - `ShapeIntersectionAnchor`: Specifies that the connection point is the intersection of the line with the node's shape + +- Lot of unit tests + +### Changed + +- Core package changes: + - Web dependency was removed from the Core package + - `Diagram` in the Core package was renamed to `DiagramBase` + - These changes were done to decouple the core from the rendering, in the future we might have a MAUI renderer +- `Diagram.GetComponentForModel` now accepts a `checkSubclasses` argument (default `true`) +- Constraints now must return a `ValueTask` instead of a simple `bool` + +### Fixed + +- Virtualization throwing a JSException (#155) + +### Removed + +- `DefaultNodeComponent` and `DefaultLinkComponent` options (see `GetComponentForModel` changes) +- `RenderLayer` from the Core package and all its usage +- `DeleteSelectionBehavior` since there is a new keyboard shortcuts system +- `GroupingBehavior` since there is a new keyboard shortcuts system +- `BaseLinkModelExtensions` since it was Obselete +- Unnecessary port refreshes when dragging a link ends or when link snapping + ## Diagrams (2.1.6) - 2021-10-31 ### Fixed diff --git a/src/Blazor.Diagrams.Core/DiagramBase.cs b/src/Blazor.Diagrams.Core/DiagramBase.cs index e5c41b1f7..f041d2e3c 100644 --- a/src/Blazor.Diagrams.Core/DiagramBase.cs +++ b/src/Blazor.Diagrams.Core/DiagramBase.cs @@ -137,7 +137,7 @@ public void Ungroup(GroupModel group) Batch(() => { group.Ungroup(); - Links.Remove(group.AllLinks.ToArray()); + Links.Remove(group.PortLinks.ToArray()); GroupUngrouped?.Invoke(group); }); } @@ -154,7 +154,7 @@ public void RemoveGroup(GroupModel group) Batch(() => { Nodes.Remove(group.Children.ToArray()); - Links.Remove(group.AllLinks.ToArray()); + Links.Remove(group.PortLinks.ToArray()); group.Ungroup(); GroupRemoved?.Invoke(group); }); diff --git a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs index bd726f795..d80218fd1 100644 --- a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs @@ -14,7 +14,7 @@ public override void Remove(NodeModel node) protected override void OnItemRemoved(NodeModel node) { - Diagram.Links.Remove(node.AllLinks.ToList()); + Diagram.Links.Remove(node.PortLinks.ToList()); Diagram.Links.Remove(node.Links.ToList()); node.Group?.RemoveChild(node); } diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs index 9072cfd55..da7ab661e 100644 --- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs +++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs @@ -43,7 +43,7 @@ public Size? Size public IReadOnlyList Ports => _ports; public IReadOnlyList Links => _links; - public IEnumerable AllLinks => Ports.SelectMany(p => p.Links); + public IEnumerable PortLinks => Ports.SelectMany(p => p.Links); public PortModel AddPort(PortModel port) { From cadb16630d347b0dd4f7609e54f7f6c0a3354e1e Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 11 Aug 2022 18:47:33 +0100 Subject: [PATCH 036/193] Add MiddleIfNoMarker option (default false) --- src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs index 35525892c..a162d9991 100644 --- a/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs @@ -12,13 +12,14 @@ public SinglePortAnchor(PortModel port, Point? offset = null) : base(port.Parent } public PortModel Port { get; } + public bool MiddleIfNoMarker { get; set; } = false; public override Point? GetPosition(BaseLinkModel link, Point[] route) { if (!Port.Initialized) return null; - if ((link.Source == this && link.SourceMarker is null) || (link.Target == this && link.TargetMarker is null)) + if (MiddleIfNoMarker && ((link.Source == this && link.SourceMarker is null) || (link.Target == this && link.TargetMarker is null))) return Port.MiddlePosition; var pt = Port.Position; From dd961d889966d62059083174775ccade215ba751 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 11 Aug 2022 19:27:04 +0100 Subject: [PATCH 037/193] Remove ShapeDefiner & Add virtual GetShape method to nodes --- .../Demos/Nodes/PortlessLinks.razor.cs | 13 +++++++---- .../Geometry/Rectangle.cs | 3 +++ src/Blazor.Diagrams.Core/Geometry/Shapes.cs | 2 -- src/Blazor.Diagrams.Core/Models/GroupModel.cs | 2 -- src/Blazor.Diagrams.Core/Models/NodeModel.cs | 23 ++++++++++--------- src/Blazor.Diagrams/Models/SvgNodeModel.cs | 8 ++----- 6 files changed, 26 insertions(+), 25 deletions(-) diff --git a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs index 0673edd04..912bc86d2 100644 --- a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs @@ -25,9 +25,9 @@ private void InitializeDiagram() { _diagram.RegisterModelComponent(); - var node1 = new NodeModel(new Point(80, 80), shape: Shapes.Rectangle); - var node2 = new RoundedNode(new Point(280, 150), shape: Shapes.Circle); - var node3 = new NodeModel(new Point(400, 300), shape: Shapes.Rectangle); + var node1 = new NodeModel(new Point(80, 80)); + var node2 = new RoundedNode(new Point(280, 150)); + var node3 = new NodeModel(new Point(400, 300)); node3.AddPort(PortAlignment.Left); _diagram.Nodes.Add(node1); _diagram.Nodes.Add(node2); @@ -49,6 +49,11 @@ private void InitializeDiagram() class RoundedNode : NodeModel { - public RoundedNode(Point position = null, ShapeDefiner shape = null) : base(position, shape) { } + public RoundedNode(Point position = null) : base(position) { } + + public override IShape GetShape() + { + return Shapes.Circle(this); + } } } diff --git a/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs b/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs index 2824ff787..dcd736b13 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs @@ -28,6 +28,9 @@ public Rectangle(double left, double top, double right, double bottom) public Rectangle(Point position, Size size) { + ArgumentNullException.ThrowIfNull(position, nameof(position)); + ArgumentNullException.ThrowIfNull(size, nameof(size)); + Left = position.X; Top = position.Y; Right = Left + size.Width; diff --git a/src/Blazor.Diagrams.Core/Geometry/Shapes.cs b/src/Blazor.Diagrams.Core/Geometry/Shapes.cs index 07d799789..cd2210d6e 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Shapes.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Shapes.cs @@ -2,8 +2,6 @@ namespace Blazor.Diagrams.Core.Geometry { - public delegate IShape ShapeDefiner(NodeModel node); - public static class Shapes { public static IShape Rectangle(NodeModel node) => new Rectangle(node.Position, node.Size!); diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index e4f711f1a..e4a029135 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -1,6 +1,5 @@ using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models.Base; using System.Collections.Generic; using System.Linq; @@ -21,7 +20,6 @@ public GroupModel(IEnumerable children, byte padding = 30) public IReadOnlyList Children => _children; public byte Padding { get; } - public IEnumerable HandledLinks => _children.SelectMany(c => c.AllLinks).Distinct(); public void AddChild(NodeModel child) { diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs index da7ab661e..7eb3558a6 100644 --- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs +++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs @@ -15,17 +15,10 @@ public class NodeModel : MovableModel public event Action? SizeChanged; public event Action? Moving; - public NodeModel(Point? position = null, ShapeDefiner? shape = null) : base(position) - { - ShapeDefiner = shape ?? Shapes.Rectangle; - } + public NodeModel(Point? position = null) : base(position) { } - public NodeModel(string id, Point? position = null, ShapeDefiner? shape = null) : base(id, position) - { - ShapeDefiner = shape ?? Shapes.Rectangle; - } + public NodeModel(string id, Point? position = null) : base(id, position) { } - public ShapeDefiner ShapeDefiner { get; } public Size? Size { get => _size; @@ -45,6 +38,8 @@ public Size? Size public IReadOnlyList Links => _links; public IEnumerable PortLinks => Ports.SelectMany(p => p.Links); + #region Ports + public PortModel AddPort(PortModel port) { _ports.Add(port); @@ -58,6 +53,12 @@ public PortModel AddPort(PortAlignment alignment = PortAlignment.Bottom) public T? GetPort(PortAlignment alignment) where T : PortModel => (T?)GetPort(alignment); + public bool RemovePort(PortModel port) => _ports.Remove(port); + + #endregion + + #region Refreshing + public void RefreshAll() { Refresh(); @@ -81,7 +82,7 @@ public void ReinitializePorts() } } - public bool RemovePort(PortModel port) => _ports.Remove(port); + #endregion public override void SetPosition(double x, double y) { @@ -125,7 +126,7 @@ public virtual void UpdatePositionSilently(double deltaX, double deltaY) return new Rectangle(left, top, right, bottom); } - public IShape GetShape() => ShapeDefiner(this); + public virtual IShape GetShape() => Shapes.Rectangle(this); private void UpdatePortPositions(double deltaX, double deltaY) { diff --git a/src/Blazor.Diagrams/Models/SvgNodeModel.cs b/src/Blazor.Diagrams/Models/SvgNodeModel.cs index 1a5252ad5..b7ab8ef05 100644 --- a/src/Blazor.Diagrams/Models/SvgNodeModel.cs +++ b/src/Blazor.Diagrams/Models/SvgNodeModel.cs @@ -5,12 +5,8 @@ namespace Blazor.Diagrams.Models { public class SvgNodeModel : NodeModel { - public SvgNodeModel(Point? position = null, ShapeDefiner? shape = null) : base(position, shape) - { - } + public SvgNodeModel(Point? position = null) : base(position) { } - public SvgNodeModel(string id, Point? position = null, ShapeDefiner? shape = null) : base(id, position, shape) - { - } + public SvgNodeModel(string id, Point? position = null) : base(id, position) { } } } From 199cd80eb43bbdd6a9cf1c7a80288a03b3dec637 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 11 Aug 2022 23:57:51 +0100 Subject: [PATCH 038/193] Add virtual GetShape method to ports --- src/Blazor.Diagrams.Core/Geometry/Ellipse.cs | 8 +++++ src/Blazor.Diagrams.Core/Geometry/IShape.cs | 1 + .../Geometry/Rectangle.cs | 19 +++++++++++ src/Blazor.Diagrams.Core/Geometry/Shapes.cs | 32 +++++++++++++------ src/Blazor.Diagrams.Core/Models/PortModel.cs | 2 ++ 5 files changed, 52 insertions(+), 10 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Geometry/Ellipse.cs b/src/Blazor.Diagrams.Core/Geometry/Ellipse.cs index 022b80afe..e64896b6d 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Ellipse.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Ellipse.cs @@ -56,5 +56,13 @@ public IEnumerable GetIntersectionsWithLine(Line line) } } } + + public Point? GetPointAtAngle(double a) + { + var t = Math.Tan(a / 360 * Math.PI); + var px = Rx * (1 - Math.Pow(t, 2)) / (1 + Math.Pow(t, 2)); + var py = Ry * 2 * t / (1 + Math.Pow(t, 2)); + return new Point(Cx + px, Cy + py); + } } } diff --git a/src/Blazor.Diagrams.Core/Geometry/IShape.cs b/src/Blazor.Diagrams.Core/Geometry/IShape.cs index d9a8ad73c..b5402d981 100644 --- a/src/Blazor.Diagrams.Core/Geometry/IShape.cs +++ b/src/Blazor.Diagrams.Core/Geometry/IShape.cs @@ -5,5 +5,6 @@ namespace Blazor.Diagrams.Core.Geometry public interface IShape { public IEnumerable GetIntersectionsWithLine(Line line); + public Point? GetPointAtAngle(double a); } } diff --git a/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs b/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs index dcd736b13..dbea08cfa 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.Json.Serialization; namespace Blazor.Diagrams.Core.Geometry @@ -89,6 +90,24 @@ public IEnumerable GetIntersectionsWithLine(Line line) } } + public Point? GetPointAtAngle(double a) + { + var vx = Math.Cos(a * Math.PI / 180); + var vy = Math.Sin(a * Math.PI / 180); + var px = Left + Width / 2; + var py = Top + Height / 2; + double? t1 = (Left - px) / vx; // left + double? t2 = (Right - px) / vx; // right + double? t3 = (Top - py) / vy; // top + double? t4 = (Bottom - py) / vy; // bottom + var t = (new[] { t1, t2, t3, t4 }).Where(n => n.HasValue && double.IsFinite(n.Value) && n.Value > 0).DefaultIfEmpty(null).Min(); + if (t == null) return null; + + var x = px + t.Value * vx; + var y = py + t.Value * vy; + return new Point(x, y); + } + public Point Center => new(Left + Width / 2, Top + Height / 2); public Point NorthEast => new(Right, Top); public Point SouthEast => new(Right, Bottom); diff --git a/src/Blazor.Diagrams.Core/Geometry/Shapes.cs b/src/Blazor.Diagrams.Core/Geometry/Shapes.cs index cd2210d6e..75fb58487 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Shapes.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Shapes.cs @@ -4,22 +4,34 @@ namespace Blazor.Diagrams.Core.Geometry { public static class Shapes { - public static IShape Rectangle(NodeModel node) => new Rectangle(node.Position, node.Size!); + public static IShape Rectangle(NodeModel node) => Rectangle(node.Position, node.Size!); - public static IShape Circle(NodeModel node) + public static IShape Circle(NodeModel node) => Circle(node.Position, node.Size!); + + public static IShape Ellipse(NodeModel node) => Ellipse(node.Position, node.Size!); + + public static IShape Rectangle(PortModel port) => Rectangle(port.Position, port.Size!); + + public static IShape Circle(PortModel port) => Circle(port.Position, port.Size!); + + public static IShape Ellipse(PortModel port) => Ellipse(port.Position, port.Size!); + + private static IShape Rectangle(Point position, Size size) => new Rectangle(position, size); + + private static IShape Circle(Point position, Size size) { - var halfWidth = node.Size!.Width / 2; - var centerX = node.Position.X + halfWidth; - var centerY = node.Position.Y + node.Size.Height / 2; + var halfWidth = size.Width / 2; + var centerX = position.X + halfWidth; + var centerY = position.Y + size.Height / 2; return new Ellipse(centerX, centerY, halfWidth, halfWidth); } - public static IShape Ellipse(NodeModel node) + private static IShape Ellipse(Point position, Size size) { - var halfWidth = node.Size!.Width / 2; - var halfHeight = node.Size.Height / 2; - var centerX = node.Position.X + halfWidth; - var centerY = node.Position.Y + halfHeight; + var halfWidth = size.Width / 2; + var halfHeight = size.Height / 2; + var centerX = position.X + halfWidth; + var centerY = position.Y + halfHeight; return new Ellipse(centerX, centerY, halfWidth, halfHeight); } } diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs index 2f2f5759f..b930de788 100644 --- a/src/Blazor.Diagrams.Core/Models/PortModel.cs +++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs @@ -52,6 +52,8 @@ public virtual bool CanAttachTo(PortModel port) public Rectangle GetBounds() => new(Position, Size); + public virtual IShape GetShape() => Shapes.Circle(this); + internal void AddLink(BaseLinkModel link) => _links.Add(link); internal void RemoveLink(BaseLinkModel link) => _links.Remove(link); From cc7e4464cc27a3fb55a5de497d06d027802a800f Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 11 Aug 2022 23:57:57 +0100 Subject: [PATCH 039/193] Add UseShapeAndAlignment option to SinglePortAnchor --- .../Anchors/SinglePortAnchor.cs | 46 ++++++++++++------- .../Behaviors/DragNewLinkBehavior.cs | 10 +--- 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs index a162d9991..5d44c2e93 100644 --- a/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs @@ -13,6 +13,7 @@ public SinglePortAnchor(PortModel port, Point? offset = null) : base(port.Parent public PortModel Port { get; } public bool MiddleIfNoMarker { get; set; } = false; + public bool UseShapeAndAlignment { get; set; } = true; public override Point? GetPosition(BaseLinkModel link, Point[] route) { @@ -22,25 +23,36 @@ public SinglePortAnchor(PortModel port, Point? offset = null) : base(port.Parent if (MiddleIfNoMarker && ((link.Source == this && link.SourceMarker is null) || (link.Target == this && link.TargetMarker is null))) return Port.MiddlePosition; + var pt = Port.Position; - switch (Port.Alignment) + if (UseShapeAndAlignment) + { + return Port.Alignment switch + { + PortAlignment.Top => Port.GetShape().GetPointAtAngle(270), + PortAlignment.TopRight => Port.GetShape().GetPointAtAngle(315), + PortAlignment.Right => Port.GetShape().GetPointAtAngle(0), + PortAlignment.BottomRight => Port.GetShape().GetPointAtAngle(45), + PortAlignment.Bottom => Port.GetShape().GetPointAtAngle(90), + PortAlignment.BottomLeft => Port.GetShape().GetPointAtAngle(135), + PortAlignment.Left => Port.GetShape().GetPointAtAngle(180), + PortAlignment.TopLeft => Port.GetShape().GetPointAtAngle(225), + _ => null, + }; + } + else { - case PortAlignment.Top: - return new Point(pt.X + Port.Size.Width / 2, pt.Y); - case PortAlignment.TopRight: - return new Point(pt.X + Port.Size.Width, pt.Y); - case PortAlignment.Right: - return new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height / 2); - case PortAlignment.BottomRight: - return new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height); - case PortAlignment.Bottom: - return new Point(pt.X + Port.Size.Width / 2, pt.Y + Port.Size.Height); - case PortAlignment.BottomLeft: - return new Point(pt.X, pt.Y + Port.Size.Height); - case PortAlignment.Left: - return new Point(pt.X, pt.Y + Port.Size.Height / 2); - default: - return pt; + return Port.Alignment switch + { + PortAlignment.Top => new Point(pt.X + Port.Size.Width / 2, pt.Y), + PortAlignment.TopRight => new Point(pt.X + Port.Size.Width, pt.Y), + PortAlignment.Right => new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height / 2), + PortAlignment.BottomRight => new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height), + PortAlignment.Bottom => new Point(pt.X + Port.Size.Width / 2, pt.Y + Port.Size.Height), + PortAlignment.BottomLeft => new Point(pt.X, pt.Y + Port.Size.Height), + PortAlignment.Left => new Point(pt.X, pt.Y + Port.Size.Height / 2), + _ => pt, + }; } } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 5d76b7506..89370a901 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -49,7 +49,7 @@ private void Start(Model? model, double clientX, double clientY) { if (port.Locked) return; _ongoingLink = Diagram.Options.Links.Factory(Diagram, port); - _ongoingLink.OnGoingPosition = _ongoingLink.Source.GetPosition(_ongoingLink); + _ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5); } else { @@ -66,13 +66,7 @@ private void Move(Model? model, double clientX, double clientY) if (_ongoingLink == null || model != null) return; - var deltaX = (clientX - _initialX) / Diagram.Zoom; - var deltaY = (clientY - _initialY) / Diagram.Zoom; - var sourcePosition = _ongoingLink.Source.GetPosition(_ongoingLink)!; // Port should be initialized already, so no null here - var sX = sourcePosition.X; - var sY = sourcePosition.Y; - - _ongoingLink.OnGoingPosition = new Point(sX + deltaX, sY + deltaY); + _ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5); if (Diagram.Options.Links.EnableSnapping) { From 9069d243f290d699a18b31c95c9b0cb8094fbe91 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 12 Aug 2022 23:06:08 +0100 Subject: [PATCH 040/193] Add support for nested groups --- .../Demos/CustomGroup/CustomGroupWidget.razor | 14 ++- .../Demos/CustomGroup/Demo.razor.cs | 2 + .../Demos/DynamicInsertions.razor.cs | 13 +++ .../SharedDemo/Demos/Groups/Grouping.razor.cs | 9 +- samples/SharedDemo/_Imports.razor | 1 - samples/SharedDemo/wwwroot/css/styles.css | 4 +- src/Blazor.Diagrams.Core/DiagramBase.cs | 16 +-- src/Blazor.Diagrams.Core/DiagramOptions.cs | 5 +- src/Blazor.Diagrams.Core/Models/GroupModel.cs | 4 +- .../Components/DefaultGroupWidget.razor | 11 ++ .../Components/DiagramCanvas.razor | 5 +- .../Components/DiagramCanvas.razor.cs | 6 +- .../Components/{Groups => }/GroupNodes.razor | 7 +- .../Groups/DefaultGroupWidget.razor | 14 --- .../Components/Groups/GroupContainer.razor | 11 -- .../Components/Groups/GroupContainer.razor.cs | 75 ------------- .../Components/Groups/GroupLinks.razor | 12 -- .../Components/Renderers/GroupRenderer.cs | 104 +++++++++++++++++- .../Extensions/StringBuilderExtensions.cs | 17 +++ src/Blazor.Diagrams/wwwroot/style.css | 2 +- src/Blazor.Diagrams/wwwroot/style.min.css | 2 +- src/Blazor.Diagrams/wwwroot/style.min.css.gz | Bin 402 -> 406 bytes 22 files changed, 185 insertions(+), 149 deletions(-) create mode 100644 src/Blazor.Diagrams/Components/DefaultGroupWidget.razor rename src/Blazor.Diagrams/Components/{Groups => }/GroupNodes.razor (59%) delete mode 100644 src/Blazor.Diagrams/Components/Groups/DefaultGroupWidget.razor delete mode 100644 src/Blazor.Diagrams/Components/Groups/GroupContainer.razor delete mode 100644 src/Blazor.Diagrams/Components/Groups/GroupContainer.razor.cs delete mode 100644 src/Blazor.Diagrams/Components/Groups/GroupLinks.razor create mode 100644 src/Blazor.Diagrams/Extensions/StringBuilderExtensions.cs diff --git a/samples/SharedDemo/Demos/CustomGroup/CustomGroupWidget.razor b/samples/SharedDemo/Demos/CustomGroup/CustomGroupWidget.razor index d87d0f964..82d7aa229 100644 --- a/samples/SharedDemo/Demos/CustomGroup/CustomGroupWidget.razor +++ b/samples/SharedDemo/Demos/CustomGroup/CustomGroupWidget.razor @@ -1,8 +1,12 @@ - - @Group.Title - - - +@Group.Title + + + +@foreach (var port in Group.Ports) +{ + +} + @code { [Parameter] public CustomGroupModel Group { get; set; } diff --git a/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs b/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs index 6016b89e5..d1848a4b9 100644 --- a/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs @@ -16,6 +16,8 @@ protected override void OnInitialized() LayoutData.Info = "Creating your own custom groups is very easy!"; LayoutData.DataChanged(); + _diagram.Options.LinksLayerOrder = 2; + _diagram.Options.NodesLayerOrder = 1; _diagram.RegisterModelComponent(); var node1 = NewNode(50, 50); diff --git a/samples/SharedDemo/Demos/DynamicInsertions.razor.cs b/samples/SharedDemo/Demos/DynamicInsertions.razor.cs index a44d15d20..3ded11382 100644 --- a/samples/SharedDemo/Demos/DynamicInsertions.razor.cs +++ b/samples/SharedDemo/Demos/DynamicInsertions.razor.cs @@ -1,4 +1,5 @@ using Blazor.Diagrams; +using Blazor.Diagrams.Core.Anchors; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; @@ -19,6 +20,18 @@ protected override void OnInitialized() diagram.Options.Groups.Enabled = true; diagram.Nodes.Add(new NodeModel(new Point(300, 50))); diagram.Nodes.Add(new NodeModel(new Point(300, 400))); + + diagram.Options.Links.Factory = (d, sp) => + { + var link = new LinkModel(new SinglePortAnchor(sp) + { + UseShapeAndAlignment = false + }) + { + SourceMarker = LinkMarker.Arrow + }; + return link; + }; } protected void AddNode() diff --git a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs index be70a2e8e..67af65ba8 100644 --- a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs @@ -13,11 +13,14 @@ protected override void OnInitialized() { base.OnInitialized(); + diagram.Options.Groups.Enabled = true; + diagram.Options.LinksLayerOrder = 2; + diagram.Options.NodesLayerOrder = 1; diagram.Options.Groups.Enabled = true; var node1 = NewNode(50, 50); var node2 = NewNode(250, 250); var node3 = NewNode(500, 100); - var node4 = NewNode(500, 250); + var node4 = NewNode(700, 350); diagram.Nodes.Add(new[] { node1, node2, node3, node4 }); diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); @@ -25,7 +28,9 @@ protected override void OnInitialized() diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); var group1 = diagram.Group(node1, node2); - diagram.Group(group1, node3); + var group2 = diagram.Group(group1, node3); + + diagram.Links.Add(new LinkModel(group2, node4)); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/_Imports.razor b/samples/SharedDemo/_Imports.razor index 41a9ddb92..f86ed02bf 100644 --- a/samples/SharedDemo/_Imports.razor +++ b/samples/SharedDemo/_Imports.razor @@ -8,5 +8,4 @@ @using Blazor.Diagrams.Core.Extensions; @using Blazor.Diagrams.Core.Geometry; @using Blazor.Diagrams.Components.Renderers; -@using Blazor.Diagrams.Components.Groups; @using SharedDemo.Components; \ No newline at end of file diff --git a/samples/SharedDemo/wwwroot/css/styles.css b/samples/SharedDemo/wwwroot/css/styles.css index 4acf446ae..c09329209 100644 --- a/samples/SharedDemo/wwwroot/css/styles.css +++ b/samples/SharedDemo/wwwroot/css/styles.css @@ -1616,12 +1616,12 @@ body { background: #40babd !important; } -.group.custom { +.group:not(.default) { outline: 2px solid black; background-color: #6fbb6e; } - .group.custom > span.title { + .group:not(.default) > span.title { padding: 20px; position: absolute; left: 50%; diff --git a/src/Blazor.Diagrams.Core/DiagramBase.cs b/src/Blazor.Diagrams.Core/DiagramBase.cs index f041d2e3c..e0496a370 100644 --- a/src/Blazor.Diagrams.Core/DiagramBase.cs +++ b/src/Blazor.Diagrams.Core/DiagramBase.cs @@ -100,9 +100,6 @@ public void Batch(Action action) /// The created group instance. public GroupModel Group(params NodeModel[] children) { - //if (children.Any(n => n.Group != null)) - // throw new InvalidOperationException("Cannot group nodes that already belong to another group"); - var group = Options.Groups.Factory(this, children); AddGroup(group); return group; @@ -114,11 +111,14 @@ public GroupModel Group(params NodeModel[] children) /// A group instance. public void AddGroup(GroupModel group) { - //foreach (var child in group.Children) - //{ - // if (child is NodeModel node && !Nodes.Contains(node)) - // throw new Exception("One of the nodes isn't in the diagram. Make sure to add all the nodes before creating the group."); - //} + foreach (var child in group.Children) + { + if (child is NodeModel n && !Nodes.Contains(n)) + throw new Exception("One of the children isn't in the diagram (Nodes). Make sure to add all the nodes before creating the group."); + + if (child is GroupModel g && !Groups.Contains(g)) + throw new Exception("One of the children isn't in the diagram (Groups). Make sure to add all the nodes before creating the group."); + } _groups.Add(group); GroupAdded?.Invoke(group); diff --git a/src/Blazor.Diagrams.Core/DiagramOptions.cs b/src/Blazor.Diagrams.Core/DiagramOptions.cs index 56b3b0d37..255c6be74 100644 --- a/src/Blazor.Diagrams.Core/DiagramOptions.cs +++ b/src/Blazor.Diagrams.Core/DiagramOptions.cs @@ -1,6 +1,5 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -using Blazor.Diagrams.Core.Events; using System; using System.ComponentModel; using System.Threading.Tasks; @@ -17,6 +16,10 @@ public class DiagramOptions public bool AllowPanning { get; set; } = true; [Description("Only render visible nodes")] public bool EnableVirtualization { get; set; } = true; + [Description("Links layer order")] + public int LinksLayerOrder { get; set; } = 0; + [Description("Nodes layer order")] + public int NodesLayerOrder { get; set; } = 0; public DiagramZoomOptions Zoom { get; set; } = new DiagramZoomOptions(); public DiagramLinkOptions Links { get; set; } = new DiagramLinkOptions(); diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index e4a029135..f530e2653 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -87,7 +87,7 @@ private void Initialize(IEnumerable children) foreach (var child in children) { _children.Add(child); - child.Group = this; + child.Group = this; child.SizeChanged += OnNodeChanged; child.Moving += OnNodeChanged; } @@ -112,8 +112,8 @@ private bool UpdateDimensions() return false; var bounds = Children.GetBounds(); - Size = new Size(bounds.Width + Padding * 2, bounds.Height + Padding * 2); Position = new Point(bounds.Left - Padding, bounds.Top - Padding); + Size = new Size(bounds.Width + Padding * 2, bounds.Height + Padding * 2); return true; } } diff --git a/src/Blazor.Diagrams/Components/DefaultGroupWidget.razor b/src/Blazor.Diagrams/Components/DefaultGroupWidget.razor new file mode 100644 index 000000000..9b2e5e008 --- /dev/null +++ b/src/Blazor.Diagrams/Components/DefaultGroupWidget.razor @@ -0,0 +1,11 @@ +@code { + [Parameter] + public GroupModel Group { get; set; } = null!; +} + + + +@foreach (var port in Group.Ports) +{ + +} diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor b/src/Blazor.Diagrams/Components/DiagramCanvas.razor index 7838c1db9..2581ddcf1 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor @@ -14,7 +14,7 @@ @ontouchend:preventDefault> @* Links *@ - + @foreach (var link in Diagram.Links) { @@ -27,9 +27,10 @@ @* Nodes *@ -
+
@foreach (var group in Diagram.Groups) { + if (group.Group != null) continue; } diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index 143b1c459..1fd3f3612 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -26,8 +26,10 @@ public partial class DiagramCanvas : IDisposable private DotNetObjectReference? _reference; private bool _shouldRender; - private string LayerStyle - => FormattableString.Invariant($"transform: translate({Diagram.Pan.X}px, {Diagram.Pan.Y}px) scale({Diagram.Zoom});"); + private string GetLayerStyle(int order) + { + return FormattableString.Invariant($"transform: translate({Diagram.Pan.X}px, {Diagram.Pan.Y}px) scale({Diagram.Zoom}); z-index: {order};"); + } protected override void OnInitialized() { diff --git a/src/Blazor.Diagrams/Components/Groups/GroupNodes.razor b/src/Blazor.Diagrams/Components/GroupNodes.razor similarity index 59% rename from src/Blazor.Diagrams/Components/Groups/GroupNodes.razor rename to src/Blazor.Diagrams/Components/GroupNodes.razor index e1a5f0ecf..af842cd44 100644 --- a/src/Blazor.Diagrams/Components/Groups/GroupNodes.razor +++ b/src/Blazor.Diagrams/Components/GroupNodes.razor @@ -1,10 +1,9 @@ @code { - [CascadingParameter] - public GroupModel Group { get; set; } + [Parameter] + public GroupModel Group { get; set; } = null!; } -
+
@foreach (var node in Group.Children) { if (node is GroupModel g) diff --git a/src/Blazor.Diagrams/Components/Groups/DefaultGroupWidget.razor b/src/Blazor.Diagrams/Components/Groups/DefaultGroupWidget.razor deleted file mode 100644 index 11f833ddf..000000000 --- a/src/Blazor.Diagrams/Components/Groups/DefaultGroupWidget.razor +++ /dev/null @@ -1,14 +0,0 @@ -@code { - [Parameter] - public GroupModel Group { get; set; } -} - - - - - - @foreach (var port in Group.Ports) - { - - } - \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor b/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor deleted file mode 100644 index cff326bee..000000000 --- a/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor +++ /dev/null @@ -1,11 +0,0 @@ -
- - @ChildContent - -
\ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor.cs b/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor.cs deleted file mode 100644 index b83a242c2..000000000 --- a/src/Blazor.Diagrams/Components/Groups/GroupContainer.razor.cs +++ /dev/null @@ -1,75 +0,0 @@ -using Blazor.Diagrams.Core; -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Extensions; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Web; -using System; - -namespace Blazor.Diagrams.Components.Groups -{ - public partial class GroupContainer : IDisposable - { - private bool _shouldRender = true; - private Size _lastSize; - - [Parameter] - public GroupModel Group { get; set; } - - [Parameter] - public string Class { get; set; } - - [Parameter] - public RenderFragment ChildContent { get; set; } - - [CascadingParameter] - public Diagram Diagram { get; set; } - - public void Dispose() - { - Group.Changed -= OnGroupChanged; - } - - protected override void OnInitialized() - { - Group.Changed += OnGroupChanged; - } - - protected override bool ShouldRender() - { - if (_shouldRender) - { - _shouldRender = false; - return true; - } - - return false; - } - - protected override void OnAfterRender(bool firstRender) - { - if (Size.Zero.Equals(Group.Size)) - return; - - // Update the port positions (and links) when the size of the group changes - // This will save us some JS trips as well as useless rerenders - - if (_lastSize == null || !_lastSize.Equals(Group.Size)) - { - Group.ReinitializePorts(); - Group.RefreshLinks(); - _lastSize = Group.Size; - } - } - - private void OnGroupChanged() - { - _shouldRender = true; - InvokeAsync(StateHasChanged); - } - - private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Group, e.ToCore()); - - private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Group, e.ToCore()); - } -} diff --git a/src/Blazor.Diagrams/Components/Groups/GroupLinks.razor b/src/Blazor.Diagrams/Components/Groups/GroupLinks.razor deleted file mode 100644 index 7f68cbddb..000000000 --- a/src/Blazor.Diagrams/Components/Groups/GroupLinks.razor +++ /dev/null @@ -1,12 +0,0 @@ -@code { - [CascadingParameter] - public GroupModel Group { get; set; } -} - - - @foreach (var link in Group.HandledLinks) - { - - } - \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs index 91f7c51d3..4a157c6af 100644 --- a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs @@ -1,24 +1,116 @@ -using Blazor.Diagrams.Components.Groups; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Web; +using System; +using System.Text; namespace Blazor.Diagrams.Components.Renderers { - public class GroupRenderer : ComponentBase + public class GroupRenderer : ComponentBase, IDisposable { + private bool _shouldRender = true; + private Size? _lastSize; + private bool _isSvg; + [CascadingParameter] - public Diagram Diagram { get; set; } + public Diagram Diagram { get; set; } = null!; [Parameter] - public GroupModel Group { get; set; } + public GroupModel Group { get; set; } = null!; + + public void Dispose() + { + Group.Changed -= OnGroupChanged; + } + + protected override void OnInitialized() + { + Group.Changed += OnGroupChanged; + } + + protected override bool ShouldRender() + { + if (_shouldRender) + { + _shouldRender = false; + return true; + } + + return false; + } + + protected override void OnAfterRender(bool firstRender) + { + if (Size.Zero.Equals(Group.Size)) + return; + + // Update the port positions (and links) when the size of the group changes + // This will save us some JS trips as well as useless rerenders + + if (_lastSize == null || !_lastSize.Equals(Group.Size)) + { + Group.ReinitializePorts(); + Group.RefreshLinks(); + _lastSize = Group.Size; + } + } + + private void OnGroupChanged() + { + _shouldRender = true; + InvokeAsync(StateHasChanged); + } + + private static string GenerateStyle(double top, double left, double width, double height) + { + return FormattableString.Invariant($"top: {top}px; left: {left}px; width: {width}px; height: {height}px"); + } protected override void BuildRenderTree(RenderTreeBuilder builder) { var componentType = Diagram.GetComponentForModel(Group) ?? typeof(DefaultGroupWidget); - builder.OpenComponent(0, componentType); - builder.AddAttribute(1, "Group", Group); + var classes = new StringBuilder("group") + .AppendIf(" locked", Group.Locked) + .AppendIf(" selected", Group.Selected) + .AppendIf(" default", componentType == typeof(DefaultGroupWidget)); + + builder.OpenElement(0, _isSvg ? "g" : "div"); + builder.AddAttribute(1, "class", classes); + builder.AddAttribute(2, "data-group-id", Group.Id); + + if (_isSvg) + { + // Todo + } + else + { + builder.AddAttribute(3, "style", GenerateStyle(Group.Position.Y, Group.Position.X, Group.Size!.Width, Group.Size.Height)); + } + + builder.AddAttribute(4, "onmousedown", EventCallback.Factory.Create(this, OnMouseDown)); + builder.AddEventStopPropagationAttribute(5, "onmousedown", true); + builder.AddAttribute(6, "onmouseup", EventCallback.Factory.Create(this, OnMouseUp)); + builder.AddEventStopPropagationAttribute(7, "onmouseup", true); + builder.AddAttribute(8, "ontouchstart", EventCallback.Factory.Create(this, OnTouchStart)); + builder.AddEventStopPropagationAttribute(9, "ontouchstart", true); + builder.AddAttribute(10, "ontouchend", EventCallback.Factory.Create(this, OnTouchEnd)); + builder.AddEventStopPropagationAttribute(11, "ontouchend", true); + builder.AddEventPreventDefaultAttribute(12, "ontouchend", true); + builder.OpenComponent(13, componentType); + builder.AddAttribute(14, "Group", Group); builder.CloseComponent(); + builder.CloseElement(); } + + private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Group, e.ToCore()); + + private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Group, e.ToCore()); + + private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Group, e.ToCore()); + + private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(Group, e.ToCore()); } } diff --git a/src/Blazor.Diagrams/Extensions/StringBuilderExtensions.cs b/src/Blazor.Diagrams/Extensions/StringBuilderExtensions.cs new file mode 100644 index 000000000..40f2c7bb5 --- /dev/null +++ b/src/Blazor.Diagrams/Extensions/StringBuilderExtensions.cs @@ -0,0 +1,17 @@ +using System.Text; + +namespace Blazor.Diagrams.Extensions +{ + public static class StringBuilderExtensions + { + public static StringBuilder AppendIf(this StringBuilder builder, string str, bool condition) + { + if (condition) + { + builder.Append(str); + } + + return builder; + } + } +} diff --git a/src/Blazor.Diagrams/wwwroot/style.css b/src/Blazor.Diagrams/wwwroot/style.css index 99b479208..5b9228c17 100644 --- a/src/Blazor.Diagrams/wwwroot/style.css +++ b/src/Blazor.Diagrams/wwwroot/style.css @@ -77,7 +77,7 @@ pointer-events: all; } - .group .layer { + .group .children { position: absolute; overflow: visible; pointer-events: none; diff --git a/src/Blazor.Diagrams/wwwroot/style.min.css b/src/Blazor.Diagrams/wwwroot/style.min.css index 8de9f3272..5757db08f 100644 --- a/src/Blazor.Diagrams/wwwroot/style.min.css +++ b/src/Blazor.Diagrams/wwwroot/style.min.css @@ -1 +1 @@ -.diagram-canvas{width:100%;height:100%;position:relative;outline:none;overflow:hidden;cursor:-webkit-grab;cursor:grab;touch-action:none;}.diagram-svg-layer,.diagram-html-layer{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.html-layer,.svg-layer{position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.node{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.node.locked{cursor:pointer;}.link{pointer-events:visiblePainted;cursor:pointer;}.link path.selection-helper:hover{stroke-opacity:.05;}.diagram-navigator{z-index:10;}.diagram-navigator .current-view{position:absolute;border:2px solid #000;}.group{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.group .layer{position:absolute;overflow:visible;pointer-events:none;}.link foreignObject{overflow:visible;pointer-events:none;} \ No newline at end of file +.diagram-canvas{width:100%;height:100%;position:relative;outline:none;overflow:hidden;cursor:-webkit-grab;cursor:grab;touch-action:none;}.diagram-svg-layer,.diagram-html-layer{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.html-layer,.svg-layer{position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.node{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.node.locked{cursor:pointer;}.link{pointer-events:visiblePainted;cursor:pointer;}.link path.selection-helper:hover{stroke-opacity:.05;}.diagram-navigator{z-index:10;}.diagram-navigator .current-view{position:absolute;border:2px solid #000;}.group{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.group .children{position:absolute;overflow:visible;pointer-events:none;}.link foreignObject{overflow:visible;pointer-events:none;} \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/style.min.css.gz b/src/Blazor.Diagrams/wwwroot/style.min.css.gz index f3fb7e1cdd4bd64e6a0323aa843d97f4a42563a0..14658b68ae0b6b37dff2ff882529101a571ce723 100644 GIT binary patch literal 406 zcmV;H0crjpiwFP!000003eA#1Zrm^oMXzF@=q@sp0@=uiXs;k!9E%xQG$h)S#OTSg z$dPh|(lZ{X$f!VTH~CUQX!Pn|rOlHy*I}d^wMlk&&WF z5G>;i4NV*{f{7I6XNGhLbTS^DgV=Oa%9M;cL3>12GN8R`=QGpPd1bqE%X5d{J4l{; z73?#lyIt};gp0N?jYc;C4lJK^2GmVU%rwYh$FyyxFfoXNks&FVA#%RVx?*v%F&`)m zilh@pqbuF0A(sn9@gH&RdCZux4XeEm4|nx$arwf)ksK8Gt}Ig)$P58G-XI5hDXIBZ zyb-qD7zE>U05*gTrVi8{!7bb6?Vc2650A^IXPb{-c2!+dd{d2QfKXyAYmOr$73BcxrRgYoDb#HtxmrexFs>OHcO0rf>YotcKlE8Cn}o;tj)AbD(6 zu+NZgSIKi1&f3J(8(jt1v3$}AP**iEQzwTN)7F{7z#s}nhNNJG$oV|$g2l{)IQ?SaNDemmE-XV9$P57*ULZSq*;4bh zcqJ^^=mg`d0XBpMrVP~V!A;BM<(?EJhx_Tvvt^IJc2-@r`KIVC`|`{oJ1P&Mhh+S0 z#*{hH9u)PqL4G!+e)un=(T>jKmF|GC-_Mm Date: Sat, 13 Aug 2022 12:05:00 +0100 Subject: [PATCH 041/193] Add support for SVG groups --- .../SharedDemo/Demos/Nodes/SvgDemo.razor.cs | 11 ++++-- .../Demos/Nodes/SvgNodeWidget.razor | 6 +-- src/Blazor.Diagrams.Core/DiagramBase.cs | 15 ++++--- .../Components/DiagramCanvas.razor | 16 +++++--- .../Components/GroupNodes.razor | 39 ++++++++++++++----- .../Components/Renderers/GroupRenderer.cs | 24 ++++++++++-- src/Blazor.Diagrams/Models/SvgGroupModel.cs | 10 +++++ .../wwwroot/default.styles.css | 22 +++++------ 8 files changed, 103 insertions(+), 40 deletions(-) create mode 100644 src/Blazor.Diagrams/Models/SvgGroupModel.cs diff --git a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs index 84c16fd72..93230b19f 100644 --- a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs @@ -1,4 +1,5 @@ using Blazor.Diagrams; +using Blazor.Diagrams.Components; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Models; @@ -22,18 +23,22 @@ protected override void OnInitialized() private void InitializeDiagram() { - _diagram.RegisterModelComponent(); + _diagram.RegisterModelComponent(); + _diagram.RegisterModelComponent(); + _diagram.RegisterModelComponent(); var node1 = NewNode(80, 80); var node2 = NewNode(280, 150); _diagram.Nodes.Add(node1); _diagram.Nodes.Add(node2); _diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + + _diagram.AddGroup(new SvgGroupModel(new[] { node1, node2 })); } - private NodeModel NewNode(double x, double y) + private NodeModel NewNode(double x, double y, bool svg = true) { - var node = new SvgNodeModel(new Point(x, y)); + var node = svg ? new SvgNodeModel(new Point(x, y)) : new NodeModel(new Point(x, y)); node.AddPort(PortAlignment.Left); node.AddPort(PortAlignment.Right); return node; diff --git a/samples/SharedDemo/Demos/Nodes/SvgNodeWidget.razor b/samples/SharedDemo/Demos/Nodes/SvgNodeWidget.razor index 5e2f159f2..932f138f0 100644 --- a/samples/SharedDemo/Demos/Nodes/SvgNodeWidget.razor +++ b/samples/SharedDemo/Demos/Nodes/SvgNodeWidget.razor @@ -1,12 +1,12 @@ - - + - + @code { diff --git a/src/Blazor.Diagrams.Core/DiagramBase.cs b/src/Blazor.Diagrams.Core/DiagramBase.cs index e0496a370..a3bc567ae 100644 --- a/src/Blazor.Diagrams.Core/DiagramBase.cs +++ b/src/Blazor.Diagrams.Core/DiagramBase.cs @@ -113,11 +113,14 @@ public void AddGroup(GroupModel group) { foreach (var child in group.Children) { - if (child is NodeModel n && !Nodes.Contains(n)) - throw new Exception("One of the children isn't in the diagram (Nodes). Make sure to add all the nodes before creating the group."); - - if (child is GroupModel g && !Groups.Contains(g)) - throw new Exception("One of the children isn't in the diagram (Groups). Make sure to add all the nodes before creating the group."); + if (child is GroupModel g) + { + if (!Groups.Contains(g)) + throw new Exception("One of the children isn't in the diagram (Groups). Make sure to add all the nodes before creating the group."); + } + else if (child is NodeModel n) + if (!Nodes.Contains(n)) + throw new Exception("One of the children isn't in the diagram (Nodes). Make sure to add all the nodes before creating the group."); } _groups.Add(group); @@ -304,7 +307,7 @@ public void SetZoom(double newZoom) if (newZoom < Options.Zoom.Minimum) newZoom = Options.Zoom.Minimum; - + Zoom = newZoom; ZoomChanged?.Invoke(); Refresh(); diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor b/src/Blazor.Diagrams/Components/DiagramCanvas.razor index 2581ddcf1..ff8872779 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor @@ -15,20 +15,26 @@ @* Links *@ - @foreach (var link in Diagram.Links) + @foreach (var node in Diagram.Nodes.OfType().Where(n => n.Group == null)) { - + } - @foreach (var node in Diagram.Nodes.OfType()) + @foreach (var group in Diagram.Groups.OfType()) { - + } + + @foreach (var link in Diagram.Links) + { + + } + @* Nodes *@
- @foreach (var group in Diagram.Groups) + @foreach (var group in Diagram.Groups.Where(n => n is not SvgGroupModel)) { if (group.Group != null) continue; diff --git a/src/Blazor.Diagrams/Components/GroupNodes.razor b/src/Blazor.Diagrams/Components/GroupNodes.razor index af842cd44..8261369db 100644 --- a/src/Blazor.Diagrams/Components/GroupNodes.razor +++ b/src/Blazor.Diagrams/Components/GroupNodes.razor @@ -1,18 +1,39 @@ @code { [Parameter] public GroupModel Group { get; set; } = null!; + + // Remove duplicated code } -
- @foreach (var node in Group.Children) - { - if (node is GroupModel g) +@if (Group is SvgGroupModel) +{ + + @foreach (var node in Group.Children) { - + if (node is GroupModel g) + { + + } + else + { + + } } - else + +} +else +{ +
+ @foreach (var node in Group.Children) { - + if (node is GroupModel g) + { + + } + else + { + + } } - } -
\ No newline at end of file +
+} diff --git a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs index 4a157c6af..4905ec160 100644 --- a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs @@ -1,6 +1,7 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Extensions; +using Blazor.Diagrams.Models; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; @@ -31,6 +32,13 @@ protected override void OnInitialized() Group.Changed += OnGroupChanged; } + protected override void OnParametersSet() + { + base.OnParametersSet(); + + _isSvg = Group is SvgGroupModel; + } + protected override bool ShouldRender() { if (_shouldRender) @@ -83,7 +91,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) if (_isSvg) { - // Todo + builder.AddAttribute(3, "transform", FormattableString.Invariant($"translate({Group.Position.X} {Group.Position.Y})")); } else { @@ -99,8 +107,18 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.AddAttribute(10, "ontouchend", EventCallback.Factory.Create(this, OnTouchEnd)); builder.AddEventStopPropagationAttribute(11, "ontouchend", true); builder.AddEventPreventDefaultAttribute(12, "ontouchend", true); - builder.OpenComponent(13, componentType); - builder.AddAttribute(14, "Group", Group); + + if (_isSvg) + { + builder.OpenElement(13, "rect"); + builder.AddAttribute(14, "width", Group.Size!.Width); + builder.AddAttribute(15, "height", Group.Size.Height); + builder.AddAttribute(16, "fill", "none"); + builder.CloseElement(); + } + + builder.OpenComponent(17, componentType); + builder.AddAttribute(18, "Group", Group); builder.CloseComponent(); builder.CloseElement(); } diff --git a/src/Blazor.Diagrams/Models/SvgGroupModel.cs b/src/Blazor.Diagrams/Models/SvgGroupModel.cs new file mode 100644 index 000000000..1d994454f --- /dev/null +++ b/src/Blazor.Diagrams/Models/SvgGroupModel.cs @@ -0,0 +1,10 @@ +using Blazor.Diagrams.Core.Models; +using System.Collections.Generic; + +namespace Blazor.Diagrams.Models +{ + public class SvgGroupModel : GroupModel + { + public SvgGroupModel(IEnumerable children, byte padding = 30) : base(children, padding) { } + } +} diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.css b/src/Blazor.Diagrams/wwwroot/default.styles.css index e4de8dbf6..ae3d72bf1 100644 --- a/src/Blazor.Diagrams/wwwroot/default.styles.css +++ b/src/Blazor.Diagrams/wwwroot/default.styles.css @@ -5,17 +5,17 @@ background-color: #f5f5f5; border: 1px solid #e8e8e8; -webkit-box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0,0,0,.12); - box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0,0,0,.12); + box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px 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; + -ms-flex-align: center; + align-items: center; -webkit-box-pack: center; - -ms-flex-pack: center; - justify-content: center; + -ms-flex-pack: center; + justify-content: center; } .default-node.selected { @@ -109,12 +109,12 @@ text-align: center; font-size: 0.875rem; -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - 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%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); } -/*# sourceMappingURL=wwwroot\default.styles.css.map */ \ No newline at end of file +/*# sourceMappingURL=wwwroot\default.styles.css.map */ From 994fd7638775fc17afdcd3607579dcea934a421b Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 13 Aug 2022 14:07:28 +0100 Subject: [PATCH 042/193] Use butt stroke linecap instead of round Because they go inside ports and prevent pointer events --- src/Blazor.Diagrams/Components/LinkWidget.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor b/src/Blazor.Diagrams/Components/LinkWidget.razor index d5dc90653..f8615561e 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor @@ -29,7 +29,7 @@ stroke="@color" stroke-width="12" d="@result.Paths[i]" - stroke-linecap="round" + stroke-linecap="butt" stroke-opacity="0" fill="none" @onmousedown="e => OnMouseDown(e, index)" From ec03d3ff0a09c5df5c7dda288f0f13547f9183d8 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 13 Aug 2022 14:09:18 +0100 Subject: [PATCH 043/193] Add custom svg group demo --- .../CustomSvgGroup/CustomSvgGroupModel.cs | 13 +++++ .../CustomSvgGroup/CustomSvgGroupWidget.razor | 20 ++++++++ .../Demos/CustomSvgGroup/Demo.razor | 7 +++ .../Demos/CustomSvgGroup/Demo.razor.cs | 45 ++++++++++++++++++ samples/SharedDemo/Layouts/DemoLayout.razor | 3 ++ samples/SharedDemo/wwwroot/css/styles.css | 35 +++++++++----- .../wwwroot/default.styles.css | 13 ++++- .../wwwroot/default.styles.min.css | 2 +- .../wwwroot/default.styles.min.css.gz | Bin 636 -> 664 bytes 9 files changed, 122 insertions(+), 16 deletions(-) create mode 100644 samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupModel.cs create mode 100644 samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupWidget.razor create mode 100644 samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor create mode 100644 samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs diff --git a/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupModel.cs b/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupModel.cs new file mode 100644 index 000000000..7a88a16cc --- /dev/null +++ b/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupModel.cs @@ -0,0 +1,13 @@ +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Models; + +namespace SharedDemo.Demos.CustomSvgGroup +{ + public class CustomSvgGroupModel : SvgGroupModel + { + public CustomSvgGroupModel(NodeModel[] children, string title, byte padding = 30) : base(children, padding) + { + Title = title; + } + } +} diff --git a/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupWidget.razor b/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupWidget.razor new file mode 100644 index 000000000..7e3535231 --- /dev/null +++ b/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupWidget.razor @@ -0,0 +1,20 @@ +@{ + var rectX = Group.Size.Width / 2 - 50; + var textX = rectX + 50; + var textY = -60 + 25.0; +} + + +@Group.Title + + + +@foreach (var port in Group.Ports) +{ + +} + + +@code { + [Parameter] public CustomSvgGroupModel Group { get; set; } +} diff --git a/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor new file mode 100644 index 000000000..e16a0214b --- /dev/null +++ b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor @@ -0,0 +1,7 @@ +@page "/demos/custom-svg-group" +@layout DemoLayout +@inject LayoutData LayoutData + + + + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs new file mode 100644 index 000000000..89e795684 --- /dev/null +++ b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs @@ -0,0 +1,45 @@ +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Models; +using SharedDemo.Demos.Nodes; + +namespace SharedDemo.Demos.CustomSvgGroup +{ + partial class Demo + { + private readonly Diagram _diagram = new Diagram(); + + protected override void OnInitialized() + { + base.OnInitialized(); + + LayoutData.Title = "Custom SVG group"; + LayoutData.Info = "Creating your own custom svg groups is very easy!"; + LayoutData.DataChanged(); + + _diagram.RegisterModelComponent(); + _diagram.RegisterModelComponent(); + + var node1 = NewNode(50, 50); + var node2 = NewNode(300, 300); + var node3 = NewNode(500, 100); + + _diagram.Nodes.Add(new[] { node1, node2, node3 }); + _diagram.AddGroup(new CustomSvgGroupModel(new[] { node2, node3 }, "Group 1")); + + _diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + _diagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + } + + private NodeModel NewNode(double x, double y) + { + var node = new SvgNodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; + } + } +} diff --git a/samples/SharedDemo/Layouts/DemoLayout.razor b/samples/SharedDemo/Layouts/DemoLayout.razor index 516906f42..97e016fd9 100644 --- a/samples/SharedDemo/Layouts/DemoLayout.razor +++ b/samples/SharedDemo/Layouts/DemoLayout.razor @@ -94,6 +94,9 @@
  • Custom group
  • +
  • + Custom SVG group +
  • diff --git a/samples/SharedDemo/wwwroot/css/styles.css b/samples/SharedDemo/wwwroot/css/styles.css index c09329209..63c008968 100644 --- a/samples/SharedDemo/wwwroot/css/styles.css +++ b/samples/SharedDemo/wwwroot/css/styles.css @@ -1616,20 +1616,29 @@ body { background: #40babd !important; } -.group:not(.default) { +div.group:not(.default) { outline: 2px solid black; background-color: #6fbb6e; } - .group:not(.default) > span.title { - padding: 20px; - position: absolute; - left: 50%; - transform: translate(-50%, -50%); - background: #eee; - border: 2px solid black; - border-radius: 50%; - background-color: #6fbb6e; - font-weight: bold; - text-transform: uppercase; - } +div.group:not(.default) > span.title { + padding: 20px; + position: absolute; + left: 50%; + transform: translate(-50%, -50%); + background: #eee; + border: 2px solid black; + border-radius: 50%; + background-color: #6fbb6e; + font-weight: bold; + text-transform: uppercase; +} + +g.group:not(.default) rect { + outline: 2px solid black; +} + +g.group:not(.default) text { + text-anchor: middle; + dominant-baseline: middle; +} \ 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 ae3d72bf1..545b934a5 100644 --- a/src/Blazor.Diagrams/wwwroot/default.styles.css +++ b/src/Blazor.Diagrams/wwwroot/default.styles.css @@ -91,15 +91,24 @@ background-color: white; } -.group.default { +div.group.default { outline: 2px solid black; background: rgb(198, 198, 198); } - .group.default.selected { + div.group.default.selected { outline: 2px solid #6e9fd4; } +g.group.default rect { + outline: 2px solid black; + fill: rgb(198, 198, 50); +} + +g.group.default.selected rect { + outline: 2px solid green; +} + .link .link-label { display: inline-block; color: #fff; diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css b/src/Blazor.Diagrams/wwwroot/default.styles.min.css index 30f8a6c81..c7cf8098f 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 .port{border:1px solid #6e9fd4;}.default-node .port,.default.group .port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .port:hover,.default-node .port.has-links,.default.group .port.has-links{background-color:#000;}.default-node .port.bottom,.default.group .port.bottom{bottom:0;left:50%;}.default-node .port.bottomleft,.default.group .port.bottomleft{bottom:0;left:0;}.default-node .port.bottomright,.default.group .port.bottomright{bottom:0;right:0;}.default-node .port.top,.default.group .port.top{top:0;left:50%;}.default-node .port.topleft,.default.group .port.topleft{top:0;left:0;}.default-node .port.topright,.default.group .port.topright{top:0;right:0;}.default-node .port.left,.default.group .port.left{left:0;top:50%;}.default-node .port.right,.default.group .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;}.group.default{outline:2px solid #000;background:#c6c6c6;}.group.default.selected{outline:2px solid #6e9fd4;}.link .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 .port{border:1px solid #6e9fd4;}.default-node .port,.default.group .port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .port:hover,.default-node .port.has-links,.default.group .port.has-links{background-color:#000;}.default-node .port.bottom,.default.group .port.bottom{bottom:0;left:50%;}.default-node .port.bottomleft,.default.group .port.bottomleft{bottom:0;left:0;}.default-node .port.bottomright,.default.group .port.bottomright{bottom:0;right:0;}.default-node .port.top,.default.group .port.top{top:0;left:50%;}.default-node .port.topleft,.default.group .port.topleft{top:0;left:0;}.default-node .port.topright,.default.group .port.topright{top:0;right:0;}.default-node .port.left,.default.group .port.left{left:0;top:50%;}.default-node .port.right,.default.group .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.group.default{outline:2px solid #000;background:#c6c6c6;}div.group.default.selected{outline:2px solid #6e9fd4;}g.group.default rect{outline:2px solid #000;fill:#c6c632;}g.group.default.selected rect{outline:2px solid #008000;}.link .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 diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz b/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz index ad48379e94c95598d296ead332e389841054bceb..f56f17dce2384618ebf28546f8f94b8258110223 100644 GIT binary patch literal 664 zcmV;J0%!dniwFP!000003bj>hj@uv*zDlH3rHvFU$JuPN_%zsz9U2T4;B0oIoUA{3 zq+X$b?bu#F(*CHBAI#(9n_&ii|NIqD$VkNv5|YC8ol$ed+b}FI@ex>lH2C2D6R9XD zqzGk|wt6Qb=_yyT5)`G9ONF1Zy&H`!-j6w8NFNE6?>JmV z*36kA_fmO6p2LkBLA2h4UgMr{pxVB)y4_e8tu2H9yg(^6GfWDsfD^;c5L2d0PCjri z#!gmflySK9Wo|x52;nRjI0a!q#jVFs>L-X9DD>U1hfvxL83FW#KPqil_F=cnPK9a3 zw?PR68aSi|=ykl0uke=9mpO-3P)cR~)~&YQw6egJQrSS%RT%Y((Ek@iiU{aVAcBGOSL09a9a%aB{UE zk;cek8nD*cjJppGV-A_|saQaAHcMzu{{?Z8nR5HH1n3PqL_eK_G_stAvWlyH9>!>G z<^yb$eV}g#Q{b8PiXs@8SXS$ghxR~J`k=e5yY4!;?IDGdu?G;gJi&h7Mg%n`#2>CfB y)nnmEP%we&Q+n55!zdzjCRKs`pS>I58QGHDxYycaKQMWqzWo7^TkT)62mk>8c0!Q= literal 636 zcmV-?0)zb@iwFP!000003bj>jj+-zLy-K90(ng9AA;~stPGj%@Zj6m=lVmsAll4cB z)GO3B5XeGE`=hb|dp@4u*fYcLpT7bM8L7EJLQ=S%88rpogkg1w3t)L+@WIU!sVFF< z2xYaldM6_3F;}t{6s3|&g_qghjn)=#s>?!4&gfzZ2RCALhUCZ$O5_FUf>3$J;UcnT z&J?+q$`kSuuH6Wt)jISV_k?$<&92jJ$GT``8T{uBDyf-aQeXv~7$?LXoaF+iAPlIu^SGD#1!4wDeKQ;&RQ5tf07K!AS{s(V+v~DRVLI{W zT?zymIHU&Xb$pMn@RrftBZoy$NoD@lt+8HrvcRQM)j~8`7!8up&r70mCNSEJGjbn( z`#s$m#T?iM7{ z7+FpOHadIa?t{aaLuUL@%pf_NIW(vLf;h`ex%-&|^aeemUp|5~vYLjnimQE|#%OIG z2iPiyK#zIM#MNnApxKBXv+-0~5)SBLOfyUx8XbWF3M089B@*Psa$=eg-*jw9*P|8H!ROv(+W|`&WdEa=*+SpP69QF9F1bSz@Okdsj z>e=_F35Z*#ab>pfZ^TIg{Mz+hCY%pSxJ-}nfZv#Pi*+Av5bYI|w}9KFfs46s)tPOv zNV5;X!Qs2N={j|-Em{);7E*w1)$(IXs>i~Spkx9y&2l^3!zdzjCRK_3%>JU_1=-A4 WyVuHNKQVct{`><@%vDc02mk<(Av8$< From 85df470d8e71ab0ee277f85323cbe4256eeb3b76 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 13 Aug 2022 15:27:09 +0100 Subject: [PATCH 044/193] Fix unit tests --- .../Behaviors/DragNewLinkBehaviorTests.cs | 38 +++++++++++-------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index 1f933c3ac..8f9f293bb 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -15,6 +15,7 @@ public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnP { // Arrange var diagram = new DiagramBase(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); var node = new NodeModel(position: new Point(100, 50)); var port = node.AddPort(new PortModel(node) { @@ -24,7 +25,7 @@ public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnP }); // Act - diagram.OnMouseDown(port, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.OnMouseDown(port, new MouseEventArgs(100, 100, 0, 0, false, false, false)); // Assert var link = diagram.Links.Single(); @@ -33,9 +34,8 @@ public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnP link.Target.Should().BeNull(); source!.Port.Should().BeSameAs(port); link.OnGoingPosition.Should().NotBeNull(); - var sourcePosition = source.GetPosition(link)!; - link.OnGoingPosition!.X.Should().Be(sourcePosition.X); - link.OnGoingPosition.Y.Should().Be(sourcePosition.Y); + link.OnGoingPosition!.X.Should().Be(95); + link.OnGoingPosition.Y.Should().Be(95); } [Fact] @@ -43,6 +43,7 @@ public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() { // Arrange var diagram = new DiagramBase(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); var factoryCalled = false; diagram.Options.Links.Factory = (d, sp) => { @@ -58,7 +59,7 @@ public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() }); // Act - diagram.OnMouseDown(port, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.OnMouseDown(port, new MouseEventArgs(100, 100, 0, 0, false, false, false)); // Assert factoryCalled.Should().BeTrue(); @@ -68,9 +69,8 @@ public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() link.Target.Should().BeNull(); source!.Port.Should().BeSameAs(port); link.OnGoingPosition.Should().NotBeNull(); - var sourcePosition = source.GetPosition(link)!; - link.OnGoingPosition!.X.Should().Be(sourcePosition.X); - link.OnGoingPosition.Y.Should().Be(sourcePosition.Y); + link.OnGoingPosition!.X.Should().Be(95); + link.OnGoingPosition.Y.Should().Be(95); } [Fact] @@ -78,6 +78,7 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() { // Arrange var diagram = new DiagramBase(); + 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) @@ -95,9 +96,8 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() // Assert var source = link.Source as SinglePortAnchor; - var sourcePosition = source!.GetPosition(link)!; - link.OnGoingPosition!.X.Should().Be(sourcePosition.X + 50); - link.OnGoingPosition.Y.Should().Be(sourcePosition.Y + 50); + link.OnGoingPosition!.X.Should().Be(145); + link.OnGoingPosition.Y.Should().Be(145); linkRefreshed.Should().BeTrue(); } @@ -106,6 +106,7 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoom { // Arrange var diagram = new DiagramBase(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); diagram.SetZoom(1.5); var node = new NodeModel(position: new Point(100, 50)); var linkRefreshed = false; @@ -124,9 +125,8 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoom // Assert var source = link.Source as SinglePortAnchor; - var sourcePosition = source!.GetPosition(link)!; - link.OnGoingPosition!.X.Should().Be(sourcePosition.X + 40); - link.OnGoingPosition.Y.Should().Be(sourcePosition.Y + 40); + link.OnGoingPosition!.X.Should().BeApproximately(101.6, 0.1); + link.OnGoingPosition.Y.Should().BeApproximately(101.6, 0.1); linkRefreshed.Should().BeTrue(); } @@ -135,6 +135,7 @@ public void Behavior_ShouldSnapToClosestPortAndRefreshPort_WhenSnappingIsEnabled { // Arrange var diagram = new DiagramBase(); + 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)); @@ -157,7 +158,7 @@ public void Behavior_ShouldSnapToClosestPortAndRefreshPort_WhenSnappingIsEnabled // Act diagram.OnMouseDown(port1, new MouseEventArgs(100, 100, 0, 0, false, false, false)); - diagram.OnMouseMove(null, new MouseEventArgs(105, 105, 0, 0, false, false, false)); + diagram.OnMouseMove(null, new MouseEventArgs(140, 100, 0, 0, false, false, false)); // Assert var link = diagram.Links.Single(); @@ -172,6 +173,7 @@ public void Behavior_ShouldNotSnapToPort_WhenSnappingIsEnabledAndPortIsNotInRadi { // Arrange var diagram = new DiagramBase(); + 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)); @@ -204,6 +206,7 @@ public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNo { // Arrange var diagram = new DiagramBase(); + 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)); @@ -226,7 +229,7 @@ public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNo // Act diagram.OnMouseDown(port1, new MouseEventArgs(100, 100, 0, 0, false, false, false)); - diagram.OnMouseMove(null, new MouseEventArgs(105, 105, 0, 0, false, false, false)); // Move towards the other port + diagram.OnMouseMove(null, new MouseEventArgs(140, 100, 0, 0, false, false, false)); // Move towards the other port diagram.OnMouseMove(null, new MouseEventArgs(100, 100, 0, 0, false, false, false)); // Move back to unsnap // Assert @@ -241,6 +244,7 @@ public void Behavior_ShouldRemoveLink_WhenMouseUpOnCanvas() { // Arrange var diagram = new DiagramBase(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); var node = new NodeModel(position: new Point(100, 50)); var port = node.AddPort(new PortModel(node) { @@ -262,6 +266,7 @@ public void Behavior_ShouldRemoveLink_WhenMouseUpOnSamePort() { // Arrange var diagram = new DiagramBase(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); var node = new NodeModel(position: new Point(100, 50)); var port = node.AddPort(new PortModel(node) { @@ -283,6 +288,7 @@ public void Behavior_ShouldSetTarget_WhenMouseUp() { // Arrange var diagram = new DiagramBase(); + 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 }); From b9d1b916d12fcecb4b7a5d1a09d242bd057b1ee9 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 13 Aug 2022 19:50:56 +0100 Subject: [PATCH 045/193] Add locked, selected & grouped classes to node container directly --- src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs | 2 +- src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs index 4905ec160..c76de3580 100644 --- a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs @@ -86,7 +86,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) .AppendIf(" default", componentType == typeof(DefaultGroupWidget)); builder.OpenElement(0, _isSvg ? "g" : "div"); - builder.AddAttribute(1, "class", classes); + builder.AddAttribute(1, "class", classes.ToString()); builder.AddAttribute(2, "data-group-id", Group.Id); if (_isSvg) diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index 4acee4888..3ae7441ed 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Components.Web; using Microsoft.JSInterop; using System; +using System.Text; using System.Threading.Tasks; namespace Blazor.Diagrams.Components.Renderers @@ -95,9 +96,13 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) return; var componentType = Diagram.GetComponentForModel(Node) ?? (_isSvg ? typeof(SvgNodeWidget) : typeof(NodeWidget)); + var classes = new StringBuilder("node") + .AppendIf(" locked", Node.Locked) + .AppendIf(" selected", Node.Selected) + .AppendIf(" grouped", Node.Group != null); builder.OpenElement(0, _isSvg ? "g" : "div"); - builder.AddAttribute(1, "class", $"node{(Node.Locked ? " locked" : string.Empty)}"); + builder.AddAttribute(1, "class", classes.ToString()); builder.AddAttribute(2, "data-node-id", Node.Id); if (_isSvg) From a5077a25d90b6bf150abb03398142c78ccd8ea3d Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 13 Aug 2022 20:19:34 +0100 Subject: [PATCH 046/193] Add @key when looping through ports --- src/Blazor.Diagrams/Components/NodeWidget.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams/Components/NodeWidget.razor b/src/Blazor.Diagrams/Components/NodeWidget.razor index 25c6515b3..ef1a8fbcc 100644 --- a/src/Blazor.Diagrams/Components/NodeWidget.razor +++ b/src/Blazor.Diagrams/Components/NodeWidget.razor @@ -2,11 +2,11 @@ @(Node.Title ?? "Title") @foreach (var port in Node.Ports) { - + }
  • @code { [Parameter] - public NodeModel Node { get; set; } + public NodeModel Node { get; set; } = null!; } \ No newline at end of file From c7198844202e3f2eb17ed8319e1d6c9f8edd6097 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 13 Aug 2022 20:21:28 +0100 Subject: [PATCH 047/193] Add @key when looping through ports --- samples/SharedDemo/Demos/BotAnswerWidget.razor | 2 +- .../SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupWidget.razor | 2 +- src/Blazor.Diagrams/Components/DefaultGroupWidget.razor | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/samples/SharedDemo/Demos/BotAnswerWidget.razor b/samples/SharedDemo/Demos/BotAnswerWidget.razor index a2c6ad938..b630fc442 100644 --- a/samples/SharedDemo/Demos/BotAnswerWidget.razor +++ b/samples/SharedDemo/Demos/BotAnswerWidget.razor @@ -15,7 +15,7 @@ @foreach (var port in Node.Ports) { - + }
    diff --git a/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupWidget.razor b/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupWidget.razor index 7e3535231..5fba8be18 100644 --- a/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupWidget.razor +++ b/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupWidget.razor @@ -11,7 +11,7 @@ @foreach (var port in Group.Ports) { - + } diff --git a/src/Blazor.Diagrams/Components/DefaultGroupWidget.razor b/src/Blazor.Diagrams/Components/DefaultGroupWidget.razor index 9b2e5e008..0d04b2789 100644 --- a/src/Blazor.Diagrams/Components/DefaultGroupWidget.razor +++ b/src/Blazor.Diagrams/Components/DefaultGroupWidget.razor @@ -7,5 +7,5 @@ @foreach (var port in Group.Ports) { - + } From f47824163a5228f423968d1d8510e5b663f36ba1 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 13 Aug 2022 20:33:31 +0100 Subject: [PATCH 048/193] Swallow ObjectDisposedException in ObserveResizes --- .../Components/DiagramCanvas.razor.cs | 2 +- .../Extensions/JSRuntimeExtensions.cs | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index 1fd3f3612..7e0cfd1e2 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -46,7 +46,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (firstRender) { Diagram.SetContainer(await JSRuntime.GetBoundingClientRect(elementReference)); - await JSRuntime.ObserveResizes(elementReference, _reference); + await JSRuntime.ObserveResizes(elementReference, _reference!); } } diff --git a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs index 137c84d72..2c1d4fe1a 100644 --- a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs +++ b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs @@ -1,6 +1,7 @@ using Blazor.Diagrams.Core.Geometry; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; +using System; using System.Threading.Tasks; namespace Blazor.Diagrams.Extensions @@ -15,7 +16,18 @@ public static async Task GetBoundingClientRect(this IJSRuntime jsRunt public static async Task ObserveResizes(this IJSRuntime jsRuntime, ElementReference element, DotNetObjectReference reference) where T : class { - await jsRuntime.InvokeVoidAsync("ZBlazorDiagrams.observe", element, reference, element.Id); + try + { + await jsRuntime.InvokeVoidAsync("ZBlazorDiagrams.observe", element, reference, element.Id); + } + catch (ObjectDisposedException) + { + // Ignore, DotNetObjectReference was likely disposed + } + catch + { + throw; + } } public static async Task UnobserveResizes(this IJSRuntime jsRuntime, ElementReference element) From 573df5ea8496ec95e3b2590bfb2024b101bd8a56 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 14 Aug 2022 12:30:39 +0100 Subject: [PATCH 049/193] SVG nested groups --- .../SharedDemo/Demos/Groups/Grouping.razor.cs | 1 - .../SharedDemo/Demos/Nodes/SvgDemo.razor.cs | 17 ++++++++++++----- src/Blazor.Diagrams.Core/DiagramBase.cs | 3 ++- .../Components/DiagramCanvas.razor | 2 +- src/Blazor.Diagrams/wwwroot/default.styles.css | 2 +- .../wwwroot/default.styles.min.css | 2 +- .../wwwroot/default.styles.min.css.gz | Bin 664 -> 666 bytes 7 files changed, 17 insertions(+), 10 deletions(-) diff --git a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs index 67af65ba8..fe10df7c1 100644 --- a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs @@ -16,7 +16,6 @@ protected override void OnInitialized() diagram.Options.Groups.Enabled = true; diagram.Options.LinksLayerOrder = 2; diagram.Options.NodesLayerOrder = 1; - diagram.Options.Groups.Enabled = true; var node1 = NewNode(50, 50); var node2 = NewNode(250, 250); var node3 = NewNode(500, 100); diff --git a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs index 93230b19f..54c26c037 100644 --- a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs @@ -27,13 +27,20 @@ private void InitializeDiagram() _diagram.RegisterModelComponent(); _diagram.RegisterModelComponent(); - var node1 = NewNode(80, 80); - var node2 = NewNode(280, 150); - _diagram.Nodes.Add(node1); - _diagram.Nodes.Add(node2); + var node1 = NewNode(50, 50); + var node2 = NewNode(250, 250); + var node3 = NewNode(500, 100); + var node4 = NewNode(700, 350); + _diagram.Nodes.Add(new[] { node1, node2, node3, node4 }); + _diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + _diagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + _diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + + var group1 = _diagram.AddGroup(new SvgGroupModel(new[] { node1, node2 })); + var group2 = _diagram.AddGroup(new SvgGroupModel(new[] { group1, node3 })); - _diagram.AddGroup(new SvgGroupModel(new[] { node1, node2 })); + _diagram.Links.Add(new LinkModel(group2, node4)); } private NodeModel NewNode(double x, double y, bool svg = true) diff --git a/src/Blazor.Diagrams.Core/DiagramBase.cs b/src/Blazor.Diagrams.Core/DiagramBase.cs index a3bc567ae..4957a62e4 100644 --- a/src/Blazor.Diagrams.Core/DiagramBase.cs +++ b/src/Blazor.Diagrams.Core/DiagramBase.cs @@ -109,7 +109,7 @@ public GroupModel Group(params NodeModel[] children) /// Adds the group to the diagram after validating it. /// /// A group instance. - public void AddGroup(GroupModel group) + public GroupModel AddGroup(GroupModel group) { foreach (var child in group.Children) { @@ -126,6 +126,7 @@ public void AddGroup(GroupModel group) _groups.Add(group); GroupAdded?.Invoke(group); Refresh(); + return group; } /// diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor b/src/Blazor.Diagrams/Components/DiagramCanvas.razor index ff8872779..4c0e2fc4a 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor @@ -20,7 +20,7 @@ } - @foreach (var group in Diagram.Groups.OfType()) + @foreach (var group in Diagram.Groups.OfType().Where(n => n.Group == null)) { } diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.css b/src/Blazor.Diagrams/wwwroot/default.styles.css index 545b934a5..fe1e2bd04 100644 --- a/src/Blazor.Diagrams/wwwroot/default.styles.css +++ b/src/Blazor.Diagrams/wwwroot/default.styles.css @@ -105,7 +105,7 @@ g.group.default rect { fill: rgb(198, 198, 50); } -g.group.default.selected rect { +g.group.default.selected > rect { outline: 2px solid green; } diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css b/src/Blazor.Diagrams/wwwroot/default.styles.min.css index c7cf8098f..76dc92a09 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 .port{border:1px solid #6e9fd4;}.default-node .port,.default.group .port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .port:hover,.default-node .port.has-links,.default.group .port.has-links{background-color:#000;}.default-node .port.bottom,.default.group .port.bottom{bottom:0;left:50%;}.default-node .port.bottomleft,.default.group .port.bottomleft{bottom:0;left:0;}.default-node .port.bottomright,.default.group .port.bottomright{bottom:0;right:0;}.default-node .port.top,.default.group .port.top{top:0;left:50%;}.default-node .port.topleft,.default.group .port.topleft{top:0;left:0;}.default-node .port.topright,.default.group .port.topright{top:0;right:0;}.default-node .port.left,.default.group .port.left{left:0;top:50%;}.default-node .port.right,.default.group .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.group.default{outline:2px solid #000;background:#c6c6c6;}div.group.default.selected{outline:2px solid #6e9fd4;}g.group.default rect{outline:2px solid #000;fill:#c6c632;}g.group.default.selected rect{outline:2px solid #008000;}.link .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 .port{border:1px solid #6e9fd4;}.default-node .port,.default.group .port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .port:hover,.default-node .port.has-links,.default.group .port.has-links{background-color:#000;}.default-node .port.bottom,.default.group .port.bottom{bottom:0;left:50%;}.default-node .port.bottomleft,.default.group .port.bottomleft{bottom:0;left:0;}.default-node .port.bottomright,.default.group .port.bottomright{bottom:0;right:0;}.default-node .port.top,.default.group .port.top{top:0;left:50%;}.default-node .port.topleft,.default.group .port.topleft{top:0;left:0;}.default-node .port.topright,.default.group .port.topright{top:0;right:0;}.default-node .port.left,.default.group .port.left{left:0;top:50%;}.default-node .port.right,.default.group .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.group.default{outline:2px solid #000;background:#c6c6c6;}div.group.default.selected{outline:2px solid #6e9fd4;}g.group.default rect{outline:2px solid #000;fill:#c6c632;}g.group.default.selected>rect{outline:2px solid #008000;}.link .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 diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz b/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz index f56f17dce2384618ebf28546f8f94b8258110223..e9f866f0a82ea904bf517b461a45cecc4eb7f1f3 100644 GIT binary patch delta 642 zcmV-|0)73M1)2qaABzY80000001CBLYi{Ep5WY&JXr)F9mg6*Silw~^He-hk1`BYS zHtNat#~x{~uz>B@t{>g~u|j?@kB@JL8TkG4S3n^n6*ovo3b$8A%?alvUd5ort98T**pMlu9lYUS|h48e6<8uPZH6IissJ9Nmb~DbL zQ>IH!-f%C*PF84?ak%!8(ItOox64k2X~iFd5(qSKNDa{2cpsnOC8LjX4y&M)%KWWc zZM|(}fh(o5fvBr6>J_2?FNn&Sz-TwF$V2$;UkZ{wx)G<9($=n&%qQZ$t0b{@m4Qjh z_#`i&wiBA*L^R?|ob_Z_o%%MW8iwKIYC$56k;OD%t+N?-9~{OUGUI<@RR@&>M7!emVzfWH}9G6*v2Q8KbqC53o`8f#!KE#MNpWpv8#h*?1}q2?sPA z(*l#4MoXZF!bq-2fdsj*oEWKE@7uV>o1O7ZRU`HK3-7z8c}d7I2|r&22WwjrN}0&( zE}dw@EQ>!VyVTV0RAhf;Y&QUox+Po}bpCjqKDjaB+?`SrDD8QZ4}Dh3dd#hwG0yAt z_R+}mzS{SBh-2NsuD@6QjW|hw-`WG83Fm_nF4J?|OUyW34gF!BgXo~3xM#YV8n~L_ z9g*3y9clIsI5<9goA!#Rv_-1}!9ohKcZ7VOlIpQ=Bq*3b^(iL3@2_DL5jvBq!2ZwP cjqreMNp9V15WY&JRi%v-EXUbwv-mXFj2#*b7T|1l zqnxZidZb>VfbG~`KhplFkRQzBHd4HA;V^_@|3#M>||FYysrel+;t z{u8MvD5MBwm9}~(BIzktvJw=fl1qi3vb`IPE#8)wm6n`R(bW?i+=$UTBqwH2A}>fE z36<|STt(K*nIiX6c|xAUjT=F<-h^J`o^YVrzO=gCSQo7=ga5ohDK#@p3ao$=!_E*> zrb|vfa4*JAR%n!Qxb%_IC4XVJ%T9%9#kWBT1R6M`2IzIXkFW5S(U&=gRZvQ0{?@Iw z-n6p7l~UP2)KwVuiqQWTMCD9iv>jLEKK%AC1xa7rh|@}GYgbC<6LH^Fl32USz$9gS zlxI+z2~BV$8gV90dNQm|eH~K`!*Fu7Ad$w%Vj8g4*^Ijn4r30P@qejUKyo%qXionH zagmvF`?Cb-4LU?Wor5&8oQATBt9>5EXl>>LY?OVVc^(UKwb}+~F`{`ko=QW)0nNs= zz@(85^_kwuUEm|+LnY;Ci1#V zC)zN};u~e>n);oJtbdH{2Eb9bgzJLNA3vq9ZcI3Lr_=;Wd*0+jpOvy6bE{^I^Lo8q zH1h0@xOs>}-NCNESN@GSNq}G51D^@!gAy*&Q`}3;I9(0>VV;9%ub{YRx|tfdnBg6f z*|QyK_6ayReD^l(6;Wx6RtJKG6kzWN`8g%kW8p|pFoEh*dL`Fi!zdzjCRKs`pS>I5 a8QGHDxYycaKQMWqzWo7^TkT)62mk=EaX1VB From 385d35198a6bae56349671a3f67b5a0b3f11db25 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 14 Aug 2022 15:23:42 +0100 Subject: [PATCH 050/193] Add DynamicAnchor An anchor that chooses the closest position from the given list. Positions given are based on the bounds of the node. --- src/Blazor.Diagrams.Core/Anchors/Anchor.cs | 34 +++++++++++ .../Anchors/DynamicAnchor.cs | 58 +++++++++++++++++++ .../Anchors/ShapeIntersectionAnchor.cs | 33 ----------- src/Blazor.Diagrams.Core/Layers/LinkLayer.cs | 6 +- .../PathGenerators/PathGenerators.Smooth.cs | 2 +- 5 files changed, 96 insertions(+), 37 deletions(-) create mode 100644 src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs diff --git a/src/Blazor.Diagrams.Core/Anchors/Anchor.cs b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs index 035c9dcbf..ae14c8adb 100644 --- a/src/Blazor.Diagrams.Core/Anchors/Anchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs @@ -2,6 +2,7 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using System; +using System.Collections.Generic; namespace Blazor.Diagrams.Core.Anchors { @@ -19,5 +20,38 @@ public Anchor(NodeModel node, Point? offset = null) public abstract Point? GetPosition(BaseLinkModel link, Point[] route); public Point? GetPosition(BaseLinkModel link) => GetPosition(link, Array.Empty()); + + protected static Point? GetOtherPosition(BaseLinkModel link, bool isTarget) + { + if (!isTarget && link.Target == null) + return link.OnGoingPosition; + + var anchor = isTarget ? link.Source : link.Target!; + return anchor switch + { + SinglePortAnchor spa => spa.Port.MiddlePosition, + ShapeIntersectionAnchor sia => sia.Node.GetBounds()?.Center ?? null, + DynamicAnchor da => da.Node.GetBounds()?.Center ?? null, + _ => throw new DiagramsException($"Unhandled Anchor type {anchor.GetType().Name} when trying to find intersection") + }; + } + + protected static Point? GetClosestPointTo(IEnumerable points, Point point) + { + var minDist = double.MaxValue; + Point? minPoint = null; + + foreach (var pt in points) + { + var dist = pt.DistanceTo(point); + if (dist < minDist) + { + minDist = dist; + minPoint = pt; + } + } + + return minPoint; + } } } diff --git a/src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs new file mode 100644 index 000000000..8ff17d24a --- /dev/null +++ b/src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs @@ -0,0 +1,58 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using System.Collections.Generic; +using System.Linq; + +namespace Blazor.Diagrams.Core.Anchors +{ + // Figure out a better name + // Generic? + public class DynamicAnchor : Anchor + { + public DynamicAnchor(NodeModel node, DynamicAnchorPosition[] positions, Point? offset = null) : base(node, offset) + { + Positions = positions; + } + + public DynamicAnchorPosition[] Positions { get; } + + public override Point? GetPosition(BaseLinkModel link, Point[] route) + { + if (Node.Size == null) + return null; + + var isTarget = link.Target == this; + var pt = route.Length > 0 ? route[isTarget ? ^1 : 0] : GetOtherPosition(link, isTarget); + return pt is null ? null : GetClosestPointTo(CalculatePositions(), pt); + } + + private IEnumerable CalculatePositions() + { + var bounds = Node.GetBounds()!; + return Positions.Select(e => + { + return new Point( + bounds.Left + e.Position.X * bounds.Width + (e.Offset?.X ?? 0), + bounds.Top + e.Position.Y * bounds.Height + (e.Offset?.Y ?? 0) + ); + }); + } + } + + public record DynamicAnchorPosition + { + public DynamicAnchorPosition(Point position, Point offset) + { + Position = position; + Offset = offset; + } + + public DynamicAnchorPosition(double x, double y) : this(new Point(x, y), Point.Zero) { } + + public DynamicAnchorPosition(double x, double y, double offsetX, double offsetY) : this(new Point(x, y), new Point(offsetX, offsetY)) { } + + public Point Position { get; } + public Point Offset { get; } + } +} diff --git a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs index 27bcab9d8..e2aa9314b 100644 --- a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs @@ -1,7 +1,6 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -using System.Collections.Generic; namespace Blazor.Diagrams.Core.Anchors { @@ -32,37 +31,5 @@ public ShapeIntersectionAnchor(NodeModel node, Point? offset = null) : base(node var intersections = Node.GetShape().GetIntersectionsWithLine(line); return GetClosestPointTo(intersections, pt); } - - private static Point? GetOtherPosition(BaseLinkModel link, bool isTarget) - { - if (!isTarget && link.Target == null) - return link.OnGoingPosition; - - var anchor = isTarget ? link.Source : link.Target!; - return anchor switch - { - SinglePortAnchor spa => spa.Port.MiddlePosition, - ShapeIntersectionAnchor sia => sia.Node.GetBounds()?.Center ?? null, - _ => throw new DiagramsException($"Unhandled Anchor type {anchor.GetType().Name} when trying to find intersection") - }; - } - - private static Point? GetClosestPointTo(IEnumerable points, Point point) - { - var minDist = double.MaxValue; - Point? minPoint = null; - - foreach (var pt in points) - { - var dist = pt.DistanceTo(point); - if (dist < minDist) - { - minDist = dist; - minPoint = pt; - } - } - - return minPoint; - } } } diff --git a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs index d719dd567..5266fb43d 100644 --- a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs @@ -58,15 +58,15 @@ private static void HandleAnchor(BaseLinkModel link, Anchor anchor, bool add) spa.Port.Refresh(); } - else if (anchor is ShapeIntersectionAnchor sia) + else if (anchor is ShapeIntersectionAnchor || anchor is DynamicAnchor) { if (add) { - sia.Node.AddLink(link); + anchor.Node.AddLink(link); } else { - sia.Node.RemoveLink(link); + anchor.Node.RemoveLink(link); } } else diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs index 00faa03f8..fdefbfd9d 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs @@ -82,7 +82,7 @@ private static Point GetCurvePoint(Point[] route, Anchor? anchor, double pX, dou { return GetCurvePoint(pX, pY, cX, cY, spa.Port.Alignment); } - else if (anchor is ShapeIntersectionAnchor) + else if (anchor is ShapeIntersectionAnchor || anchor is DynamicAnchor) { if (Math.Abs(route[0].X - route[1].X) >= Math.Abs(route[0].Y - route[1].Y)) { From 78f947dce0d2292248365847ef92126f5f96878b Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 14 Aug 2022 15:44:04 +0100 Subject: [PATCH 051/193] Add unit tests for DynamicAnchor --- .../Anchors/DynamicAnchorTests.cs | 196 ++++++++++++++++++ .../Blazor.Diagrams.Core.Tests.csproj | 4 - 2 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs new file mode 100644 index 000000000..f47fa81a3 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs @@ -0,0 +1,196 @@ +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using FluentAssertions; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Anchors +{ + public class DynamicAnchorTests + { + [Fact] + public void GetPosition_ShouldReturnNull_WhenNodesSizeIsNull() + { + // Arrange + var node = new NodeModel(position: new Point(120, 95)) + { + Size = null + }; + var positions = new[] + { + new DynamicAnchorPosition(0.0, 0.0), + new DynamicAnchorPosition(0.5, 0.0), + new DynamicAnchorPosition(1.0, 0.0), + new DynamicAnchorPosition(0.0, 0.5), + new DynamicAnchorPosition(0.5, 0.5), + new DynamicAnchorPosition(1.0, 0.5), + new DynamicAnchorPosition(0.0, 1.0), + new DynamicAnchorPosition(0.5, 1.0), + new DynamicAnchorPosition(1.0, 1.0) + }; + var anchor1 = new DynamicAnchor(node, positions); + var anchor2 = new DynamicAnchor(node, positions); + var link = new LinkModel(anchor1, anchor2); + + // Act + var position = anchor1.GetPosition(link); + + // Assert + position.Should().BeNull(); + } + + [Fact] + public void GetPosition_ShouldReturnClosestPositionToOtherNodesCenter_WhenRouteIsEmpty() + { + // Arrange + var node1 = new NodeModel(position: new Point(120, 95)) + { + Size = new Size(100, 60) + }; + var node2 = new NodeModel(position: new Point(200, 60)) + { + Size = new Size(100, 60) + }; + var positions = new[] + { + new DynamicAnchorPosition(0.0, 0.0), // 120,95 + new DynamicAnchorPosition(0.5, 0.0), // 170,95 + new DynamicAnchorPosition(1.0, 0.0), // 220,95 + new DynamicAnchorPosition(0.0, 0.5), // 120,125 + new DynamicAnchorPosition(0.5, 0.5), // 170,125 + new DynamicAnchorPosition(1.0, 0.5), // 220,125 + new DynamicAnchorPosition(0.0, 1.0), // 120,155 + new DynamicAnchorPosition(0.5, 1.0), // 170,155 + new DynamicAnchorPosition(1.0, 1.0) // 220,155 + }; + var anchor1 = new DynamicAnchor(node1, positions); + var anchor2 = new DynamicAnchor(node2, positions); + var link = new LinkModel(anchor1, anchor2); + + // Act + var position = anchor1.GetPosition(link); + + // Assert + position.Should().NotBeNull(); + position!.X.Should().Be(220); + position.Y.Should().Be(95); + } + + [Fact] + public void GetPosition_ShouldReturnClosestPositionToOtherNodesCenterWithOffset_WhenRouteIsEmpty() + { + // Arrange + var node1 = new NodeModel(position: new Point(120, 95)) + { + Size = new Size(100, 60) + }; + var node2 = new NodeModel(position: new Point(200, 60)) + { + Size = new Size(100, 60) + }; + var positions = new[] + { + new DynamicAnchorPosition(0.0, 0.0), // 120,95 + new DynamicAnchorPosition(0.5, 0.0), // 170,95 + new DynamicAnchorPosition(1.0, 0.0, 10, -10), // 230,85 + new DynamicAnchorPosition(0.0, 0.5), // 120,125 + new DynamicAnchorPosition(0.5, 0.5), // 170,125 + new DynamicAnchorPosition(1.0, 0.5), // 220,125 + new DynamicAnchorPosition(0.0, 1.0), // 120,155 + new DynamicAnchorPosition(0.5, 1.0), // 170,155 + new DynamicAnchorPosition(1.0, 1.0) // 220,155 + }; + var anchor1 = new DynamicAnchor(node1, positions); + var anchor2 = new DynamicAnchor(node2, positions); + var link = new LinkModel(anchor1, anchor2); + + // Act + var position = anchor1.GetPosition(link); + + // Assert + position.Should().NotBeNull(); + position!.X.Should().Be(230); + position.Y.Should().Be(85); + } + + [Fact] + public void GetPosition_ShouldReturnClosestPositionToFirstVertex_WhenRouteIsNotEmpty() + { + // Arrange + var node1 = new NodeModel(position: new Point(120, 95)) + { + Size = new Size(100, 60) + }; + var node2 = new NodeModel(position: new Point(300, 60)) + { + Size = new Size(100, 60) + }; + var positions = new[] + { + new DynamicAnchorPosition(0.0, 0.0), // 120,95 + new DynamicAnchorPosition(0.5, 0.0), // 170,95 + new DynamicAnchorPosition(1.0, 0.0), // 220,95 + new DynamicAnchorPosition(0.0, 0.5), // 120,125 + new DynamicAnchorPosition(0.5, 0.5), // 170,125 + new DynamicAnchorPosition(1.0, 0.5), // 220,125 + new DynamicAnchorPosition(0.0, 1.0), // 120,155 + new DynamicAnchorPosition(0.5, 1.0), // 170,155 + new DynamicAnchorPosition(1.0, 1.0) // 220,155 + }; + var anchor1 = new DynamicAnchor(node1, positions); + var anchor2 = new DynamicAnchor(node2, positions); + var link = new LinkModel(anchor1, anchor2); + + // Act + var position = anchor1.GetPosition(link, new Point[] + { + new Point(280, 115) // Vertex + }); + + // Assert + position.Should().NotBeNull(); + position!.X.Should().Be(220); + position.Y.Should().Be(125); + } + + [Fact] + public void GetPosition_ShouldReturnClosestPositionToLastVertex_WhenRouteIsNotEmptyAndIsTarget() + { + // Arrange + var node1 = new NodeModel(position: new Point(120, 95)) + { + Size = new Size(100, 60) + }; + var node2 = new NodeModel(position: new Point(300, 60)) + { + Size = new Size(100, 60) + }; + var positions = new[] + { + new DynamicAnchorPosition(0.0, 0.0), // 300, 60 + new DynamicAnchorPosition(0.5, 0.0), // 350, 60 + new DynamicAnchorPosition(1.0, 0.0), // 400, 60 + new DynamicAnchorPosition(0.0, 0.5), // 300, 90 + new DynamicAnchorPosition(0.5, 0.5), // 350, 90 + new DynamicAnchorPosition(1.0, 0.5), // 400, 90 + new DynamicAnchorPosition(0.0, 1.0), // 300, 120 + new DynamicAnchorPosition(0.5, 1.0), // 350, 120 + new DynamicAnchorPosition(1.0, 1.0) // 400, 120 + }; + var anchor1 = new DynamicAnchor(node1, positions); + var anchor2 = new DynamicAnchor(node2, positions); + var link = new LinkModel(anchor1, anchor2); + + // Act + var position = anchor2.GetPosition(link, new Point[] + { + new Point(280, 115) // Vertex + }); + + // Assert + position.Should().NotBeNull(); + position!.X.Should().Be(300); + position.Y.Should().Be(120); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj index 251ad7c14..8d8622a35 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj +++ b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj @@ -24,8 +24,4 @@ - - - - From 96542bb464c789cb5c1b7e0fb72ca7cf3157bf65 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 17 Aug 2022 21:04:05 +0100 Subject: [PATCH 052/193] Make DynamicAnchor more generic --- src/Blazor.Diagrams.Core/Anchors/Anchor.cs | 1 + .../Dynamic/BoundsBasedPositionProvider.cs | 27 +++++ .../Anchors/Dynamic/DynamicAnchor.cs | 36 +++++++ .../Dynamic/IDynamicAnchorPositionProvider.cs | 10 ++ .../Dynamic/ShapeAnglePositionProvider.cs | 25 +++++ .../Anchors/DynamicAnchor.cs | 58 ----------- src/Blazor.Diagrams.Core/Layers/LinkLayer.cs | 1 + .../PathGenerators/PathGenerators.Smooth.cs | 1 + .../Components/NavigatorWidget.razor | 11 ++- .../Extensions/JSRuntimeExtensions.cs | 4 +- .../Anchors/DynamicAnchorTests.cs | 99 ++++++++++--------- 11 files changed, 160 insertions(+), 113 deletions(-) create mode 100644 src/Blazor.Diagrams.Core/Anchors/Dynamic/BoundsBasedPositionProvider.cs create mode 100644 src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs create mode 100644 src/Blazor.Diagrams.Core/Anchors/Dynamic/IDynamicAnchorPositionProvider.cs create mode 100644 src/Blazor.Diagrams.Core/Anchors/Dynamic/ShapeAnglePositionProvider.cs delete mode 100644 src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs diff --git a/src/Blazor.Diagrams.Core/Anchors/Anchor.cs b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs index ae14c8adb..5904e35e7 100644 --- a/src/Blazor.Diagrams.Core/Anchors/Anchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs @@ -3,6 +3,7 @@ using Blazor.Diagrams.Core.Models.Base; using System; using System.Collections.Generic; +using Blazor.Diagrams.Core.Anchors.Dynamic; namespace Blazor.Diagrams.Core.Anchors { diff --git a/src/Blazor.Diagrams.Core/Anchors/Dynamic/BoundsBasedPositionProvider.cs b/src/Blazor.Diagrams.Core/Anchors/Dynamic/BoundsBasedPositionProvider.cs new file mode 100644 index 000000000..7a5d59c36 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Anchors/Dynamic/BoundsBasedPositionProvider.cs @@ -0,0 +1,27 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Anchors.Dynamic; + +public class BoundsBasedPositionProvider : IDynamicAnchorPositionProvider +{ + public BoundsBasedPositionProvider(double x, double y, double offsetX = 0, double offsetY = 0) + { + X = x; + Y = y; + OffsetX = offsetX; + OffsetY = offsetY; + } + + public double X { get; } + public double Y { get; } + public double OffsetX { get; } + public double OffsetY { get; } + + public Point GetPosition(NodeModel node, BaseLinkModel _) + { + var bounds = node.GetBounds()!; + return new Point(bounds.Left + X * bounds.Width + OffsetX, bounds.Top + Y * bounds.Height + OffsetY); + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs new file mode 100644 index 000000000..083b1d57d --- /dev/null +++ b/src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Anchors.Dynamic +{ + // Figure out a better name + // Generic? + public class DynamicAnchor : Anchor + { + public DynamicAnchor(NodeModel node, IDynamicAnchorPositionProvider[] providers, Point? offset = null) + : base(node, offset) + { + if (providers.Length == 0) + throw new InvalidOperationException("No providers provided"); + + Providers = providers; + } + + public IDynamicAnchorPositionProvider[] Providers { get; } + + public override Point? GetPosition(BaseLinkModel link, Point[] route) + { + if (Node.Size == null) + return null; + + var isTarget = link.Target == this; + var pt = route.Length > 0 ? route[isTarget ? ^1 : 0] : GetOtherPosition(link, isTarget); + var positions = Providers.Select(e => e.GetPosition(Node, link)); + return pt is null ? null : GetClosestPointTo(positions, pt); + } + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Anchors/Dynamic/IDynamicAnchorPositionProvider.cs b/src/Blazor.Diagrams.Core/Anchors/Dynamic/IDynamicAnchorPositionProvider.cs new file mode 100644 index 000000000..b5ac73bd6 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Anchors/Dynamic/IDynamicAnchorPositionProvider.cs @@ -0,0 +1,10 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Anchors.Dynamic; + +public interface IDynamicAnchorPositionProvider +{ + public Point GetPosition(NodeModel node, BaseLinkModel link); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Anchors/Dynamic/ShapeAnglePositionProvider.cs b/src/Blazor.Diagrams.Core/Anchors/Dynamic/ShapeAnglePositionProvider.cs new file mode 100644 index 000000000..0bfc98939 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Anchors/Dynamic/ShapeAnglePositionProvider.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.Anchors.Dynamic; + +public class ShapeAnglePositionProvider : IDynamicAnchorPositionProvider +{ + public ShapeAnglePositionProvider(double angle, double offsetX = 0, double offsetY = 0) + { + Angle = angle; + OffsetX = offsetX; + OffsetY = offsetY; + } + + public double Angle { get; } + public double OffsetX { get; } + public double OffsetY { get; } + + public Point GetPosition(NodeModel node, BaseLinkModel link) + { + var shape = node.GetShape(); + return shape.GetPointAtAngle(Angle)?.Add(OffsetX, OffsetY) ?? Point.Zero; + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs deleted file mode 100644 index 8ff17d24a..000000000 --- a/src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; -using System.Collections.Generic; -using System.Linq; - -namespace Blazor.Diagrams.Core.Anchors -{ - // Figure out a better name - // Generic? - public class DynamicAnchor : Anchor - { - public DynamicAnchor(NodeModel node, DynamicAnchorPosition[] positions, Point? offset = null) : base(node, offset) - { - Positions = positions; - } - - public DynamicAnchorPosition[] Positions { get; } - - public override Point? GetPosition(BaseLinkModel link, Point[] route) - { - if (Node.Size == null) - return null; - - var isTarget = link.Target == this; - var pt = route.Length > 0 ? route[isTarget ? ^1 : 0] : GetOtherPosition(link, isTarget); - return pt is null ? null : GetClosestPointTo(CalculatePositions(), pt); - } - - private IEnumerable CalculatePositions() - { - var bounds = Node.GetBounds()!; - return Positions.Select(e => - { - return new Point( - bounds.Left + e.Position.X * bounds.Width + (e.Offset?.X ?? 0), - bounds.Top + e.Position.Y * bounds.Height + (e.Offset?.Y ?? 0) - ); - }); - } - } - - public record DynamicAnchorPosition - { - public DynamicAnchorPosition(Point position, Point offset) - { - Position = position; - Offset = offset; - } - - public DynamicAnchorPosition(double x, double y) : this(new Point(x, y), Point.Zero) { } - - public DynamicAnchorPosition(double x, double y, double offsetX, double offsetY) : this(new Point(x, y), new Point(offsetX, offsetY)) { } - - public Point Position { get; } - public Point Offset { get; } - } -} diff --git a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs index 5266fb43d..61f9673ae 100644 --- a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs @@ -1,4 +1,5 @@ using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Anchors.Dynamic; using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Core.Layers diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs index fdefbfd9d..5e8bd1644 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs @@ -3,6 +3,7 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using System; +using Blazor.Diagrams.Core.Anchors.Dynamic; namespace Blazor.Diagrams.Core { diff --git a/src/Blazor.Diagrams/Components/NavigatorWidget.razor b/src/Blazor.Diagrams/Components/NavigatorWidget.razor index e2608834d..8c24b9c3e 100644 --- a/src/Blazor.Diagrams/Components/NavigatorWidget.razor +++ b/src/Blazor.Diagrams/Components/NavigatorWidget.razor @@ -14,7 +14,8 @@ left: @(addedCurrentViewX.ToInvariantString())px; width: @(currentViewWidth.ToInvariantString())px; height: @(currentViewHeight.ToInvariantString())px; - border: 2px solid @FillColor;">
    + border: 2px solid @FillColor;"> +
    @foreach (var node in Diagram.Nodes.Where(n => n.Size != null)) @@ -27,7 +28,8 @@ + height="@height.ToInvariantString()"> +
    } @@ -43,9 +45,10 @@ stroke-width="2" fill="none" width="@width.ToInvariantString()" - height="@height.ToInvariantString()"> + height="@height.ToInvariantString()"> + } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs index 2c1d4fe1a..b17d6ba2b 100644 --- a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs +++ b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs @@ -13,7 +13,7 @@ public static async Task GetBoundingClientRect(this IJSRuntime jsRunt return await jsRuntime.InvokeAsync("ZBlazorDiagrams.getBoundingClientRect", element); } - public static async Task ObserveResizes(this IJSRuntime jsRuntime, ElementReference element, + public static async Task ObserveResizes(this IJSRuntime jsRuntime, ElementReference element, DotNetObjectReference reference) where T : class { try @@ -35,4 +35,4 @@ public static async Task UnobserveResizes(this IJSRuntime jsRuntime, ElementRefe await jsRuntime.InvokeVoidAsync("ZBlazorDiagrams.unobserve", element, element.Id); } } -} +} \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs index f47fa81a3..f384694e5 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs @@ -1,4 +1,5 @@ using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Anchors.Dynamic; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using FluentAssertions; @@ -16,20 +17,20 @@ public void GetPosition_ShouldReturnNull_WhenNodesSizeIsNull() { Size = null }; - var positions = new[] - { - new DynamicAnchorPosition(0.0, 0.0), - new DynamicAnchorPosition(0.5, 0.0), - new DynamicAnchorPosition(1.0, 0.0), - new DynamicAnchorPosition(0.0, 0.5), - new DynamicAnchorPosition(0.5, 0.5), - new DynamicAnchorPosition(1.0, 0.5), - new DynamicAnchorPosition(0.0, 1.0), - new DynamicAnchorPosition(0.5, 1.0), - new DynamicAnchorPosition(1.0, 1.0) + var providers = new[] + { + new BoundsBasedPositionProvider(0.0, 0.0), + new BoundsBasedPositionProvider(0.5, 0.0), + new BoundsBasedPositionProvider(1.0, 0.0), + new BoundsBasedPositionProvider(0.0, 0.5), + new BoundsBasedPositionProvider(0.5, 0.5), + new BoundsBasedPositionProvider(1.0, 0.5), + new BoundsBasedPositionProvider(0.0, 1.0), + new BoundsBasedPositionProvider(0.5, 1.0), + new BoundsBasedPositionProvider(1.0, 1.0) }; - var anchor1 = new DynamicAnchor(node, positions); - var anchor2 = new DynamicAnchor(node, positions); + var anchor1 = new DynamicAnchor(node, providers); + var anchor2 = new DynamicAnchor(node, providers); var link = new LinkModel(anchor1, anchor2); // Act @@ -53,15 +54,15 @@ public void GetPosition_ShouldReturnClosestPositionToOtherNodesCenter_WhenRouteI }; var positions = new[] { - new DynamicAnchorPosition(0.0, 0.0), // 120,95 - new DynamicAnchorPosition(0.5, 0.0), // 170,95 - new DynamicAnchorPosition(1.0, 0.0), // 220,95 - new DynamicAnchorPosition(0.0, 0.5), // 120,125 - new DynamicAnchorPosition(0.5, 0.5), // 170,125 - new DynamicAnchorPosition(1.0, 0.5), // 220,125 - new DynamicAnchorPosition(0.0, 1.0), // 120,155 - new DynamicAnchorPosition(0.5, 1.0), // 170,155 - new DynamicAnchorPosition(1.0, 1.0) // 220,155 + new BoundsBasedPositionProvider(0.0, 0.0), // 120,95 + new BoundsBasedPositionProvider(0.5, 0.0), // 170,95 + new BoundsBasedPositionProvider(1.0, 0.0), // 220,95 + new BoundsBasedPositionProvider(0.0, 0.5), // 120,125 + new BoundsBasedPositionProvider(0.5, 0.5), // 170,125 + new BoundsBasedPositionProvider(1.0, 0.5), // 220,125 + new BoundsBasedPositionProvider(0.0, 1.0), // 120,155 + new BoundsBasedPositionProvider(0.5, 1.0), // 170,155 + new BoundsBasedPositionProvider(1.0, 1.0) // 220,155 }; var anchor1 = new DynamicAnchor(node1, positions); var anchor2 = new DynamicAnchor(node2, positions); @@ -90,15 +91,15 @@ public void GetPosition_ShouldReturnClosestPositionToOtherNodesCenterWithOffset_ }; var positions = new[] { - new DynamicAnchorPosition(0.0, 0.0), // 120,95 - new DynamicAnchorPosition(0.5, 0.0), // 170,95 - new DynamicAnchorPosition(1.0, 0.0, 10, -10), // 230,85 - new DynamicAnchorPosition(0.0, 0.5), // 120,125 - new DynamicAnchorPosition(0.5, 0.5), // 170,125 - new DynamicAnchorPosition(1.0, 0.5), // 220,125 - new DynamicAnchorPosition(0.0, 1.0), // 120,155 - new DynamicAnchorPosition(0.5, 1.0), // 170,155 - new DynamicAnchorPosition(1.0, 1.0) // 220,155 + new BoundsBasedPositionProvider(0.0, 0.0), // 120,95 + new BoundsBasedPositionProvider(0.5, 0.0), // 170,95 + new BoundsBasedPositionProvider(1.0, 0.0, 10, -10), // 230,85 + new BoundsBasedPositionProvider(0.0, 0.5), // 120,125 + new BoundsBasedPositionProvider(0.5, 0.5), // 170,125 + new BoundsBasedPositionProvider(1.0, 0.5), // 220,125 + new BoundsBasedPositionProvider(0.0, 1.0), // 120,155 + new BoundsBasedPositionProvider(0.5, 1.0), // 170,155 + new BoundsBasedPositionProvider(1.0, 1.0) // 220,155 }; var anchor1 = new DynamicAnchor(node1, positions); var anchor2 = new DynamicAnchor(node2, positions); @@ -127,15 +128,15 @@ public void GetPosition_ShouldReturnClosestPositionToFirstVertex_WhenRouteIsNotE }; var positions = new[] { - new DynamicAnchorPosition(0.0, 0.0), // 120,95 - new DynamicAnchorPosition(0.5, 0.0), // 170,95 - new DynamicAnchorPosition(1.0, 0.0), // 220,95 - new DynamicAnchorPosition(0.0, 0.5), // 120,125 - new DynamicAnchorPosition(0.5, 0.5), // 170,125 - new DynamicAnchorPosition(1.0, 0.5), // 220,125 - new DynamicAnchorPosition(0.0, 1.0), // 120,155 - new DynamicAnchorPosition(0.5, 1.0), // 170,155 - new DynamicAnchorPosition(1.0, 1.0) // 220,155 + new BoundsBasedPositionProvider(0.0, 0.0), // 120,95 + new BoundsBasedPositionProvider(0.5, 0.0), // 170,95 + new BoundsBasedPositionProvider(1.0, 0.0), // 220,95 + new BoundsBasedPositionProvider(0.0, 0.5), // 120,125 + new BoundsBasedPositionProvider(0.5, 0.5), // 170,125 + new BoundsBasedPositionProvider(1.0, 0.5), // 220,125 + new BoundsBasedPositionProvider(0.0, 1.0), // 120,155 + new BoundsBasedPositionProvider(0.5, 1.0), // 170,155 + new BoundsBasedPositionProvider(1.0, 1.0) // 220,155 }; var anchor1 = new DynamicAnchor(node1, positions); var anchor2 = new DynamicAnchor(node2, positions); @@ -167,15 +168,15 @@ public void GetPosition_ShouldReturnClosestPositionToLastVertex_WhenRouteIsNotEm }; var positions = new[] { - new DynamicAnchorPosition(0.0, 0.0), // 300, 60 - new DynamicAnchorPosition(0.5, 0.0), // 350, 60 - new DynamicAnchorPosition(1.0, 0.0), // 400, 60 - new DynamicAnchorPosition(0.0, 0.5), // 300, 90 - new DynamicAnchorPosition(0.5, 0.5), // 350, 90 - new DynamicAnchorPosition(1.0, 0.5), // 400, 90 - new DynamicAnchorPosition(0.0, 1.0), // 300, 120 - new DynamicAnchorPosition(0.5, 1.0), // 350, 120 - new DynamicAnchorPosition(1.0, 1.0) // 400, 120 + new BoundsBasedPositionProvider(0.0, 0.0), // 300, 60 + new BoundsBasedPositionProvider(0.5, 0.0), // 350, 60 + new BoundsBasedPositionProvider(1.0, 0.0), // 400, 60 + new BoundsBasedPositionProvider(0.0, 0.5), // 300, 90 + new BoundsBasedPositionProvider(0.5, 0.5), // 350, 90 + new BoundsBasedPositionProvider(1.0, 0.5), // 400, 90 + new BoundsBasedPositionProvider(0.0, 1.0), // 300, 120 + new BoundsBasedPositionProvider(0.5, 1.0), // 350, 120 + new BoundsBasedPositionProvider(1.0, 1.0) // 400, 120 }; var anchor1 = new DynamicAnchor(node1, positions); var anchor2 = new DynamicAnchor(node2, positions); From 507491db68db7c6f5388907032b906f3c898a0a6 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 17 Aug 2022 21:31:18 +0100 Subject: [PATCH 053/193] Add unit tests for ShapeAnglePositionProvider --- .../ShapeAnglePositionProviderTests.cs | 45 +++++++++++++++++++ .../Blazor.Diagrams.Core.Tests.csproj | 1 + 2 files changed, 46 insertions(+) create mode 100644 tests/Blazor.Diagrams.Core.Tests/Anchors/Dynamic/ShapeAnglePositionProviderTests.cs diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/Dynamic/ShapeAnglePositionProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/Dynamic/ShapeAnglePositionProviderTests.cs new file mode 100644 index 000000000..f53081ab9 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/Dynamic/ShapeAnglePositionProviderTests.cs @@ -0,0 +1,45 @@ +using Blazor.Diagrams.Core.Anchors.Dynamic; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using FluentAssertions; +using Moq; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Anchors.Dynamic; + +public class ShapeAnglePositionProviderTests +{ + [Fact] + public void GetPosition_ShouldUseGetPointAtAngleOfShape() + { + // Arrange + var shapeMock = new Mock(); + var nodeMock = new Mock(Point.Zero); + var provider = new ShapeAnglePositionProvider(70); + nodeMock.Setup(n => n.GetShape()).Returns(shapeMock.Object); + + // Act + var position = provider.GetPosition(nodeMock.Object, null!); + + // Assert + shapeMock.Verify(m => m.GetPointAtAngle(70), Times.Once); + } + + [Fact] + public void GetPosition_ShouldUseOffset_WhenProvided() + { + // Arrange + var shapeMock = new Mock(); + var nodeMock = new Mock(Point.Zero); + var provider = new ShapeAnglePositionProvider(70, 5, -10); + nodeMock.Setup(n => n.GetShape()).Returns(shapeMock.Object); + shapeMock.Setup(s => s.GetPointAtAngle(70)).Returns(new Point(100, 50)); + + // Act + var position = provider.GetPosition(nodeMock.Object, null!); + + // Assert + position.X.Should().Be(105); + position.Y.Should().Be(40); + } +} \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj index 8d8622a35..85db1b8cb 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj +++ b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj @@ -10,6 +10,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive all From 5f954b54efdc4974c408243da08e208f77ebb986 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 18 Aug 2022 18:16:32 +0100 Subject: [PATCH 054/193] Make position providers not tied to Dynamic anchor --- .../Anchors/Dynamic/DynamicAnchor.cs | 7 ++++--- .../Dynamic/IDynamicAnchorPositionProvider.cs | 10 ---------- src/Blazor.Diagrams.Core/Models/Base/IHasBounds.cs | 8 ++++++++ src/Blazor.Diagrams.Core/Models/Base/IHasShape.cs | 8 ++++++++ src/Blazor.Diagrams.Core/Models/NodeModel.cs | 6 ++++-- src/Blazor.Diagrams.Core/Models/PortModel.cs | 2 +- .../BoundsBasedPositionProvider.cs | 12 +++++++----- .../Positions/IPositionProvider.cs | 9 +++++++++ .../ShapeAnglePositionProvider.cs | 12 +++++++----- .../Dynamic/ShapeAnglePositionProviderTests.cs | 8 ++++---- .../Anchors/DynamicAnchorTests.cs | 1 + 11 files changed, 53 insertions(+), 30 deletions(-) delete mode 100644 src/Blazor.Diagrams.Core/Anchors/Dynamic/IDynamicAnchorPositionProvider.cs create mode 100644 src/Blazor.Diagrams.Core/Models/Base/IHasBounds.cs create mode 100644 src/Blazor.Diagrams.Core/Models/Base/IHasShape.cs rename src/Blazor.Diagrams.Core/{Anchors/Dynamic => Positions}/BoundsBasedPositionProvider.cs (61%) create mode 100644 src/Blazor.Diagrams.Core/Positions/IPositionProvider.cs rename src/Blazor.Diagrams.Core/{Anchors/Dynamic => Positions}/ShapeAnglePositionProvider.cs (58%) diff --git a/src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs index 083b1d57d..01d4d3150 100644 --- a/src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs @@ -4,6 +4,7 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Positions; namespace Blazor.Diagrams.Core.Anchors.Dynamic { @@ -11,7 +12,7 @@ namespace Blazor.Diagrams.Core.Anchors.Dynamic // Generic? public class DynamicAnchor : Anchor { - public DynamicAnchor(NodeModel node, IDynamicAnchorPositionProvider[] providers, Point? offset = null) + public DynamicAnchor(NodeModel node, IPositionProvider[] providers, Point? offset = null) : base(node, offset) { if (providers.Length == 0) @@ -20,7 +21,7 @@ public DynamicAnchor(NodeModel node, IDynamicAnchorPositionProvider[] providers, Providers = providers; } - public IDynamicAnchorPositionProvider[] Providers { get; } + public IPositionProvider[] Providers { get; } public override Point? GetPosition(BaseLinkModel link, Point[] route) { @@ -29,7 +30,7 @@ public DynamicAnchor(NodeModel node, IDynamicAnchorPositionProvider[] providers, var isTarget = link.Target == this; var pt = route.Length > 0 ? route[isTarget ? ^1 : 0] : GetOtherPosition(link, isTarget); - var positions = Providers.Select(e => e.GetPosition(Node, link)); + var positions = Providers.Select(e => e.GetPosition(Node)); return pt is null ? null : GetClosestPointTo(positions, pt); } } diff --git a/src/Blazor.Diagrams.Core/Anchors/Dynamic/IDynamicAnchorPositionProvider.cs b/src/Blazor.Diagrams.Core/Anchors/Dynamic/IDynamicAnchorPositionProvider.cs deleted file mode 100644 index b5ac73bd6..000000000 --- a/src/Blazor.Diagrams.Core/Anchors/Dynamic/IDynamicAnchorPositionProvider.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; - -namespace Blazor.Diagrams.Core.Anchors.Dynamic; - -public interface IDynamicAnchorPositionProvider -{ - public Point GetPosition(NodeModel node, BaseLinkModel link); -} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Models/Base/IHasBounds.cs b/src/Blazor.Diagrams.Core/Models/Base/IHasBounds.cs new file mode 100644 index 000000000..47f1a1dae --- /dev/null +++ b/src/Blazor.Diagrams.Core/Models/Base/IHasBounds.cs @@ -0,0 +1,8 @@ +using Blazor.Diagrams.Core.Geometry; + +namespace Blazor.Diagrams.Core.Models.Base; + +public interface IHasBounds +{ + public Rectangle? GetBounds(); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Models/Base/IHasShape.cs b/src/Blazor.Diagrams.Core/Models/Base/IHasShape.cs new file mode 100644 index 000000000..c4aace600 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Models/Base/IHasShape.cs @@ -0,0 +1,8 @@ +using Blazor.Diagrams.Core.Geometry; + +namespace Blazor.Diagrams.Core.Models.Base; + +public interface IHasShape +{ + public IShape GetShape(); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs index 7eb3558a6..863f146ba 100644 --- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs +++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs @@ -6,7 +6,7 @@ namespace Blazor.Diagrams.Core.Models { - public class NodeModel : MovableModel + public class NodeModel : MovableModel, IHasBounds, IHasShape { private readonly List _ports = new List(); private readonly List _links = new List(); @@ -103,7 +103,9 @@ public virtual void UpdatePositionSilently(double deltaX, double deltaY) Refresh(); } - public Rectangle? GetBounds(bool includePorts = false) + public Rectangle? GetBounds() => GetBounds(false); + + public Rectangle? GetBounds(bool includePorts) { if (Size == null) return null; diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs index b930de788..e20c2fe38 100644 --- a/src/Blazor.Diagrams.Core/Models/PortModel.cs +++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs @@ -4,7 +4,7 @@ namespace Blazor.Diagrams.Core.Models { - public class PortModel : Model + public class PortModel : Model, IHasBounds, IHasShape { private readonly List _links = new(4); diff --git a/src/Blazor.Diagrams.Core/Anchors/Dynamic/BoundsBasedPositionProvider.cs b/src/Blazor.Diagrams.Core/Positions/BoundsBasedPositionProvider.cs similarity index 61% rename from src/Blazor.Diagrams.Core/Anchors/Dynamic/BoundsBasedPositionProvider.cs rename to src/Blazor.Diagrams.Core/Positions/BoundsBasedPositionProvider.cs index 7a5d59c36..1fca0f40f 100644 --- a/src/Blazor.Diagrams.Core/Anchors/Dynamic/BoundsBasedPositionProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/BoundsBasedPositionProvider.cs @@ -1,10 +1,9 @@ using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core.Anchors.Dynamic; +namespace Blazor.Diagrams.Core.Positions; -public class BoundsBasedPositionProvider : IDynamicAnchorPositionProvider +public class BoundsBasedPositionProvider : IPositionProvider { public BoundsBasedPositionProvider(double x, double y, double offsetX = 0, double offsetY = 0) { @@ -19,9 +18,12 @@ public BoundsBasedPositionProvider(double x, double y, double offsetX = 0, doubl public double OffsetX { get; } public double OffsetY { get; } - public Point GetPosition(NodeModel node, BaseLinkModel _) + public Point GetPosition(Model model) { - var bounds = node.GetBounds()!; + if (model is not IHasBounds ihb) + throw new DiagramsException("BoundsBasedPositionProvider requires an IHasBounds model"); + + var bounds = ihb.GetBounds()!; return new Point(bounds.Left + X * bounds.Width + OffsetX, bounds.Top + Y * bounds.Height + OffsetY); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Positions/IPositionProvider.cs b/src/Blazor.Diagrams.Core/Positions/IPositionProvider.cs new file mode 100644 index 000000000..5ee284abc --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/IPositionProvider.cs @@ -0,0 +1,9 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions; + +public interface IPositionProvider +{ + public Point GetPosition(Model model); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Anchors/Dynamic/ShapeAnglePositionProvider.cs b/src/Blazor.Diagrams.Core/Positions/ShapeAnglePositionProvider.cs similarity index 58% rename from src/Blazor.Diagrams.Core/Anchors/Dynamic/ShapeAnglePositionProvider.cs rename to src/Blazor.Diagrams.Core/Positions/ShapeAnglePositionProvider.cs index 0bfc98939..cba061aa2 100644 --- a/src/Blazor.Diagrams.Core/Anchors/Dynamic/ShapeAnglePositionProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/ShapeAnglePositionProvider.cs @@ -1,10 +1,9 @@ using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core.Anchors.Dynamic; +namespace Blazor.Diagrams.Core.Positions; -public class ShapeAnglePositionProvider : IDynamicAnchorPositionProvider +public class ShapeAnglePositionProvider : IPositionProvider { public ShapeAnglePositionProvider(double angle, double offsetX = 0, double offsetY = 0) { @@ -17,9 +16,12 @@ public ShapeAnglePositionProvider(double angle, double offsetX = 0, double offse public double OffsetX { get; } public double OffsetY { get; } - public Point GetPosition(NodeModel node, BaseLinkModel link) + public Point GetPosition(Model model) { - var shape = node.GetShape(); + if (model is not IHasShape ihs) + throw new DiagramsException("ShapeAnglePositionProvider requires an IHasShape model"); + + var shape = ihs.GetShape(); return shape.GetPointAtAngle(Angle)?.Add(OffsetX, OffsetY) ?? Point.Zero; } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/Dynamic/ShapeAnglePositionProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/Dynamic/ShapeAnglePositionProviderTests.cs index f53081ab9..5113a685e 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Anchors/Dynamic/ShapeAnglePositionProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/Dynamic/ShapeAnglePositionProviderTests.cs @@ -1,6 +1,6 @@ -using Blazor.Diagrams.Core.Anchors.Dynamic; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Positions; using FluentAssertions; using Moq; using Xunit; @@ -19,12 +19,12 @@ public void GetPosition_ShouldUseGetPointAtAngleOfShape() nodeMock.Setup(n => n.GetShape()).Returns(shapeMock.Object); // Act - var position = provider.GetPosition(nodeMock.Object, null!); + var position = provider.GetPosition(nodeMock.Object); // Assert shapeMock.Verify(m => m.GetPointAtAngle(70), Times.Once); } - + [Fact] public void GetPosition_ShouldUseOffset_WhenProvided() { @@ -36,7 +36,7 @@ public void GetPosition_ShouldUseOffset_WhenProvided() shapeMock.Setup(s => s.GetPointAtAngle(70)).Returns(new Point(100, 50)); // Act - var position = provider.GetPosition(nodeMock.Object, null!); + var position = provider.GetPosition(nodeMock.Object); // Assert position.X.Should().Be(105); diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs index f384694e5..47f25b317 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs @@ -2,6 +2,7 @@ using Blazor.Diagrams.Core.Anchors.Dynamic; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Positions; using FluentAssertions; using Xunit; From ad1361cada6c360702b9aa6fb1341783b105fb7e Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 18 Aug 2022 18:21:33 +0100 Subject: [PATCH 055/193] Fix namespace --- .../Blazor.Diagrams.Core.Tests.csproj | 4 ++++ .../Dynamic => Positions}/ShapeAnglePositionProviderTests.cs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) rename tests/Blazor.Diagrams.Core.Tests/{Anchors/Dynamic => Positions}/ShapeAnglePositionProviderTests.cs (95%) diff --git a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj index 85db1b8cb..e38fcaff0 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj +++ b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj @@ -25,4 +25,8 @@ + + + + diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/Dynamic/ShapeAnglePositionProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/ShapeAnglePositionProviderTests.cs similarity index 95% rename from tests/Blazor.Diagrams.Core.Tests/Anchors/Dynamic/ShapeAnglePositionProviderTests.cs rename to tests/Blazor.Diagrams.Core.Tests/Positions/ShapeAnglePositionProviderTests.cs index 5113a685e..63b5790a2 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Anchors/Dynamic/ShapeAnglePositionProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/ShapeAnglePositionProviderTests.cs @@ -5,7 +5,7 @@ using Moq; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Anchors.Dynamic; +namespace Blazor.Diagrams.Core.Tests.Positions; public class ShapeAnglePositionProviderTests { From 944b662c59446d3583c098be251d41e78a535341 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 24 Aug 2022 19:26:34 +0100 Subject: [PATCH 056/193] Fix links not refreshing after ReconnectLinksToClosestPorts --- .../LinksReconnectionAlgorithms.cs | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs index 8c57478b6..11f37c2b0 100644 --- a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs +++ b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs @@ -1,8 +1,8 @@ using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Anchors; -using Blazor.Diagrams.Core.Models; using System.Collections.Generic; using System.Linq; +using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Algorithms { @@ -11,7 +11,7 @@ public static class LinksReconnectionAlgorithms public static void ReconnectLinksToClosestPorts(this DiagramBase diagram) { // Only refresh ports once - var portsToRefresh = new HashSet(); + var modelsToRefresh = new HashSet(); foreach (var link in diagram.Links.ToArray()) { @@ -45,21 +45,25 @@ public static void ReconnectLinksToClosestPorts(this DiagramBase diagram) // Reconnect if (spa1.Port != minSourcePort) { - portsToRefresh.Add(spa1.Port); - portsToRefresh.Add(minSourcePort); + modelsToRefresh.Add(spa1.Port); + modelsToRefresh.Add(minSourcePort); link.SetSource(new SinglePortAnchor(minSourcePort)); + modelsToRefresh.Add(link); } if (spa2.Port != minTargetPort) { - portsToRefresh.Add(spa2.Port); - portsToRefresh.Add(minTargetPort); + modelsToRefresh.Add(spa2.Port); + modelsToRefresh.Add(minTargetPort); link.SetTarget(new SinglePortAnchor(minTargetPort)); + modelsToRefresh.Add(link); } } - foreach (var port in portsToRefresh) - port.Refresh(); + foreach (var model in modelsToRefresh) + { + model.Refresh(); + } } } } From 1bce97ae3d9e24a0ecbedc2166de9ad14c144078 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 24 Aug 2022 19:27:11 +0100 Subject: [PATCH 057/193] Make PathGenerators return SvgPath and cache the paths on links --- .../Blazor.Diagrams.Core.csproj | 4 ++ src/Blazor.Diagrams.Core/Layers/LinkLayer.cs | 2 + .../Models/Base/BaseLinkModel.cs | 53 +++++++++++++++++-- .../PathGenerators/PathGeneratorResult.cs | 8 ++- .../PathGenerators/PathGenerators.Smooth.cs | 10 ++-- .../PathGenerators/PathGenerators.Straight.cs | 6 +-- .../Routers/Routers.Orthogonal.cs | 5 +- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 1 - .../Components/LinkWidget.razor | 41 ++++++++------ .../Components/LinkWidget.razor.cs | 1 - .../Components/Renderers/LinkLabelRenderer.cs | 18 +++---- 11 files changed, 105 insertions(+), 44 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index f51e63c9b..a73af7b82 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -25,4 +25,8 @@ + + + + diff --git a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs index 61f9673ae..13df2ddcb 100644 --- a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs @@ -10,6 +10,7 @@ public LinkLayer(DiagramBase diagram) : base(diagram) { } protected override void OnItemAdded(BaseLinkModel link) { + link.Diagram = Diagram; HandleAnchor(link, link.Source, true); if (link.Target != null) HandleAnchor(link, link.Target, true); @@ -22,6 +23,7 @@ protected override void OnItemAdded(BaseLinkModel link) protected override void OnItemRemoved(BaseLinkModel link) { + link.Diagram = null; HandleAnchor(link, link.Source, false); if (link.Target != null) HandleAnchor(link, link.Target, false); diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index f2a98ee3a..2a12d9370 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -2,10 +2,11 @@ using Blazor.Diagrams.Core.Geometry; using System; using System.Collections.Generic; +using SvgPathProperties; namespace Blazor.Diagrams.Core.Models.Base { - public abstract class BaseLinkModel : SelectableModel + public abstract class BaseLinkModel : SelectableModel, IHasBounds { public event Action? SourceChanged; public event Action? TargetChanged; @@ -24,6 +25,9 @@ protected BaseLinkModel(string id, Anchor source, Anchor? target = null) : base( public Anchor Source { get; private set; } public Anchor? Target { get; private set; } + public DiagramBase? Diagram { get; internal set; } + public PathGeneratorResult GeneratedPathResult { get; private set; } = PathGeneratorResult.Empty; + public SvgPath[] Paths => GeneratedPathResult.Paths; public bool IsAttached => Target != null; public Point? OnGoingPosition { get; set; } public Router? Router { get; set; } @@ -31,8 +35,29 @@ protected BaseLinkModel(string id, Anchor source, Anchor? target = null) : base( public LinkMarker? SourceMarker { get; set; } public LinkMarker? TargetMarker { get; set; } public bool Segmentable { get; set; } = false; - public List Vertices { get; } = new List(); - public List Labels { get; set; } = new List(); + public List Vertices { get; } = new(); + public List Labels { get; } = new(); + + public override void Refresh() + { + if (Diagram != null) + { + var router = Router ?? Diagram.Options.Links.DefaultRouter; + var pathGenerator = PathGenerator ?? Diagram.Options.Links.DefaultPathGenerator; + var route = router(Diagram, this); + var source = Source.GetPosition(this, route); + var target = Target is null ? OnGoingPosition : Target.GetPosition(this, route); + if (source != null && target != null) + { + GeneratedPathResult = pathGenerator(Diagram, this, route, source, target); + base.Refresh(); + return; + } + } + + GeneratedPathResult = PathGeneratorResult.Empty; + base.Refresh(); + } public void SetSource(Anchor anchor) { @@ -55,5 +80,27 @@ public void SetTarget(Anchor? anchor) Target = anchor; TargetChanged?.Invoke(this, old, Target); } + + public Rectangle? GetBounds() + { + if (Paths.Length == 0) + return Rectangle.Zero; + + var minX = double.PositiveInfinity; + var minY = double.PositiveInfinity; + var maxX = double.NegativeInfinity; + var maxY = double.NegativeInfinity; + + foreach (var path in Paths) + { + var bbox = path.GetBBox(); + minX = Math.Min(minX, bbox.Left); + minY = Math.Min(minY, bbox.Top); + maxX = Math.Max(maxX, bbox.Right); + maxY = Math.Max(maxY, bbox.Bottom); + } + + return new Rectangle(minX, minY, maxX, maxY); + } } } diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGeneratorResult.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGeneratorResult.cs index 780deb17b..43a7f6787 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGeneratorResult.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGeneratorResult.cs @@ -1,10 +1,14 @@ using Blazor.Diagrams.Core.Geometry; +using SvgPathProperties; +using System; namespace Blazor.Diagrams.Core { public class PathGeneratorResult { - public PathGeneratorResult(string[] paths, double? sourceMarkerAngle = null, Point? sourceMarkerPosition = null, + public static PathGeneratorResult Empty { get; } = new(Array.Empty()); + + public PathGeneratorResult(SvgPath[] paths, double? sourceMarkerAngle = null, Point? sourceMarkerPosition = null, double? targetMarkerAngle = null, Point? targetMarkerPosition = null) { Paths = paths; @@ -14,7 +18,7 @@ public PathGeneratorResult(string[] paths, double? sourceMarkerAngle = null, Poi TargetMarkerPosition = targetMarkerPosition; } - public string[] Paths { get; } + public SvgPath[] Paths { get; } public double? SourceMarkerAngle { get; } public Point? SourceMarkerPosition { get; } public double? TargetMarkerAngle { get; } diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs index 5e8bd1644..7539ae3f0 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs @@ -4,6 +4,7 @@ using Blazor.Diagrams.Core.Models.Base; using System; using Blazor.Diagrams.Core.Anchors.Dynamic; +using SvgPathProperties; namespace Blazor.Diagrams.Core { @@ -32,7 +33,10 @@ public static PathGeneratorResult Smooth(DiagramBase _, BaseLinkModel link, Poin targetAngle = TargetMarkerAdjustement(route, link.TargetMarker.Width); } - var path = FormattableString.Invariant($"M {route[0].X} {route[0].Y} C {route[1].X} {route[1].Y}, {route[2].X} {route[2].Y}, {route[3].X} {route[3].Y}"); + var path = new SvgPath() + .AddMoveTo(route[0].X, route[0].Y) + .AddCubicBezierCurve(route[1].X, route[1].Y, route[2].X, route[2].Y, route[3].X, route[3].Y); + return new PathGeneratorResult(new[] { path }, sourceAngle, route[0], targetAngle, route[^1]); } @@ -52,13 +56,13 @@ private static PathGeneratorResult CurveThroughPoints(Point[] route, BaseLinkMod } BezierSpline.GetCurveControlPoints(route, out var firstControlPoints, out var secondControlPoints); - var paths = new string[firstControlPoints.Length]; + var paths = new SvgPath[firstControlPoints.Length]; for (var i = 0; i < firstControlPoints.Length; i++) { var cp1 = firstControlPoints[i]; var cp2 = secondControlPoints[i]; - paths[i] = FormattableString.Invariant($"M {route[i].X} {route[i].Y} C {cp1.X} {cp1.Y}, {cp2.X} {cp2.Y}, {route[i + 1].X} {route[i + 1].Y}"); + paths[i] = new SvgPath().AddMoveTo(route[i].X, route[i].Y).AddCubicBezierCurve(cp1.X, cp1.Y, cp2.X, cp2.Y, route[i + 1].X, route[i + 1].Y); } // Todo: adjust marker positions based on closest control points diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs index 23fbbc1cd..1920aef6a 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs @@ -1,6 +1,6 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; -using System; +using SvgPathProperties; namespace Blazor.Diagrams.Core { @@ -22,10 +22,10 @@ public static PathGeneratorResult Straight(DiagramBase _, BaseLinkModel link, Po targetAngle = TargetMarkerAdjustement(route, link.TargetMarker.Width); } - var paths = new string[route.Length - 1]; + var paths = new SvgPath[route.Length - 1]; for (var i = 0; i < route.Length - 1; i++) { - paths[i] = FormattableString.Invariant($"M {route[i].X} {route[i].Y} L {route[i + 1].X} {route[i + 1].Y}"); + paths[i] = new SvgPath().AddMoveTo(route[i].X, route[i].Y).AddLineTo(route[i + 1].X, route[i + 1].Y); } return new PathGeneratorResult(paths, sourceAngle, route[0], targetAngle, route[^1]); diff --git a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs index 23030b966..a94ceae0c 100644 --- a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs +++ b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs @@ -5,7 +5,6 @@ using Blazor.Diagrams.Core.Models.Base; using System; using System.Collections.Generic; -using System.Data; using System.Linq; // Implementation taken from the JS version: https://gist.github.com/menendezpoo/4a8894c152383b9d7a870c24a04447e4 @@ -17,10 +16,10 @@ public static partial class Routers public static Point[] Orthogonal(DiagramBase _, BaseLinkModel link) { if (link.Source is not SinglePortAnchor spa1) - throw new Exception("Orthogonal router doesn't work with portless links yet"); + throw new Exception("Orthogonal router doesn't work with port-less links yet"); if (link.Target is not null && link.Target is not SinglePortAnchor) - throw new Exception("Orthogonal router doesn't work with portless links yet"); + throw new Exception("Orthogonal router doesn't work with port-less links yet"); var sourcePort = spa1.Port; var targetAnchor = (link.Target as SinglePortAnchor); diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index 408d2cbc8..5a2ac0301 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -22,7 +22,6 @@ - diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor b/src/Blazor.Diagrams/Components/LinkWidget.razor index f8615561e..df318403f 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor @@ -1,34 +1,26 @@ @using SvgPathProperties; @{ - var router = Link.Router ?? Diagram.Options.Links.DefaultRouter; - var pathGenerator = Link.PathGenerator ?? Diagram.Options.Links.DefaultPathGenerator; - var route = router(Diagram, Link); - var source = Link.Source.GetPosition(Link, route); - var target = Link.Target is null ? Link.OnGoingPosition : Link.Target.GetPosition(Link, route); - if (source == null || target == null) - return; - - var result = pathGenerator(Diagram, Link, route, source, target); - var color = Link.Selected ? (Link.SelectedColor ?? Diagram.Options.Links.DefaultSelectedColor) - : (Link.Color ?? Diagram.Options.Links.DefaultColor); - var paths = Link.Labels.Count > 0 ? result.Paths.Select(p => new SVGPathProperties(p)).ToArray() : Array.Empty(); + var color = Link.Selected ? (Link.SelectedColor ?? Diagram.Options.Links.DefaultSelectedColor) : (Link.Color ?? Diagram.Options.Links.DefaultColor); + var result = Link.GeneratedPathResult; + var bounds = Link.GetBounds()?.Inflate(10, 10); } @for (var i = 0; i < result.Paths.Length; i++) { var index = i; - + stroke="@color"/> @if (Link.IsAttached) { + SelectedColor="@selectedColor"/> } } @foreach (var label in Link.Labels) { - + +} + +@if (bounds != null) +{ + + } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs index f26ace83a..bb5845c83 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs @@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Blazor.Diagrams.Extensions; -using Blazor.Diagrams.Core.Geometry; namespace Blazor.Diagrams.Components { diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs index 4bf4a8f47..9d15ccdfa 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs @@ -1,22 +1,18 @@ -using Blazor.Diagrams.Core; -using Blazor.Diagrams.Core.Extensions; -using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using SvgPathProperties; using System; -using System.Globalization; using System.Linq; -using System.Text; namespace Blazor.Diagrams.Components.Renderers { public class LinkLabelRenderer : ComponentBase, IDisposable { - [CascadingParameter] public Diagram Diagram { get; set; } - [Parameter] public LinkLabelModel Label { get; set; } - [Parameter] public SVGPathProperties[] Paths { get; set; } + [CascadingParameter] public Diagram Diagram { get; set; } = null!; + [Parameter] public LinkLabelModel Label { get; set; } = null!; + [Parameter] public SvgPath[] Paths { get; set; } = null!; public void Dispose() { @@ -43,9 +39,9 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) private void OnLabelChanged() => InvokeAsync(StateHasChanged); - private Point FindPosition() + private Point? FindPosition() { - var totalLength = Paths.Sum(p => p.GetTotalLength()); + var totalLength = Paths.Sum(p => p.Length); var length = Label.Distance switch { @@ -57,7 +53,7 @@ private Point FindPosition() foreach (var path in Paths) { - var pathLength = path.GetTotalLength(); + var pathLength = path.Length; if (length < pathLength) { var pt = path.GetPointAtLength(length); From 875f58715831d65f8f84925cae28116743eb465d Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 26 Aug 2022 18:40:02 +0100 Subject: [PATCH 058/193] Switch to pointer events instead of mouse/touch separately --- samples/SharedDemo/Demos/Events.razor.cs | 20 ++---- .../Demos/Groups/CustomShortcut.razor.cs | 6 +- .../SharedDemo/Demos/Groups/Dynamic.razor.cs | 3 + .../SharedDemo/Demos/Groups/Factory.razor.cs | 2 + .../Behaviors/DragMovablesBehavior.cs | 36 ++++------- .../Behaviors/DragNewLinkBehavior.cs | 39 +++--------- .../Behaviors/EventsBehavior.cs | 29 +++++---- .../Behaviors/KeyboardShortcutsBehavior.cs | 2 +- .../Behaviors/PanBehavior.cs | 34 +++-------- .../Behaviors/SelectionBehavior.cs | 35 ++++++----- src/Blazor.Diagrams.Core/DiagramBase.cs | 35 +++++------ .../Events/PointerEventArgs.cs | 5 ++ src/Blazor.Diagrams.Core/Layers/LinkLayer.cs | 4 +- .../Models/Base/BaseLinkModel.cs | 36 ++++++----- .../Components/DiagramCanvas.razor | 14 ++--- .../Components/DiagramCanvas.razor.cs | 23 +++---- .../Components/LinkVertexWidget.razor | 13 ++-- .../Components/LinkVertexWidget.razor.cs | 29 ++++----- .../Components/LinkWidget.razor | 10 +-- .../Components/LinkWidget.razor.cs | 13 +--- .../Components/Renderers/GroupRenderer.cs | 33 ++++------ .../Components/Renderers/LinkRenderer.cs | 25 +++----- .../Components/Renderers/NodeRenderer.cs | 38 ++++-------- .../Components/Renderers/PortRenderer.cs | 26 +++----- .../Components/SelectionBoxWidget.razor.cs | 18 +++--- .../Extensions/EventsExtensions.cs | 21 +++---- .../Behaviors/DragNewLinkBehaviorTests.cs | 61 +++++++++++++------ .../Behaviors/EventsBehaviorTests.cs | 36 +++++------ 28 files changed, 272 insertions(+), 374 deletions(-) create mode 100644 src/Blazor.Diagrams.Core/Events/PointerEventArgs.cs diff --git a/samples/SharedDemo/Demos/Events.razor.cs b/samples/SharedDemo/Demos/Events.razor.cs index 396846e9f..c576ba38a 100644 --- a/samples/SharedDemo/Demos/Events.razor.cs +++ b/samples/SharedDemo/Demos/Events.razor.cs @@ -44,37 +44,25 @@ private void RegisterEvents() diagram.Links.Removed += (l) => events.Add($"Links.Removed, LinkId={l.Id}"); - diagram.MouseDown += (m, e) => + diagram.PointerDown += (m, e) => { events.Add($"MouseDown, Type={m?.GetType().Name}, ModelId={m?.Id}"); StateHasChanged(); }; - diagram.MouseUp += (m, e) => + diagram.PointerUp += (m, e) => { events.Add($"MouseUp, Type={m?.GetType().Name}, ModelId={m?.Id}"); StateHasChanged(); }; - diagram.TouchStart += (m, e) => - { - events.Add($"TouchStart, Type={m?.GetType().Name}, ModelId={m?.Id}"); - StateHasChanged(); - }; - - diagram.TouchEnd += (m, e) => - { - events.Add($"TouchEnd, Type={m?.GetType().Name}, ModelId={m?.Id}"); - StateHasChanged(); - }; - - diagram.MouseClick += (m, e) => + diagram.PointerClick += (m, e) => { events.Add($"MouseClick, Type={m?.GetType().Name}, ModelId={m?.Id}"); StateHasChanged(); }; - diagram.MouseDoubleClick += (m, e) => + diagram.PointerDoubleClick += (m, e) => { events.Add($"MouseDoubleClick, Type={m?.GetType().Name}, ModelId={m?.Id}"); StateHasChanged(); diff --git a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs index a8c05df47..98a45ec9d 100644 --- a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs +++ b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs @@ -18,9 +18,11 @@ protected override void OnInitialized() LayoutData.DataChanged(); _diagram.Options.Groups.Enabled = true; + _diagram.Options.LinksLayerOrder = 2; + _diagram.Options.NodesLayerOrder = 1; var ksb = _diagram.GetBehavior(); - ksb.RemoveShortcut("G", true, false, true); - ksb.SetShortcut("K", true, true, false, KeyboardShortcutsDefaults.Grouping); + ksb.RemoveShortcut("g", true, false, true); + ksb.SetShortcut("k", true, true, false, KeyboardShortcutsDefaults.Grouping); var node1 = NewNode(50, 50); var node2 = NewNode(250, 250); diff --git a/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs b/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs index a6991d609..4315041dd 100644 --- a/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs @@ -16,6 +16,9 @@ protected override void OnInitialized() LayoutData.Title = "Dynamic Groups"; LayoutData.Info = "You can create and modify groups dynamically!"; LayoutData.DataChanged(); + + _diagram.Options.LinksLayerOrder = 2; + _diagram.Options.NodesLayerOrder = 1; var node1 = NewNode(50, 150); var node2 = NewNode(250, 350); diff --git a/samples/SharedDemo/Demos/Groups/Factory.razor.cs b/samples/SharedDemo/Demos/Groups/Factory.razor.cs index 28045d4f0..d33fefcd7 100644 --- a/samples/SharedDemo/Demos/Groups/Factory.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Factory.razor.cs @@ -18,6 +18,8 @@ protected override void OnInitialized() LayoutData.DataChanged(); diagram.Options.Groups.Enabled = true; + diagram.Options.LinksLayerOrder = 2; + diagram.Options.NodesLayerOrder = 1; diagram.Options.Groups.Factory = (diagram, children) => { var group = new GroupModel(children, 25); diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index db085507d..47896770f 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -14,27 +14,16 @@ public class DragMovablesBehavior : Behavior public DragMovablesBehavior(DiagramBase diagram) : base(diagram) { - Diagram.MouseDown += OnMouseDown; - Diagram.MouseMove += OnMouseMove; - Diagram.MouseUp += OnMouseUp; - Diagram.TouchStart += OnTouchStart; - Diagram.TouchMove += OnTouchMove; - Diagram.TouchEnd += OnTouchEnd; + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; } - private void OnTouchStart(Model model, TouchEventArgs e) - => Start(model, e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY); + private void OnPointerDown(Model? model, PointerEventArgs e) => Start(model, e.ClientX, e.ClientY); - private void OnTouchMove(Model model, TouchEventArgs e) - => Move(e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY); + private void OnPointerMove(Model? model, PointerEventArgs e) => Move(e.ClientX, e.ClientY); - private void OnTouchEnd(Model model, TouchEventArgs e) => End(); - - private void OnMouseDown(Model model, MouseEventArgs e) => Start(model, e.ClientX, e.ClientY); - - private void OnMouseMove(Model model, MouseEventArgs e) => Move(e.ClientX, e.ClientY); - - private void OnMouseUp(Model model, MouseEventArgs e) => End(); + private void OnPointerUp(Model? model, PointerEventArgs e) => End(); private void Start(Model model, double clientX, double clientY) { @@ -62,7 +51,7 @@ private void Move(double clientX, double clientY) foreach (var sm in Diagram.GetSelectedModels()) { - if (!(sm is MovableModel node) || node.Locked) + if (sm is not MovableModel node || node.Locked) continue; var initialPosition = _initialPositions[i]; @@ -90,17 +79,14 @@ private double ApplyGridSize(double n) // 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) / gridSize); + return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); } public override void Dispose() { - Diagram.MouseDown -= OnMouseDown; - Diagram.MouseMove -= OnMouseMove; - Diagram.MouseUp -= OnMouseUp; - Diagram.TouchStart -= OnTouchStart; - Diagram.TouchMove -= OnTouchMove; - Diagram.TouchEnd -= OnTouchEnd; + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; } } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 89370a901..5d4d8fec6 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -1,5 +1,4 @@ -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Events; using System.Linq; @@ -9,29 +8,16 @@ namespace Blazor.Diagrams.Core.Behaviors { public class DragNewLinkBehavior : Behavior { - private double _initialX; - private double _initialY; private BaseLinkModel? _ongoingLink; public DragNewLinkBehavior(DiagramBase diagram) : base(diagram) { - Diagram.MouseDown += OnMouseDown; - Diagram.MouseMove += OnMouseMove; - Diagram.MouseUp += OnMouseUp; - Diagram.TouchStart += OnTouchStart; - Diagram.TouchMove += OnTouchMove; - Diagram.TouchEnd += OnTouchEnd; + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; } - private void OnTouchStart(Model? model, TouchEventArgs e) - => Start(model, e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY); - - private void OnTouchMove(Model? model, TouchEventArgs e) - => Move(model, e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY); - - private void OnTouchEnd(Model? model, TouchEventArgs e) => End(model); - - private void OnMouseDown(Model? model, MouseEventArgs e) + private void OnPointerDown(Model? model, MouseEventArgs e) { if (e.Button != (int)MouseEventButton.Left) return; @@ -39,9 +25,9 @@ private void OnMouseDown(Model? model, MouseEventArgs e) Start(model, e.ClientX, e.ClientY); } - private void OnMouseMove(Model? model, MouseEventArgs e) => Move(model, e.ClientX, e.ClientY); + private void OnPointerMove(Model? model, MouseEventArgs e) => Move(model, e.ClientX, e.ClientY); - private void OnMouseUp(Model? model, MouseEventArgs e) => End(model); + private void OnPointerUp(Model? model, MouseEventArgs e) => End(model); private void Start(Model? model, double clientX, double clientY) { @@ -56,8 +42,6 @@ private void Start(Model? model, double clientX, double clientY) return; } - _initialX = clientX; - _initialY = clientY; Diagram.Links.Add(_ongoingLink); } @@ -123,12 +107,9 @@ private void End(Model? model) public override void Dispose() { - Diagram.MouseDown -= OnMouseDown; - Diagram.MouseMove -= OnMouseMove; - Diagram.MouseUp -= OnMouseUp; - Diagram.TouchStart -= OnTouchStart; - Diagram.TouchMove -= OnTouchMove; - Diagram.TouchEnd -= OnTouchEnd; + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; } } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs index 490dafb54..37e4ab0cc 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs @@ -1,6 +1,5 @@ using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Events; -using System; using System.Diagnostics; namespace Blazor.Diagrams.Core.Behaviors @@ -16,30 +15,30 @@ public EventsBehavior(DiagramBase diagram) : base(diagram) { _mouseClickSw = new Stopwatch(); - Diagram.MouseDown += OnMouseDown; - Diagram.MouseMove += OnMouseMove; - Diagram.MouseUp += OnMouseUp; - Diagram.MouseClick += OnMouseClick; + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + Diagram.PointerClick += OnPointerClick; } - private void OnMouseClick(Model? model, MouseEventArgs e) + private void OnPointerClick(Model? model, PointerEventArgs e) { if (_mouseClickSw.IsRunning && _mouseClickSw.ElapsedMilliseconds <= 500) { - Diagram.OnMouseDoubleClick(model, e); + Diagram.TriggerPointerDoubleClick(model, e); } _mouseClickSw.Restart(); } - private void OnMouseDown(Model? model, MouseEventArgs e) + private void OnPointerDown(Model? model, PointerEventArgs e) { _captureMouseMove = true; _mouseMovedCount = 0; _model = model; } - private void OnMouseMove(Model? model, MouseEventArgs e) + private void OnPointerMove(Model? model, PointerEventArgs e) { if (!_captureMouseMove) return; @@ -47,7 +46,7 @@ private void OnMouseMove(Model? model, MouseEventArgs e) _mouseMovedCount++; } - private void OnMouseUp(Model? model, MouseEventArgs e) + private void OnPointerUp(Model? model, PointerEventArgs e) { if (!_captureMouseMove) return; // Only set by OnMouseDown _captureMouseMove = false; @@ -55,17 +54,17 @@ private void OnMouseUp(Model? model, MouseEventArgs e) if (_model == model) { - Diagram.OnMouseClick(model, e); + Diagram.TriggerPointerClick(model, e); _model = null; } } public override void Dispose() { - Diagram.MouseDown -= OnMouseDown; - Diagram.MouseMove -= OnMouseMove; - Diagram.MouseUp -= OnMouseUp; - Diagram.MouseClick -= OnMouseClick; + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + Diagram.PointerClick -= OnPointerClick; _model = null; } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs index d79f396ce..2434ec767 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs @@ -14,7 +14,7 @@ public KeyboardShortcutsBehavior(DiagramBase diagram) : base(diagram) { _shortcuts = new Dictionary>(); SetShortcut("Delete", false, false, false, KeyboardShortcutsDefaults.DeleteSelection); - SetShortcut("G", true, false, true, KeyboardShortcutsDefaults.Grouping); + SetShortcut("g", true, false, true, KeyboardShortcutsDefaults.Grouping); Diagram.KeyDown += OnDiagramKeyDown; } diff --git a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs index 186796769..661b11b07 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs @@ -12,23 +12,12 @@ public class PanBehavior : Behavior public PanBehavior(DiagramBase diagram) : base(diagram) { - Diagram.MouseDown += OnMouseDown; - Diagram.MouseMove += OnMouseMove; - Diagram.MouseUp += OnMouseUp; - Diagram.TouchStart += OnTouchStart; - Diagram.TouchMove += OnTouchmove; - Diagram.TouchEnd += OnTouchEnd; + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; } - private void OnTouchStart(Model model, TouchEventArgs e) - => Start(model, e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY, e.ShiftKey); - - private void OnTouchmove(Model model, TouchEventArgs e) - => Move(e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY); - - private void OnTouchEnd(Model model, TouchEventArgs e) => End(); - - private void OnMouseDown(Model model, MouseEventArgs e) + private void OnPointerDown(Model? model, PointerEventArgs e) { if (e.Button != (int)MouseEventButton.Left) return; @@ -36,11 +25,11 @@ private void OnMouseDown(Model model, MouseEventArgs e) Start(model, e.ClientX, e.ClientY, e.ShiftKey); } - private void OnMouseMove(Model model, MouseEventArgs e) => Move(e.ClientX, e.ClientY); + private void OnPointerMove(Model? model, PointerEventArgs e) => Move(e.ClientX, e.ClientY); - private void OnMouseUp(Model model, MouseEventArgs e) => End(); + private void OnPointerUp(Model? model, PointerEventArgs e) => End(); - private void Start(Model model, double clientX, double clientY, bool shiftKey) + private void Start(Model? model, double clientX, double clientY, bool shiftKey) { if (!Diagram.Options.AllowPanning || model != null || shiftKey) return; @@ -70,12 +59,9 @@ private void End() public override void Dispose() { - Diagram.MouseDown -= OnMouseDown; - Diagram.MouseMove -= OnMouseMove; - Diagram.MouseUp -= OnMouseUp; - Diagram.TouchStart -= OnTouchStart; - Diagram.TouchMove -= OnTouchmove; - Diagram.TouchEnd -= OnTouchEnd; + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; } } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs index db99ae7b4..3d4b4421b 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs @@ -7,37 +7,36 @@ public class SelectionBehavior : Behavior { public SelectionBehavior(DiagramBase diagram) : base(diagram) { - Diagram.MouseDown += OnMouseDown; - Diagram.TouchStart += OnTouchStart; + Diagram.PointerDown += OnPointerDown; } - private void OnTouchStart(Model model, TouchEventArgs e) => Process(model, e.CtrlKey); + private void OnPointerDown(Model? model, PointerEventArgs e) => Process(model, e.CtrlKey); - private void OnMouseDown(Model model, MouseEventArgs e) => Process(model, e.CtrlKey); - - private void Process(Model model, bool ctrlKey) + private void Process(Model? model, bool ctrlKey) { - if (model == null) - { - Diagram.UnselectAll(); - } - else if (model is SelectableModel sm) + switch (model) { - if (ctrlKey && sm.Selected) - { + case null: + Diagram.UnselectAll(); + break; + case SelectableModel sm when ctrlKey && sm.Selected: Diagram.UnselectModel(sm); - } - else if (!sm.Selected) + break; + case SelectableModel sm: { - Diagram.SelectModel(sm, !ctrlKey || !Diagram.Options.AllowMultiSelection); + if (!sm.Selected) + { + Diagram.SelectModel(sm, !ctrlKey || !Diagram.Options.AllowMultiSelection); + } + + break; } } } public override void Dispose() { - Diagram.MouseDown -= OnMouseDown; - Diagram.TouchStart -= OnTouchStart; + Diagram.PointerDown -= OnPointerDown; } } } diff --git a/src/Blazor.Diagrams.Core/DiagramBase.cs b/src/Blazor.Diagrams.Core/DiagramBase.cs index 4957a62e4..a14f10261 100644 --- a/src/Blazor.Diagrams.Core/DiagramBase.cs +++ b/src/Blazor.Diagrams.Core/DiagramBase.cs @@ -20,16 +20,15 @@ public class DiagramBase : Model private readonly Dictionary _behaviors; private readonly List _groups; - public event Action? MouseDown; - public event Action? MouseMove; - public event Action? MouseUp; + 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? MouseClick; - public event Action? MouseDoubleClick; - public event Action? TouchStart; - public event Action? TouchMove; - public event Action? TouchEnd; + public event Action? PointerClick; + public event Action? PointerDoubleClick; public event Action? SelectionChanged; public event Action? GroupAdded; @@ -350,25 +349,23 @@ public Point GetScreenPoint(double clientX, double clientY) #region Events - internal void OnMouseDown(Model? model, MouseEventArgs e) => MouseDown?.Invoke(model, e); + internal void TriggerPointerDown(Model? model, PointerEventArgs e) => PointerDown?.Invoke(model, e); - internal void OnMouseMove(Model? model, MouseEventArgs e) => MouseMove?.Invoke(model, e); + internal void TriggerPointerMove(Model? model, PointerEventArgs e) => PointerMove?.Invoke(model, e); - internal void OnMouseUp(Model? model, MouseEventArgs e) => MouseUp?.Invoke(model, e); + internal void TriggerPointerUp(Model? model, PointerEventArgs e) => PointerUp?.Invoke(model, e); + + internal void TriggerPointerEnter(Model? model, PointerEventArgs e) => PointerEnter?.Invoke(model, e); + + internal void TriggerPointerLeave(Model? model, PointerEventArgs e) => PointerLeave?.Invoke(model, e); internal void OnKeyDown(KeyboardEventArgs e) => KeyDown?.Invoke(e); internal void OnWheel(WheelEventArgs e) => Wheel?.Invoke(e); - internal void OnMouseClick(Model? model, MouseEventArgs e) => MouseClick?.Invoke(model, e); + internal void TriggerPointerClick(Model? model, PointerEventArgs e) => PointerClick?.Invoke(model, e); - internal void OnMouseDoubleClick(Model? model, MouseEventArgs e) => MouseDoubleClick?.Invoke(model, e); - - internal void OnTouchStart(Model? model, TouchEventArgs e) => TouchStart?.Invoke(model, e); - - internal void OnTouchMove(Model? model, TouchEventArgs e) => TouchMove?.Invoke(model, e); - - internal void OnTouchEnd(Model? model, TouchEventArgs e) => TouchEnd?.Invoke(model, e); + internal void TriggerPointerDoubleClick(Model? model, PointerEventArgs e) => PointerDoubleClick?.Invoke(model, e); #endregion } diff --git a/src/Blazor.Diagrams.Core/Events/PointerEventArgs.cs b/src/Blazor.Diagrams.Core/Events/PointerEventArgs.cs new file mode 100644 index 000000000..558f5c13d --- /dev/null +++ b/src/Blazor.Diagrams.Core/Events/PointerEventArgs.cs @@ -0,0 +1,5 @@ +namespace Blazor.Diagrams.Core.Events; + +public record PointerEventArgs(double ClientX, double ClientY, long Button, long Buttons, bool CtrlKey, bool ShiftKey, + bool AltKey, long PointerId, float Width, float Height, float Pressure, float TiltX, float TiltY, + string PointerType, bool IsPrimary) : MouseEventArgs(ClientX, ClientY, Button, Buttons, CtrlKey, ShiftKey, AltKey); \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs index 13df2ddcb..8680b4455 100644 --- a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs @@ -13,6 +13,7 @@ protected override void OnItemAdded(BaseLinkModel link) link.Diagram = Diagram; HandleAnchor(link, link.Source, true); if (link.Target != null) HandleAnchor(link, link.Target, true); + link.Refresh(); link.Source.Node.Group?.Refresh(); link.Target?.Node.Group?.Refresh(); @@ -26,6 +27,7 @@ protected override void OnItemRemoved(BaseLinkModel link) link.Diagram = null; HandleAnchor(link, link.Source, false); if (link.Target != null) HandleAnchor(link, link.Target, false); + link.Refresh(); link.Source.Node.Group?.Refresh(); link.Target?.Node.Group?.Refresh(); @@ -61,7 +63,7 @@ private static void HandleAnchor(BaseLinkModel link, Anchor anchor, bool add) spa.Port.Refresh(); } - else if (anchor is ShapeIntersectionAnchor || anchor is DynamicAnchor) + else if (anchor is ShapeIntersectionAnchor or DynamicAnchor) { if (add) { diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index 2a12d9370..b506b5b61 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -40,22 +40,7 @@ protected BaseLinkModel(string id, Anchor source, Anchor? target = null) : base( public override void Refresh() { - if (Diagram != null) - { - var router = Router ?? Diagram.Options.Links.DefaultRouter; - var pathGenerator = PathGenerator ?? Diagram.Options.Links.DefaultPathGenerator; - var route = router(Diagram, this); - var source = Source.GetPosition(this, route); - var target = Target is null ? OnGoingPosition : Target.GetPosition(this, route); - if (source != null && target != null) - { - GeneratedPathResult = pathGenerator(Diagram, this, route, source, target); - base.Refresh(); - return; - } - } - - GeneratedPathResult = PathGeneratorResult.Empty; + GeneratePath(); base.Refresh(); } @@ -102,5 +87,24 @@ public void SetTarget(Anchor? anchor) return new Rectangle(minX, minY, maxX, maxY); } + + private void GeneratePath() + { + if (Diagram != null) + { + var router = Router ?? Diagram.Options.Links.DefaultRouter; + var pathGenerator = PathGenerator ?? Diagram.Options.Links.DefaultPathGenerator; + var route = router(Diagram, this); + var source = Source.GetPosition(this, route); + var target = Target is null ? OnGoingPosition : Target.GetPosition(this, route); + if (source != null && target != null) + { + GeneratedPathResult = pathGenerator(Diagram, this, route, source, target); + return; + } + } + + GeneratedPathResult = PathGeneratorResult.Empty; + } } } diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor b/src/Blazor.Diagrams/Components/DiagramCanvas.razor index 4c0e2fc4a..80199737a 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor @@ -1,17 +1,13 @@ 
    + @onpointermove:preventDefault + @onwheel:stopPropagation> @* Links *@ diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index 7e0cfd1e2..e5e8a555a 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -55,31 +55,22 @@ protected override async Task OnAfterRenderAsync(bool firstRender) protected override bool ShouldRender() { - if (_shouldRender) - { - _shouldRender = false; - return true; - } - - return false; + if (!_shouldRender) return false; + + _shouldRender = false; + return true; } - private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(null, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) => Diagram.TriggerPointerDown(null, e.ToCore()); - private void OnMouseMove(MouseEventArgs e) => Diagram.OnMouseMove(null, e.ToCore()); + private void OnPointerMove(PointerEventArgs e) => Diagram.TriggerPointerMove(null, e.ToCore()); - private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(null, e.ToCore()); + private void OnPointerUp(PointerEventArgs e) => Diagram.TriggerPointerUp(null, e.ToCore()); private void OnKeyDown(KeyboardEventArgs e) => Diagram.OnKeyDown(e.ToCore()); private void OnWheel(WheelEventArgs e) => Diagram.OnWheel(e.ToCore()); - private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(null, e.ToCore()); - - private void OnTouchMove(TouchEventArgs e) => Diagram.OnTouchMove(null, e.ToCore()); - - private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(null, e.ToCore()); - private void OnDiagramChanged() { _shouldRender = true; diff --git a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor index bebfd820c..19febf708 100644 --- a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor +++ b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor @@ -4,12 +4,7 @@ fill="@ColorToUse" cursor="move" @ondblclick="OnDoubleClick" - @onmousedown="OnMouseDown" - @onmousedown:stopPropagation - @onmouseup="OnMouseUp" - @onmouseup:stopPropagation - @ontouchstart="OnTouchStart" - @ontouchstart:stopPropagation - @ontouchend="OnTouchEnd" - @ontouchend:preventDefault - @ontouchend:stopPropagation /> \ No newline at end of file + @onpointerdown="OnPointerDown" + @onpointerup="OnPointerUp" + @onpointerdown:stopPropagation + @onpointerup:stopPropagation /> \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs index 82d395813..51d2290c8 100644 --- a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs @@ -11,12 +11,12 @@ public partial class LinkVertexWidget : IDisposable { private bool _shouldRender = true; - [CascadingParameter] public Diagram Diagram { get; set; } - [Parameter] public LinkVertexModel Vertex { get; set; } - [Parameter] public string Color { get; set; } - [Parameter] public string SelectedColor { get; set; } + [CascadingParameter] public Diagram Diagram { get; set; } = null!; + [Parameter] public LinkVertexModel Vertex { get; set; } = null!; + [Parameter] public string? Color { get; set; } + [Parameter] public string? SelectedColor { get; set; } - private string ColorToUse => Vertex.Selected ? SelectedColor : Color; + private string? ColorToUse => Vertex.Selected ? SelectedColor : Color; public void Dispose() { @@ -30,13 +30,10 @@ protected override void OnInitialized() protected override bool ShouldRender() { - if (_shouldRender) - { - _shouldRender = false; - return true; - } - - return false; + if (!_shouldRender) return false; + + _shouldRender = false; + return true; } private void OnVertexChanged() @@ -45,13 +42,9 @@ private void OnVertexChanged() InvokeAsync(StateHasChanged); } - private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Vertex, e.ToCore()); - - private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Vertex, e.ToCore()); - - private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Vertex, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) => Diagram.TriggerPointerDown(Vertex, e.ToCore()); - private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(Vertex, e.ToCore()); + private void OnPointerUp(PointerEventArgs e) => Diagram.TriggerPointerUp(Vertex, e.ToCore()); private void OnDoubleClick(MouseEventArgs e) { diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor b/src/Blazor.Diagrams/Components/LinkWidget.razor index df318403f..f0979510a 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor @@ -1,6 +1,4 @@ -@using SvgPathProperties; - -@{ +@{ var color = Link.Selected ? (Link.SelectedColor ?? Diagram.Options.Links.DefaultSelectedColor) : (Link.Color ?? Diagram.Options.Links.DefaultColor); var result = Link.GeneratedPathResult; var bounds = Link.GetBounds()?.Inflate(10, 10); @@ -24,10 +22,8 @@ stroke-linecap="butt" stroke-opacity="0" fill="none" - @onmousedown="e => OnMouseDown(e, index)" - @onmousedown:stopPropagation="@Link.Segmentable" - @ontouchstart="e => OnTouchStart(e, index)" - @ontouchstart:stopPropagation="@Link.Segmentable"/> + @onpointerdown="e => OnPointerDown(e, index)" + @onpointerdown:stopPropagation="@Link.Segmentable" /> } } diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs index bb5845c83..626904eb2 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs @@ -13,22 +13,13 @@ public partial class LinkWidget [Parameter] public LinkModel Link { get; set; } = null!; - private void OnMouseDown(MouseEventArgs e, int index) + private void OnPointerDown(PointerEventArgs e, int index) { if (!Link.Segmentable) return; var vertex = CreateVertex(e.ClientX, e.ClientY, index); - Diagram.OnMouseDown(vertex, e.ToCore()); - } - - private void OnTouchStart(TouchEventArgs e, int index) - { - if (!Link.Segmentable) - return; - - var vertex = CreateVertex(e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY, index); - Diagram.OnTouchStart(vertex, e.ToCore()); + Diagram.TriggerPointerDown(vertex, e.ToCore()); } private LinkVertexModel CreateVertex(double clientX, double clientY, int index) diff --git a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs index c76de3580..7ace4ac67 100644 --- a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs @@ -98,37 +98,28 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.AddAttribute(3, "style", GenerateStyle(Group.Position.Y, Group.Position.X, Group.Size!.Width, Group.Size.Height)); } - builder.AddAttribute(4, "onmousedown", EventCallback.Factory.Create(this, OnMouseDown)); - builder.AddEventStopPropagationAttribute(5, "onmousedown", true); - builder.AddAttribute(6, "onmouseup", EventCallback.Factory.Create(this, OnMouseUp)); - builder.AddEventStopPropagationAttribute(7, "onmouseup", true); - builder.AddAttribute(8, "ontouchstart", EventCallback.Factory.Create(this, OnTouchStart)); - builder.AddEventStopPropagationAttribute(9, "ontouchstart", true); - builder.AddAttribute(10, "ontouchend", EventCallback.Factory.Create(this, OnTouchEnd)); - builder.AddEventStopPropagationAttribute(11, "ontouchend", true); - builder.AddEventPreventDefaultAttribute(12, "ontouchend", true); + builder.AddAttribute(4, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); + builder.AddEventStopPropagationAttribute(5, "onpointerdown", true); + builder.AddAttribute(6, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); + builder.AddEventStopPropagationAttribute(7, "onpointerup", true); if (_isSvg) { - builder.OpenElement(13, "rect"); - builder.AddAttribute(14, "width", Group.Size!.Width); - builder.AddAttribute(15, "height", Group.Size.Height); - builder.AddAttribute(16, "fill", "none"); + builder.OpenElement(8, "rect"); + builder.AddAttribute(9, "width", Group.Size!.Width); + builder.AddAttribute(10, "height", Group.Size.Height); + builder.AddAttribute(11, "fill", "none"); builder.CloseElement(); } - builder.OpenComponent(17, componentType); - builder.AddAttribute(18, "Group", Group); + builder.OpenComponent(12, componentType); + builder.AddAttribute(13, "Group", Group); builder.CloseComponent(); builder.CloseElement(); } - private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Group, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) => Diagram.TriggerPointerDown(Group, e.ToCore()); - private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Group, e.ToCore()); - - private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Group, e.ToCore()); - - private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(Group, e.ToCore()); + private void OnPointerUp(PointerEventArgs e) => Diagram.TriggerPointerUp(Group, e.ToCore()); } } diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs index b800a3733..ef8715a32 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs @@ -39,17 +39,12 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.OpenElement(0, "g"); builder.AddAttribute(1, "class", "link"); builder.AddAttribute(2, "data-link-id", Link.Id); - builder.AddAttribute(3, "onmousedown", EventCallback.Factory.Create(this, OnMouseDown)); - builder.AddEventStopPropagationAttribute(4, "onmousedown", true); - builder.AddAttribute(5, "onmouseup", EventCallback.Factory.Create(this, OnMouseUp)); - builder.AddEventStopPropagationAttribute(6, "onmouseup", true); - builder.AddAttribute(7, "ontouchstart", EventCallback.Factory.Create(this, OnTouchStart)); - builder.AddEventStopPropagationAttribute(8, "ontouchstart", true); - builder.AddAttribute(9, "ontouchend", EventCallback.Factory.Create(this, OnTouchEnd)); - builder.AddEventStopPropagationAttribute(10, "ontouchend", true); - builder.AddEventPreventDefaultAttribute(11, "ontouchend", true); - builder.OpenComponent(12, componentType); - builder.AddAttribute(13, "Link", Link); + builder.AddAttribute(3, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); + builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); + builder.AddAttribute(5, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); + builder.AddEventStopPropagationAttribute(6, "onpointerup", true); + builder.OpenComponent(7, componentType); + builder.AddAttribute(8, "Link", Link); builder.CloseComponent(); builder.CloseElement(); } @@ -62,12 +57,8 @@ private void OnLinkChanged() InvokeAsync(StateHasChanged); } - private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Link, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) => Diagram.TriggerPointerDown(Link, e.ToCore()); - private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Link, e.ToCore()); - - private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Link, e.ToCore()); - - private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(Link, e.ToCore()); + private void OnPointerUp(PointerEventArgs e) => Diagram.TriggerPointerUp(Link, e.ToCore()); } } diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index 3ae7441ed..5717f36f3 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -81,13 +81,10 @@ protected override void OnParametersSet() protected override bool ShouldRender() { - if (_shouldRender) - { - _shouldRender = false; - return true; - } - - return false; + if (!_shouldRender) return false; + + _shouldRender = false; + return true; } protected override void BuildRenderTree(RenderTreeBuilder builder) @@ -114,18 +111,13 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.AddAttribute(3, "style", $"top: {Node.Position.Y.ToInvariantString()}px; left: {Node.Position.X.ToInvariantString()}px"); } - builder.AddAttribute(4, "onmousedown", EventCallback.Factory.Create(this, OnMouseDown)); - builder.AddEventStopPropagationAttribute(5, "onmousedown", true); - builder.AddAttribute(6, "onmouseup", EventCallback.Factory.Create(this, OnMouseUp)); - builder.AddEventStopPropagationAttribute(7, "onmouseup", true); - builder.AddAttribute(8, "ontouchstart", EventCallback.Factory.Create(this, OnTouchStart)); - builder.AddEventStopPropagationAttribute(9, "ontouchstart", true); - builder.AddAttribute(10, "ontouchend", EventCallback.Factory.Create(this, OnTouchEnd)); - builder.AddEventStopPropagationAttribute(11, "ontouchend", true); - builder.AddEventPreventDefaultAttribute(12, "ontouchend", true); - builder.AddElementReferenceCapture(13, value => _element = value); - builder.OpenComponent(14, componentType); - builder.AddAttribute(15, "Node", Node); + builder.AddAttribute(4, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); + builder.AddEventStopPropagationAttribute(5, "onpointerdown", true); + builder.AddAttribute(6, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); + builder.AddEventStopPropagationAttribute(7, "onpointerup", true); + builder.AddElementReferenceCapture(8, value => _element = value); + builder.OpenComponent(9, componentType); + builder.AddAttribute(10, "Node", Node); builder.CloseComponent(); builder.CloseElement(); } @@ -172,12 +164,8 @@ private void ReRender() InvokeAsync(StateHasChanged); } - private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Node, e.ToCore()); - - private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Node, e.ToCore()); - - private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Node, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) => Diagram.TriggerPointerDown(Node, e.ToCore()); - private void OnTouchEnd(TouchEventArgs e) => Diagram.OnTouchEnd(Node, e.ToCore()); + private void OnPointerUp(PointerEventArgs e) => Diagram.TriggerPointerUp(Node, e.ToCore()); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs index 3b7dffed2..887dcc28c 100644 --- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs @@ -61,15 +61,10 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.OpenElement(0, _isParentSvg ? "g" : "div"); builder.AddAttribute(1, "class", "port" + " " + (Port.Alignment.ToString().ToLower()) + " " + (Port.Links.Count > 0 ? "has-links" : "") + " " + (Class)); builder.AddAttribute(2, "data-port-id", Port.Id); - builder.AddAttribute(3, "onmousedown", EventCallback.Factory.Create(this, OnMouseDown)); - builder.AddEventStopPropagationAttribute(4, "onmousedown", true); - builder.AddAttribute(5, "onmouseup", EventCallback.Factory.Create(this, OnMouseUp)); - builder.AddEventStopPropagationAttribute(6, "onmouseup", true); - builder.AddAttribute(7, "ontouchstart", EventCallback.Factory.Create(this, OnTouchStart)); - builder.AddEventStopPropagationAttribute(8, "ontouchstart", true); - builder.AddAttribute(9, "ontouchend", EventCallback.Factory.Create(this, OnTouchEnd)); - builder.AddEventStopPropagationAttribute(10, "ontouchend", true); - builder.AddEventPreventDefaultAttribute(11, "ontouchend", true); + builder.AddAttribute(3, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); + builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); + builder.AddAttribute(5, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); + builder.AddEventStopPropagationAttribute(6, "onpointerup", true); builder.AddElementReferenceCapture(12, (__value) => { _element = __value; }); builder.AddContent(13, ChildContent); builder.CloseElement(); @@ -86,14 +81,13 @@ protected override async Task OnAfterRenderAsync(bool firstRender) } } - private void OnMouseDown(MouseEventArgs e) => Diagram.OnMouseDown(Port, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) => Diagram.TriggerPointerDown(Port, e.ToCore()); - private void OnMouseUp(MouseEventArgs e) => Diagram.OnMouseUp(Port, e.ToCore()); - - private void OnTouchStart(TouchEventArgs e) => Diagram.OnTouchStart(Port, e.ToCore()); - - private void OnTouchEnd(TouchEventArgs e) - => Diagram.OnTouchEnd(FindPortOn(e.ChangedTouches[0].ClientX, e.ChangedTouches[0].ClientY), e.ToCore()); + private void OnPointerUp(PointerEventArgs e) + { + var model = e.PointerType == "mouse" ? Port : FindPortOn(e.ClientX, e.ClientY); + Diagram.TriggerPointerUp(model, e.ToCore()); + } private PortModel? FindPortOn(double clientX, double clientY) { diff --git a/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs b/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs index 66561d3b1..a8546103a 100644 --- a/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs @@ -21,15 +21,15 @@ public partial class SelectionBoxWidget : IDisposable protected override void OnInitialized() { - Diagram.MouseDown += OnMouseDown; - Diagram.MouseMove += OnMouseMove; - Diagram.MouseUp += OnMouseUp; + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; } private string GenerateStyle() => FormattableString.Invariant($"position: absolute; background: {Background}; top: {_selectionBoxTopLeft.Y}px; left: {_selectionBoxTopLeft.X}px; width: {_selectionBoxSize.Width}px; height: {_selectionBoxSize.Height}px;"); - private void OnMouseDown(Model model, MouseEventArgs e) + private void OnPointerDown(Model model, MouseEventArgs e) { if (model != null || !e.ShiftKey) return; @@ -37,7 +37,7 @@ private void OnMouseDown(Model model, MouseEventArgs e) _initialClientPoint = new Point(e.ClientX, e.ClientY); } - private void OnMouseMove(Model model, MouseEventArgs e) + private void OnPointerMove(Model model, MouseEventArgs e) { if (_initialClientPoint == null) return; @@ -75,7 +75,7 @@ private void SetSelectionBoxInformation(MouseEventArgs e) _selectionBoxSize = new Size(eX - sX, eY - sY); } - private void OnMouseUp(Model model, MouseEventArgs e) + private void OnPointerUp(Model model, MouseEventArgs e) { _initialClientPoint = null; _selectionBoxTopLeft = null; @@ -85,9 +85,9 @@ private void OnMouseUp(Model model, MouseEventArgs e) public void Dispose() { - Diagram.MouseDown -= OnMouseDown; - Diagram.MouseMove -= OnMouseMove; - Diagram.MouseUp -= OnMouseUp; + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; } } } diff --git a/src/Blazor.Diagrams/Extensions/EventsExtensions.cs b/src/Blazor.Diagrams/Extensions/EventsExtensions.cs index c382523de..d597ed371 100644 --- a/src/Blazor.Diagrams/Extensions/EventsExtensions.cs +++ b/src/Blazor.Diagrams/Extensions/EventsExtensions.cs @@ -6,9 +6,16 @@ namespace Blazor.Diagrams.Extensions { public static class EventsExtensions { - public static MouseEventArgs ToCore(this Web.MouseEventArgs e) + public static PointerEventArgs ToCore(this Web.PointerEventArgs e) { - return new MouseEventArgs(e.ClientX, e.ClientY, e.Button, e.Buttons, e.CtrlKey, e.ShiftKey, e.AltKey); + return new PointerEventArgs(e.ClientX, e.ClientY, e.Button, e.Buttons, e.CtrlKey, e.ShiftKey, e.AltKey, + e.PointerId, e.Width, e.Height, e.Pressure, e.TiltX, e.TiltY, e.PointerType, e.IsPrimary); + } + + public static PointerEventArgs ToCore(this Web.MouseEventArgs e) + { + return new PointerEventArgs(e.ClientX, e.ClientY, e.Button, e.Buttons, e.CtrlKey, e.ShiftKey, e.AltKey, + 0, 0, 0, 0, 0, 0, string.Empty, false); } public static KeyboardEventArgs ToCore(this Web.KeyboardEventArgs e) @@ -20,15 +27,5 @@ public static WheelEventArgs ToCore(this Web.WheelEventArgs e) { return new WheelEventArgs(e.ClientX, e.ClientY, e.Button, e.Buttons, e.CtrlKey, e.ShiftKey, e.AltKey, e.DeltaX, e.DeltaY, e.DeltaZ, e.DeltaMode); } - - public static TouchEventArgs ToCore(this Web.TouchEventArgs e) - { - return new TouchEventArgs(e.ChangedTouches.Select(ToCore).ToArray(), e.CtrlKey, e.ShiftKey, e.AltKey); - } - - public static TouchPoint ToCore(this Web.TouchPoint e) - { - return new TouchPoint(e.Identifier, e.ClientX, e.ClientY); - } } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index 8f9f293bb..1a7aa7004 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -25,7 +25,8 @@ public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnP }); // Act - diagram.OnMouseDown(port, new MouseEventArgs(100, 100, 0, 0, false, false, false)); + 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(); @@ -59,7 +60,8 @@ public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() }); // Act - diagram.OnMouseDown(port, new MouseEventArgs(100, 100, 0, 0, false, false, false)); + 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(); @@ -89,10 +91,12 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() }); // Act - diagram.OnMouseDown(port, new MouseEventArgs(100, 100, 0, 0, false, false, false)); + 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.OnMouseMove(null, new MouseEventArgs(150, 150, 0, 0, false, false, false)); + 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; @@ -118,10 +122,12 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoom }); // Act - diagram.OnMouseDown(port, new MouseEventArgs(100, 100, 0, 0, false, false, false)); + 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.OnMouseMove(null, new MouseEventArgs(160, 160, 0, 0, false, false, false)); + 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; @@ -157,8 +163,10 @@ public void Behavior_ShouldSnapToClosestPortAndRefreshPort_WhenSnappingIsEnabled port2.Changed += () => port2Refreshed = true; // Act - diagram.OnMouseDown(port1, new MouseEventArgs(100, 100, 0, 0, false, false, false)); - diagram.OnMouseMove(null, new MouseEventArgs(140, 100, 0, 0, false, false, false)); + 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(); @@ -193,8 +201,10 @@ public void Behavior_ShouldNotSnapToPort_WhenSnappingIsEnabledAndPortIsNotInRadi }); // Act - diagram.OnMouseDown(port1, new MouseEventArgs(100, 100, 0, 0, false, false, false)); - diagram.OnMouseMove(null, new MouseEventArgs(105, 105, 0, 0, false, false, false)); + 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(); @@ -228,9 +238,14 @@ public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNo port2.Changed += () => port2Refreshes++; // Act - diagram.OnMouseDown(port1, new MouseEventArgs(100, 100, 0, 0, false, false, false)); - diagram.OnMouseMove(null, new MouseEventArgs(140, 100, 0, 0, false, false, false)); // Move towards the other port - diagram.OnMouseMove(null, new MouseEventArgs(100, 100, 0, 0, false, false, false)); // Move back to unsnap + 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(); @@ -254,8 +269,10 @@ public void Behavior_ShouldRemoveLink_WhenMouseUpOnCanvas() }); // Act - diagram.OnMouseDown(port, new MouseEventArgs(0, 0, 0, 0, false, false, false)); - diagram.OnMouseUp(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + 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(); @@ -276,8 +293,10 @@ public void Behavior_ShouldRemoveLink_WhenMouseUpOnSamePort() }); // Act - diagram.OnMouseDown(port, new MouseEventArgs(0, 0, 0, 0, false, false, false)); - diagram.OnMouseUp(port, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + 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(); @@ -308,8 +327,10 @@ public void Behavior_ShouldSetTarget_WhenMouseUp() port2.Changed += () => port2Refreshes++; // Act - diagram.OnMouseDown(port1, new MouseEventArgs(100, 100, 0, 0, false, false, false)); - diagram.OnMouseUp(port2, new MouseEventArgs(105, 105, 0, 0, false, false, false)); + 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(); @@ -320,4 +341,4 @@ public void Behavior_ShouldSetTarget_WhenMouseUp() port2Refreshes.Should().Be(1); } } -} +} \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs index a15516d2a..704bbfa65 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs @@ -17,9 +17,9 @@ public void Behavior_ShouldNotTriggerMouseClick_WhenItsRemoved() var eventTriggered = false; // Act - diagram.MouseClick += (m, e) => eventTriggered = true; - diagram.OnMouseDown(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); - diagram.OnMouseUp(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.PointerClick += (m, e) => eventTriggered = true; + diagram.TriggerPointerDown(null, 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 eventTriggered.Should().BeFalse(); @@ -33,9 +33,9 @@ public void Behavior_ShouldTriggerMouseClick_WhenMouseDownThenUpWithoutMove() var eventTriggered = false; // Act - diagram.MouseClick += (m, e) => eventTriggered = true; - diagram.OnMouseDown(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); - diagram.OnMouseUp(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.PointerClick += (m, e) => eventTriggered = true; + diagram.TriggerPointerDown(null, 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 eventTriggered.Should().BeTrue(); @@ -49,10 +49,10 @@ public void Behavior_ShouldNotTriggerMouseClick_WhenMouseMoves() var eventTriggered = false; // Act - diagram.MouseClick += (m, e) => eventTriggered = true; - diagram.OnMouseDown(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); - diagram.OnMouseMove(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); - diagram.OnMouseUp(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.PointerClick += (m, e) => eventTriggered = true; + diagram.TriggerPointerDown(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, 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 eventTriggered.Should().BeFalse(); @@ -66,9 +66,9 @@ public void Behavior_ShouldTriggerMouseDoubleClick_WhenTwoMouseClicksHappenWithi var eventTriggered = false; // Act - diagram.MouseDoubleClick += (m, e) => eventTriggered = true; - diagram.OnMouseClick(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); - diagram.OnMouseClick(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.PointerDoubleClick += (m, e) => eventTriggered = true; + diagram.TriggerPointerClick(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerClick(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); // Assert eventTriggered.Should().BeTrue(); @@ -82,10 +82,10 @@ public async Task Behavior_ShouldNotTriggerMouseDoubleClick_WhenTimeExceeds500() var eventTriggered = false; // Act - diagram.MouseDoubleClick += (m, e) => eventTriggered = true; - diagram.OnMouseClick(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.PointerDoubleClick += (m, e) => eventTriggered = true; + diagram.TriggerPointerClick(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); await Task.Delay(520); - diagram.OnMouseClick(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.TriggerPointerClick(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); // Assert eventTriggered.Should().BeFalse(); @@ -99,8 +99,8 @@ public void Behavior_ShouldTriggerMouseClick_OnlyWhenMouseDownWasAlsoTriggered_I var eventTriggered = false; // Act - diagram.MouseClick += (m, e) => eventTriggered = true; - diagram.OnMouseUp(null, new MouseEventArgs(0, 0, 0, 0, false, false, false)); + diagram.PointerClick += (m, e) => eventTriggered = true; + diagram.TriggerPointerUp(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); // Assert eventTriggered.Should().BeFalse(); From 51a880eaf290ba6f44ee7f40c6c4e03c2d903b52 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 26 Aug 2022 18:58:40 +0100 Subject: [PATCH 059/193] Add PointerEnter/Leave events (nodes only for now) --- .../Components/Renderers/NodeRenderer.cs | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index 5717f36f3..0fa1e6081 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -115,9 +115,11 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.AddEventStopPropagationAttribute(5, "onpointerdown", true); builder.AddAttribute(6, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); builder.AddEventStopPropagationAttribute(7, "onpointerup", true); - builder.AddElementReferenceCapture(8, value => _element = value); - builder.OpenComponent(9, componentType); - builder.AddAttribute(10, "Node", Node); + builder.AddAttribute(8, "onmouseenter", EventCallback.Factory.Create(this, OnMouseEnter)); + builder.AddAttribute(9, "onmouseleave", EventCallback.Factory.Create(this, OnMouseLeave)); + builder.AddElementReferenceCapture(10, value => _element = value); + builder.OpenComponent(11, componentType); + builder.AddAttribute(12, "Node", Node); builder.CloseComponent(); builder.CloseElement(); } @@ -167,5 +169,17 @@ private void ReRender() private void OnPointerDown(PointerEventArgs e) => Diagram.TriggerPointerDown(Node, e.ToCore()); private void OnPointerUp(PointerEventArgs e) => Diagram.TriggerPointerUp(Node, e.ToCore()); + + private void OnMouseEnter(MouseEventArgs e) + { + Console.WriteLine("On mouse enter"); + Diagram.TriggerPointerLeave(Node, e.ToCore()); + } + + private void OnMouseLeave(MouseEventArgs e) + { + Console.WriteLine("On mouse leave"); + Diagram.TriggerPointerEnter(Node, e.ToCore()); + } } } \ No newline at end of file From e447dd045c525e4086072284b0853ae84fedde9b Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 27 Aug 2022 13:04:44 +0100 Subject: [PATCH 060/193] Make Diagram abstract / Add BlazorDiagram derived / Abstract options and blazor specific options --- docs/CustomNodesLinks/Pages/Index.razor | 14 +-- docs/Diagram-Demo/Pages/Diagrams.razor | 4 +- docs/Layouts/Pages/Index.razor | 4 +- .../ReconnectLinksToClosestPorts.razor | 2 +- .../ReconnectLinksToClosestPorts.razor.cs | 10 +- .../SharedDemo/Demos/CustomGroup/Demo.razor | 2 +- .../Demos/CustomGroup/Demo.razor.cs | 16 +-- .../SharedDemo/Demos/CustomLink/Demo.razor | 2 +- .../SharedDemo/Demos/CustomLink/Demo.razor.cs | 10 +- samples/SharedDemo/Demos/CustomNode.razor | 2 +- samples/SharedDemo/Demos/CustomNode.razor.cs | 6 +- .../SharedDemo/Demos/CustomPort/Demo.razor | 2 +- .../SharedDemo/Demos/CustomPort/Demo.razor.cs | 8 +- .../Demos/CustomSvgGroup/Demo.razor | 2 +- .../Demos/CustomSvgGroup/Demo.razor.cs | 14 +-- samples/SharedDemo/Demos/DragAndDrop.razor | 2 +- samples/SharedDemo/Demos/DragAndDrop.razor.cs | 8 +- .../SharedDemo/Demos/DynamicInsertions.razor | 2 +- .../Demos/DynamicInsertions.razor.cs | 30 +++--- samples/SharedDemo/Demos/Events.razor | 2 +- samples/SharedDemo/Demos/Events.razor.cs | 26 ++--- .../Demos/Groups/CustomShortcut.razor | 2 +- .../Demos/Groups/CustomShortcut.razor.cs | 14 +-- samples/SharedDemo/Demos/Groups/Dynamic.razor | 2 +- .../SharedDemo/Demos/Groups/Dynamic.razor.cs | 24 ++--- samples/SharedDemo/Demos/Groups/Factory.razor | 2 +- .../SharedDemo/Demos/Groups/Factory.razor.cs | 14 +-- .../SharedDemo/Demos/Groups/Grouping.razor | 2 +- .../SharedDemo/Demos/Groups/Grouping.razor.cs | 22 ++--- .../SharedDemo/Demos/Links/LabelsDemo.razor | 2 +- .../Demos/Links/LabelsDemo.razor.cs | 18 ++-- .../SharedDemo/Demos/Links/MarkersDemo.razor | 2 +- .../Demos/Links/MarkersDemo.razor.cs | 22 ++--- .../Demos/Links/PathGeneratorsDemo.razor | 2 +- .../Demos/Links/PathGeneratorsDemo.razor.cs | 6 +- .../SharedDemo/Demos/Links/RoutersDemo.razor | 2 +- .../Demos/Links/RoutersDemo.razor.cs | 6 +- .../SharedDemo/Demos/Links/SnappingDemo.razor | 2 +- .../Demos/Links/SnappingDemo.razor.cs | 6 +- .../SharedDemo/Demos/Links/VerticesDemo.razor | 2 +- .../Demos/Links/VerticesDemo.razor.cs | 6 +- samples/SharedDemo/Demos/Locked.razor | 2 +- samples/SharedDemo/Demos/Locked.razor.cs | 6 +- .../Demos/Nodes/PortlessLinks.razor | 2 +- .../Demos/Nodes/PortlessLinks.razor.cs | 14 +-- samples/SharedDemo/Demos/Nodes/SvgDemo.razor | 2 +- .../SharedDemo/Demos/Nodes/SvgDemo.razor.cs | 22 ++--- samples/SharedDemo/Demos/Performance.razor | 2 +- samples/SharedDemo/Demos/Performance.razor.cs | 6 +- samples/SharedDemo/Demos/Simple.razor | 2 +- samples/SharedDemo/Demos/Simple.razor.cs | 14 +-- samples/SharedDemo/Demos/SnapToGrid.razor | 2 +- samples/SharedDemo/Demos/SnapToGrid.razor.cs | 8 +- samples/SharedDemo/Demos/ZoomToFit.razor | 4 +- samples/SharedDemo/Demos/ZoomToFit.razor.cs | 6 +- samples/SharedDemo/Options.razor | 3 +- .../LinksReconnectionAlgorithms.cs | 2 +- src/Blazor.Diagrams.Core/Behavior.cs | 4 +- .../Behaviors/DebugEventsBehavior.cs | 2 +- .../Behaviors/DragMovablesBehavior.cs | 2 +- .../Behaviors/DragNewLinkBehavior.cs | 2 +- .../Behaviors/EventsBehavior.cs | 2 +- .../Behaviors/KeyboardShortcutsBehavior.cs | 8 +- .../Behaviors/KeyboardShortcutsDefaults.cs | 4 +- .../Behaviors/PanBehavior.cs | 2 +- .../Behaviors/SelectionBehavior.cs | 2 +- .../Behaviors/ZoomBehavior.cs | 4 +- src/Blazor.Diagrams.Core/Delegates.cs | 8 +- .../{DiagramBase.cs => Diagram.cs} | 53 +++++----- src/Blazor.Diagrams.Core/DiagramOptions.cs | 99 ------------------- src/Blazor.Diagrams.Core/Layers/BaseLayer.cs | 4 +- src/Blazor.Diagrams.Core/Layers/LinkLayer.cs | 2 +- src/Blazor.Diagrams.Core/Layers/NodeLayer.cs | 2 +- .../Models/Base/BaseLinkModel.cs | 2 +- .../Options/DiagramConstraintsOptions.cs | 13 +++ .../Options/DiagramGroupOptions.cs | 10 ++ .../Options/DiagramLinkOptions.cs | 25 +++++ .../Options/DiagramOptions.cs | 14 +++ .../Options/DiagramZoomOptions.cs | 35 +++++++ .../PathGenerators/PathGenerators.Smooth.cs | 2 +- .../PathGenerators/PathGenerators.Straight.cs | 2 +- .../Routers/Routers.Normal.cs | 2 +- .../Routers/Routers.Orthogonal.cs | 2 +- .../{Diagram.cs => BlazorDiagram.cs} | 22 +++-- .../Components/DiagramCanvas.razor | 14 +-- .../Components/DiagramCanvas.razor.cs | 22 ++--- .../Components/LinkVertexWidget.razor.cs | 6 +- .../Components/LinkWidget.razor | 6 +- .../Components/LinkWidget.razor.cs | 6 +- .../Components/NavigatorWidget.razor | 34 +++---- .../Components/NavigatorWidget.razor.cs | 96 +++++++++--------- .../Components/Renderers/GroupRenderer.cs | 8 +- .../Components/Renderers/LinkLabelRenderer.cs | 4 +- .../Components/Renderers/LinkRenderer.cs | 8 +- .../Components/Renderers/NodeRenderer.cs | 40 ++++---- .../Components/Renderers/PortRenderer.cs | 20 ++-- .../Components/SelectionBoxWidget.razor.cs | 28 +++--- .../BlazorDiagramConstraintsOptions.cs | 7 ++ .../Options/BlazorDiagramGroupOptions.cs | 7 ++ .../Options/BlazorDiagramLinkOptions.cs | 9 ++ .../Options/BlazorDiagramOptions.cs | 14 +++ .../Options/BlazorDiagramZoomOptions.cs | 7 ++ .../Behaviors/DragNewLinkBehaviorTests.cs | 20 ++-- .../Behaviors/EventsBehaviorTests.cs | 12 +-- .../KeyboardShortcutsBehaviorTests.cs | 12 +-- .../KeyboardShortcutsDefaultsTests.cs | 10 +- .../DiagramTests.cs | 14 +-- .../Blazor.Diagrams.Core.Tests/TestDiagram.cs | 13 +++ tests/Blazor.Diagrams.Tests/DiagramTests.cs | 10 +- 109 files changed, 625 insertions(+), 554 deletions(-) rename src/Blazor.Diagrams.Core/{DiagramBase.cs => Diagram.cs} (83%) delete mode 100644 src/Blazor.Diagrams.Core/DiagramOptions.cs create mode 100644 src/Blazor.Diagrams.Core/Options/DiagramConstraintsOptions.cs create mode 100644 src/Blazor.Diagrams.Core/Options/DiagramGroupOptions.cs create mode 100644 src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs create mode 100644 src/Blazor.Diagrams.Core/Options/DiagramOptions.cs create mode 100644 src/Blazor.Diagrams.Core/Options/DiagramZoomOptions.cs rename src/Blazor.Diagrams/{Diagram.cs => BlazorDiagram.cs} (67%) create mode 100644 src/Blazor.Diagrams/Options/BlazorDiagramConstraintsOptions.cs create mode 100644 src/Blazor.Diagrams/Options/BlazorDiagramGroupOptions.cs create mode 100644 src/Blazor.Diagrams/Options/BlazorDiagramLinkOptions.cs create mode 100644 src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs create mode 100644 src/Blazor.Diagrams/Options/BlazorDiagramZoomOptions.cs create mode 100644 tests/Blazor.Diagrams.Core.Tests/TestDiagram.cs diff --git a/docs/CustomNodesLinks/Pages/Index.razor b/docs/CustomNodesLinks/Pages/Index.razor index 465443080..39e69e95e 100644 --- a/docs/CustomNodesLinks/Pages/Index.razor +++ b/docs/CustomNodesLinks/Pages/Index.razor @@ -20,13 +20,13 @@ or it will not be rendered. 100vh = 100% viewport height -->
    - +
    @code { - private Diagram _diagram { get; set; } + private BlazorDiagram BlazorDiagram { get; set; } protected override void OnInitialized() { @@ -44,11 +44,11 @@ or it will not be rendered. Inverse = false, // Whether to inverse the direction of the zoom when using the wheel } }; - _diagram = new Diagram(options); + BlazorDiagram = new BlazorDiagram(options); // connect node/link to renderer - _diagram.RegisterModelComponent(); - _diagram.RegisterModelComponent(); + BlazorDiagram.RegisterModelComponent(); + BlazorDiagram.RegisterModelComponent(); Setup(); } @@ -58,11 +58,11 @@ or it will not be rendered. var node1 = new DiagramNode("Node 0", new Point(50, 50)); var node2 = new DiagramNode("Node 1", new Point(300, 300)); var node3 = new DiagramNode("Node 2", new Point(300, 50)); - _diagram.Nodes.Add(new[] { node1, node2, node3 }); + BlazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); // use portless links so connection points move around when we move node var link = new DiagramLink($"{node1.Name}-->{node2.Name}", node1, node2); - _diagram.Links.Add(link); + BlazorDiagram.Links.Add(link); } } diff --git a/docs/Diagram-Demo/Pages/Diagrams.razor b/docs/Diagram-Demo/Pages/Diagrams.razor index 8f6e8bba7..3ce1f8bbe 100644 --- a/docs/Diagram-Demo/Pages/Diagrams.razor +++ b/docs/Diagram-Demo/Pages/Diagrams.razor @@ -25,7 +25,7 @@ or it will not be rendered.
    @code { - private DiagramBase Diagram { get; set; } + private Diagram Diagram { get; set; } protected override void OnInitialized() { @@ -43,7 +43,7 @@ or it will not be rendered. Inverse = false, // Whether to inverse the direction of the zoom when using the wheel } }; - Diagram = new DiagramBase(options); + Diagram = new Diagram(options); Setup(); } diff --git a/docs/Layouts/Pages/Index.razor b/docs/Layouts/Pages/Index.razor index 9b9667787..3fd6e0b9e 100644 --- a/docs/Layouts/Pages/Index.razor +++ b/docs/Layouts/Pages/Index.razor @@ -36,7 +36,7 @@ or it will not be rendered. @code { - private DiagramBase _diagram { get; set; } + private Diagram _diagram { get; set; } private string _layout; @@ -56,7 +56,7 @@ or it will not be rendered. Inverse = false, // Whether to inverse the direction of the zoom when using the wheel } }; - _diagram = new DiagramBase(options); + _diagram = new Diagram(options); Setup(); } diff --git a/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor b/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor index 9d4b341e0..09fdbd3e3 100644 --- a/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor +++ b/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor @@ -18,7 +18,7 @@ - + diff --git a/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor.cs b/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor.cs index 491ec8b58..27604b658 100644 --- a/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor.cs +++ b/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor.cs @@ -8,7 +8,7 @@ namespace SharedDemo.Demos.Algorithms { public class ReconnectLinksToClosestPortsComponent : ComponentBase { - protected readonly Diagram diagram = new Diagram(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -17,13 +17,13 @@ protected override void OnInitialized() var node1 = NewNode(50, 50); var node2 = NewNode(300, 300); var node3 = NewNode(300, 50); - diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Top), node2.GetPort(PortAlignment.Right))); - diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Bottom), node3.GetPort(PortAlignment.Top))); - diagram.Nodes.Add(new[] { node1, node2, node3 }); + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Top), node2.GetPort(PortAlignment.Right))); + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Bottom), node3.GetPort(PortAlignment.Top))); + BlazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); } - protected void ReconnectLinks() => diagram.ReconnectLinksToClosestPorts(); + protected void ReconnectLinks() => BlazorDiagram.ReconnectLinksToClosestPorts(); private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/CustomGroup/Demo.razor b/samples/SharedDemo/Demos/CustomGroup/Demo.razor index cff05d4d7..f1c12d705 100644 --- a/samples/SharedDemo/Demos/CustomGroup/Demo.razor +++ b/samples/SharedDemo/Demos/CustomGroup/Demo.razor @@ -2,6 +2,6 @@ @layout DemoLayout @inject LayoutData LayoutData - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs b/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs index d1848a4b9..538cac3dc 100644 --- a/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs @@ -6,7 +6,7 @@ namespace SharedDemo.Demos.CustomGroup { partial class Demo { - private readonly Diagram _diagram = new Diagram(); + private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -16,19 +16,19 @@ protected override void OnInitialized() LayoutData.Info = "Creating your own custom groups is very easy!"; LayoutData.DataChanged(); - _diagram.Options.LinksLayerOrder = 2; - _diagram.Options.NodesLayerOrder = 1; - _diagram.RegisterModelComponent(); + _blazorDiagram.Options.LinksLayerOrder = 2; + _blazorDiagram.Options.NodesLayerOrder = 1; + _blazorDiagram.RegisterModelComponent(); var node1 = NewNode(50, 50); var node2 = NewNode(300, 300); var node3 = NewNode(500, 100); - _diagram.Nodes.Add(new[] { node1, node2, node3 }); - _diagram.AddGroup(new CustomGroupModel(new[] { node2, node3 }, "Group 1")); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + _blazorDiagram.AddGroup(new CustomGroupModel(new[] { node2, node3 }, "Group 1")); - _diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - _diagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/CustomLink/Demo.razor b/samples/SharedDemo/Demos/CustomLink/Demo.razor index bd20c21ca..1ac8f54e2 100644 --- a/samples/SharedDemo/Demos/CustomLink/Demo.razor +++ b/samples/SharedDemo/Demos/CustomLink/Demo.razor @@ -2,6 +2,6 @@ @layout DemoLayout @inject LayoutData LayoutData - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs b/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs index 27e7cab73..e8169b9a8 100644 --- a/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs @@ -6,7 +6,7 @@ namespace SharedDemo.Demos.CustomLink { partial class Demo { - private readonly Diagram _diagram = new Diagram(); + private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -16,16 +16,16 @@ protected override void OnInitialized() LayoutData.Info = "Creating your own custom links is very easy!"; LayoutData.DataChanged(); - _diagram.RegisterModelComponent(); + _blazorDiagram.RegisterModelComponent(); // Also usable: _diagram.Options.Links.DefaultLinkComponent = typeof(ThickLink); var node1 = NewNode(50, 50); var node2 = NewNode(300, 300); var node3 = NewNode(500, 50); - _diagram.Nodes.Add(new[] { node1, node2, node3 }); - _diagram.Links.Add(new ThickLink(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - _diagram.Links.Add(new ThickLink(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + _blazorDiagram.Links.Add(new ThickLink(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new ThickLink(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/CustomNode.razor b/samples/SharedDemo/Demos/CustomNode.razor index e5992c69a..7337da801 100644 --- a/samples/SharedDemo/Demos/CustomNode.razor +++ b/samples/SharedDemo/Demos/CustomNode.razor @@ -14,6 +14,6 @@ } } - + diff --git a/samples/SharedDemo/Demos/CustomNode.razor.cs b/samples/SharedDemo/Demos/CustomNode.razor.cs index add6bed08..e4343600a 100644 --- a/samples/SharedDemo/Demos/CustomNode.razor.cs +++ b/samples/SharedDemo/Demos/CustomNode.razor.cs @@ -7,13 +7,13 @@ namespace SharedDemo.Demos { public class CustomNodeComponent : ComponentBase { - protected readonly Diagram diagram = new Diagram(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { base.OnInitialized(); - diagram.RegisterModelComponent(); + BlazorDiagram.RegisterModelComponent(); var node = new NodeModel(new Point(20, 20)); node.AddPort(PortAlignment.Top); @@ -21,7 +21,7 @@ protected override void OnInitialized() node.AddPort(PortAlignment.Bottom); node.AddPort(PortAlignment.Left); - diagram.Nodes.Add(new[] { node, NewNode(100, 100), NewNode(300, 300) }); + BlazorDiagram.Nodes.Add(new[] { node, NewNode(100, 100), NewNode(300, 300) }); } private BotAnswerNode NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/CustomPort/Demo.razor b/samples/SharedDemo/Demos/CustomPort/Demo.razor index b8675208a..d6ef4f590 100644 --- a/samples/SharedDemo/Demos/CustomPort/Demo.razor +++ b/samples/SharedDemo/Demos/CustomPort/Demo.razor @@ -2,6 +2,6 @@ @layout DemoLayout @inject LayoutData LayoutData - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs b/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs index 00d39cd46..bd0871759 100644 --- a/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs @@ -6,7 +6,7 @@ namespace SharedDemo.Demos.CustomPort { partial class Demo { - private readonly Diagram _diagram = new Diagram(); + private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -17,12 +17,12 @@ protected override void OnInitialized() "In this example, you can only attach links from/to ports with the same color."; LayoutData.DataChanged(); - _diagram.RegisterModelComponent(replace: true); + _blazorDiagram.RegisterModelComponent(replace: true); var node1 = NewNode(50, 50); var node2 = NewNode(300, 300); - _diagram.Nodes.Add(new[] { node1, node2, NewNode(500, 50) }); - _diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Top), node2.GetPort(PortAlignment.Top))); + _blazorDiagram.Nodes.Add(new[] { node1, node2, NewNode(500, 50) }); + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Top), node2.GetPort(PortAlignment.Top))); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor index e16a0214b..a5a181249 100644 --- a/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor +++ b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor @@ -2,6 +2,6 @@ @layout DemoLayout @inject LayoutData LayoutData - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs index 89e795684..733c64df2 100644 --- a/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs @@ -8,7 +8,7 @@ namespace SharedDemo.Demos.CustomSvgGroup { partial class Demo { - private readonly Diagram _diagram = new Diagram(); + private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -18,18 +18,18 @@ protected override void OnInitialized() LayoutData.Info = "Creating your own custom svg groups is very easy!"; LayoutData.DataChanged(); - _diagram.RegisterModelComponent(); - _diagram.RegisterModelComponent(); + _blazorDiagram.RegisterModelComponent(); + _blazorDiagram.RegisterModelComponent(); var node1 = NewNode(50, 50); var node2 = NewNode(300, 300); var node3 = NewNode(500, 100); - _diagram.Nodes.Add(new[] { node1, node2, node3 }); - _diagram.AddGroup(new CustomSvgGroupModel(new[] { node2, node3 }, "Group 1")); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + _blazorDiagram.AddGroup(new CustomSvgGroupModel(new[] { node2, node3 }, "Group 1")); - _diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - _diagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/DragAndDrop.razor b/samples/SharedDemo/Demos/DragAndDrop.razor index fcead75a2..24b6c340e 100644 --- a/samples/SharedDemo/Demos/DragAndDrop.razor +++ b/samples/SharedDemo/Demos/DragAndDrop.razor @@ -25,7 +25,7 @@
    - +
    diff --git a/samples/SharedDemo/Demos/DragAndDrop.razor.cs b/samples/SharedDemo/Demos/DragAndDrop.razor.cs index e56110123..7968545dd 100644 --- a/samples/SharedDemo/Demos/DragAndDrop.razor.cs +++ b/samples/SharedDemo/Demos/DragAndDrop.razor.cs @@ -6,7 +6,7 @@ namespace SharedDemo.Demos { public partial class DragAndDrop { - private readonly Diagram _diagram = new Diagram(); + private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); private int? _draggedType; protected override void OnInitialized() @@ -17,7 +17,7 @@ protected override void OnInitialized() LayoutData.Info = "A very simple drag & drop implementation using the HTML5 events."; LayoutData.DataChanged(); - _diagram.RegisterModelComponent(); + _blazorDiagram.RegisterModelComponent(); } private void OnDragStart(int key) @@ -31,11 +31,11 @@ private void OnDrop(DragEventArgs e) if (_draggedType == null) // Unkown item return; - var position = _diagram.GetRelativeMousePoint(e.ClientX, e.ClientY); + var position = _blazorDiagram.GetRelativeMousePoint(e.ClientX, e.ClientY); var node = _draggedType == 0 ? new NodeModel(position) : new BotAnswerNode(position); node.AddPort(PortAlignment.Top); node.AddPort(PortAlignment.Bottom); - _diagram.Nodes.Add(node); + _blazorDiagram.Nodes.Add(node); _draggedType = null; } } diff --git a/samples/SharedDemo/Demos/DynamicInsertions.razor b/samples/SharedDemo/Demos/DynamicInsertions.razor index 252882cf2..9992424e8 100644 --- a/samples/SharedDemo/Demos/DynamicInsertions.razor +++ b/samples/SharedDemo/Demos/DynamicInsertions.razor @@ -22,6 +22,6 @@ - + diff --git a/samples/SharedDemo/Demos/DynamicInsertions.razor.cs b/samples/SharedDemo/Demos/DynamicInsertions.razor.cs index 3ded11382..d80abe22c 100644 --- a/samples/SharedDemo/Demos/DynamicInsertions.razor.cs +++ b/samples/SharedDemo/Demos/DynamicInsertions.razor.cs @@ -11,17 +11,17 @@ namespace SharedDemo.Demos public class DynamicInsertionsComponent : ComponentBase { private static readonly Random _random = new Random(); - protected readonly Diagram diagram = new Diagram(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { base.OnInitialized(); - diagram.Options.Groups.Enabled = true; - diagram.Nodes.Add(new NodeModel(new Point(300, 50))); - diagram.Nodes.Add(new NodeModel(new Point(300, 400))); + BlazorDiagram.Options.Groups.Enabled = true; + BlazorDiagram.Nodes.Add(new NodeModel(new Point(300, 50))); + BlazorDiagram.Nodes.Add(new NodeModel(new Point(300, 400))); - diagram.Options.Links.Factory = (d, sp) => + BlazorDiagram.Options.Links.Factory = (d, sp) => { var link = new LinkModel(new SinglePortAnchor(sp) { @@ -36,20 +36,20 @@ protected override void OnInitialized() protected void AddNode() { - var x = _random.Next(0, (int)diagram.Container.Width - 120); - var y = _random.Next(0, (int)diagram.Container.Height - 100); - diagram.Nodes.Add(new NodeModel(new Point(x, y))); + var x = _random.Next(0, (int)BlazorDiagram.Container.Width - 120); + var y = _random.Next(0, (int)BlazorDiagram.Container.Height - 100); + BlazorDiagram.Nodes.Add(new NodeModel(new Point(x, y))); } protected void RemoveNode() { - var i = _random.Next(0, diagram.Nodes.Count); - diagram.Nodes.Remove(diagram.Nodes[i]); + var i = _random.Next(0, BlazorDiagram.Nodes.Count); + BlazorDiagram.Nodes.Remove(BlazorDiagram.Nodes[i]); } protected void AddPort() { - var node = diagram.Nodes.FirstOrDefault(n => n.Selected); + var node = BlazorDiagram.Nodes.FirstOrDefault(n => n.Selected); if (node == null) return; @@ -66,7 +66,7 @@ protected void AddPort() protected void RemovePort() { - var node = diagram.Nodes.FirstOrDefault(n => n.Selected); + var node = BlazorDiagram.Nodes.FirstOrDefault(n => n.Selected); if (node == null) return; @@ -76,14 +76,14 @@ protected void RemovePort() var i = _random.Next(0, node.Ports.Count); var port = node.Ports[i]; - diagram.Links.Remove(port.Links.ToArray()); + BlazorDiagram.Links.Remove(port.Links.ToArray()); node.RemovePort(port); node.Refresh(); } protected void AddLink() { - var selectedNodes = diagram.Nodes.Where(n => n.Selected).ToArray(); + var selectedNodes = BlazorDiagram.Nodes.Where(n => n.Selected).ToArray(); if (selectedNodes.Length != 2) return; @@ -95,7 +95,7 @@ protected void AddLink() var sourcePort = node1.Ports[_random.Next(0, node1.Ports.Count)]; var targetPort = node2.Ports[_random.Next(0, node2.Ports.Count)]; - diagram.Links.Add(new LinkModel(sourcePort, targetPort)); + BlazorDiagram.Links.Add(new LinkModel(sourcePort, targetPort)); } } } diff --git a/samples/SharedDemo/Demos/Events.razor b/samples/SharedDemo/Demos/Events.razor index 938ccd34f..85af8f2e1 100644 --- a/samples/SharedDemo/Demos/Events.razor +++ b/samples/SharedDemo/Demos/Events.razor @@ -16,7 +16,7 @@ }
    - +
    diff --git a/samples/SharedDemo/Demos/Events.razor.cs b/samples/SharedDemo/Demos/Events.razor.cs index c576ba38a..f0d843694 100644 --- a/samples/SharedDemo/Demos/Events.razor.cs +++ b/samples/SharedDemo/Demos/Events.razor.cs @@ -8,7 +8,7 @@ namespace SharedDemo.Demos { public class EventsComponent : ComponentBase { - protected readonly Diagram diagram = new Diagram(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); protected readonly List events = new List(); protected override void OnInitialized() @@ -19,50 +19,50 @@ protected override void OnInitialized() var node1 = NewNode(50, 50); var node2 = NewNode(300, 300); - diagram.Nodes.Add(new[] { node1, node2, NewNode(300, 50) }); - diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + BlazorDiagram.Nodes.Add(new[] { node1, node2, NewNode(300, 50) }); + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); } private void RegisterEvents() { - diagram.Changed += () => + BlazorDiagram.Changed += () => { events.Add("Changed"); StateHasChanged(); }; - diagram.Nodes.Added += (n) => events.Add($"NodesAdded, NodeId={n.Id}"); - diagram.Nodes.Removed += (n) => events.Add($"NodesRemoved, NodeId={n.Id}"); + BlazorDiagram.Nodes.Added += (n) => events.Add($"NodesAdded, NodeId={n.Id}"); + BlazorDiagram.Nodes.Removed += (n) => events.Add($"NodesRemoved, NodeId={n.Id}"); - diagram.SelectionChanged += (m) => + BlazorDiagram.SelectionChanged += (m) => { events.Add($"SelectionChanged, Id={m.Id}, Type={m.GetType().Name}, Selected={m.Selected}"); StateHasChanged(); }; - diagram.Links.Added += (l) => events.Add($"Links.Added, LinkId={l.Id}"); + BlazorDiagram.Links.Added += (l) => events.Add($"Links.Added, LinkId={l.Id}"); - diagram.Links.Removed += (l) => events.Add($"Links.Removed, LinkId={l.Id}"); + BlazorDiagram.Links.Removed += (l) => events.Add($"Links.Removed, LinkId={l.Id}"); - diagram.PointerDown += (m, e) => + BlazorDiagram.PointerDown += (m, e) => { events.Add($"MouseDown, Type={m?.GetType().Name}, ModelId={m?.Id}"); StateHasChanged(); }; - diagram.PointerUp += (m, e) => + BlazorDiagram.PointerUp += (m, e) => { events.Add($"MouseUp, Type={m?.GetType().Name}, ModelId={m?.Id}"); StateHasChanged(); }; - diagram.PointerClick += (m, e) => + BlazorDiagram.PointerClick += (m, e) => { events.Add($"MouseClick, Type={m?.GetType().Name}, ModelId={m?.Id}"); StateHasChanged(); }; - diagram.PointerDoubleClick += (m, e) => + BlazorDiagram.PointerDoubleClick += (m, e) => { events.Add($"MouseDoubleClick, Type={m?.GetType().Name}, ModelId={m?.Id}"); StateHasChanged(); diff --git a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor index c4856196f..656ee3d22 100644 --- a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor +++ b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor @@ -2,7 +2,7 @@ @layout DemoLayout @inject LayoutData LayoutData - + diff --git a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs index 98a45ec9d..acb071f2b 100644 --- a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs +++ b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs @@ -7,7 +7,7 @@ namespace SharedDemo.Demos.Groups { public partial class CustomShortcut { - private Diagram _diagram = new Diagram(); + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -17,19 +17,19 @@ protected override void OnInitialized() LayoutData.Info = "You can customize what needs to be pressed to group selected nodes. Ctrl+Shift+k in this example."; LayoutData.DataChanged(); - _diagram.Options.Groups.Enabled = true; - _diagram.Options.LinksLayerOrder = 2; - _diagram.Options.NodesLayerOrder = 1; - var ksb = _diagram.GetBehavior(); + _blazorDiagram.Options.Groups.Enabled = true; + _blazorDiagram.Options.LinksLayerOrder = 2; + _blazorDiagram.Options.NodesLayerOrder = 1; + var ksb = _blazorDiagram.GetBehavior(); ksb.RemoveShortcut("g", true, false, true); ksb.SetShortcut("k", true, true, false, KeyboardShortcutsDefaults.Grouping); var node1 = NewNode(50, 50); var node2 = NewNode(250, 250); var node3 = NewNode(500, 100); - _diagram.Nodes.Add(new[] { node1, node2, node3 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - _diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/Groups/Dynamic.razor b/samples/SharedDemo/Demos/Groups/Dynamic.razor index d5ac1f9cc..a1661c8e4 100644 --- a/samples/SharedDemo/Demos/Groups/Dynamic.razor +++ b/samples/SharedDemo/Demos/Groups/Dynamic.razor @@ -8,7 +8,7 @@ - + diff --git a/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs b/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs index 4315041dd..e1b3545b7 100644 --- a/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs @@ -7,7 +7,7 @@ namespace SharedDemo.Demos.Groups { public partial class Dynamic { - private readonly Diagram _diagram = new Diagram(); + private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -17,19 +17,19 @@ protected override void OnInitialized() LayoutData.Info = "You can create and modify groups dynamically!"; LayoutData.DataChanged(); - _diagram.Options.LinksLayerOrder = 2; - _diagram.Options.NodesLayerOrder = 1; + _blazorDiagram.Options.LinksLayerOrder = 2; + _blazorDiagram.Options.NodesLayerOrder = 1; var node1 = NewNode(50, 150); var node2 = NewNode(250, 350); var node3 = NewNode(500, 200); - _diagram.Nodes.Add(new[] { node1, node2, node3 }); - _diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); } private void AddEmptyGroup() { - _diagram.AddGroup(new GroupModel(Array.Empty()) + _blazorDiagram.AddGroup(new GroupModel(Array.Empty()) { Position = new Point(100, 100) }); @@ -37,15 +37,15 @@ private void AddEmptyGroup() private void AddChildToGroup() { - if (_diagram.Groups.Count == 0) + if (_blazorDiagram.Groups.Count == 0) return; - foreach (var node in _diagram.Nodes) + foreach (var node in _blazorDiagram.Nodes) { if (node.Group == null) { - _diagram.Groups[0].AddChild(node); - _diagram.Refresh(); + _blazorDiagram.Groups[0].AddChild(node); + _blazorDiagram.Refresh(); return; } } @@ -53,12 +53,12 @@ private void AddChildToGroup() private void RemoveChildFromGroup() { - foreach (var node in _diagram.Nodes) + foreach (var node in _blazorDiagram.Nodes) { if (node.Group != null) { node.Group.RemoveChild(node); - _diagram.Refresh(); + _blazorDiagram.Refresh(); return; } } diff --git a/samples/SharedDemo/Demos/Groups/Factory.razor b/samples/SharedDemo/Demos/Groups/Factory.razor index 1283913c4..db02420eb 100644 --- a/samples/SharedDemo/Demos/Groups/Factory.razor +++ b/samples/SharedDemo/Demos/Groups/Factory.razor @@ -2,7 +2,7 @@ @layout DemoLayout @inject LayoutData LayoutData - + diff --git a/samples/SharedDemo/Demos/Groups/Factory.razor.cs b/samples/SharedDemo/Demos/Groups/Factory.razor.cs index d33fefcd7..2b5ef9a34 100644 --- a/samples/SharedDemo/Demos/Groups/Factory.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Factory.razor.cs @@ -6,7 +6,7 @@ namespace SharedDemo.Demos.Groups { public partial class Factory { - protected readonly Diagram diagram = new Diagram(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -17,10 +17,10 @@ protected override void OnInitialized() "Try to group nodes using CTRL+ALT+G now."; LayoutData.DataChanged(); - diagram.Options.Groups.Enabled = true; - diagram.Options.LinksLayerOrder = 2; - diagram.Options.NodesLayerOrder = 1; - diagram.Options.Groups.Factory = (diagram, children) => + BlazorDiagram.Options.Groups.Enabled = true; + BlazorDiagram.Options.LinksLayerOrder = 2; + BlazorDiagram.Options.NodesLayerOrder = 1; + BlazorDiagram.Options.Groups.Factory = (diagram, children) => { var group = new GroupModel(children, 25); group.AddPort(PortAlignment.Top); @@ -33,9 +33,9 @@ protected override void OnInitialized() var node1 = NewNode(50, 50); var node2 = NewNode(250, 250); var node3 = NewNode(500, 100); - diagram.Nodes.Add(new[] { node1, node2, node3 }); + BlazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/Groups/Grouping.razor b/samples/SharedDemo/Demos/Groups/Grouping.razor index ee82cd1ad..ef0b3d17d 100644 --- a/samples/SharedDemo/Demos/Groups/Grouping.razor +++ b/samples/SharedDemo/Demos/Groups/Grouping.razor @@ -15,7 +15,7 @@ } } - + diff --git a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs index fe10df7c1..116db073c 100644 --- a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs @@ -7,29 +7,29 @@ namespace SharedDemo.Demos { public class GroupingComponent : ComponentBase { - protected readonly Diagram diagram = new Diagram(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { base.OnInitialized(); - diagram.Options.Groups.Enabled = true; - diagram.Options.LinksLayerOrder = 2; - diagram.Options.NodesLayerOrder = 1; + BlazorDiagram.Options.Groups.Enabled = true; + BlazorDiagram.Options.LinksLayerOrder = 2; + BlazorDiagram.Options.NodesLayerOrder = 1; var node1 = NewNode(50, 50); var node2 = NewNode(250, 250); var node3 = NewNode(500, 100); var node4 = NewNode(700, 350); - diagram.Nodes.Add(new[] { node1, node2, node3, node4 }); + BlazorDiagram.Nodes.Add(new[] { node1, node2, node3, node4 }); - diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - diagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); - diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + BlazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); - var group1 = diagram.Group(node1, node2); - var group2 = diagram.Group(group1, node3); + var group1 = BlazorDiagram.Group(node1, node2); + var group2 = BlazorDiagram.Group(group1, node3); - diagram.Links.Add(new LinkModel(group2, node4)); + BlazorDiagram.Links.Add(new LinkModel(group2, node4)); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/Links/LabelsDemo.razor b/samples/SharedDemo/Demos/Links/LabelsDemo.razor index fd8929975..e4552a081 100644 --- a/samples/SharedDemo/Demos/Links/LabelsDemo.razor +++ b/samples/SharedDemo/Demos/Links/LabelsDemo.razor @@ -2,6 +2,6 @@ @layout DemoLayout @inject LayoutData LayoutData - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Links/LabelsDemo.razor.cs b/samples/SharedDemo/Demos/Links/LabelsDemo.razor.cs index 830c9864b..a5104fdff 100644 --- a/samples/SharedDemo/Demos/Links/LabelsDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/LabelsDemo.razor.cs @@ -6,7 +6,7 @@ namespace SharedDemo.Demos.Links { public partial class LabelsDemo { - private Diagram _diagram = new Diagram(); + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -25,41 +25,41 @@ private void InitializeDiagram() var node1 = NewNode(50, 50); var node2 = NewNode(400, 50); - _diagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); var link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); link.Labels.Add(new LinkLabelModel(link, "Content")); - _diagram.Links.Add(link); + _blazorDiagram.Links.Add(link); node1 = NewNode(50, 160); node2 = NewNode(400, 160); - _diagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); link.Labels.Add(new LinkLabelModel(link, "0.25", 0.3)); link.Labels.Add(new LinkLabelModel(link, "0.75", 0.7)); - _diagram.Links.Add(link); + _blazorDiagram.Links.Add(link); node1 = NewNode(50, 270); node2 = NewNode(400, 270); - _diagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); link.Labels.Add(new LinkLabelModel(link, "50", 50)); link.Labels.Add(new LinkLabelModel(link, "-50", -50)); - _diagram.Links.Add(link); + _blazorDiagram.Links.Add(link); node1 = NewNode(50, 380); node2 = NewNode(400, 380); - _diagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); link.Labels.Add(new LinkLabelModel(link, "(0,-20)", 50, new Point(0, -20))); link.Labels.Add(new LinkLabelModel(link, "(0,20)", -50, new Point(0, 20))); - _diagram.Links.Add(link); + _blazorDiagram.Links.Add(link); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/Links/MarkersDemo.razor b/samples/SharedDemo/Demos/Links/MarkersDemo.razor index 37e4b9431..f5bdd37ca 100644 --- a/samples/SharedDemo/Demos/Links/MarkersDemo.razor +++ b/samples/SharedDemo/Demos/Links/MarkersDemo.razor @@ -2,6 +2,6 @@ @layout DemoLayout @inject LayoutData LayoutData - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Links/MarkersDemo.razor.cs b/samples/SharedDemo/Demos/Links/MarkersDemo.razor.cs index e4163be2c..8f876d1af 100644 --- a/samples/SharedDemo/Demos/Links/MarkersDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/MarkersDemo.razor.cs @@ -6,7 +6,7 @@ namespace SharedDemo.Demos.Links { public partial class MarkersDemo { - private Diagram _diagram = new Diagram(); + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -24,57 +24,57 @@ private void InitializeDiagram() var node1 = NewNode(50, 50); var node2 = NewNode(400, 50); - _diagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); var link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); link.SourceMarker = LinkMarker.Arrow; link.TargetMarker = LinkMarker.Arrow; link.Labels.Add(new LinkLabelModel(link, "Arrow")); - _diagram.Links.Add(link); + _blazorDiagram.Links.Add(link); node1 = NewNode(50, 160); node2 = NewNode(400, 160); - _diagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); link.SourceMarker = LinkMarker.Circle; link.TargetMarker = LinkMarker.Circle; link.Labels.Add(new LinkLabelModel(link, "Circle")); - _diagram.Links.Add(link); + _blazorDiagram.Links.Add(link); node1 = NewNode(50, 270); node2 = NewNode(400, 270); - _diagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); link.SourceMarker = LinkMarker.Square; link.TargetMarker = LinkMarker.Square; link.Labels.Add(new LinkLabelModel(link, "Square")); - _diagram.Links.Add(link); + _blazorDiagram.Links.Add(link); node1 = NewNode(50, 380); node2 = NewNode(400, 380); - _diagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); link.SourceMarker = LinkMarker.NewRectangle(10, 20); link.TargetMarker = LinkMarker.NewArrow(20, 10); link.Labels.Add(new LinkLabelModel(link, "Factory")); - _diagram.Links.Add(link); + _blazorDiagram.Links.Add(link); node1 = NewNode(50, 490); node2 = NewNode(400, 490); - _diagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); link.SourceMarker = new LinkMarker("M 0 -8 L 3 -8 3 8 0 8 z M 4 -8 7 -8 7 8 4 8 z M 8 -8 16 0 8 8 z", 16); link.TargetMarker = new LinkMarker("M 0 -8 L 8 -8 4 0 8 8 0 8 4 0 z", 8); link.Labels.Add(new LinkLabelModel(link, "Custom")); - _diagram.Links.Add(link); + _blazorDiagram.Links.Add(link); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor b/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor index 9b0e08c86..c268430bd 100644 --- a/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor +++ b/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor @@ -2,6 +2,6 @@ @layout DemoLayout @inject LayoutData LayoutData - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs b/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs index 679ffa998..b8892b30c 100644 --- a/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs @@ -7,7 +7,7 @@ namespace SharedDemo.Demos.Links { public partial class PathGeneratorsDemo { - private Diagram _diagram = new Diagram(); + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -27,7 +27,7 @@ private void InitializeDiagram() var node2 = NewNode(300, 350); var node3 = NewNode(400, 100); - _diagram.Nodes.Add(new[] { node1, node2, node3 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); var link1 = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) { @@ -43,7 +43,7 @@ private void InitializeDiagram() }; link2.Labels.Add(new LinkLabelModel(link2, "Smooth")); - _diagram.Links.Add(new[] { link1, link2 }); + _blazorDiagram.Links.Add(new[] { link1, link2 }); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/Links/RoutersDemo.razor b/samples/SharedDemo/Demos/Links/RoutersDemo.razor index c64a33dc4..cf8d6c6c0 100644 --- a/samples/SharedDemo/Demos/Links/RoutersDemo.razor +++ b/samples/SharedDemo/Demos/Links/RoutersDemo.razor @@ -2,6 +2,6 @@ @layout DemoLayout @inject LayoutData LayoutData - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs b/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs index 6d660ff05..1e5581e0c 100644 --- a/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs @@ -7,7 +7,7 @@ namespace SharedDemo.Demos.Links { public partial class RoutersDemo { - private Diagram _diagram = new Diagram(); + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -27,7 +27,7 @@ private void InitializeDiagram() var node2 = NewNode(300, 350); var node3 = NewNode(400, 100); - _diagram.Nodes.Add(new[] { node1, node2, node3 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); var link1 = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) { @@ -42,7 +42,7 @@ private void InitializeDiagram() }; link2.Labels.Add(new LinkLabelModel(link2, "Orthogonal")); - _diagram.Links.Add(new[] { link1, link2 }); + _blazorDiagram.Links.Add(new[] { link1, link2 }); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/Links/SnappingDemo.razor b/samples/SharedDemo/Demos/Links/SnappingDemo.razor index 414cc359c..6dd9b6f8b 100644 --- a/samples/SharedDemo/Demos/Links/SnappingDemo.razor +++ b/samples/SharedDemo/Demos/Links/SnappingDemo.razor @@ -2,6 +2,6 @@ @layout DemoLayout @inject LayoutData LayoutData - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Links/SnappingDemo.razor.cs b/samples/SharedDemo/Demos/Links/SnappingDemo.razor.cs index 0bf3b2b1d..3d11e0ff9 100644 --- a/samples/SharedDemo/Demos/Links/SnappingDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/SnappingDemo.razor.cs @@ -6,7 +6,7 @@ namespace SharedDemo.Demos.Links { public partial class SnappingDemo { - private Diagram _diagram = new Diagram(); + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -21,8 +21,8 @@ protected override void OnInitialized() private void InitializeDiagram() { - _diagram.Options.Links.EnableSnapping = true; - _diagram.Nodes.Add(new[] { NewNode(50, 80), NewNode(200, 350), NewNode(400, 100) }); + _blazorDiagram.Options.Links.EnableSnapping = true; + _blazorDiagram.Nodes.Add(new[] { NewNode(50, 80), NewNode(200, 350), NewNode(400, 100) }); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/Links/VerticesDemo.razor b/samples/SharedDemo/Demos/Links/VerticesDemo.razor index 977f35122..0b5379ea1 100644 --- a/samples/SharedDemo/Demos/Links/VerticesDemo.razor +++ b/samples/SharedDemo/Demos/Links/VerticesDemo.razor @@ -2,6 +2,6 @@ @layout DemoLayout @inject LayoutData LayoutData - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs b/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs index c346f95c7..d17146860 100644 --- a/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs @@ -7,7 +7,7 @@ namespace SharedDemo.Demos.Links { public partial class VerticesDemo { - private Diagram _diagram = new Diagram(); + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -27,7 +27,7 @@ private void InitializeDiagram() var node2 = NewNode(200, 350); var node3 = NewNode(400, 100); - _diagram.Nodes.Add(new[] { node1, node2, node3 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); var link1 = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) { @@ -47,7 +47,7 @@ private void InitializeDiagram() link2.Vertices.Add(new LinkVertexModel(link2, new Point(400, 324))); link2.Vertices.Add(new LinkVertexModel(link2, new Point(326, 180))); - _diagram.Links.Add(new[] { link1, link2 }); + _blazorDiagram.Links.Add(new[] { link1, link2 }); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/Locked.razor b/samples/SharedDemo/Demos/Locked.razor index 05d122e27..788af122b 100644 --- a/samples/SharedDemo/Demos/Locked.razor +++ b/samples/SharedDemo/Demos/Locked.razor @@ -16,6 +16,6 @@ } } - + diff --git a/samples/SharedDemo/Demos/Locked.razor.cs b/samples/SharedDemo/Demos/Locked.razor.cs index 5baf498b4..a0124d22e 100644 --- a/samples/SharedDemo/Demos/Locked.razor.cs +++ b/samples/SharedDemo/Demos/Locked.razor.cs @@ -7,7 +7,7 @@ namespace SharedDemo { public class LockedComponent : ComponentBase { - protected readonly Diagram diagram = new Diagram(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -15,13 +15,13 @@ protected override void OnInitialized() var node1 = NewNode(50, 50); var node2 = NewNode(300, 300); - diagram.Nodes.Add(new[] { node1, node2, NewNode(300, 50) }); + BlazorDiagram.Nodes.Add(new[] { node1, node2, NewNode(300, 50) }); var link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) { Locked = true }; - diagram.Links.Add(link); + BlazorDiagram.Links.Add(link); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor index bad7eadd1..ac6606a18 100644 --- a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor +++ b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor @@ -2,6 +2,6 @@ @layout DemoLayout @inject LayoutData LayoutData - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs index 912bc86d2..e518567f0 100644 --- a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs @@ -7,7 +7,7 @@ namespace SharedDemo.Demos.Nodes { public partial class PortlessLinks { - private Diagram _diagram = new Diagram(); + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -23,22 +23,22 @@ protected override void OnInitialized() private void InitializeDiagram() { - _diagram.RegisterModelComponent(); + _blazorDiagram.RegisterModelComponent(); var node1 = new NodeModel(new Point(80, 80)); var node2 = new RoundedNode(new Point(280, 150)); var node3 = new NodeModel(new Point(400, 300)); node3.AddPort(PortAlignment.Left); - _diagram.Nodes.Add(node1); - _diagram.Nodes.Add(node2); - _diagram.Nodes.Add(node3); - _diagram.Links.Add(new LinkModel(node1, node2) + _blazorDiagram.Nodes.Add(node1); + _blazorDiagram.Nodes.Add(node2); + _blazorDiagram.Nodes.Add(node3); + _blazorDiagram.Links.Add(new LinkModel(node1, node2) { SourceMarker = LinkMarker.Arrow, TargetMarker = LinkMarker.Arrow, Segmentable = true }); - _diagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node2), new SinglePortAnchor(node3.GetPort(PortAlignment.Left))) + _blazorDiagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node2), new SinglePortAnchor(node3.GetPort(PortAlignment.Left))) { SourceMarker = LinkMarker.Arrow, TargetMarker = LinkMarker.Arrow, diff --git a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor index dd14d327f..4fcbaecc6 100644 --- a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor +++ b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor @@ -2,6 +2,6 @@ @layout DemoLayout @inject LayoutData LayoutData - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs index 54c26c037..2bff55fde 100644 --- a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs @@ -8,7 +8,7 @@ namespace SharedDemo.Demos.Nodes { public partial class SvgDemo { - private Diagram _diagram = new Diagram(); + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -23,24 +23,24 @@ protected override void OnInitialized() private void InitializeDiagram() { - _diagram.RegisterModelComponent(); - _diagram.RegisterModelComponent(); - _diagram.RegisterModelComponent(); + _blazorDiagram.RegisterModelComponent(); + _blazorDiagram.RegisterModelComponent(); + _blazorDiagram.RegisterModelComponent(); var node1 = NewNode(50, 50); var node2 = NewNode(250, 250); var node3 = NewNode(500, 100); var node4 = NewNode(700, 350); - _diagram.Nodes.Add(new[] { node1, node2, node3, node4 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3, node4 }); - _diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - _diagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); - _diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); - var group1 = _diagram.AddGroup(new SvgGroupModel(new[] { node1, node2 })); - var group2 = _diagram.AddGroup(new SvgGroupModel(new[] { group1, node3 })); + var group1 = _blazorDiagram.AddGroup(new SvgGroupModel(new[] { node1, node2 })); + var group2 = _blazorDiagram.AddGroup(new SvgGroupModel(new[] { group1, node3 })); - _diagram.Links.Add(new LinkModel(group2, node4)); + _blazorDiagram.Links.Add(new LinkModel(group2, node4)); } private NodeModel NewNode(double x, double y, bool svg = true) diff --git a/samples/SharedDemo/Demos/Performance.razor b/samples/SharedDemo/Demos/Performance.razor index c3c45b851..6e5c2da97 100644 --- a/samples/SharedDemo/Demos/Performance.razor +++ b/samples/SharedDemo/Demos/Performance.razor @@ -14,6 +14,6 @@ } } - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Performance.razor.cs b/samples/SharedDemo/Demos/Performance.razor.cs index a8d61b6ca..37937fb64 100644 --- a/samples/SharedDemo/Demos/Performance.razor.cs +++ b/samples/SharedDemo/Demos/Performance.razor.cs @@ -7,7 +7,7 @@ namespace SharedDemo.Demos { public class PerformanceCompoent : ComponentBase { - protected readonly Diagram diagram = new Diagram(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -23,8 +23,8 @@ protected override void OnInitialized() var sourcePort = node1.AddPort(PortAlignment.Right); var targetPort = node2.AddPort(PortAlignment.Left); - diagram.Nodes.Add(new[] { node1, node2 }); - diagram.Links.Add(new LinkModel(sourcePort, targetPort)); + BlazorDiagram.Nodes.Add(new[] { node1, node2 }); + BlazorDiagram.Links.Add(new LinkModel(sourcePort, targetPort)); } } } diff --git a/samples/SharedDemo/Demos/Simple.razor b/samples/SharedDemo/Demos/Simple.razor index 26cfc86b2..0fcfe137c 100644 --- a/samples/SharedDemo/Demos/Simple.razor +++ b/samples/SharedDemo/Demos/Simple.razor @@ -29,7 +29,7 @@ - + diff --git a/samples/SharedDemo/Demos/Simple.razor.cs b/samples/SharedDemo/Demos/Simple.razor.cs index 18ab1de51..51510afe6 100644 --- a/samples/SharedDemo/Demos/Simple.razor.cs +++ b/samples/SharedDemo/Demos/Simple.razor.cs @@ -8,7 +8,7 @@ namespace SharedDemo { public class SimpleComponent : ComponentBase { - protected readonly Diagram diagram = new Diagram(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -17,14 +17,14 @@ protected override void OnInitialized() var node1 = NewNode(50, 50); var node2 = NewNode(300, 300); var node3 = NewNode(300, 50); - diagram.Nodes.Add(new[] { node1, node2, node3 }); + BlazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) { SourceMarker = LinkMarker.Arrow, TargetMarker = LinkMarker.Arrow }); - diagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Right)) + BlazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Right)) { Router = Routers.Orthogonal, PathGenerator = PathGenerators.Straight, @@ -33,12 +33,12 @@ protected override void OnInitialized() }); } - protected void ToggleZoom() => diagram.Options.Zoom.Enabled = !diagram.Options.Zoom.Enabled; + protected void ToggleZoom() => BlazorDiagram.Options.Zoom.Enabled = !BlazorDiagram.Options.Zoom.Enabled; - protected void TogglePanning() => diagram.Options.AllowPanning = !diagram.Options.AllowPanning; + protected void TogglePanning() => BlazorDiagram.Options.AllowPanning = !BlazorDiagram.Options.AllowPanning; protected void ToggleVirtualization() - => diagram.Options.EnableVirtualization = !diagram.Options.EnableVirtualization; + => BlazorDiagram.Options.EnableVirtualization = !BlazorDiagram.Options.EnableVirtualization; private NodeModel NewNode(double x, double y) { diff --git a/samples/SharedDemo/Demos/SnapToGrid.razor b/samples/SharedDemo/Demos/SnapToGrid.razor index e1c2de7a9..992c3cc21 100644 --- a/samples/SharedDemo/Demos/SnapToGrid.razor +++ b/samples/SharedDemo/Demos/SnapToGrid.razor @@ -15,7 +15,7 @@ } } - + diff --git a/samples/SharedDemo/Demos/SnapToGrid.razor.cs b/samples/SharedDemo/Demos/SnapToGrid.razor.cs index 74c65fe7d..2575a67b6 100644 --- a/samples/SharedDemo/Demos/SnapToGrid.razor.cs +++ b/samples/SharedDemo/Demos/SnapToGrid.razor.cs @@ -1,14 +1,14 @@ using Blazor.Diagrams; -using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Options; using Microsoft.AspNetCore.Components; namespace SharedDemo { public class SnapToGridComponent : ComponentBase { - protected readonly Diagram diagram = new Diagram(new DiagramOptions + protected readonly BlazorDiagram BlazorDiagram = new(new BlazorDiagramOptions { GridSize = 50 }); @@ -19,8 +19,8 @@ protected override void OnInitialized() var node1 = NewNode(50, 50); var node2 = NewNode(300, 300); - diagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - diagram.Nodes.Add(new[] { node1, node2, NewNode(300, 50) }); + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + BlazorDiagram.Nodes.Add(new[] { node1, node2, NewNode(300, 50) }); } private NodeModel NewNode(double x, double y) diff --git a/samples/SharedDemo/Demos/ZoomToFit.razor b/samples/SharedDemo/Demos/ZoomToFit.razor index 341b9a5ad..b9df4fd98 100644 --- a/samples/SharedDemo/Demos/ZoomToFit.razor +++ b/samples/SharedDemo/Demos/ZoomToFit.razor @@ -17,8 +17,8 @@ + @onclick="() => BlazorDiagram.ZoomToFit(50)">Zoom to fit - + diff --git a/samples/SharedDemo/Demos/ZoomToFit.razor.cs b/samples/SharedDemo/Demos/ZoomToFit.razor.cs index 69561b81e..db28b5ac2 100644 --- a/samples/SharedDemo/Demos/ZoomToFit.razor.cs +++ b/samples/SharedDemo/Demos/ZoomToFit.razor.cs @@ -7,7 +7,7 @@ namespace SharedDemo.Demos { public class ZoomToFitComponent : ComponentBase { - protected readonly Diagram diagram = new Diagram(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); protected override void OnInitialized() { @@ -23,8 +23,8 @@ protected override void OnInitialized() var sourcePort = node1.AddPort(PortAlignment.Right); var targetPort = node2.AddPort(PortAlignment.Left); - diagram.Nodes.Add(new[] { node1, node2 }); - diagram.Links.Add(new LinkModel(sourcePort, targetPort)); + BlazorDiagram.Nodes.Add(new[] { node1, node2 }); + BlazorDiagram.Links.Add(new LinkModel(sourcePort, targetPort)); } } } diff --git a/samples/SharedDemo/Options.razor b/samples/SharedDemo/Options.razor index 07fc4a294..dc509576d 100644 --- a/samples/SharedDemo/Options.razor +++ b/samples/SharedDemo/Options.razor @@ -1,4 +1,5 @@ @page "/options" +@using Blazor.Diagrams.Options @inherits DocPage @inject LayoutData LayoutData @@ -37,7 +38,7 @@ - @foreach (var possibleOption in ReflectionUtils.ExtractPossibleOptions()) + @foreach (var possibleOption in ReflectionUtils.ExtractPossibleOptions()) { @possibleOption.Name diff --git a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs index 11f37c2b0..a367ba52a 100644 --- a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs +++ b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs @@ -8,7 +8,7 @@ namespace Blazor.Diagrams.Algorithms { public static class LinksReconnectionAlgorithms { - public static void ReconnectLinksToClosestPorts(this DiagramBase diagram) + public static void ReconnectLinksToClosestPorts(this Diagram diagram) { // Only refresh ports once var modelsToRefresh = new HashSet(); diff --git a/src/Blazor.Diagrams.Core/Behavior.cs b/src/Blazor.Diagrams.Core/Behavior.cs index 7cd683c7c..2d81088f5 100644 --- a/src/Blazor.Diagrams.Core/Behavior.cs +++ b/src/Blazor.Diagrams.Core/Behavior.cs @@ -4,12 +4,12 @@ namespace Blazor.Diagrams.Core { public abstract class Behavior : IDisposable { - public Behavior(DiagramBase diagram) + public Behavior(Diagram diagram) { Diagram = diagram; } - protected DiagramBase Diagram { get; } + protected Diagram Diagram { get; } public abstract void Dispose(); } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs index 8ecc8b931..88aaafd41 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs @@ -7,7 +7,7 @@ namespace Blazor.Diagrams.Core.Behaviors { public class DebugEventsBehavior : Behavior { - public DebugEventsBehavior(DiagramBase diagram) : base(diagram) + public DebugEventsBehavior(Diagram diagram) : base(diagram) { Diagram.Changed += Diagram_Changed; Diagram.ContainerChanged += Diagram_ContainerChanged; diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 47896770f..50193ffbb 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -12,7 +12,7 @@ public class DragMovablesBehavior : Behavior private double? _lastClientX; private double? _lastClientY; - public DragMovablesBehavior(DiagramBase diagram) : base(diagram) + public DragMovablesBehavior(Diagram diagram) : base(diagram) { Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 5d4d8fec6..c2ae120a5 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -10,7 +10,7 @@ public class DragNewLinkBehavior : Behavior { private BaseLinkModel? _ongoingLink; - public DragNewLinkBehavior(DiagramBase diagram) : base(diagram) + public DragNewLinkBehavior(Diagram diagram) : base(diagram) { Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; diff --git a/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs index 37e4ab0cc..107659baf 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs @@ -11,7 +11,7 @@ public class EventsBehavior : Behavior private bool _captureMouseMove; private int _mouseMovedCount; - public EventsBehavior(DiagramBase diagram) : base(diagram) + public EventsBehavior(Diagram diagram) : base(diagram) { _mouseClickSw = new Stopwatch(); diff --git a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs index 2434ec767..c48293617 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs @@ -8,18 +8,18 @@ namespace Blazor.Diagrams.Core.Behaviors { public class KeyboardShortcutsBehavior : Behavior { - private readonly Dictionary> _shortcuts; + private readonly Dictionary> _shortcuts; - public KeyboardShortcutsBehavior(DiagramBase diagram) : base(diagram) + public KeyboardShortcutsBehavior(Diagram diagram) : base(diagram) { - _shortcuts = new Dictionary>(); + _shortcuts = new Dictionary>(); SetShortcut("Delete", false, false, false, KeyboardShortcutsDefaults.DeleteSelection); SetShortcut("g", true, false, true, KeyboardShortcutsDefaults.Grouping); Diagram.KeyDown += OnDiagramKeyDown; } - public void SetShortcut(string key, bool ctrl, bool shift, bool alt, Func action) + public void SetShortcut(string key, bool ctrl, bool shift, bool alt, Func action) { var k = KeysUtils.GetStringRepresentation(ctrl, shift, alt, key); _shortcuts[k] = action; diff --git a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs index 4c486a073..91f0d0727 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs @@ -7,7 +7,7 @@ namespace Blazor.Diagrams.Core.Behaviors { public static class KeyboardShortcutsDefaults { - public static async ValueTask DeleteSelection(DiagramBase diagram) + public static async ValueTask DeleteSelection(Diagram diagram) { var wasSuspended = diagram.SuspendRefresh; if (!wasSuspended) diagram.SuspendRefresh = true; @@ -38,7 +38,7 @@ public static async ValueTask DeleteSelection(DiagramBase diagram) } } - public static ValueTask Grouping(DiagramBase diagram) + public static ValueTask Grouping(Diagram diagram) { if (!diagram.Options.Groups.Enabled) return ValueTask.CompletedTask; diff --git a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs index 661b11b07..3e94b9d58 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs @@ -10,7 +10,7 @@ public class PanBehavior : Behavior private double _lastClientX; private double _lastClientY; - public PanBehavior(DiagramBase diagram) : base(diagram) + public PanBehavior(Diagram diagram) : base(diagram) { Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; diff --git a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs index 3d4b4421b..94c730eee 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs @@ -5,7 +5,7 @@ namespace Blazor.Diagrams.Core.Behaviors { public class SelectionBehavior : Behavior { - public SelectionBehavior(DiagramBase diagram) : base(diagram) + public SelectionBehavior(Diagram diagram) : base(diagram) { Diagram.PointerDown += OnPointerDown; } diff --git a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs index 34fe783a6..c145d35e3 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs @@ -8,7 +8,7 @@ namespace Blazor.Diagrams.Core.Behaviors { public class ZoomBehavior : Behavior { - public ZoomBehavior(DiagramBase diagram) : base(diagram) + public ZoomBehavior(Diagram diagram) : base(diagram) { Diagram.Wheel += Diagram_Wheel; } @@ -21,7 +21,7 @@ private void Diagram_Wheel(WheelEventArgs e) if (!Diagram.Options.Zoom.Enabled) return; - var scale = Math.Clamp(Diagram.Options.Zoom.ScaleFactor, 1.01, 2); + 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; diff --git a/src/Blazor.Diagrams.Core/Delegates.cs b/src/Blazor.Diagrams.Core/Delegates.cs index 8cb400281..e1c2afd7a 100644 --- a/src/Blazor.Diagrams.Core/Delegates.cs +++ b/src/Blazor.Diagrams.Core/Delegates.cs @@ -4,11 +4,11 @@ namespace Blazor.Diagrams.Core { - public delegate Point[] Router(DiagramBase diagram, BaseLinkModel link); + public delegate Point[] Router(Diagram diagram, BaseLinkModel link); - public delegate PathGeneratorResult PathGenerator(DiagramBase diagram, BaseLinkModel link, Point[] route, Point source, Point target); + public delegate PathGeneratorResult PathGenerator(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target); - public delegate BaseLinkModel LinkFactory(DiagramBase diagram, PortModel sourcePort); + public delegate BaseLinkModel LinkFactory(Diagram diagram, PortModel sourcePort); - public delegate GroupModel GroupFactory(DiagramBase diagram, NodeModel[] children); + public delegate GroupModel GroupFactory(Diagram diagram, NodeModel[] children); } diff --git a/src/Blazor.Diagrams.Core/DiagramBase.cs b/src/Blazor.Diagrams.Core/Diagram.cs similarity index 83% rename from src/Blazor.Diagrams.Core/DiagramBase.cs rename to src/Blazor.Diagrams.Core/Diagram.cs index a14f10261..210c87630 100644 --- a/src/Blazor.Diagrams.Core/DiagramBase.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -9,13 +9,15 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using Blazor.Diagrams.Core.Options; [assembly: InternalsVisibleTo("Blazor.Diagrams")] [assembly: InternalsVisibleTo("Blazor.Diagrams.Tests")] [assembly: InternalsVisibleTo("Blazor.Diagrams.Core.Tests")] + namespace Blazor.Diagrams.Core { - public class DiagramBase : Model + public abstract class Diagram { private readonly Dictionary _behaviors; private readonly List _groups; @@ -38,13 +40,13 @@ public class DiagramBase : Model public event Action? PanChanged; public event Action? ZoomChanged; public event Action? ContainerChanged; + public event Action? Changed; - public DiagramBase(DiagramOptions? options = null) + protected Diagram() { _behaviors = new Dictionary(); _groups = new List(); - Options = options ?? new DiagramOptions(); Nodes = new NodeLayer(this); Links = new LinkLayer(this); @@ -57,21 +59,21 @@ public DiagramBase(DiagramOptions? options = null) RegisterBehavior(new KeyboardShortcutsBehavior(this)); } + public abstract DiagramOptions Options { get; } public NodeLayer Nodes { get; } public LinkLayer Links { get; } public IReadOnlyList Groups => _groups; public Rectangle? Container { get; private set; } public Point Pan { get; private set; } = Point.Zero; public double Zoom { get; private set; } = 1; - public DiagramOptions Options { get; } public bool SuspendRefresh { get; set; } - public override void Refresh() + public void Refresh() { if (SuspendRefresh) return; - base.Refresh(); + Changed?.Invoke(); } public void Batch(Action action) @@ -115,11 +117,13 @@ public GroupModel AddGroup(GroupModel group) if (child is GroupModel g) { if (!Groups.Contains(g)) - throw new Exception("One of the children isn't in the diagram (Groups). Make sure to add all the nodes before creating the group."); + throw new Exception( + "One of the children isn't in the diagram (Groups). Make sure to add all the nodes before creating the group."); } else if (child is NodeModel n) if (!Nodes.Contains(n)) - throw new Exception("One of the children isn't in the diagram (Nodes). Make sure to add all the nodes before creating the group."); + throw new Exception( + "One of the children isn't in the diagram (Nodes). Make sure to add all the nodes before creating the group."); } _groups.Add(group); @@ -326,7 +330,8 @@ public void SetContainer(Rectangle newRect) 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"); + 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); } @@ -334,7 +339,8 @@ public Point GetRelativeMousePoint(double clientX, double clientY) 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"); + 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); } @@ -342,31 +348,32 @@ public Point GetRelativePoint(double clientX, double clientY) 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"); + 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 Events - internal 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 TriggerPointerUp(Model? model, PointerEventArgs e) => PointerUp?.Invoke(model, e); - internal void TriggerPointerMove(Model? model, PointerEventArgs e) => PointerMove?.Invoke(model, e); + public void TriggerPointerEnter(Model? model, PointerEventArgs e) => PointerEnter?.Invoke(model, e); - internal void TriggerPointerUp(Model? model, PointerEventArgs e) => PointerUp?.Invoke(model, e); - - internal void TriggerPointerEnter(Model? model, PointerEventArgs e) => PointerEnter?.Invoke(model, e); - - internal void TriggerPointerLeave(Model? model, PointerEventArgs e) => PointerLeave?.Invoke(model, e); + public void TriggerPointerLeave(Model? model, PointerEventArgs e) => PointerLeave?.Invoke(model, e); - internal void OnKeyDown(KeyboardEventArgs e) => KeyDown?.Invoke(e); + public void TriggerKeyDown(KeyboardEventArgs e) => KeyDown?.Invoke(e); - internal void OnWheel(WheelEventArgs e) => Wheel?.Invoke(e); + public void TriggerWheel(WheelEventArgs e) => Wheel?.Invoke(e); - internal void TriggerPointerClick(Model? model, PointerEventArgs e) => PointerClick?.Invoke(model, e); + public void TriggerPointerClick(Model? model, PointerEventArgs e) => PointerClick?.Invoke(model, e); - internal void TriggerPointerDoubleClick(Model? model, PointerEventArgs e) => PointerDoubleClick?.Invoke(model, e); + public void TriggerPointerDoubleClick(Model? model, PointerEventArgs e) => PointerDoubleClick?.Invoke(model, e); #endregion } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/DiagramOptions.cs b/src/Blazor.Diagrams.Core/DiagramOptions.cs deleted file mode 100644 index 255c6be74..000000000 --- a/src/Blazor.Diagrams.Core/DiagramOptions.cs +++ /dev/null @@ -1,99 +0,0 @@ -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; -using System; -using System.ComponentModel; -using System.Threading.Tasks; - -namespace Blazor.Diagrams.Core -{ - public class DiagramOptions - { - [Description("The grid size (grid-based snaping")] - public int? GridSize { get; set; } - [Description("Whether to allow users to select multiple nodes at once using CTRL or not")] - public bool AllowMultiSelection { get; set; } = true; - [Description("Whether to allow panning or not")] - public bool AllowPanning { get; set; } = true; - [Description("Only render visible nodes")] - public bool EnableVirtualization { get; set; } = true; - [Description("Links layer order")] - public int LinksLayerOrder { get; set; } = 0; - [Description("Nodes layer order")] - public int NodesLayerOrder { get; set; } = 0; - - public DiagramZoomOptions Zoom { get; set; } = new DiagramZoomOptions(); - public DiagramLinkOptions Links { get; set; } = new DiagramLinkOptions(); - public DiagramGroupOptions Groups { get; set; } = new DiagramGroupOptions(); - public DiagramConstraintsOptions Constraints { get; set; } = new DiagramConstraintsOptions(); - } - - /// - /// All the options regarding links. - /// - public class DiagramLinkOptions - { - [Description("The default color for links")] - public string DefaultColor { get; set; } = "black"; - [Description("The default color for selected links")] - public string DefaultSelectedColor { get; set; } = "rgb(110, 159, 212)"; - [Description("Default Router for links")] - public Router DefaultRouter { get; set; } = Routers.Normal; - [Description("Default PathGenerator for links")] - public PathGenerator DefaultPathGenerator { get; set; } = PathGenerators.Smooth; - [Description("Whether to enable link snapping")] - public bool EnableSnapping { get; set; } = false; - [Description("Link snapping radius")] - public double SnappingRadius { get; set; } = 50; - [Description("Link model factory")] - public LinkFactory Factory { get; set; } = (diagram, sourcePort) => new LinkModel(sourcePort); - } - - /// - /// All the options regarding zooming. - /// - public class DiagramZoomOptions - { - [Description("Whether to allow zooming or not")] - public bool Enabled { get; set; } = true; - [Description("Whether to inverse the zoom direction or not")] - public bool Inverse { get; set; } - [Description("Minimum value allowed")] - public double Minimum { get { return _minimum; } set { SetMinimum(value); } } - private double _minimum = 0.1; - [Description("Maximum value allowed")] - public double Maximum { get; set; } = 2; - [Description("Zoom Scale Factor. Should be between 1.01 and 2. Default is 1.05.")] - public double ScaleFactor { get; set; } = 1.05; - - private void SetMinimum(double minValue) - { - if (minValue <= 0) - throw new ArgumentException($"(Zoom Options) =>{nameof(Minimum)} cannot be equal or lower than 0"); - _minimum = minValue; - } - } - - /// - /// All the options regarding groups. - /// - public class DiagramGroupOptions - { - [Description("Whether to allow users to group/ungroup nodes")] - public bool Enabled { get; set; } - [Description("Group model factory")] - public GroupFactory Factory { get; set; } = (diagram, children) => new GroupModel(children); - } - - /// - /// All the options regarding diagram constraints, such as deciding whether to delete a node or not. - /// - public class DiagramConstraintsOptions - { - [Description("Decide if a node can/should be deleted")] - public Func> ShouldDeleteNode { get; set; } = _ => ValueTask.FromResult(true); - [Description("Decide if a link can/should be deleted")] - public Func> ShouldDeleteLink { get; set; } = _ => ValueTask.FromResult(true); - [Description("Decide if a group can/should be deleted")] - public Func> ShouldDeleteGroup { get; set; } = _ => ValueTask.FromResult(true); - } -} diff --git a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs index 236235a17..bfdb6ff36 100644 --- a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs @@ -12,7 +12,7 @@ public abstract class BaseLayer : IReadOnlyList where T : Model public event Action? Added; public event Action? Removed; - public BaseLayer(DiagramBase diagram) + public BaseLayer(Diagram diagram) { Diagram = diagram; } @@ -98,7 +98,7 @@ protected virtual void OnItemAdded(T item) { } protected virtual void OnItemRemoved(T item) { } - public DiagramBase Diagram { get; } + public Diagram Diagram { get; } public int Count => _items.Count; public T this[int index] => _items[index]; diff --git a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs index 8680b4455..823414abc 100644 --- a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs @@ -6,7 +6,7 @@ namespace Blazor.Diagrams.Core.Layers { public class LinkLayer : BaseLayer { - public LinkLayer(DiagramBase diagram) : base(diagram) { } + public LinkLayer(Diagram diagram) : base(diagram) { } protected override void OnItemAdded(BaseLinkModel link) { diff --git a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs index d80218fd1..7a93e421f 100644 --- a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs @@ -5,7 +5,7 @@ namespace Blazor.Diagrams.Core.Layers { public class NodeLayer : BaseLayer { - public NodeLayer(DiagramBase diagram) : base(diagram) { } + public NodeLayer(Diagram diagram) : base(diagram) { } public override void Remove(NodeModel node) { diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index b506b5b61..ac6da75a9 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -25,7 +25,7 @@ protected BaseLinkModel(string id, Anchor source, Anchor? target = null) : base( public Anchor Source { get; private set; } public Anchor? Target { get; private set; } - public DiagramBase? Diagram { get; internal set; } + public Diagram? Diagram { get; internal set; } public PathGeneratorResult GeneratedPathResult { get; private set; } = PathGeneratorResult.Empty; public SvgPath[] Paths => GeneratedPathResult.Paths; public bool IsAttached => Target != null; diff --git a/src/Blazor.Diagrams.Core/Options/DiagramConstraintsOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramConstraintsOptions.cs new file mode 100644 index 000000000..9b14c7f42 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Options/DiagramConstraintsOptions.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Options; + +public class DiagramConstraintsOptions +{ + public Func> ShouldDeleteNode { get; set; } = _ => ValueTask.FromResult(true); + public Func> ShouldDeleteLink { get; set; } = _ => ValueTask.FromResult(true); + public Func> ShouldDeleteGroup { get; set; } = _ => ValueTask.FromResult(true); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Options/DiagramGroupOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramGroupOptions.cs new file mode 100644 index 000000000..80c5dffc8 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Options/DiagramGroupOptions.cs @@ -0,0 +1,10 @@ +using Blazor.Diagrams.Core.Models; + +namespace Blazor.Diagrams.Core.Options; + +public class DiagramGroupOptions +{ + public bool Enabled { get; set; } + + public GroupFactory Factory { get; set; } = (diagram, children) => new GroupModel(children); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs new file mode 100644 index 000000000..6c7a4364d --- /dev/null +++ b/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs @@ -0,0 +1,25 @@ +using System; +using Blazor.Diagrams.Core.Models; + +namespace Blazor.Diagrams.Core.Options; + +public class DiagramLinkOptions +{ + private double _snappingRadius = 50; + + public Router DefaultRouter { get; set; } = Routers.Normal; + public PathGenerator DefaultPathGenerator { get; set; } = PathGenerators.Smooth; + public bool EnableSnapping { get; set; } = false; + public double SnappingRadius + { + get => _snappingRadius; + set + { + if (value <= 0) + throw new ArgumentException($"SnappingRadius must be greater than zero"); + + _snappingRadius = value; + } + } + public LinkFactory Factory { get; set; } = (diagram, sourcePort) => new LinkModel(sourcePort); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs new file mode 100644 index 000000000..81270d511 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs @@ -0,0 +1,14 @@ +namespace Blazor.Diagrams.Core.Options; + +public class DiagramOptions +{ + public int? GridSize { get; set; } + public bool AllowMultiSelection { get; set; } = true; + public bool AllowPanning { get; set; } = true; + public bool EnableVirtualization { get; set; } = true; // Todo: behavior + + public DiagramZoomOptions Zoom { get; set; } = new(); + public DiagramLinkOptions Links { get; set; } = new(); + public DiagramGroupOptions Groups { get; set; } = new(); + public DiagramConstraintsOptions Constraints { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Options/DiagramZoomOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramZoomOptions.cs new file mode 100644 index 000000000..4a2f866a9 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Options/DiagramZoomOptions.cs @@ -0,0 +1,35 @@ +using System; + +namespace Blazor.Diagrams.Core.Options; + +public class DiagramZoomOptions +{ + private double _minimum = 0.1; + private double _scaleFactor = 1.05; + + public bool Enabled { get; set; } = true; + public bool Inverse { get; set; } + public double Minimum + { + get => _minimum; + set + { + if (value <= 0) + throw new ArgumentException($"Minimum can't be less than zero"); + + _minimum = value; + } + } + public double Maximum { get; set; } = 2; + public double ScaleFactor + { + get => _scaleFactor; + set + { + if (value is < 1.01 or > 2) + throw new ArgumentException($"ScaleFactor can't be lower than 1.01 or greater than 2"); + + _scaleFactor = value; + } + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs index 7539ae3f0..90df06520 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs @@ -12,7 +12,7 @@ public static partial class PathGenerators { private const double _margin = 125; - public static PathGeneratorResult Smooth(DiagramBase _, BaseLinkModel link, Point[] route, Point source, Point target) + public static PathGeneratorResult Smooth(Diagram _, BaseLinkModel link, Point[] route, Point source, Point target) { route = ConcatRouteAndSourceAndTarget(route, source, target); diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs index 1920aef6a..f22fab8f6 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs @@ -6,7 +6,7 @@ namespace Blazor.Diagrams.Core { public static partial class PathGenerators { - public static PathGeneratorResult Straight(DiagramBase _, BaseLinkModel link, Point[] route, Point source, Point target) + public static PathGeneratorResult Straight(Diagram _, BaseLinkModel link, Point[] route, Point source, Point target) { route = ConcatRouteAndSourceAndTarget(route, source, target); double? sourceAngle = null; diff --git a/src/Blazor.Diagrams.Core/Routers/Routers.Normal.cs b/src/Blazor.Diagrams.Core/Routers/Routers.Normal.cs index 6a0d826da..e27f84312 100644 --- a/src/Blazor.Diagrams.Core/Routers/Routers.Normal.cs +++ b/src/Blazor.Diagrams.Core/Routers/Routers.Normal.cs @@ -6,7 +6,7 @@ namespace Blazor.Diagrams.Core { public static partial class Routers { - public static Point[] Normal(DiagramBase _, BaseLinkModel link) + public static Point[] Normal(Diagram _, BaseLinkModel link) { return link.Vertices.Select(v => v.Position).ToArray(); } diff --git a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs index a94ceae0c..bd2145753 100644 --- a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs +++ b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs @@ -13,7 +13,7 @@ namespace Blazor.Diagrams.Core { public static partial class Routers { - public static Point[] Orthogonal(DiagramBase _, BaseLinkModel link) + public static Point[] Orthogonal(Diagram _, BaseLinkModel link) { if (link.Source is not SinglePortAnchor spa1) throw new Exception("Orthogonal router doesn't work with port-less links yet"); diff --git a/src/Blazor.Diagrams/Diagram.cs b/src/Blazor.Diagrams/BlazorDiagram.cs similarity index 67% rename from src/Blazor.Diagrams/Diagram.cs rename to src/Blazor.Diagrams/BlazorDiagram.cs index d787ef396..654a2d724 100644 --- a/src/Blazor.Diagrams/Diagram.cs +++ b/src/Blazor.Diagrams/BlazorDiagram.cs @@ -3,20 +3,28 @@ using Microsoft.AspNetCore.Components; using System; using System.Collections.Generic; +using Blazor.Diagrams.Options; namespace Blazor.Diagrams { - public class Diagram : DiagramBase + public class BlazorDiagram : Diagram { private readonly Dictionary _componentByModelMapping; - public Diagram(DiagramOptions? options = null) : base(options) + public BlazorDiagram(BlazorDiagramOptions? options = null) { _componentByModelMapping = new Dictionary(); + + Options = options ?? new BlazorDiagramOptions(); } - public void RegisterModelComponent(bool replace = false) where M : Model where C : ComponentBase - => RegisterModelComponent(typeof(M), typeof(C), replace); + public override BlazorDiagramOptions Options { get; } + + public void RegisterModelComponent(bool replace = false) + where TModel : Model where TComponent : ComponentBase + { + RegisterModelComponent(typeof(TModel), typeof(TComponent), replace); + } public void RegisterModelComponent(Type modelType, Type componentType, bool replace = false) { @@ -45,10 +53,10 @@ public void RegisterModelComponent(Type modelType, Type componentType, bool repl return null; } - public Type? GetComponentForModel(bool checkSubclasses = true) where M : Model - => GetComponentForModel(typeof(M), checkSubclasses); + public Type? GetComponentForModel(bool checkSubclasses = true) where TModel : Model + => GetComponentForModel(typeof(TModel), checkSubclasses); public Type? GetComponentForModel(Model model, bool checkSubclasses = true) => GetComponentForModel(model.GetType(), checkSubclasses); } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor b/src/Blazor.Diagrams/Components/DiagramCanvas.razor index 80199737a..12dff5fcc 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor @@ -10,18 +10,18 @@ @onwheel:stopPropagation> @* Links *@ - - @foreach (var node in Diagram.Nodes.OfType().Where(n => n.Group == null)) + + @foreach (var node in BlazorDiagram.Nodes.OfType().Where(n => n.Group == null)) { } - @foreach (var group in Diagram.Groups.OfType().Where(n => n.Group == null)) + @foreach (var group in BlazorDiagram.Groups.OfType().Where(n => n.Group == null)) { } - @foreach (var link in Diagram.Links) + @foreach (var link in BlazorDiagram.Links) { } @@ -29,14 +29,14 @@ @* Nodes *@ -
    - @foreach (var group in Diagram.Groups.Where(n => n is not SvgGroupModel)) +
    + @foreach (var group in BlazorDiagram.Groups.Where(n => n is not SvgGroupModel)) { if (group.Group != null) continue; } - @foreach (var node in Diagram.Nodes.Where(n => n is not SvgNodeModel && n.Group == null)) + @foreach (var node in BlazorDiagram.Nodes.Where(n => n is not SvgNodeModel && n.Group == null)) { } diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index e5e8a555a..d01914d4d 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -11,7 +11,7 @@ namespace Blazor.Diagrams.Components public partial class DiagramCanvas : IDisposable { [CascadingParameter] - public Diagram Diagram { get; set; } = null!; + public BlazorDiagram BlazorDiagram { get; set; } = null!; [Parameter] public RenderFragment? Widgets { get; set; } @@ -28,7 +28,7 @@ public partial class DiagramCanvas : IDisposable private string GetLayerStyle(int order) { - return FormattableString.Invariant($"transform: translate({Diagram.Pan.X}px, {Diagram.Pan.Y}px) scale({Diagram.Zoom}); z-index: {order};"); + return FormattableString.Invariant($"transform: translate({BlazorDiagram.Pan.X}px, {BlazorDiagram.Pan.Y}px) scale({BlazorDiagram.Zoom}); z-index: {order};"); } protected override void OnInitialized() @@ -36,7 +36,7 @@ protected override void OnInitialized() base.OnInitialized(); _reference = DotNetObjectReference.Create(this); - Diagram.Changed += OnDiagramChanged; + BlazorDiagram.Changed += OnDiagramChanged; } protected override async Task OnAfterRenderAsync(bool firstRender) @@ -45,13 +45,13 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (firstRender) { - Diagram.SetContainer(await JSRuntime.GetBoundingClientRect(elementReference)); + BlazorDiagram.SetContainer(await JSRuntime.GetBoundingClientRect(elementReference)); await JSRuntime.ObserveResizes(elementReference, _reference!); } } [JSInvokable] - public void OnResize(Rectangle rect) => Diagram.SetContainer(rect); + public void OnResize(Rectangle rect) => BlazorDiagram.SetContainer(rect); protected override bool ShouldRender() { @@ -61,15 +61,15 @@ protected override bool ShouldRender() return true; } - private void OnPointerDown(PointerEventArgs e) => Diagram.TriggerPointerDown(null, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) => BlazorDiagram.TriggerPointerDown(null, e.ToCore()); - private void OnPointerMove(PointerEventArgs e) => Diagram.TriggerPointerMove(null, e.ToCore()); + private void OnPointerMove(PointerEventArgs e) => BlazorDiagram.TriggerPointerMove(null, e.ToCore()); - private void OnPointerUp(PointerEventArgs e) => Diagram.TriggerPointerUp(null, e.ToCore()); + private void OnPointerUp(PointerEventArgs e) => BlazorDiagram.TriggerPointerUp(null, e.ToCore()); - private void OnKeyDown(KeyboardEventArgs e) => Diagram.OnKeyDown(e.ToCore()); + private void OnKeyDown(KeyboardEventArgs e) => BlazorDiagram.TriggerKeyDown(e.ToCore()); - private void OnWheel(WheelEventArgs e) => Diagram.OnWheel(e.ToCore()); + private void OnWheel(WheelEventArgs e) => BlazorDiagram.TriggerWheel(e.ToCore()); private void OnDiagramChanged() { @@ -79,7 +79,7 @@ private void OnDiagramChanged() public void Dispose() { - Diagram.Changed -= OnDiagramChanged; + BlazorDiagram.Changed -= OnDiagramChanged; if (_reference == null) return; diff --git a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs index 51d2290c8..520981dc9 100644 --- a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs @@ -11,7 +11,7 @@ public partial class LinkVertexWidget : IDisposable { private bool _shouldRender = true; - [CascadingParameter] public Diagram Diagram { get; set; } = null!; + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; [Parameter] public LinkVertexModel Vertex { get; set; } = null!; [Parameter] public string? Color { get; set; } [Parameter] public string? SelectedColor { get; set; } @@ -42,9 +42,9 @@ private void OnVertexChanged() InvokeAsync(StateHasChanged); } - private void OnPointerDown(PointerEventArgs e) => Diagram.TriggerPointerDown(Vertex, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) => BlazorDiagram.TriggerPointerDown(Vertex, e.ToCore()); - private void OnPointerUp(PointerEventArgs e) => Diagram.TriggerPointerUp(Vertex, e.ToCore()); + private void OnPointerUp(PointerEventArgs e) => BlazorDiagram.TriggerPointerUp(Vertex, e.ToCore()); private void OnDoubleClick(MouseEventArgs e) { diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor b/src/Blazor.Diagrams/Components/LinkWidget.razor index f0979510a..2851d3b50 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor @@ -1,5 +1,5 @@ @{ - var color = Link.Selected ? (Link.SelectedColor ?? Diagram.Options.Links.DefaultSelectedColor) : (Link.Color ?? Diagram.Options.Links.DefaultColor); + var color = Link.Selected ? (Link.SelectedColor ?? BlazorDiagram.Options.Links.DefaultSelectedColor) : (Link.Color ?? BlazorDiagram.Options.Links.DefaultColor); var result = Link.GeneratedPathResult; var bounds = Link.GetBounds()?.Inflate(10, 10); } @@ -43,8 +43,8 @@ @if (Link.Vertices.Count > 0) { - var selectedColor = Link.SelectedColor ?? Diagram.Options.Links.DefaultSelectedColor; - var normalColor = Link.Color ?? Diagram.Options.Links.DefaultColor; + var selectedColor = Link.SelectedColor ?? BlazorDiagram.Options.Links.DefaultSelectedColor; + var normalColor = Link.Color ?? BlazorDiagram.Options.Links.DefaultColor; @foreach (var vertex in Link.Vertices) { diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs index 626904eb2..8d92d2378 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs @@ -8,7 +8,7 @@ namespace Blazor.Diagrams.Components public partial class LinkWidget { [CascadingParameter] - public Diagram Diagram { get; set; } = null!; + public BlazorDiagram BlazorDiagram { get; set; } = null!; [Parameter] public LinkModel Link { get; set; } = null!; @@ -19,12 +19,12 @@ private void OnPointerDown(PointerEventArgs e, int index) return; var vertex = CreateVertex(e.ClientX, e.ClientY, index); - Diagram.TriggerPointerDown(vertex, e.ToCore()); + BlazorDiagram.TriggerPointerDown(vertex, e.ToCore()); } private LinkVertexModel CreateVertex(double clientX, double clientY, int index) { - var rPt = Diagram.GetRelativeMousePoint(clientX, clientY); + var rPt = BlazorDiagram.GetRelativeMousePoint(clientX, clientY); var vertex = new LinkVertexModel(Link, rPt); Link.Vertices.Insert(index, vertex); return vertex; diff --git a/src/Blazor.Diagrams/Components/NavigatorWidget.razor b/src/Blazor.Diagrams/Components/NavigatorWidget.razor index 8c24b9c3e..123633d79 100644 --- a/src/Blazor.Diagrams/Components/NavigatorWidget.razor +++ b/src/Blazor.Diagrams/Components/NavigatorWidget.razor @@ -1,11 +1,11 @@ -@if (Diagram?.Container != null) +@if (BlazorDiagram?.Container != null) { - var addedNodeX = Math.Max(0, Diagram.Pan.X) + NodePositionAdjustment.X; - var addedNodeY = Math.Max(0, Diagram.Pan.Y) + NodePositionAdjustment.Y; - var addedCurrentViewX = (Math.Abs(Math.Min(0, Diagram.Pan.X)) + NodePositionAdjustment.X) * XFactor; - var addedCurrentViewY = (Math.Abs(Math.Min(0, Diagram.Pan.Y)) + NodePositionAdjustment.Y) * YFactor; - var currentViewWidth = Diagram.Container.Width * XFactor; - var currentViewHeight = Diagram.Container.Height * YFactor; + var addedNodeX = Math.Max(0, BlazorDiagram.Pan.X) + NodePositionAdjustment.X; + var addedNodeY = Math.Max(0, BlazorDiagram.Pan.Y) + NodePositionAdjustment.Y; + var addedCurrentViewX = (Math.Abs(Math.Min(0, BlazorDiagram.Pan.X)) + NodePositionAdjustment.X) * XFactor; + var addedCurrentViewY = (Math.Abs(Math.Min(0, BlazorDiagram.Pan.Y)) + NodePositionAdjustment.Y) * YFactor; + var currentViewWidth = BlazorDiagram.Container.Width * XFactor; + var currentViewHeight = BlazorDiagram.Container.Height * YFactor;
    @@ -18,12 +18,12 @@
    - @foreach (var node in Diagram.Nodes.Where(n => n.Size != null)) + @foreach (var node in BlazorDiagram.Nodes.Where(n => n.Size != null)) { - var left = ((node.Position.X * Diagram.Zoom) + addedNodeX) * XFactor; - var top = ((node.Position.Y * Diagram.Zoom) + addedNodeY) * YFactor; - var width = node.Size.Width * Diagram.Zoom * XFactor; - var height = node.Size.Height * Diagram.Zoom * YFactor; + var left = ((node.Position.X * BlazorDiagram.Zoom) + addedNodeX) * XFactor; + var top = ((node.Position.Y * BlazorDiagram.Zoom) + addedNodeY) * YFactor; + var width = node.Size.Width * BlazorDiagram.Zoom * XFactor; + var height = node.Size.Height * BlazorDiagram.Zoom * YFactor; } - @foreach (var group in Diagram.Groups.Where(g => !Size.Zero.Equals(g.Size))) + @foreach (var group in BlazorDiagram.Groups.Where(g => !Size.Zero.Equals(g.Size))) { - var left = (Math.Max(0, group.Position.X * Diagram.Zoom) + addedNodeX) * XFactor; - var top = (Math.Max(0, group.Position.Y * Diagram.Zoom) + addedNodeY) * YFactor; - var width = group.Size.Width * Diagram.Zoom * XFactor; - var height = group.Size.Height * Diagram.Zoom * YFactor; + var left = (Math.Max(0, group.Position.X * BlazorDiagram.Zoom) + addedNodeX) * XFactor; + var top = (Math.Max(0, group.Position.Y * BlazorDiagram.Zoom) + addedNodeY) * YFactor; + var width = group.Size.Width * BlazorDiagram.Zoom * XFactor; + var height = group.Size.Height * BlazorDiagram.Zoom * YFactor; n.Size?.Equals(Size.Zero) == false).ToList(); if (nodes.Count == 0) return; var bounds = nodes.GetBounds(); - var nodesMinX = bounds.Left * Diagram.Zoom; - var nodesMaxX = bounds.Right * Diagram.Zoom; - var nodesMinY = bounds.Top * Diagram.Zoom; - var nodesMaxY = bounds.Bottom * Diagram.Zoom; + var nodesMinX = bounds.Left * BlazorDiagram.Zoom; + var nodesMaxX = bounds.Right * BlazorDiagram.Zoom; + var nodesMinY = bounds.Top * BlazorDiagram.Zoom; + var nodesMaxY = bounds.Bottom * BlazorDiagram.Zoom; (double fullSizeWidth, double fullSizeHeight) = GetFullSize(nodesMaxX, nodesMaxY); AdjustFullSizeWithNodesRect(nodesMinX, nodesMinY, ref fullSizeWidth, ref fullSizeHeight); @@ -90,12 +90,12 @@ private void AdjustFullSizeWithNodesRect(double nodesMinX, double nodesMinY, ref // Width if (nodesMinX < 0) { - var temp = nodesMinX + Diagram.Pan.X; - if (Diagram.Pan.X > 0 && temp < 0) + var temp = nodesMinX + BlazorDiagram.Pan.X; + if (BlazorDiagram.Pan.X > 0 && temp < 0) { fullSizeWidth += Math.Abs(temp); } - else if (Diagram.Pan.X <= 0) + else if (BlazorDiagram.Pan.X <= 0) { fullSizeWidth += Math.Abs(nodesMinX); } @@ -104,12 +104,12 @@ private void AdjustFullSizeWithNodesRect(double nodesMinX, double nodesMinY, ref // Height if (nodesMinY < 0) { - var temp = nodesMinY + Diagram.Pan.Y; - if (Diagram.Pan.Y > 0 && temp < 0) + var temp = nodesMinY + BlazorDiagram.Pan.Y; + if (BlazorDiagram.Pan.Y > 0 && temp < 0) { fullSizeHeight += Math.Abs(temp); } - else if (Diagram.Pan.Y <= 0) + else if (BlazorDiagram.Pan.Y <= 0) { fullSizeHeight += Math.Abs(nodesMinY); } @@ -118,24 +118,24 @@ private void AdjustFullSizeWithNodesRect(double nodesMinX, double nodesMinY, ref private (double width, double height) GetFullSize(double nodesMaxX, double nodesMaxY) { - var nodesLayerWidth = Math.Max(Diagram.Container.Width * Diagram.Zoom, nodesMaxX); - var nodesLayerHeight = Math.Max(Diagram.Container.Height * Diagram.Zoom, nodesMaxY); + var nodesLayerWidth = Math.Max(BlazorDiagram.Container.Width * BlazorDiagram.Zoom, nodesMaxX); + var nodesLayerHeight = Math.Max(BlazorDiagram.Container.Height * BlazorDiagram.Zoom, nodesMaxY); double fullWidth; double fullHeight; - if (Diagram.Zoom == 1) + if (BlazorDiagram.Zoom == 1) { - fullWidth = Diagram.Container.Width + Math.Abs(Diagram.Pan.X); - fullHeight = Diagram.Container.Height + Math.Abs(Diagram.Pan.Y); + fullWidth = BlazorDiagram.Container.Width + Math.Abs(BlazorDiagram.Pan.X); + fullHeight = BlazorDiagram.Container.Height + Math.Abs(BlazorDiagram.Pan.Y); } - else if (Diagram.Zoom > 1) + else if (BlazorDiagram.Zoom > 1) { // Width - if (Diagram.Pan.X < 0) + if (BlazorDiagram.Pan.X < 0) { - if (nodesLayerWidth + Diagram.Pan.X < Diagram.Container.Width) + if (nodesLayerWidth + BlazorDiagram.Pan.X < BlazorDiagram.Container.Width) { - fullWidth = Diagram.Container.Width + Math.Abs(Diagram.Pan.X); + fullWidth = BlazorDiagram.Container.Width + Math.Abs(BlazorDiagram.Pan.X); } else { @@ -144,15 +144,15 @@ private void AdjustFullSizeWithNodesRect(double nodesMinX, double nodesMinY, ref } else { - fullWidth = nodesLayerWidth + Diagram.Pan.X; + fullWidth = nodesLayerWidth + BlazorDiagram.Pan.X; } // Height - if (Diagram.Pan.Y < 0) + if (BlazorDiagram.Pan.Y < 0) { - if (nodesLayerHeight + Diagram.Pan.Y < Diagram.Container.Height) + if (nodesLayerHeight + BlazorDiagram.Pan.Y < BlazorDiagram.Container.Height) { - fullHeight = Diagram.Container.Height + Math.Abs(Diagram.Pan.Y); + fullHeight = BlazorDiagram.Container.Height + Math.Abs(BlazorDiagram.Pan.Y); } else { @@ -161,29 +161,29 @@ private void AdjustFullSizeWithNodesRect(double nodesMinX, double nodesMinY, ref } else { - fullHeight = nodesLayerHeight + Diagram.Pan.Y; + fullHeight = nodesLayerHeight + BlazorDiagram.Pan.Y; } } else { // Width - if (Diagram.Pan.X > 0) + if (BlazorDiagram.Pan.X > 0) { - fullWidth = Math.Max(nodesLayerWidth + Diagram.Pan.X, Diagram.Container.Width); + fullWidth = Math.Max(nodesLayerWidth + BlazorDiagram.Pan.X, BlazorDiagram.Container.Width); } else { - fullWidth = Diagram.Container.Width + Math.Abs(Diagram.Pan.X); + fullWidth = BlazorDiagram.Container.Width + Math.Abs(BlazorDiagram.Pan.X); } // Height - if (Diagram.Pan.Y > 0) + if (BlazorDiagram.Pan.Y > 0) { - fullHeight = Math.Max(nodesLayerHeight + Diagram.Pan.Y, Diagram.Container.Height); + fullHeight = Math.Max(nodesLayerHeight + BlazorDiagram.Pan.Y, BlazorDiagram.Container.Height); } else { - fullHeight = Diagram.Container.Height + Math.Abs(Diagram.Pan.Y); + fullHeight = BlazorDiagram.Container.Height + Math.Abs(BlazorDiagram.Pan.Y); } } @@ -192,15 +192,15 @@ private void AdjustFullSizeWithNodesRect(double nodesMinX, double nodesMinY, ref public void Dispose() { - if (Diagram != null) + if (BlazorDiagram != null) { - Diagram.Changed -= Diagram_Changed; - Diagram.Nodes.Added -= Diagram_NodesAdded; - Diagram.Nodes.Removed -= Diagram_NodesRemoved; - foreach (var node in Diagram.Nodes) + BlazorDiagram.Changed -= Diagram_Changed; + BlazorDiagram.Nodes.Added -= Diagram_NodesAdded; + BlazorDiagram.Nodes.Removed -= Diagram_NodesRemoved; + foreach (var node in BlazorDiagram.Nodes) node.Changed -= Refresh; - foreach (var group in Diagram.Groups) + foreach (var group in BlazorDiagram.Groups) group.Changed -= Refresh; } } diff --git a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs index 7ace4ac67..95e195bdb 100644 --- a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs @@ -17,7 +17,7 @@ public class GroupRenderer : ComponentBase, IDisposable private bool _isSvg; [CascadingParameter] - public Diagram Diagram { get; set; } = null!; + public BlazorDiagram BlazorDiagram { get; set; } = null!; [Parameter] public GroupModel Group { get; set; } = null!; @@ -79,7 +79,7 @@ private static string GenerateStyle(double top, double left, double width, doubl protected override void BuildRenderTree(RenderTreeBuilder builder) { - var componentType = Diagram.GetComponentForModel(Group) ?? typeof(DefaultGroupWidget); + var componentType = BlazorDiagram.GetComponentForModel(Group) ?? typeof(DefaultGroupWidget); var classes = new StringBuilder("group") .AppendIf(" locked", Group.Locked) .AppendIf(" selected", Group.Selected) @@ -118,8 +118,8 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.CloseElement(); } - private void OnPointerDown(PointerEventArgs e) => Diagram.TriggerPointerDown(Group, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) => BlazorDiagram.TriggerPointerDown(Group, e.ToCore()); - private void OnPointerUp(PointerEventArgs e) => Diagram.TriggerPointerUp(Group, e.ToCore()); + private void OnPointerUp(PointerEventArgs e) => BlazorDiagram.TriggerPointerUp(Group, e.ToCore()); } } diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs index 9d15ccdfa..5e52946d1 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs @@ -10,7 +10,7 @@ namespace Blazor.Diagrams.Components.Renderers { public class LinkLabelRenderer : ComponentBase, IDisposable { - [CascadingParameter] public Diagram Diagram { get; set; } = null!; + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; [Parameter] public LinkLabelModel Label { get; set; } = null!; [Parameter] public SvgPath[] Paths { get; set; } = null!; @@ -26,7 +26,7 @@ protected override void OnInitialized() protected override void BuildRenderTree(RenderTreeBuilder builder) { - var component = Diagram.GetComponentForModel(Label) ?? typeof(DefaultLinkLabelWidget); + var component = BlazorDiagram.GetComponentForModel(Label) ?? typeof(DefaultLinkLabelWidget); var position = FindPosition(); if (position == null) return; diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs index ef8715a32..3d55aad65 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs @@ -13,7 +13,7 @@ public class LinkRenderer : ComponentBase, IDisposable private bool _shouldRender = true; [CascadingParameter] - public Diagram Diagram { get; set; } + public BlazorDiagram BlazorDiagram { get; set; } [Parameter] public BaseLinkModel Link { get; set; } @@ -34,7 +34,7 @@ protected override void OnInitialized() protected override void BuildRenderTree(RenderTreeBuilder builder) { - var componentType = Diagram.GetComponentForModel(Link) ?? typeof(LinkWidget); + var componentType = BlazorDiagram.GetComponentForModel(Link) ?? typeof(LinkWidget); builder.OpenElement(0, "g"); builder.AddAttribute(1, "class", "link"); @@ -57,8 +57,8 @@ private void OnLinkChanged() InvokeAsync(StateHasChanged); } - private void OnPointerDown(PointerEventArgs e) => Diagram.TriggerPointerDown(Link, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) => BlazorDiagram.TriggerPointerDown(Link, e.ToCore()); - private void OnPointerUp(PointerEventArgs e) => Diagram.TriggerPointerUp(Link, e.ToCore()); + private void OnPointerUp(PointerEventArgs e) => BlazorDiagram.TriggerPointerUp(Link, e.ToCore()); } } diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index 0fa1e6081..5b404b914 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -23,7 +23,7 @@ public class NodeRenderer : ComponentBase, IDisposable private bool _isSvg; [CascadingParameter] - public Diagram Diagram { get; set; } = null!; + public BlazorDiagram BlazorDiagram { get; set; } = null!; [Parameter] public NodeModel Node { get; set; } = null!; @@ -33,9 +33,9 @@ public class NodeRenderer : ComponentBase, IDisposable public void Dispose() { - Diagram.PanChanged -= CheckVisibility; - Diagram.ZoomChanged -= CheckVisibility; - Diagram.ContainerChanged -= CheckVisibility; + BlazorDiagram.PanChanged -= CheckVisibility; + BlazorDiagram.ZoomChanged -= CheckVisibility; + BlazorDiagram.ContainerChanged -= CheckVisibility; Node.Changed -= ReRender; if (_element.Id != null) @@ -51,7 +51,7 @@ public void OnResize(Size size) if (Size.Zero.Equals(size)) return; - size = new Size(size.Width / Diagram.Zoom, size.Height / Diagram.Zoom); + size = new Size(size.Width / BlazorDiagram.Zoom, size.Height / BlazorDiagram.Zoom); if (Node.Size != null && Node.Size.Width.AlmostEqualTo(size.Width) && Node.Size.Height.AlmostEqualTo(size.Height)) return; @@ -66,9 +66,9 @@ protected override void OnInitialized() base.OnInitialized(); _reference = DotNetObjectReference.Create(this); - Diagram.PanChanged += CheckVisibility; - Diagram.ZoomChanged += CheckVisibility; - Diagram.ContainerChanged += CheckVisibility; + BlazorDiagram.PanChanged += CheckVisibility; + BlazorDiagram.ZoomChanged += CheckVisibility; + BlazorDiagram.ContainerChanged += CheckVisibility; Node.Changed += ReRender; } @@ -92,7 +92,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) if (!_isVisible) return; - var componentType = Diagram.GetComponentForModel(Node) ?? (_isSvg ? typeof(SvgNodeWidget) : typeof(NodeWidget)); + var componentType = BlazorDiagram.GetComponentForModel(Node) ?? (_isSvg ? typeof(SvgNodeWidget) : typeof(NodeWidget)); var classes = new StringBuilder("node") .AppendIf(" locked", Node.Locked) .AppendIf(" selected", Node.Selected) @@ -138,19 +138,19 @@ protected override async Task OnAfterRenderAsync(bool firstRender) private void CheckVisibility() { // _isVisible must be true in case virtualization gets disabled and some nodes are hidden - if (!Diagram.Options.EnableVirtualization && _isVisible) + if (!BlazorDiagram.Options.EnableVirtualization && _isVisible) return; if (Node.Size == null) return; - var left = Node.Position.X * Diagram.Zoom + Diagram.Pan.X; - var top = Node.Position.Y * Diagram.Zoom + Diagram.Pan.Y; - var right = left + Node.Size.Width * Diagram.Zoom; - var bottom = top + Node.Size.Height * Diagram.Zoom; + var left = Node.Position.X * BlazorDiagram.Zoom + BlazorDiagram.Pan.X; + var top = Node.Position.Y * BlazorDiagram.Zoom + BlazorDiagram.Pan.Y; + var right = left + Node.Size.Width * BlazorDiagram.Zoom; + var bottom = top + Node.Size.Height * BlazorDiagram.Zoom; - var isVisible = right > 0 && left < Diagram.Container.Width && bottom > 0 && - top < Diagram.Container.Height; + var isVisible = right > 0 && left < BlazorDiagram.Container.Width && bottom > 0 && + top < BlazorDiagram.Container.Height; if (_isVisible != isVisible) { @@ -166,20 +166,20 @@ private void ReRender() InvokeAsync(StateHasChanged); } - private void OnPointerDown(PointerEventArgs e) => Diagram.TriggerPointerDown(Node, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) => BlazorDiagram.TriggerPointerDown(Node, e.ToCore()); - private void OnPointerUp(PointerEventArgs e) => Diagram.TriggerPointerUp(Node, e.ToCore()); + private void OnPointerUp(PointerEventArgs e) => BlazorDiagram.TriggerPointerUp(Node, e.ToCore()); private void OnMouseEnter(MouseEventArgs e) { Console.WriteLine("On mouse enter"); - Diagram.TriggerPointerLeave(Node, e.ToCore()); + BlazorDiagram.TriggerPointerLeave(Node, e.ToCore()); } private void OnMouseLeave(MouseEventArgs e) { Console.WriteLine("On mouse leave"); - Diagram.TriggerPointerEnter(Node, e.ToCore()); + BlazorDiagram.TriggerPointerEnter(Node, e.ToCore()); } } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs index 887dcc28c..007867203 100644 --- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs @@ -21,7 +21,7 @@ public class PortRenderer : ComponentBase, IDisposable private bool _isParentSvg; [CascadingParameter] - public Diagram Diagram { get; set; } = null!; + public BlazorDiagram BlazorDiagram { get; set; } = null!; [Inject] private IJSRuntime JSRuntime { get; set; } = null!; @@ -81,25 +81,25 @@ protected override async Task OnAfterRenderAsync(bool firstRender) } } - private void OnPointerDown(PointerEventArgs e) => Diagram.TriggerPointerDown(Port, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) => BlazorDiagram.TriggerPointerDown(Port, e.ToCore()); private void OnPointerUp(PointerEventArgs e) { var model = e.PointerType == "mouse" ? Port : FindPortOn(e.ClientX, e.ClientY); - Diagram.TriggerPointerUp(model, e.ToCore()); + BlazorDiagram.TriggerPointerUp(model, e.ToCore()); } private PortModel? FindPortOn(double clientX, double clientY) { - var allPorts = Diagram.Nodes.SelectMany(n => n.Ports) - .Union(Diagram.Groups.SelectMany(g => g.Ports)); + var allPorts = BlazorDiagram.Nodes.SelectMany(n => n.Ports) + .Union(BlazorDiagram.Groups.SelectMany(g => g.Ports)); foreach (var port in allPorts) { if (!port.Initialized) continue; - var relativePt = Diagram.GetRelativeMousePoint(clientX, clientY); + var relativePt = BlazorDiagram.GetRelativeMousePoint(clientX, clientY); if (port.GetBounds().ContainsPoint(relativePt)) return port; } @@ -110,13 +110,13 @@ private void OnPointerUp(PointerEventArgs e) private async Task UpdateDimensions() { _updatingDimensions = true; - var zoom = Diagram.Zoom; - var pan = Diagram.Pan; + var zoom = BlazorDiagram.Zoom; + var pan = BlazorDiagram.Pan; var rect = await JSRuntime.GetBoundingClientRect(_element); Port.Size = new Size(rect.Width / zoom, rect.Height / zoom); - Port.Position = new Point((rect.Left - Diagram.Container.Left - pan.X) / zoom, - (rect.Top - Diagram.Container.Top - pan.Y) / zoom); + Port.Position = new Point((rect.Left - BlazorDiagram.Container.Left - pan.X) / zoom, + (rect.Top - BlazorDiagram.Container.Top - pan.Y) / zoom); Port.Initialized = true; _updatingDimensions = false; diff --git a/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs b/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs index a8546103a..e76a322fc 100644 --- a/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs @@ -14,16 +14,16 @@ public partial class SelectionBoxWidget : IDisposable private Size _selectionBoxSize; [CascadingParameter] - public Diagram Diagram { get; set; } + public BlazorDiagram BlazorDiagram { get; set; } [Parameter] public string Background { get; set; } = "rgb(110 159 212 / 25%);"; protected override void OnInitialized() { - Diagram.PointerDown += OnPointerDown; - Diagram.PointerMove += OnPointerMove; - Diagram.PointerUp += OnPointerUp; + BlazorDiagram.PointerDown += OnPointerDown; + BlazorDiagram.PointerMove += OnPointerMove; + BlazorDiagram.PointerUp += OnPointerUp; } private string GenerateStyle() @@ -44,21 +44,21 @@ private void OnPointerMove(Model model, MouseEventArgs e) SetSelectionBoxInformation(e); - var start = Diagram.GetRelativeMousePoint(_initialClientPoint.X, _initialClientPoint.Y); - var end = Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY); + var start = BlazorDiagram.GetRelativeMousePoint(_initialClientPoint.X, _initialClientPoint.Y); + var end = BlazorDiagram.GetRelativeMousePoint(e.ClientX, e.ClientY); (var sX, var sY) = (Math.Min(start.X, end.X), Math.Min(start.Y, end.Y)); (var eX, var 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) + foreach (var node in BlazorDiagram.Nodes) { if (bounds.Overlap(node.GetBounds())) { - Diagram.SelectModel(node, false); + BlazorDiagram.SelectModel(node, false); } else if (node.Selected) { - Diagram.UnselectModel(node); + BlazorDiagram.UnselectModel(node); } } @@ -67,8 +67,8 @@ private void OnPointerMove(Model model, MouseEventArgs e) private void SetSelectionBoxInformation(MouseEventArgs e) { - var start = Diagram.GetRelativePoint(_initialClientPoint.X, _initialClientPoint.Y); - var end = Diagram.GetRelativePoint(e.ClientX, e.ClientY); + var start = BlazorDiagram.GetRelativePoint(_initialClientPoint.X, _initialClientPoint.Y); + var end = BlazorDiagram.GetRelativePoint(e.ClientX, e.ClientY); (var sX, var sY) = (Math.Min(start.X, end.X), Math.Min(start.Y, end.Y)); (var eX, var eY) = (Math.Max(start.X, end.X), Math.Max(start.Y, end.Y)); _selectionBoxTopLeft = new Point(sX, sY); @@ -85,9 +85,9 @@ private void OnPointerUp(Model model, MouseEventArgs e) public void Dispose() { - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; + BlazorDiagram.PointerDown -= OnPointerDown; + BlazorDiagram.PointerMove -= OnPointerMove; + BlazorDiagram.PointerUp -= OnPointerUp; } } } diff --git a/src/Blazor.Diagrams/Options/BlazorDiagramConstraintsOptions.cs b/src/Blazor.Diagrams/Options/BlazorDiagramConstraintsOptions.cs new file mode 100644 index 000000000..6c330ff65 --- /dev/null +++ b/src/Blazor.Diagrams/Options/BlazorDiagramConstraintsOptions.cs @@ -0,0 +1,7 @@ +using Blazor.Diagrams.Core.Options; + +namespace Blazor.Diagrams.Options; + +public class BlazorDiagramConstraintsOptions : DiagramConstraintsOptions +{ +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Options/BlazorDiagramGroupOptions.cs b/src/Blazor.Diagrams/Options/BlazorDiagramGroupOptions.cs new file mode 100644 index 000000000..385508296 --- /dev/null +++ b/src/Blazor.Diagrams/Options/BlazorDiagramGroupOptions.cs @@ -0,0 +1,7 @@ +using Blazor.Diagrams.Core.Options; + +namespace Blazor.Diagrams.Options; + +public class BlazorDiagramGroupOptions : DiagramGroupOptions +{ +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Options/BlazorDiagramLinkOptions.cs b/src/Blazor.Diagrams/Options/BlazorDiagramLinkOptions.cs new file mode 100644 index 000000000..307533490 --- /dev/null +++ b/src/Blazor.Diagrams/Options/BlazorDiagramLinkOptions.cs @@ -0,0 +1,9 @@ +using Blazor.Diagrams.Core.Options; + +namespace Blazor.Diagrams.Options; + +public class BlazorDiagramLinkOptions : DiagramLinkOptions +{ + public string DefaultColor { get; set; } = "black"; + public string DefaultSelectedColor { get; set; } = "rgb(110, 159, 212)"; +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs b/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs new file mode 100644 index 000000000..755978b3a --- /dev/null +++ b/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs @@ -0,0 +1,14 @@ +using Blazor.Diagrams.Core.Options; + +namespace Blazor.Diagrams.Options; + +public class BlazorDiagramOptions : DiagramOptions +{ + public int LinksLayerOrder { get; set; } = 0; + public int NodesLayerOrder { get; set; } = 0; + + public new BlazorDiagramZoomOptions Zoom { get; set; } = new(); + public new BlazorDiagramLinkOptions Links { get; set; } = new(); + public new BlazorDiagramGroupOptions Groups { get; set; } = new(); + public new BlazorDiagramConstraintsOptions Constraints { get; set; } = new(); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Options/BlazorDiagramZoomOptions.cs b/src/Blazor.Diagrams/Options/BlazorDiagramZoomOptions.cs new file mode 100644 index 000000000..711faa1e9 --- /dev/null +++ b/src/Blazor.Diagrams/Options/BlazorDiagramZoomOptions.cs @@ -0,0 +1,7 @@ +using Blazor.Diagrams.Core.Options; + +namespace Blazor.Diagrams.Options; + +public class BlazorDiagramZoomOptions : DiagramZoomOptions +{ +} \ 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 1a7aa7004..76f6c8e89 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -14,7 +14,7 @@ public class DragNewLinkBehaviorTests public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnPort() { // Arrange - var diagram = new DiagramBase(); + 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) @@ -43,7 +43,7 @@ public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnP public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); var factoryCalled = false; diagram.Options.Links.Factory = (d, sp) => @@ -79,7 +79,7 @@ public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); var node = new NodeModel(position: new Point(100, 50)); var linkRefreshed = false; @@ -109,7 +109,7 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoomIsChanged() { // Arrange - var diagram = new DiagramBase(); + 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)); @@ -140,7 +140,7 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoom public void Behavior_ShouldSnapToClosestPortAndRefreshPort_WhenSnappingIsEnabledAndPortIsInRadius() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); diagram.Options.Links.EnableSnapping = true; diagram.Options.Links.SnappingRadius = 60; @@ -180,7 +180,7 @@ public void Behavior_ShouldSnapToClosestPortAndRefreshPort_WhenSnappingIsEnabled public void Behavior_ShouldNotSnapToPort_WhenSnappingIsEnabledAndPortIsNotInRadius() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); diagram.Options.Links.EnableSnapping = true; diagram.Options.Links.SnappingRadius = 50; @@ -215,7 +215,7 @@ public void Behavior_ShouldNotSnapToPort_WhenSnappingIsEnabledAndPortIsNotInRadi public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNotInRadiusAnymore() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); diagram.Options.Links.EnableSnapping = true; diagram.Options.Links.SnappingRadius = 56; @@ -258,7 +258,7 @@ public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNo public void Behavior_ShouldRemoveLink_WhenMouseUpOnCanvas() { // Arrange - var diagram = new DiagramBase(); + 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) @@ -282,7 +282,7 @@ public void Behavior_ShouldRemoveLink_WhenMouseUpOnCanvas() public void Behavior_ShouldRemoveLink_WhenMouseUpOnSamePort() { // Arrange - var diagram = new DiagramBase(); + 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) @@ -306,7 +306,7 @@ public void Behavior_ShouldRemoveLink_WhenMouseUpOnSamePort() public void Behavior_ShouldSetTarget_WhenMouseUp() { // Arrange - var diagram = new DiagramBase(); + 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)); diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs index 704bbfa65..1c6d16738 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs @@ -12,7 +12,7 @@ public class EventsBehaviorTests public void Behavior_ShouldNotTriggerMouseClick_WhenItsRemoved() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.UnregisterBehavior(); var eventTriggered = false; @@ -29,7 +29,7 @@ public void Behavior_ShouldNotTriggerMouseClick_WhenItsRemoved() public void Behavior_ShouldTriggerMouseClick_WhenMouseDownThenUpWithoutMove() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); var eventTriggered = false; // Act @@ -45,7 +45,7 @@ public void Behavior_ShouldTriggerMouseClick_WhenMouseDownThenUpWithoutMove() public void Behavior_ShouldNotTriggerMouseClick_WhenMouseMoves() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); var eventTriggered = false; // Act @@ -62,7 +62,7 @@ public void Behavior_ShouldNotTriggerMouseClick_WhenMouseMoves() public void Behavior_ShouldTriggerMouseDoubleClick_WhenTwoMouseClicksHappenWithinTime() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); var eventTriggered = false; // Act @@ -78,7 +78,7 @@ public void Behavior_ShouldTriggerMouseDoubleClick_WhenTwoMouseClicksHappenWithi public async Task Behavior_ShouldNotTriggerMouseDoubleClick_WhenTimeExceeds500() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); var eventTriggered = false; // Act @@ -95,7 +95,7 @@ public async Task Behavior_ShouldNotTriggerMouseDoubleClick_WhenTimeExceeds500() public void Behavior_ShouldTriggerMouseClick_OnlyWhenMouseDownWasAlsoTriggered_Issue204() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); var eventTriggered = false; // Act diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs index 626b19780..0bc9f3c0e 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs @@ -19,7 +19,7 @@ public class KeyboardShortcutsBehaviorTests public void Behavior_ShouldExecuteAction_WhenCombinationIsPressed(string key, bool ctrl, bool shift, bool alt) { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); var ksb = diagram.GetBehavior()!; var executed = false; @@ -30,7 +30,7 @@ public void Behavior_ShouldExecuteAction_WhenCombinationIsPressed(string key, bo }); // Act - diagram.OnKeyDown(new KeyboardEventArgs(key, key, 0, ctrl, shift, alt)); + diagram.TriggerKeyDown(new KeyboardEventArgs(key, key, 0, ctrl, shift, alt)); // Assert executed.Should().BeTrue(); @@ -40,7 +40,7 @@ public void Behavior_ShouldExecuteAction_WhenCombinationIsPressed(string key, bo public void Behavior_ShouldDoNothing_WhenRemoved() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); var ksb = diagram.GetBehavior()!; diagram.UnregisterBehavior(); var executed = false; @@ -52,7 +52,7 @@ public void Behavior_ShouldDoNothing_WhenRemoved() }); // Act - diagram.OnKeyDown(new KeyboardEventArgs("A", "A", 0, false, false, false)); + diagram.TriggerKeyDown(new KeyboardEventArgs("A", "A", 0, false, false, false)); // Assert executed.Should().BeFalse(); @@ -62,7 +62,7 @@ public void Behavior_ShouldDoNothing_WhenRemoved() public void SetShortcut_ShouldOverride() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); var ksb = diagram.GetBehavior()!; var executed1 = false; var executed2 = false; @@ -80,7 +80,7 @@ public void SetShortcut_ShouldOverride() }); // Act - diagram.OnKeyDown(new KeyboardEventArgs("A", "A", 0, false, false, false)); + diagram.TriggerKeyDown(new KeyboardEventArgs("A", "A", 0, false, false, false)); // Assert executed1.Should().BeFalse(); diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs index d4a88104b..ed6012924 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs @@ -13,7 +13,7 @@ public class KeyboardShortcutsDefaultsTests public async Task DeleteSelection_ShouldNotDeleteModel_WhenItsLocked() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.Nodes.Add(new NodeModel { Selected = true, @@ -32,7 +32,7 @@ public async Task DeleteSelection_ShouldTakeIntoAccountGroupConstraint() { // Arrange var funcCalled = false; - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.Options.Constraints.ShouldDeleteGroup = _ => { funcCalled = true; @@ -56,7 +56,7 @@ public async Task DeleteSelection_ShouldTakeIntoAccountNodeConstraint() { // Arrange var funcCalled = false; - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.Options.Constraints.ShouldDeleteNode = _ => { funcCalled = true; @@ -80,7 +80,7 @@ public async Task DeleteSelection_ShouldTakeIntoAccountLinkConstraint() { // Arrange var funcCalled = false; - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.Options.Constraints.ShouldDeleteLink = _ => { funcCalled = true; @@ -108,7 +108,7 @@ public async Task DeleteSelection_ShouldTakeIntoAccountLinkConstraint() public async Task DeleteSelection_ShouldResultInSingleRefresh() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.Nodes.Add(new NodeModel[] { new NodeModel { Selected = true }, diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs index 46d9d4b8f..b8e7d4da3 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs @@ -12,7 +12,7 @@ public class DiagramTests public void GetScreenPoint_ShouldReturnCorrectPoint() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); // Act diagram.SetZoom(1.234); @@ -29,7 +29,7 @@ public void GetScreenPoint_ShouldReturnCorrectPoint() public void ZoomToFit_ShouldUseSelectedNodesIfAny() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.SetContainer(new Rectangle(new Point(0, 0), new Size(1080, 768))); diagram.Nodes.Add(new NodeModel(new Point(50, 50)) { @@ -50,7 +50,7 @@ public void ZoomToFit_ShouldUseSelectedNodesIfAny() public void ZoomToFit_ShouldUseNodesWhenNoneSelected() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.SetContainer(new Rectangle(new Point(0, 0), new Size(1080, 768))); diagram.Nodes.Add(new NodeModel(new Point(50, 50)) { @@ -70,7 +70,7 @@ public void ZoomToFit_ShouldUseNodesWhenNoneSelected() public void ZoomToFit_ShouldTriggerAppropriateEvents() { // Arrange - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.SetContainer(new Rectangle(new Point(0, 0), new Size(1080, 768))); diagram.Nodes.Add(new NodeModel(new Point(50, 50)) { @@ -98,7 +98,7 @@ public void ZoomToFit_ShouldTriggerAppropriateEvents() [InlineData(0.1)] public void Zoom_ShoulClampToMinimumValue(double zoomValue) { - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); diagram.SetZoom(zoomValue); Assert.Equal(diagram.Zoom, diagram.Options.Zoom.Minimum); } @@ -109,7 +109,7 @@ public void Zoom_ShoulClampToMinimumValue(double zoomValue) [InlineData(-0.00001)] public void Zoom_ThrowExceptionWhenLessThan0(double zoomValue) { - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); Assert.Throws(() => diagram.SetZoom(zoomValue)); } @@ -119,7 +119,7 @@ public void Zoom_ThrowExceptionWhenLessThan0(double zoomValue) [InlineData(-0.00001)] public void ZoomOptions_ThrowExceptionWhenLessThan0(double zoomValue) { - var diagram = new DiagramBase(); + var diagram = new TestDiagram(); Assert.Throws(() => diagram.Options.Zoom.Minimum = zoomValue); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/TestDiagram.cs b/tests/Blazor.Diagrams.Core.Tests/TestDiagram.cs new file mode 100644 index 000000000..1f3355a01 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/TestDiagram.cs @@ -0,0 +1,13 @@ +using Blazor.Diagrams.Core.Options; + +namespace Blazor.Diagrams.Core.Tests; + +public class TestDiagram : Diagram +{ + public TestDiagram() + { + Options = new DiagramOptions(); + } + + public override DiagramOptions Options { get; } +} \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Tests/DiagramTests.cs index 21fce90ba..852355aa6 100644 --- a/tests/Blazor.Diagrams.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Tests/DiagramTests.cs @@ -13,7 +13,7 @@ public class DiagramTests public void GetComponentForModel_ShouldReturnComponentType_WhenModelTypeWasRegistered() { // Arrange - var diagram = new Diagram(); + var diagram = new BlazorDiagram(); diagram.RegisterModelComponent(); // Act @@ -27,7 +27,7 @@ public void GetComponentForModel_ShouldReturnComponentType_WhenModelTypeWasRegis public void GetComponentForModel_ShouldReturnNull_WhenModelTypeWasNotRegistered() { // Arrange - var diagram = new Diagram(); + var diagram = new BlazorDiagram(); // Act var componentType = diagram.GetComponentForModel(); @@ -40,7 +40,7 @@ public void GetComponentForModel_ShouldReturnNull_WhenModelTypeWasNotRegistered( public void GetComponentForModel_ShouldReturnComponentType_WhenInheritedModelTypeWasRegistered() { // Arrange - var diagram = new Diagram(); + var diagram = new BlazorDiagram(); diagram.RegisterModelComponent(); // Act @@ -54,7 +54,7 @@ public void GetComponentForModel_ShouldReturnComponentType_WhenInheritedModelTyp public void GetComponentForModel_ShouldReturnSpecificComponentType_WhenInheritedAndSpecificModelTypeWasRegistered() { // Arrange - var diagram = new Diagram(); + var diagram = new BlazorDiagram(); diagram.RegisterModelComponent(); diagram.RegisterModelComponent(); @@ -69,7 +69,7 @@ public void GetComponentForModel_ShouldReturnSpecificComponentType_WhenInherited public void GetComponentForModel_ShouldReturnNull_WhenCheckSubclassesIsFalse() { // Arrange - var diagram = new Diagram(); + var diagram = new BlazorDiagram(); diagram.RegisterModelComponent(); // Act From c7a410812ef94e6e170ac38d1ea61fff9ef4c6f5 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 28 Aug 2022 23:35:30 +0100 Subject: [PATCH 061/193] Add Model to Changed event & Add new user actions feature --- src/Blazor.Diagrams.Core/Anchors/Anchor.cs | 5 +- src/Blazor.Diagrams.Core/Diagram.cs | 4 + src/Blazor.Diagrams.Core/Layers/LinkLayer.cs | 2 + src/Blazor.Diagrams.Core/Layers/NodeLayer.cs | 2 + src/Blazor.Diagrams.Core/Models/Base/Model.cs | 4 +- src/Blazor.Diagrams.Core/Models/NodeModel.cs | 4 +- .../Positions/BoundsBasedPositionProvider.cs | 7 +- .../Positions/IPositionProvider.cs | 2 +- .../Positions/ShapeAnglePositionProvider.cs | 4 +- .../UserActions/Default/RemoveUserAction.cs | 28 ++++++ .../UserActions/UserAction.cs | 17 ++++ .../UserActions/UserActionsBehavior.cs | 61 ++++++++++++ .../UserActions/UserActionsContainer.cs | 60 ++++++++++++ .../UserActions/UserActionsLayer.cs | 50 ++++++++++ .../UserActions/UserActionsType.cs | 7 ++ src/Blazor.Diagrams/BlazorDiagram.cs | 7 +- .../BlazorDiagramsException.cs | 10 ++ .../Components/DiagramCanvas.razor | 5 +- .../Components/LinkVertexWidget.razor.cs | 3 +- .../Components/NavigatorWidget.razor.cs | 5 +- .../Components/Renderers/GroupRenderer.cs | 3 +- .../Components/Renderers/LinkLabelRenderer.cs | 3 +- .../Components/Renderers/LinkRenderer.cs | 2 +- .../Components/Renderers/NodeRenderer.cs | 24 +++-- .../Components/Renderers/PortRenderer.cs | 3 +- .../UserActions/RemoveUserActionWidget.razor | 24 +++++ .../UserActionsLayerRenderer.razor | 33 +++++++ .../UserActionsLayerRenderer.razor.cs | 88 ++++++++++++++++++ src/Blazor.Diagrams/_Imports.razor | 1 + src/Blazor.Diagrams/wwwroot/style.css | 60 +++++++----- src/Blazor.Diagrams/wwwroot/style.min.css | 2 +- src/Blazor.Diagrams/wwwroot/style.min.css.gz | Bin 406 -> 424 bytes 32 files changed, 472 insertions(+), 58 deletions(-) create mode 100644 src/Blazor.Diagrams.Core/UserActions/Default/RemoveUserAction.cs create mode 100644 src/Blazor.Diagrams.Core/UserActions/UserAction.cs create mode 100644 src/Blazor.Diagrams.Core/UserActions/UserActionsBehavior.cs create mode 100644 src/Blazor.Diagrams.Core/UserActions/UserActionsContainer.cs create mode 100644 src/Blazor.Diagrams.Core/UserActions/UserActionsLayer.cs create mode 100644 src/Blazor.Diagrams.Core/UserActions/UserActionsType.cs create mode 100644 src/Blazor.Diagrams/BlazorDiagramsException.cs create mode 100644 src/Blazor.Diagrams/Components/UserActions/RemoveUserActionWidget.razor create mode 100644 src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor create mode 100644 src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor.cs diff --git a/src/Blazor.Diagrams.Core/Anchors/Anchor.cs b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs index 5904e35e7..0ab53f15c 100644 --- a/src/Blazor.Diagrams.Core/Anchors/Anchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs @@ -37,13 +37,16 @@ public Anchor(NodeModel node, Point? offset = null) }; } - protected static Point? GetClosestPointTo(IEnumerable points, Point point) + protected static Point? GetClosestPointTo(IEnumerable points, Point point) { var minDist = double.MaxValue; Point? minPoint = null; foreach (var pt in points) { + if (pt == null) + continue; + var dist = pt.DistanceTo(point); if (dist < minDist) { diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 210c87630..c1a685ba7 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -10,6 +10,7 @@ using System.Linq; using System.Runtime.CompilerServices; using Blazor.Diagrams.Core.Options; +using Blazor.Diagrams.Core.UserActions; [assembly: InternalsVisibleTo("Blazor.Diagrams")] [assembly: InternalsVisibleTo("Blazor.Diagrams.Tests")] @@ -49,6 +50,7 @@ protected Diagram() Nodes = new NodeLayer(this); Links = new LinkLayer(this); + UserActions = new UserActionsLayer(); RegisterBehavior(new SelectionBehavior(this)); RegisterBehavior(new DragMovablesBehavior(this)); @@ -57,11 +59,13 @@ protected Diagram() RegisterBehavior(new ZoomBehavior(this)); RegisterBehavior(new EventsBehavior(this)); RegisterBehavior(new KeyboardShortcutsBehavior(this)); + RegisterBehavior(new UserActionsBehavior(this)); } public abstract DiagramOptions Options { get; } public NodeLayer Nodes { get; } public LinkLayer Links { get; } + public UserActionsLayer UserActions { get; } public IReadOnlyList Groups => _groups; public Rectangle? Container { get; private set; } public Point Pan { get; private set; } = Point.Zero; diff --git a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs index 823414abc..1acdb5617 100644 --- a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs @@ -34,6 +34,8 @@ protected override void OnItemRemoved(BaseLinkModel link) link.SourceChanged -= OnLinkSourceChanged; link.TargetChanged -= OnLinkTargetChanged; + + Diagram.UserActions.RemoveFor(link); } private void OnLinkSourceChanged(BaseLinkModel link, Anchor old, Anchor @new) diff --git a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs index 7a93e421f..61011df69 100644 --- a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs @@ -14,9 +14,11 @@ public override void Remove(NodeModel node) protected override void OnItemRemoved(NodeModel node) { + // Todo: batch Diagram.Links.Remove(node.PortLinks.ToList()); Diagram.Links.Remove(node.Links.ToList()); node.Group?.RemoveChild(node); + Diagram.UserActions.RemoveFor(node); } } } diff --git a/src/Blazor.Diagrams.Core/Models/Base/Model.cs b/src/Blazor.Diagrams.Core/Models/Base/Model.cs index d2656ff2a..cca4356ec 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/Model.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/Model.cs @@ -11,11 +11,11 @@ public Model(string id) Id = id; } - public event Action? Changed; + public event Action? Changed; public string Id { get; } public bool Locked { get; set; } - public virtual void Refresh() => Changed?.Invoke(); + public virtual void Refresh() => Changed?.Invoke(this); } } diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs index 863f146ba..d97127331 100644 --- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs +++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs @@ -8,8 +8,8 @@ namespace Blazor.Diagrams.Core.Models { public class NodeModel : MovableModel, IHasBounds, IHasShape { - private readonly List _ports = new List(); - private readonly List _links = new List(); + private readonly List _ports = new(); + private readonly List _links = new(); private Size? _size; public event Action? SizeChanged; diff --git a/src/Blazor.Diagrams.Core/Positions/BoundsBasedPositionProvider.cs b/src/Blazor.Diagrams.Core/Positions/BoundsBasedPositionProvider.cs index 1fca0f40f..ae7e8a88e 100644 --- a/src/Blazor.Diagrams.Core/Positions/BoundsBasedPositionProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/BoundsBasedPositionProvider.cs @@ -18,12 +18,15 @@ public BoundsBasedPositionProvider(double x, double y, double offsetX = 0, doubl public double OffsetX { get; } public double OffsetY { get; } - public Point GetPosition(Model model) + public Point? GetPosition(Model model) { if (model is not IHasBounds ihb) throw new DiagramsException("BoundsBasedPositionProvider requires an IHasBounds model"); - var bounds = ihb.GetBounds()!; + var bounds = ihb.GetBounds(); + if (bounds == null) + return null; + return new Point(bounds.Left + X * bounds.Width + OffsetX, bounds.Top + Y * bounds.Height + OffsetY); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Positions/IPositionProvider.cs b/src/Blazor.Diagrams.Core/Positions/IPositionProvider.cs index 5ee284abc..5321d9397 100644 --- a/src/Blazor.Diagrams.Core/Positions/IPositionProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/IPositionProvider.cs @@ -5,5 +5,5 @@ namespace Blazor.Diagrams.Core.Positions; public interface IPositionProvider { - public Point GetPosition(Model model); + public Point? GetPosition(Model model); } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Positions/ShapeAnglePositionProvider.cs b/src/Blazor.Diagrams.Core/Positions/ShapeAnglePositionProvider.cs index cba061aa2..acbf02142 100644 --- a/src/Blazor.Diagrams.Core/Positions/ShapeAnglePositionProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/ShapeAnglePositionProvider.cs @@ -16,12 +16,12 @@ public ShapeAnglePositionProvider(double angle, double offsetX = 0, double offse public double OffsetX { get; } public double OffsetY { get; } - public Point GetPosition(Model model) + public Point? GetPosition(Model model) { if (model is not IHasShape ihs) throw new DiagramsException("ShapeAnglePositionProvider requires an IHasShape model"); var shape = ihs.GetShape(); - return shape.GetPointAtAngle(Angle)?.Add(OffsetX, OffsetY) ?? Point.Zero; + return shape.GetPointAtAngle(Angle)?.Add(OffsetX, OffsetY); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/UserActions/Default/RemoveUserAction.cs b/src/Blazor.Diagrams.Core/UserActions/Default/RemoveUserAction.cs new file mode 100644 index 000000000..303d2a3c4 --- /dev/null +++ b/src/Blazor.Diagrams.Core/UserActions/Default/RemoveUserAction.cs @@ -0,0 +1,28 @@ +using System.Threading.Tasks; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Positions; + +namespace Blazor.Diagrams.Core.UserActions.Default; + +public class RemoveUserAction : UserAction +{ + public RemoveUserAction(double x, double y, double offsetX = 0, double offsetY = 0) + : base(new BoundsBasedPositionProvider(x, y, offsetX, offsetY)) + { + } + + public RemoveUserAction(IPositionProvider positionProvider) : base(positionProvider) + { + } + + public override ValueTask Execute(Diagram diagram, Model model) + { + if (model is NodeModel node) + { + diagram.Nodes.Remove(node); + } + + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/UserActions/UserAction.cs b/src/Blazor.Diagrams.Core/UserActions/UserAction.cs new file mode 100644 index 000000000..523e3eea7 --- /dev/null +++ b/src/Blazor.Diagrams.Core/UserActions/UserAction.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Positions; + +namespace Blazor.Diagrams.Core.UserActions; + +public abstract class UserAction +{ + protected UserAction(IPositionProvider positionProvider) + { + PositionProvider = positionProvider; + } + + public IPositionProvider PositionProvider { get; } + + public abstract ValueTask Execute(Diagram diagram, Model model); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/UserActions/UserActionsBehavior.cs b/src/Blazor.Diagrams.Core/UserActions/UserActionsBehavior.cs new file mode 100644 index 000000000..200ebc408 --- /dev/null +++ b/src/Blazor.Diagrams.Core/UserActions/UserActionsBehavior.cs @@ -0,0 +1,61 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.UserActions; + +public class UserActionsBehavior : Behavior +{ + public UserActionsBehavior(Diagram diagram) : base(diagram) + { + Diagram.PointerEnter += OnPointerEnter; + Diagram.PointerLeave += OnPointerLeave; + Diagram.SelectionChanged += OnSelectionChanged; + } + + private void OnSelectionChanged(SelectableModel model) + { + var userActions = Diagram.UserActions.GetFor(model); + if (userActions is not { Type: UserActionsType.OnSelection }) + return; + + if (model.Selected) + { + userActions.Show(); + } + else + { + userActions.Hide(); + } + } + + private void OnPointerEnter(Model? model, PointerEventArgs e) + { + if (model == null) + return; + + var userActions = Diagram.UserActions.GetFor(model); + if (userActions is not { Type: UserActionsType.OnHover }) + return; + + userActions.Show(); + } + + private void OnPointerLeave(Model? model, PointerEventArgs e) + { + if (model == null) + return; + + var userActions = Diagram.UserActions.GetFor(model); + if (userActions is not { Type: UserActionsType.OnHover }) + return; + + userActions.Hide(); + } + + public override void Dispose() + { + Diagram.PointerEnter -= OnPointerEnter; + Diagram.PointerLeave -= OnPointerLeave; + Diagram.SelectionChanged -= OnSelectionChanged; + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/UserActions/UserActionsContainer.cs b/src/Blazor.Diagrams.Core/UserActions/UserActionsContainer.cs new file mode 100644 index 000000000..a10d828ae --- /dev/null +++ b/src/Blazor.Diagrams.Core/UserActions/UserActionsContainer.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.UserActions; + +public class UserActionsContainer : IReadOnlyList +{ + private readonly List _actions = new(4); + + public event Action? Changed; + + public UserActionsContainer(Model model, UserActionsType type = UserActionsType.OnSelection) + { + Model = model; + Type = type; + } + + public Model Model { get; } + public UserActionsType Type { get; set; } + public bool Visible { get; private set; } + + public void Show() + { + if (Visible) + return; + + Visible = true; + Changed?.Invoke(Model); + } + + public void Hide() + { + if (!Visible) + return; + + Visible = false; + Changed?.Invoke(Model); + } + + public void Add(UserAction action) + { + _actions.Add(action); + Changed?.Invoke(Model); + } + + public void Remove(UserAction action) + { + if (_actions.Remove(action)) + { + Changed?.Invoke(Model); + } + } + + public int Count => _actions.Count; + public UserAction this[int index] => _actions[index]; + public IEnumerator GetEnumerator() => _actions.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _actions.GetEnumerator(); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/UserActions/UserActionsLayer.cs b/src/Blazor.Diagrams.Core/UserActions/UserActionsLayer.cs new file mode 100644 index 000000000..352b477fd --- /dev/null +++ b/src/Blazor.Diagrams.Core/UserActions/UserActionsLayer.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.UserActions; + +public class UserActionsLayer +{ + private readonly Dictionary _containers; + + public event Action? Changed; + + public UserActionsLayer() + { + _containers = new Dictionary(); + } + + public IReadOnlyCollection Models => _containers.Keys; + + public UserActionsContainer AddFor(Model model, UserActionsType type = UserActionsType.OnSelection) + { + if (_containers.ContainsKey(model)) + return _containers[model]; + + var container = new UserActionsContainer(model, type); + container.Changed += OnChanged; + model.Changed += OnChanged; + _containers.Add(model, container); + return container; + } + + public UserActionsContainer? GetFor(Model model) + { + return _containers.TryGetValue(model, out var container) ? container : null; + } + + public bool RemoveFor(Model model) + { + if (!_containers.TryGetValue(model, out var container)) + return false; + + container.Changed -= OnChanged; + model.Changed -= OnChanged; + _containers.Remove(model); + Changed?.Invoke(model); + return true; + } + + private void OnChanged(Model cause) => Changed?.Invoke(cause); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/UserActions/UserActionsType.cs b/src/Blazor.Diagrams.Core/UserActions/UserActionsType.cs new file mode 100644 index 000000000..9a2445403 --- /dev/null +++ b/src/Blazor.Diagrams.Core/UserActions/UserActionsType.cs @@ -0,0 +1,7 @@ +namespace Blazor.Diagrams.Core.UserActions; + +public enum UserActionsType +{ + OnHover, + OnSelection +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/BlazorDiagram.cs b/src/Blazor.Diagrams/BlazorDiagram.cs index 654a2d724..077e58164 100644 --- a/src/Blazor.Diagrams/BlazorDiagram.cs +++ b/src/Blazor.Diagrams/BlazorDiagram.cs @@ -3,6 +3,8 @@ using Microsoft.AspNetCore.Components; using System; using System.Collections.Generic; +using Blazor.Diagrams.Components.UserActions; +using Blazor.Diagrams.Core.UserActions.Default; using Blazor.Diagrams.Options; namespace Blazor.Diagrams @@ -13,7 +15,10 @@ public class BlazorDiagram : Diagram public BlazorDiagram(BlazorDiagramOptions? options = null) { - _componentByModelMapping = new Dictionary(); + _componentByModelMapping = new Dictionary + { + [typeof(RemoveUserAction)] = typeof(RemoveUserActionWidget) + }; Options = options ?? new BlazorDiagramOptions(); } diff --git a/src/Blazor.Diagrams/BlazorDiagramsException.cs b/src/Blazor.Diagrams/BlazorDiagramsException.cs new file mode 100644 index 000000000..63e815ccf --- /dev/null +++ b/src/Blazor.Diagrams/BlazorDiagramsException.cs @@ -0,0 +1,10 @@ +using System; + +namespace Blazor.Diagrams; + +public class BlazorDiagramsException : Exception +{ + public BlazorDiagramsException(string? message) : base(message) + { + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor b/src/Blazor.Diagrams/Components/DiagramCanvas.razor index 12dff5fcc..11923c1b3 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor @@ -25,7 +25,8 @@ { } - + + @* Nodes *@ @@ -40,6 +41,8 @@ { } + +
    @Widgets diff --git a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs index 520981dc9..9cc31f941 100644 --- a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using System; +using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Components { @@ -36,7 +37,7 @@ protected override bool ShouldRender() return true; } - private void OnVertexChanged() + private void OnVertexChanged(Model _) { _shouldRender = true; InvokeAsync(StateHasChanged); diff --git a/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs b/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs index a4ad51339..471135591 100644 --- a/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Components; using System; using System.Linq; +using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Components { @@ -40,7 +41,7 @@ protected override void OnParametersSet() } - private void Diagram_Changed() => Refresh(); + private void Diagram_Changed() => Refresh(null); private void Diagram_NodesAdded(NodeModel node) { @@ -56,7 +57,7 @@ private void Diagram_NodesRemoved(NodeModel node) private void Diagram_GroupRemoved(GroupModel group) => group.Changed -= Refresh; - private void Refresh() + private void Refresh(Model? _) { if (BlazorDiagram != null) { diff --git a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs index 95e195bdb..5200fec68 100644 --- a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Components.Web; using System; using System.Text; +using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Components.Renderers { @@ -66,7 +67,7 @@ protected override void OnAfterRender(bool firstRender) } } - private void OnGroupChanged() + private void OnGroupChanged(Model _) { _shouldRender = true; InvokeAsync(StateHasChanged); diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs index 5e52946d1..dbcb5dbaf 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs @@ -5,6 +5,7 @@ using SvgPathProperties; using System; using System.Linq; +using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Components.Renderers { @@ -37,7 +38,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.CloseComponent(); } - private void OnLabelChanged() => InvokeAsync(StateHasChanged); + private void OnLabelChanged(Model _) => InvokeAsync(StateHasChanged); private Point? FindPosition() { diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs index 3d55aad65..74d8b64e9 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs @@ -51,7 +51,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) protected override void OnAfterRender(bool firstRender) => _shouldRender = false; - private void OnLinkChanged() + private void OnLinkChanged(Model _) { _shouldRender = true; InvokeAsync(StateHasChanged); diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index 5b404b914..6f53700bc 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -10,6 +10,8 @@ using System; using System.Text; using System.Threading.Tasks; +using Blazor.Diagrams.Core; +using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Components.Renderers { @@ -36,7 +38,7 @@ public void Dispose() BlazorDiagram.PanChanged -= CheckVisibility; BlazorDiagram.ZoomChanged -= CheckVisibility; BlazorDiagram.ContainerChanged -= CheckVisibility; - Node.Changed -= ReRender; + Node.Changed -= OnNodeChanged; if (_element.Id != null) _ = JsRuntime.UnobserveResizes(_element); @@ -69,7 +71,7 @@ protected override void OnInitialized() BlazorDiagram.PanChanged += CheckVisibility; BlazorDiagram.ZoomChanged += CheckVisibility; BlazorDiagram.ContainerChanged += CheckVisibility; - Node.Changed += ReRender; + Node.Changed += OnNodeChanged; } protected override void OnParametersSet() @@ -81,7 +83,8 @@ protected override void OnParametersSet() protected override bool ShouldRender() { - if (!_shouldRender) return false; + if (!_shouldRender) + return false; _shouldRender = false; return true; @@ -121,6 +124,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.OpenComponent(11, componentType); builder.AddAttribute(12, "Node", Node); builder.CloseComponent(); + builder.CloseElement(); } @@ -160,6 +164,8 @@ private void CheckVisibility() } } + private void OnNodeChanged(Model _) => ReRender(); + private void ReRender() { _shouldRender = true; @@ -170,16 +176,8 @@ private void ReRender() private void OnPointerUp(PointerEventArgs e) => BlazorDiagram.TriggerPointerUp(Node, e.ToCore()); - private void OnMouseEnter(MouseEventArgs e) - { - Console.WriteLine("On mouse enter"); - BlazorDiagram.TriggerPointerLeave(Node, e.ToCore()); - } + private void OnMouseEnter(MouseEventArgs e) => BlazorDiagram.TriggerPointerEnter(Node, e.ToCore()); - private void OnMouseLeave(MouseEventArgs e) - { - Console.WriteLine("On mouse leave"); - BlazorDiagram.TriggerPointerEnter(Node, e.ToCore()); - } + private void OnMouseLeave(MouseEventArgs e) => BlazorDiagram.TriggerPointerLeave(Node, e.ToCore()); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs index 007867203..05eaaa77c 100644 --- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs @@ -8,6 +8,7 @@ using Blazor.Diagrams.Core.Geometry; using Microsoft.AspNetCore.Components.Rendering; using System.Linq; +using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Models; namespace Blazor.Diagrams.Components.Renderers @@ -132,7 +133,7 @@ private async Task UpdateDimensions() } } - private async void OnPortChanged() + private async void OnPortChanged(Model _) { // If an update is ongoing and the port is refreshed again, // it's highly likely the port needs to be refreshed (e.g. link added) diff --git a/src/Blazor.Diagrams/Components/UserActions/RemoveUserActionWidget.razor b/src/Blazor.Diagrams/Components/UserActions/RemoveUserActionWidget.razor new file mode 100644 index 000000000..ee87b56ed --- /dev/null +++ b/src/Blazor.Diagrams/Components/UserActions/RemoveUserActionWidget.razor @@ -0,0 +1,24 @@ +@using Blazor.Diagrams.Core.UserActions +@using Blazor.Diagrams.Core.Models.Base + +@if (Model is SvgNodeModel) +{ + + +} +else +{ + + + + +} + +@code +{ + [Parameter] + public UserAction Action { get; set; } = null!; + + [Parameter] + public Model Model { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor b/src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor new file mode 100644 index 000000000..66b337db5 --- /dev/null +++ b/src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor @@ -0,0 +1,33 @@ +@foreach (var model in BlazorDiagram.UserActions.Models) +{ + var userActions = BlazorDiagram.UserActions.GetFor(model)!; + if (!userActions.Visible || userActions.Count == 0) + continue; + + if (Svg && model is SvgNodeModel or SvgGroupModel) + { + + } + else if (!Svg && model is not SvgNodeModel && model is not SvgGroupModel) + { + + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor.cs b/src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor.cs new file mode 100644 index 000000000..240e78e92 --- /dev/null +++ b/src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor.cs @@ -0,0 +1,88 @@ +using System; +using System.Threading.Tasks; +using Blazor.Diagrams.Core.Extensions; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.UserActions; +using Blazor.Diagrams.Models; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; + +namespace Blazor.Diagrams.Components.UserActions; + +public partial class UserActionsLayerRenderer : IDisposable +{ + private bool _shouldRender; + + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; + + [Parameter] public bool Svg { get; set; } + + protected override void OnInitialized() + { + BlazorDiagram.UserActions.Changed += OnUserActionsChanged; + } + + protected override bool ShouldRender() + { + if (!_shouldRender) + return false; + + _shouldRender = false; + return true; + } + + private void OnUserActionsChanged(Model cause) + { + if ((Svg && cause is SvgNodeModel or SvgGroupModel) || + (!Svg && cause is not SvgNodeModel && cause is not SvgGroupModel)) + { + _shouldRender = true; + InvokeAsync(StateHasChanged); + } + } + + private RenderFragment RenderUserAction(Model model, UserAction action, Point position, bool svg) + { + var componentType = BlazorDiagram.GetComponentForModel(action.GetType()); + if (componentType == null) + throw new BlazorDiagramsException( + $"A component couldn't be found for the user action {action.GetType().Name}"); + + return builder => + { + builder.OpenElement(0, svg ? "g" : "div"); + builder.AddAttribute(1, "class", $"user-action {action.GetType().Name}"); + if (svg) + { + builder.AddAttribute(2, "transform", + $"translate({position.X.ToInvariantString()} {position.Y.ToInvariantString()})"); + } + else + { + builder.AddAttribute(2, "style", + $"top: {position.Y.ToInvariantString()}px; left: {position.X.ToInvariantString()}px"); + } + + builder.AddAttribute(3, "onpointerdown", + EventCallback.Factory.Create(this, e => OnPointerDown(e, model, action))); + builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); + + builder.OpenComponent(5, componentType); + builder.AddAttribute(6, "Action", action); + builder.AddAttribute(7, "Model", model); + builder.CloseComponent(); + builder.CloseElement(); + }; + } + + private async Task OnPointerDown(PointerEventArgs e, Model model, UserAction action) + { + await action.Execute(BlazorDiagram, model); + } + + public void Dispose() + { + BlazorDiagram.UserActions.Changed -= OnUserActionsChanged; + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/_Imports.razor b/src/Blazor.Diagrams/_Imports.razor index 76771ae75..cfc0a4fec 100644 --- a/src/Blazor.Diagrams/_Imports.razor +++ b/src/Blazor.Diagrams/_Imports.razor @@ -5,3 +5,4 @@ @using Blazor.Diagrams.Components.Renderers @using Blazor.Diagrams.Core.Geometry; @using Blazor.Diagrams.Models; +@using Blazor.Diagrams.Components.UserActions; diff --git a/src/Blazor.Diagrams/wwwroot/style.css b/src/Blazor.Diagrams/wwwroot/style.css index 5b9228c17..d90d4f149 100644 --- a/src/Blazor.Diagrams/wwwroot/style.css +++ b/src/Blazor.Diagrams/wwwroot/style.css @@ -17,8 +17,8 @@ position: absolute; pointer-events: none; -webkit-transform-origin: 0px 0px; - -ms-transform-origin: 0px 0px; - transform-origin: 0px 0px; + -ms-transform-origin: 0px 0px; + transform-origin: 0px 0px; width: 100%; height: 100%; overflow: visible; @@ -28,8 +28,8 @@ position: absolute; pointer-events: none; -webkit-transform-origin: 0px 0px; - -ms-transform-origin: 0px 0px; - transform-origin: 0px 0px; + -ms-transform-origin: 0px 0px; + transform-origin: 0px 0px; width: 100%; height: 100%; overflow: visible; @@ -38,53 +38,63 @@ .node { position: absolute; -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; cursor: move; pointer-events: all; } - .node.locked { - cursor: pointer; - } +.node.locked { + cursor: pointer; +} .link { pointer-events: visiblePainted; cursor: pointer; } - .link path.selection-helper:hover { - stroke-opacity: 0.05; - } +.link path.selection-helper:hover { + stroke-opacity: 0.05; +} .diagram-navigator { z-index: 10; } - .diagram-navigator .current-view { - position: absolute; - border: 2px solid black; - } +.diagram-navigator .current-view { + position: absolute; + border: 2px solid black; +} .group { position: absolute; -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; cursor: move; pointer-events: all; } - .group .children { - position: absolute; - overflow: visible; - pointer-events: none; - } +.group .children { + position: absolute; + overflow: visible; + pointer-events: none; +} .link foreignObject { overflow: visible; pointer-events: none; } + +div.user-action { + position: absolute; +} + +.user-action { + pointer-events: all; + cursor: pointer; +} + /*# sourceMappingURL=wwwroot\style.css.map */ \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/style.min.css b/src/Blazor.Diagrams/wwwroot/style.min.css index 5757db08f..0ba94bad3 100644 --- a/src/Blazor.Diagrams/wwwroot/style.min.css +++ b/src/Blazor.Diagrams/wwwroot/style.min.css @@ -1 +1 @@ -.diagram-canvas{width:100%;height:100%;position:relative;outline:none;overflow:hidden;cursor:-webkit-grab;cursor:grab;touch-action:none;}.diagram-svg-layer,.diagram-html-layer{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.html-layer,.svg-layer{position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.node{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.node.locked{cursor:pointer;}.link{pointer-events:visiblePainted;cursor:pointer;}.link path.selection-helper:hover{stroke-opacity:.05;}.diagram-navigator{z-index:10;}.diagram-navigator .current-view{position:absolute;border:2px solid #000;}.group{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.group .children{position:absolute;overflow:visible;pointer-events:none;}.link foreignObject{overflow:visible;pointer-events:none;} \ No newline at end of file +.diagram-canvas{width:100%;height:100%;position:relative;outline:none;overflow:hidden;cursor:-webkit-grab;cursor:grab;touch-action:none;}.diagram-svg-layer,.diagram-html-layer{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.html-layer,.svg-layer{position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.node{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.node.locked{cursor:pointer;}.link{pointer-events:visiblePainted;cursor:pointer;}.link path.selection-helper:hover{stroke-opacity:.05;}.diagram-navigator{z-index:10;}.diagram-navigator .current-view{position:absolute;border:2px solid #000;}.group{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.group .children{position:absolute;overflow:visible;pointer-events:none;}.link foreignObject{overflow:visible;pointer-events:none;}div.user-action{position:absolute;}.user-action{pointer-events:all;cursor:pointer;} \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/style.min.css.gz b/src/Blazor.Diagrams/wwwroot/style.min.css.gz index 14658b68ae0b6b37dff2ff882529101a571ce723..5e97c64e384c0a936a3eb1eacbcec3a9dfbad655 100644 GIT binary patch literal 424 zcmV;Z0ayMXiwFP!000003eA*FZrm^og|A|u=q@tU0@>JyXs;k!9E%xQG$h)S#K_6I z=#g@T(m&%AGb+$!R+b+v^5esU-+%osEt)QxUbQAnCe0VL?5b~Cf2|#0=Xg60l#r1^ z6~PC{CUhfm{vx6sHg4!xCaGF*50$;EhgT_P#T3qP7CA(;Q~rp2jTxwn5thT!d{ Sez(SL1pWYL|JCvR1ONb69?Zo6 literal 406 zcmV;H0crjpiwFP!000003eA#1Zrm^oMXzF@=q@sp0@=uiXs;k!9E%xQG$h)S#OTSg z$dPh|(lZ{X$f!VTH~CUQX!Pn|rOlHy*I}d^wMlk&&WF z5G>;i4NV*{f{7I6XNGhLbTS^DgV=Oa%9M;cL3>12GN8R`=QGpPd1bqE%X5d{J4l{; z73?#lyIt};gp0N?jYc;C4lJK^2GmVU%rwYh$FyyxFfoXNks&FVA#%RVx?*v%F&`)m zilh@pqbuF0A(sn9@gH&RdCZux4XeEm4|nx$arwf)ksK8Gt}Ig)$P58G-XI5hDXIBZ zyb-qD7zE>U05*gTrVi8{!7bb6?Vc2650A^IXPb{-c2!+dd{d2 Date: Mon, 29 Aug 2022 12:17:22 +0100 Subject: [PATCH 062/193] Rename user actions to Controls and add BoundaryControl Layers also return the added item (node/link) --- src/Blazor.Diagrams.Core/Controls/Control.cs | 9 +++ .../ControlsBehavior.cs} | 26 ++++----- .../ControlsContainer.cs} | 18 +++--- .../ControlsLayer.cs} | 16 +++--- .../Controls/ControlsType.cs | 7 +++ .../Controls/Default/BoundaryControl.cs | 22 ++++++++ .../Default/RemoveControl.cs} | 10 ++-- .../ExecutableControl.cs} | 11 ++-- src/Blazor.Diagrams.Core/Diagram.cs | 8 +-- src/Blazor.Diagrams.Core/Layers/BaseLayer.cs | 3 +- src/Blazor.Diagrams.Core/Layers/LinkLayer.cs | 2 +- src/Blazor.Diagrams.Core/Layers/NodeLayer.cs | 2 +- .../UserActions/UserActionsType.cs | 7 --- src/Blazor.Diagrams/BlazorDiagram.cs | 7 ++- .../Controls/BoundaryControlWidget.razor | 35 ++++++++++++ .../Controls/ControlsLayerRenderer.razor | 33 +++++++++++ .../ControlsLayerRenderer.razor.cs} | 52 ++++++++++-------- .../RemoveControlWidget.razor} | 5 +- .../Components/DiagramCanvas.razor | 4 +- .../Components/LinkWidget.razor | 16 ------ .../UserActionsLayerRenderer.razor | 33 ----------- .../Extensions/ModelExtensions.cs | 12 ++++ src/Blazor.Diagrams/_Imports.razor | 6 +- src/Blazor.Diagrams/wwwroot/style.css | 4 +- src/Blazor.Diagrams/wwwroot/style.min.css | 2 +- src/Blazor.Diagrams/wwwroot/style.min.css.gz | Bin 424 -> 430 bytes .../Behaviors/DragNewLinkBehaviorTests.cs | 10 ++-- .../Components/LinkVertexWidgetTests.cs | 2 +- 28 files changed, 218 insertions(+), 144 deletions(-) create mode 100644 src/Blazor.Diagrams.Core/Controls/Control.cs rename src/Blazor.Diagrams.Core/{UserActions/UserActionsBehavior.cs => Controls/ControlsBehavior.cs} (59%) rename src/Blazor.Diagrams.Core/{UserActions/UserActionsContainer.cs => Controls/ControlsContainer.cs} (62%) rename src/Blazor.Diagrams.Core/{UserActions/UserActionsLayer.cs => Controls/ControlsLayer.cs} (66%) create mode 100644 src/Blazor.Diagrams.Core/Controls/ControlsType.cs create mode 100644 src/Blazor.Diagrams.Core/Controls/Default/BoundaryControl.cs rename src/Blazor.Diagrams.Core/{UserActions/Default/RemoveUserAction.cs => Controls/Default/RemoveControl.cs} (62%) rename src/Blazor.Diagrams.Core/{UserActions/UserAction.cs => Controls/ExecutableControl.cs} (50%) delete mode 100644 src/Blazor.Diagrams.Core/UserActions/UserActionsType.cs create mode 100644 src/Blazor.Diagrams/Components/Controls/BoundaryControlWidget.razor create mode 100644 src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor rename src/Blazor.Diagrams/Components/{UserActions/UserActionsLayerRenderer.razor.cs => Controls/ControlsLayerRenderer.razor.cs} (54%) rename src/Blazor.Diagrams/Components/{UserActions/RemoveUserActionWidget.razor => Controls/RemoveControlWidget.razor} (78%) delete mode 100644 src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor create mode 100644 src/Blazor.Diagrams/Extensions/ModelExtensions.cs diff --git a/src/Blazor.Diagrams.Core/Controls/Control.cs b/src/Blazor.Diagrams.Core/Controls/Control.cs new file mode 100644 index 000000000..8af4e470c --- /dev/null +++ b/src/Blazor.Diagrams.Core/Controls/Control.cs @@ -0,0 +1,9 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Controls; + +public abstract class Control +{ + public abstract Point? GetPosition(Model model); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/UserActions/UserActionsBehavior.cs b/src/Blazor.Diagrams.Core/Controls/ControlsBehavior.cs similarity index 59% rename from src/Blazor.Diagrams.Core/UserActions/UserActionsBehavior.cs rename to src/Blazor.Diagrams.Core/Controls/ControlsBehavior.cs index 200ebc408..05148b5e6 100644 --- a/src/Blazor.Diagrams.Core/UserActions/UserActionsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Controls/ControlsBehavior.cs @@ -1,11 +1,11 @@ using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core.UserActions; +namespace Blazor.Diagrams.Core.Controls; -public class UserActionsBehavior : Behavior +public class ControlsBehavior : Behavior { - public UserActionsBehavior(Diagram diagram) : base(diagram) + public ControlsBehavior(Diagram diagram) : base(diagram) { Diagram.PointerEnter += OnPointerEnter; Diagram.PointerLeave += OnPointerLeave; @@ -14,17 +14,17 @@ public UserActionsBehavior(Diagram diagram) : base(diagram) private void OnSelectionChanged(SelectableModel model) { - var userActions = Diagram.UserActions.GetFor(model); - if (userActions is not { Type: UserActionsType.OnSelection }) + var controls = Diagram.Controls.GetFor(model); + if (controls is not { Type: ControlsType.OnSelection }) return; if (model.Selected) { - userActions.Show(); + controls.Show(); } else { - userActions.Hide(); + controls.Hide(); } } @@ -33,11 +33,11 @@ private void OnPointerEnter(Model? model, PointerEventArgs e) if (model == null) return; - var userActions = Diagram.UserActions.GetFor(model); - if (userActions is not { Type: UserActionsType.OnHover }) + var controls = Diagram.Controls.GetFor(model); + if (controls is not { Type: ControlsType.OnHover }) return; - userActions.Show(); + controls.Show(); } private void OnPointerLeave(Model? model, PointerEventArgs e) @@ -45,11 +45,11 @@ private void OnPointerLeave(Model? model, PointerEventArgs e) if (model == null) return; - var userActions = Diagram.UserActions.GetFor(model); - if (userActions is not { Type: UserActionsType.OnHover }) + var controls = Diagram.Controls.GetFor(model); + if (controls is not { Type: ControlsType.OnHover }) return; - userActions.Hide(); + controls.Hide(); } public override void Dispose() diff --git a/src/Blazor.Diagrams.Core/UserActions/UserActionsContainer.cs b/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs similarity index 62% rename from src/Blazor.Diagrams.Core/UserActions/UserActionsContainer.cs rename to src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs index a10d828ae..83a98660c 100644 --- a/src/Blazor.Diagrams.Core/UserActions/UserActionsContainer.cs +++ b/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs @@ -3,22 +3,22 @@ using System.Collections.Generic; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core.UserActions; +namespace Blazor.Diagrams.Core.Controls; -public class UserActionsContainer : IReadOnlyList +public class ControlsContainer : IReadOnlyList { - private readonly List _actions = new(4); + private readonly List _actions = new(4); public event Action? Changed; - public UserActionsContainer(Model model, UserActionsType type = UserActionsType.OnSelection) + public ControlsContainer(Model model, ControlsType type = ControlsType.OnSelection) { Model = model; Type = type; } public Model Model { get; } - public UserActionsType Type { get; set; } + public ControlsType Type { get; set; } public bool Visible { get; private set; } public void Show() @@ -39,13 +39,13 @@ public void Hide() Changed?.Invoke(Model); } - public void Add(UserAction action) + public void Add(Control action) { _actions.Add(action); Changed?.Invoke(Model); } - public void Remove(UserAction action) + public void Remove(Control action) { if (_actions.Remove(action)) { @@ -54,7 +54,7 @@ public void Remove(UserAction action) } public int Count => _actions.Count; - public UserAction this[int index] => _actions[index]; - public IEnumerator GetEnumerator() => _actions.GetEnumerator(); + public Control this[int index] => _actions[index]; + public IEnumerator GetEnumerator() => _actions.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _actions.GetEnumerator(); } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/UserActions/UserActionsLayer.cs b/src/Blazor.Diagrams.Core/Controls/ControlsLayer.cs similarity index 66% rename from src/Blazor.Diagrams.Core/UserActions/UserActionsLayer.cs rename to src/Blazor.Diagrams.Core/Controls/ControlsLayer.cs index 352b477fd..2a3cbd0f7 100644 --- a/src/Blazor.Diagrams.Core/UserActions/UserActionsLayer.cs +++ b/src/Blazor.Diagrams.Core/Controls/ControlsLayer.cs @@ -2,34 +2,34 @@ using System.Collections.Generic; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core.UserActions; +namespace Blazor.Diagrams.Core.Controls; -public class UserActionsLayer +public class ControlsLayer { - private readonly Dictionary _containers; + private readonly Dictionary _containers; public event Action? Changed; - public UserActionsLayer() + public ControlsLayer() { - _containers = new Dictionary(); + _containers = new Dictionary(); } public IReadOnlyCollection Models => _containers.Keys; - public UserActionsContainer AddFor(Model model, UserActionsType type = UserActionsType.OnSelection) + public ControlsContainer AddFor(Model model, ControlsType type = ControlsType.OnSelection) { if (_containers.ContainsKey(model)) return _containers[model]; - var container = new UserActionsContainer(model, type); + var container = new ControlsContainer(model, type); container.Changed += OnChanged; model.Changed += OnChanged; _containers.Add(model, container); return container; } - public UserActionsContainer? GetFor(Model model) + public ControlsContainer? GetFor(Model model) { return _containers.TryGetValue(model, out var container) ? container : null; } diff --git a/src/Blazor.Diagrams.Core/Controls/ControlsType.cs b/src/Blazor.Diagrams.Core/Controls/ControlsType.cs new file mode 100644 index 000000000..5997e72c9 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Controls/ControlsType.cs @@ -0,0 +1,7 @@ +namespace Blazor.Diagrams.Core.Controls; + +public enum ControlsType +{ + OnHover, + OnSelection +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Controls/Default/BoundaryControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/BoundaryControl.cs new file mode 100644 index 000000000..4af8502f0 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Controls/Default/BoundaryControl.cs @@ -0,0 +1,22 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Controls.Default; + +public class BoundaryControl : Control +{ + public Rectangle Bounds { get; private set; } = Rectangle.Zero; + + public override Point? GetPosition(Model model) + { + if (model is not IHasBounds hb) + return null; + + var bounds = hb.GetBounds(); + if (bounds == null) + return null; + + Bounds = bounds.Inflate(10, 10); + return Bounds.NorthWest; + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/UserActions/Default/RemoveUserAction.cs b/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs similarity index 62% rename from src/Blazor.Diagrams.Core/UserActions/Default/RemoveUserAction.cs rename to src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs index 303d2a3c4..00b4beb6f 100644 --- a/src/Blazor.Diagrams.Core/UserActions/Default/RemoveUserAction.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs @@ -3,16 +3,16 @@ using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Positions; -namespace Blazor.Diagrams.Core.UserActions.Default; +namespace Blazor.Diagrams.Core.Controls.Default; -public class RemoveUserAction : UserAction +public class RemoveControl : ExecutableControl { - public RemoveUserAction(double x, double y, double offsetX = 0, double offsetY = 0) + public RemoveControl(double x, double y, double offsetX = 0, double offsetY = 0) : base(new BoundsBasedPositionProvider(x, y, offsetX, offsetY)) { } - public RemoveUserAction(IPositionProvider positionProvider) : base(positionProvider) + public RemoveControl(IPositionProvider positionProvider) : base(positionProvider) { } @@ -22,7 +22,7 @@ public override ValueTask Execute(Diagram diagram, Model model) { diagram.Nodes.Remove(node); } - + return ValueTask.CompletedTask; } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/UserActions/UserAction.cs b/src/Blazor.Diagrams.Core/Controls/ExecutableControl.cs similarity index 50% rename from src/Blazor.Diagrams.Core/UserActions/UserAction.cs rename to src/Blazor.Diagrams.Core/Controls/ExecutableControl.cs index 523e3eea7..4aad9780f 100644 --- a/src/Blazor.Diagrams.Core/UserActions/UserAction.cs +++ b/src/Blazor.Diagrams.Core/Controls/ExecutableControl.cs @@ -1,17 +1,20 @@ using System.Threading.Tasks; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Positions; -namespace Blazor.Diagrams.Core.UserActions; +namespace Blazor.Diagrams.Core.Controls; -public abstract class UserAction +public abstract class ExecutableControl : Control { - protected UserAction(IPositionProvider positionProvider) + public IPositionProvider PositionProvider { get; } + + protected ExecutableControl(IPositionProvider positionProvider) { PositionProvider = positionProvider; } - public IPositionProvider PositionProvider { get; } + public override Point? GetPosition(Model model) => PositionProvider.GetPosition(model); public abstract ValueTask Execute(Diagram diagram, Model model); } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index c1a685ba7..930de19eb 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -10,7 +10,7 @@ using System.Linq; using System.Runtime.CompilerServices; using Blazor.Diagrams.Core.Options; -using Blazor.Diagrams.Core.UserActions; +using Blazor.Diagrams.Core.Controls; [assembly: InternalsVisibleTo("Blazor.Diagrams")] [assembly: InternalsVisibleTo("Blazor.Diagrams.Tests")] @@ -50,7 +50,7 @@ protected Diagram() Nodes = new NodeLayer(this); Links = new LinkLayer(this); - UserActions = new UserActionsLayer(); + Controls = new ControlsLayer(); RegisterBehavior(new SelectionBehavior(this)); RegisterBehavior(new DragMovablesBehavior(this)); @@ -59,13 +59,13 @@ protected Diagram() RegisterBehavior(new ZoomBehavior(this)); RegisterBehavior(new EventsBehavior(this)); RegisterBehavior(new KeyboardShortcutsBehavior(this)); - RegisterBehavior(new UserActionsBehavior(this)); + RegisterBehavior(new ControlsBehavior(this)); } public abstract DiagramOptions Options { get; } public NodeLayer Nodes { get; } public LinkLayer Links { get; } - public UserActionsLayer UserActions { get; } + public ControlsLayer Controls { get; } public IReadOnlyList Groups => _groups; public Rectangle? Container { get; private set; } public Point Pan { get; private set; } = Point.Zero; diff --git a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs index bfdb6ff36..2556a1ec4 100644 --- a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs @@ -17,7 +17,7 @@ public BaseLayer(Diagram diagram) Diagram = diagram; } - public virtual void Add(T item) + public virtual T Add(T item) { if (item is null) throw new ArgumentNullException(nameof(item)); @@ -26,6 +26,7 @@ public virtual void Add(T item) OnItemAdded(item); Added?.Invoke(item); Diagram.Refresh(); + return item; } public virtual void Add(IEnumerable items) diff --git a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs index 1acdb5617..3b7df38d0 100644 --- a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs @@ -35,7 +35,7 @@ protected override void OnItemRemoved(BaseLinkModel link) link.SourceChanged -= OnLinkSourceChanged; link.TargetChanged -= OnLinkTargetChanged; - Diagram.UserActions.RemoveFor(link); + Diagram.Controls.RemoveFor(link); } private void OnLinkSourceChanged(BaseLinkModel link, Anchor old, Anchor @new) diff --git a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs index 61011df69..9ab67021f 100644 --- a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs @@ -18,7 +18,7 @@ protected override void OnItemRemoved(NodeModel node) Diagram.Links.Remove(node.PortLinks.ToList()); Diagram.Links.Remove(node.Links.ToList()); node.Group?.RemoveChild(node); - Diagram.UserActions.RemoveFor(node); + Diagram.Controls.RemoveFor(node); } } } diff --git a/src/Blazor.Diagrams.Core/UserActions/UserActionsType.cs b/src/Blazor.Diagrams.Core/UserActions/UserActionsType.cs deleted file mode 100644 index 9a2445403..000000000 --- a/src/Blazor.Diagrams.Core/UserActions/UserActionsType.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Blazor.Diagrams.Core.UserActions; - -public enum UserActionsType -{ - OnHover, - OnSelection -} \ No newline at end of file diff --git a/src/Blazor.Diagrams/BlazorDiagram.cs b/src/Blazor.Diagrams/BlazorDiagram.cs index 077e58164..7623a99e4 100644 --- a/src/Blazor.Diagrams/BlazorDiagram.cs +++ b/src/Blazor.Diagrams/BlazorDiagram.cs @@ -3,8 +3,8 @@ using Microsoft.AspNetCore.Components; using System; using System.Collections.Generic; -using Blazor.Diagrams.Components.UserActions; -using Blazor.Diagrams.Core.UserActions.Default; +using Blazor.Diagrams.Components.Controls; +using Blazor.Diagrams.Core.Controls.Default; using Blazor.Diagrams.Options; namespace Blazor.Diagrams @@ -17,7 +17,8 @@ public BlazorDiagram(BlazorDiagramOptions? options = null) { _componentByModelMapping = new Dictionary { - [typeof(RemoveUserAction)] = typeof(RemoveUserActionWidget) + [typeof(RemoveControl)] = typeof(RemoveControlWidget), + [typeof(BoundaryControl)] = typeof(BoundaryControlWidget) }; Options = options ?? new BlazorDiagramOptions(); diff --git a/src/Blazor.Diagrams/Components/Controls/BoundaryControlWidget.razor b/src/Blazor.Diagrams/Components/Controls/BoundaryControlWidget.razor new file mode 100644 index 000000000..64d4f2feb --- /dev/null +++ b/src/Blazor.Diagrams/Components/Controls/BoundaryControlWidget.razor @@ -0,0 +1,35 @@ +@if (Model.IsSvg()) +{ + + +} +else +{ + + + + +} + +@code +{ + [Parameter] + public BoundaryControl Control { get; set; } = null!; + + [Parameter] + public Model Model { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor new file mode 100644 index 000000000..79d463e7e --- /dev/null +++ b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor @@ -0,0 +1,33 @@ +@foreach (var model in BlazorDiagram.Controls.Models) +{ + var controls = BlazorDiagram.Controls.GetFor(model)!; + if (!controls.Visible || controls.Count == 0) + continue; + + if (Svg && model.IsSvg()) + { + + @foreach (var control in controls) + { + var position = control.GetPosition(model); + if (position == null) + continue; + + @RenderControl(model, control, position, true) + } + + } + else + { +
    + @foreach (var control in controls) + { + var position = control.GetPosition(model); + if (position == null) + continue; + + @RenderControl(model, control, position, false) + } +
    + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor.cs b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs similarity index 54% rename from src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor.cs rename to src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs index 240e78e92..472310dc6 100644 --- a/src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor.cs +++ b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs @@ -3,14 +3,14 @@ using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; -using Blazor.Diagrams.Core.UserActions; -using Blazor.Diagrams.Models; +using Blazor.Diagrams.Core.Controls; +using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; -namespace Blazor.Diagrams.Components.UserActions; +namespace Blazor.Diagrams.Components.Controls; -public partial class UserActionsLayerRenderer : IDisposable +public partial class ControlsLayerRenderer : IDisposable { private bool _shouldRender; @@ -20,7 +20,7 @@ public partial class UserActionsLayerRenderer : IDisposable protected override void OnInitialized() { - BlazorDiagram.UserActions.Changed += OnUserActionsChanged; + BlazorDiagram.Controls.Changed += OnControlsChanged; } protected override bool ShouldRender() @@ -32,27 +32,27 @@ protected override bool ShouldRender() return true; } - private void OnUserActionsChanged(Model cause) + private void OnControlsChanged(Model cause) { - if ((Svg && cause is SvgNodeModel or SvgGroupModel) || - (!Svg && cause is not SvgNodeModel && cause is not SvgGroupModel)) - { - _shouldRender = true; - InvokeAsync(StateHasChanged); - } + if (Svg != cause.IsSvg()) + return; + + _shouldRender = true; + InvokeAsync(StateHasChanged); } - private RenderFragment RenderUserAction(Model model, UserAction action, Point position, bool svg) + private RenderFragment RenderControl(Model model, Control control, Point position, bool svg) { - var componentType = BlazorDiagram.GetComponentForModel(action.GetType()); + var componentType = BlazorDiagram.GetComponentForModel(control.GetType()); if (componentType == null) throw new BlazorDiagramsException( - $"A component couldn't be found for the user action {action.GetType().Name}"); + $"A component couldn't be found for the user action {control.GetType().Name}"); return builder => { builder.OpenElement(0, svg ? "g" : "div"); - builder.AddAttribute(1, "class", $"user-action {action.GetType().Name}"); + builder.AddAttribute(1, "class", + $"{(control is ExecutableControl ? "executable " : "")}control {control.GetType().Name}"); if (svg) { builder.AddAttribute(2, "transform", @@ -64,25 +64,31 @@ private RenderFragment RenderUserAction(Model model, UserAction action, Point po $"top: {position.Y.ToInvariantString()}px; left: {position.X.ToInvariantString()}px"); } - builder.AddAttribute(3, "onpointerdown", - EventCallback.Factory.Create(this, e => OnPointerDown(e, model, action))); - builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); + if (control is ExecutableControl ec) + { + builder.AddAttribute(3, "onpointerdown", + EventCallback.Factory.Create(this, e => OnPointerDown(e, model, ec))); + builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); + } builder.OpenComponent(5, componentType); - builder.AddAttribute(6, "Action", action); + builder.AddAttribute(6, "Control", control); builder.AddAttribute(7, "Model", model); builder.CloseComponent(); builder.CloseElement(); }; } - private async Task OnPointerDown(PointerEventArgs e, Model model, UserAction action) + private async Task OnPointerDown(PointerEventArgs e, Model model, ExecutableControl control) { - await action.Execute(BlazorDiagram, model); + if (e.Button == 0 || e.Buttons == 1) + { + await control.Execute(BlazorDiagram, model); + } } public void Dispose() { - BlazorDiagram.UserActions.Changed -= OnUserActionsChanged; + BlazorDiagram.Controls.Changed -= OnControlsChanged; } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/UserActions/RemoveUserActionWidget.razor b/src/Blazor.Diagrams/Components/Controls/RemoveControlWidget.razor similarity index 78% rename from src/Blazor.Diagrams/Components/UserActions/RemoveUserActionWidget.razor rename to src/Blazor.Diagrams/Components/Controls/RemoveControlWidget.razor index ee87b56ed..cc9ac84cd 100644 --- a/src/Blazor.Diagrams/Components/UserActions/RemoveUserActionWidget.razor +++ b/src/Blazor.Diagrams/Components/Controls/RemoveControlWidget.razor @@ -1,6 +1,3 @@ -@using Blazor.Diagrams.Core.UserActions -@using Blazor.Diagrams.Core.Models.Base - @if (Model is SvgNodeModel) { @@ -17,7 +14,7 @@ else @code { [Parameter] - public UserAction Action { get; set; } = null!; + public Control Control { get; set; } = null!; [Parameter] public Model Model { get; set; } = null!; diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor b/src/Blazor.Diagrams/Components/DiagramCanvas.razor index 11923c1b3..f89ae47f5 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor @@ -26,7 +26,7 @@ } - + @* Nodes *@ @@ -42,7 +42,7 @@ } - +
    @Widgets diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor b/src/Blazor.Diagrams/Components/LinkWidget.razor index 2851d3b50..1cd6d3987 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor @@ -1,7 +1,6 @@ @{ var color = Link.Selected ? (Link.SelectedColor ?? BlazorDiagram.Options.Links.DefaultSelectedColor) : (Link.Color ?? BlazorDiagram.Options.Links.DefaultColor); var result = Link.GeneratedPathResult; - var bounds = Link.GetBounds()?.Inflate(10, 10); } @for (var i = 0; i < result.Paths.Length; i++) @@ -59,19 +58,4 @@ @foreach (var label in Link.Labels) { -} - -@if (bounds != null) -{ - - } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor b/src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor deleted file mode 100644 index 66b337db5..000000000 --- a/src/Blazor.Diagrams/Components/UserActions/UserActionsLayerRenderer.razor +++ /dev/null @@ -1,33 +0,0 @@ -@foreach (var model in BlazorDiagram.UserActions.Models) -{ - var userActions = BlazorDiagram.UserActions.GetFor(model)!; - if (!userActions.Visible || userActions.Count == 0) - continue; - - if (Svg && model is SvgNodeModel or SvgGroupModel) - { - - } - else if (!Svg && model is not SvgNodeModel && model is not SvgGroupModel) - { - - } -} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Extensions/ModelExtensions.cs b/src/Blazor.Diagrams/Extensions/ModelExtensions.cs new file mode 100644 index 000000000..72649df94 --- /dev/null +++ b/src/Blazor.Diagrams/Extensions/ModelExtensions.cs @@ -0,0 +1,12 @@ +using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Models; + +namespace Blazor.Diagrams.Extensions; + +public static class ModelExtensions +{ + public static bool IsSvg(this Model model) + { + return model is SvgNodeModel or SvgGroupModel or BaseLinkModel; + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/_Imports.razor b/src/Blazor.Diagrams/_Imports.razor index cfc0a4fec..141b2dffa 100644 --- a/src/Blazor.Diagrams/_Imports.razor +++ b/src/Blazor.Diagrams/_Imports.razor @@ -2,7 +2,11 @@ @using Blazor.Diagrams.Core; @using Blazor.Diagrams.Core.Extensions; @using Blazor.Diagrams.Core.Models; +@using Blazor.Diagrams.Core.Models.Base; @using Blazor.Diagrams.Components.Renderers @using Blazor.Diagrams.Core.Geometry; @using Blazor.Diagrams.Models; -@using Blazor.Diagrams.Components.UserActions; +@using Blazor.Diagrams.Components.Controls; +@using Blazor.Diagrams.Core.Controls; +@using Blazor.Diagrams.Core.Controls.Default; +@using Blazor.Diagrams.Extensions; \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/style.css b/src/Blazor.Diagrams/wwwroot/style.css index d90d4f149..5c5ea6e36 100644 --- a/src/Blazor.Diagrams/wwwroot/style.css +++ b/src/Blazor.Diagrams/wwwroot/style.css @@ -88,11 +88,11 @@ pointer-events: none; } -div.user-action { +div.control { position: absolute; } -.user-action { +.executable.control { pointer-events: all; cursor: pointer; } diff --git a/src/Blazor.Diagrams/wwwroot/style.min.css b/src/Blazor.Diagrams/wwwroot/style.min.css index 0ba94bad3..a5e469062 100644 --- a/src/Blazor.Diagrams/wwwroot/style.min.css +++ b/src/Blazor.Diagrams/wwwroot/style.min.css @@ -1 +1 @@ -.diagram-canvas{width:100%;height:100%;position:relative;outline:none;overflow:hidden;cursor:-webkit-grab;cursor:grab;touch-action:none;}.diagram-svg-layer,.diagram-html-layer{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.html-layer,.svg-layer{position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.node{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.node.locked{cursor:pointer;}.link{pointer-events:visiblePainted;cursor:pointer;}.link path.selection-helper:hover{stroke-opacity:.05;}.diagram-navigator{z-index:10;}.diagram-navigator .current-view{position:absolute;border:2px solid #000;}.group{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.group .children{position:absolute;overflow:visible;pointer-events:none;}.link foreignObject{overflow:visible;pointer-events:none;}div.user-action{position:absolute;}.user-action{pointer-events:all;cursor:pointer;} \ No newline at end of file +.diagram-canvas{width:100%;height:100%;position:relative;outline:none;overflow:hidden;cursor:-webkit-grab;cursor:grab;touch-action:none;}.diagram-svg-layer,.diagram-html-layer{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.html-layer,.svg-layer{position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.node{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.node.locked{cursor:pointer;}.link{pointer-events:visiblePainted;cursor:pointer;}.link path.selection-helper:hover{stroke-opacity:.05;}.diagram-navigator{z-index:10;}.diagram-navigator .current-view{position:absolute;border:2px solid #000;}.group{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.group .children{position:absolute;overflow:visible;pointer-events:none;}.link foreignObject{overflow:visible;pointer-events:none;}div.control{position:absolute;}.executable.control{pointer-events:all;cursor:pointer;} \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/style.min.css.gz b/src/Blazor.Diagrams/wwwroot/style.min.css.gz index 5e97c64e384c0a936a3eb1eacbcec3a9dfbad655..e206de36c0f3cb2481b820cb7219179b4b97320c 100644 GIT binary patch literal 430 zcmV;f0a5-RiwFP!000003eA#1j@vK{MXzF@=x$_a3Up&1qP>DFF%}zHG$h)|j4>zc zqDSf#N+*sxNK~N9Sw;C0$;a>i`{%FPq3M$ulr?cSc{!nD-#lymx%Gg(=j(Z-jEoeU z1i>)Q(9*;KBQ%ks{Fxyg0-YL<&OvPLlrp78ouE4+D;dz;w9A=kvR)Z`X?f}Jwu9uk zSHZkMdfFw=L%3=S)7a=Xz=7o_T>y345i<>P*fC9)DNGEaU}Q)NW{8}xvu;?NOw0#L zgCgmKvC)-o)sV{Ze|LDQ_Z6293>?Wpf$zpLWr55P!14w;&}&J} zkK%){Wn&PGdjm`e8%!O@9>Fc!wBceARlE!I?{VP9W4WJl#8jF1|? znz3X~bOc4CvB)n?t-rp@Xf!kShAAzt3ZsLwxLCTbY8jjaNX-bRf}0&BCnLX%=SmcG z)hDgjPJNJyXs;k!9E%xQG$h)S#K_6I z=#g@T(m&%AGb+$!R+b+v^5esU-+%osEt)QxUbQAnCe0VL?5b~Cf2|#0=Xg60l#r1^ z6~PC{CUhfm{vx6sHg4!xCaGF*50$;EhgT_P#T3qP7CA(;Q~rp2jTxwn5thT!d{ Sez(SL1pWYL|JCvR1ONb69?Zo6 diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index 76f6c8e89..5a48319d4 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -94,7 +94,7 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() 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; + 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)); @@ -125,7 +125,7 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoom 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; + 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)); @@ -160,7 +160,7 @@ public void Behavior_ShouldSnapToClosestPortAndRefreshPort_WhenSnappingIsEnabled Size = new Size(10, 20) }); var port2Refreshed = false; - port2.Changed += () => port2Refreshed = true; + port2.Changed += _ => port2Refreshed = true; // Act diagram.TriggerPointerDown(port1, @@ -235,7 +235,7 @@ public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNo Size = new Size(10, 20) }); var port2Refreshes = 0; - port2.Changed += () => port2Refreshes++; + port2.Changed += _ => port2Refreshes++; // Act diagram.TriggerPointerDown(port1, @@ -324,7 +324,7 @@ public void Behavior_ShouldSetTarget_WhenMouseUp() Size = new Size(10, 20) }); var port2Refreshes = 0; - port2.Changed += () => port2Refreshes++; + port2.Changed += _ => port2Refreshes++; // Act diagram.TriggerPointerDown(port1, diff --git a/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs index fcca0b773..ff06c1846 100644 --- a/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs +++ b/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs @@ -94,7 +94,7 @@ public async Task ShouldDeleteItselfAndRefreshParent_WhenDoubleClicked() int linkRefreshes = 0; var vertex = new LinkVertexModel(link, new Point(10.5, 20)); link.Vertices.Add(vertex); - link.Changed += () => linkRefreshes++; + link.Changed += _ => linkRefreshes++; // Act var cut = ctx.RenderComponent(parameters => parameters From 72f27fb9ce14f94a2f28848fe441a27d49ffae45 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 29 Aug 2022 13:13:31 +0100 Subject: [PATCH 063/193] Add LinkPathPositionProvider --- .../Controls/ControlsContainer.cs | 7 ++- .../Controls/ControlsLayer.cs | 6 +-- .../Positions/LinkPathPositionProvider.cs | 48 +++++++++++++++++++ .../Controls/ControlsLayerRenderer.razor | 2 +- .../Controls/ControlsLayerRenderer.razor.cs | 6 +-- 5 files changed, 60 insertions(+), 9 deletions(-) create mode 100644 src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs diff --git a/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs b/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs index 83a98660c..ea0da8e6b 100644 --- a/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs +++ b/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs @@ -39,18 +39,21 @@ public void Hide() Changed?.Invoke(Model); } - public void Add(Control action) + public ControlsContainer Add(Control action) { _actions.Add(action); Changed?.Invoke(Model); + return this; } - public void Remove(Control action) + public ControlsContainer Remove(Control action) { if (_actions.Remove(action)) { Changed?.Invoke(Model); } + + return this; } public int Count => _actions.Count; diff --git a/src/Blazor.Diagrams.Core/Controls/ControlsLayer.cs b/src/Blazor.Diagrams.Core/Controls/ControlsLayer.cs index 2a3cbd0f7..ed9ff74d4 100644 --- a/src/Blazor.Diagrams.Core/Controls/ControlsLayer.cs +++ b/src/Blazor.Diagrams.Core/Controls/ControlsLayer.cs @@ -8,7 +8,7 @@ public class ControlsLayer { private readonly Dictionary _containers; - public event Action? Changed; + public event Action? ChangeCaused; public ControlsLayer() { @@ -42,9 +42,9 @@ public bool RemoveFor(Model model) container.Changed -= OnChanged; model.Changed -= OnChanged; _containers.Remove(model); - Changed?.Invoke(model); + ChangeCaused?.Invoke(model); return true; } - private void OnChanged(Model cause) => Changed?.Invoke(cause); + private void OnChanged(Model cause) => ChangeCaused?.Invoke(cause); } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs b/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs new file mode 100644 index 000000000..65e0110b3 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs @@ -0,0 +1,48 @@ +using System.Linq; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions; + +public class LinkPathPositionProvider : IPositionProvider +{ + public LinkPathPositionProvider(double distance) + { + Distance = distance; + } + + public double Distance { get; } + + public Point? GetPosition(Model model) + { + if (model is not BaseLinkModel link) + throw new DiagramsException("LinkPathPositionProvider requires a link model"); + + if (link.Paths.Length <= 0) + return null; + + var totalLength = link.Paths.Sum(p => p.Length); + + var length = Distance switch + { + >= 0 and <= 1 => Distance * totalLength, + > 1 => Distance, + < 0 => totalLength + Distance + }; + + foreach (var path in link.Paths) + { + var pathLength = path.Length; + if (length < pathLength) + { + var pt = path.GetPointAtLength(length); + return new Point(pt.X, pt.Y); + } + + length -= pathLength; + } + + return null; + + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor index 79d463e7e..7146c2316 100644 --- a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor +++ b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor @@ -17,7 +17,7 @@ } } - else + else if (!Svg && !model.IsSvg()) {
    @foreach (var control in controls) diff --git a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs index 472310dc6..5422473fc 100644 --- a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs +++ b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs @@ -20,7 +20,7 @@ public partial class ControlsLayerRenderer : IDisposable protected override void OnInitialized() { - BlazorDiagram.Controls.Changed += OnControlsChanged; + BlazorDiagram.Controls.ChangeCaused += OnControlsChangeCaused; } protected override bool ShouldRender() @@ -32,7 +32,7 @@ protected override bool ShouldRender() return true; } - private void OnControlsChanged(Model cause) + private void OnControlsChangeCaused(Model cause) { if (Svg != cause.IsSvg()) return; @@ -89,6 +89,6 @@ private async Task OnPointerDown(PointerEventArgs e, Model model, ExecutableCont public void Dispose() { - BlazorDiagram.Controls.Changed -= OnControlsChanged; + BlazorDiagram.Controls.ChangeCaused -= OnControlsChangeCaused; } } \ No newline at end of file From f8e02b58035ca4168288154a921f30247022d2ed Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 29 Aug 2022 13:17:58 +0100 Subject: [PATCH 064/193] Add offset parameter to LinkPathPositionProvider --- .../Positions/LinkPathPositionProvider.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs b/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs index 65e0110b3..51f959c13 100644 --- a/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs @@ -1,3 +1,4 @@ +using System; using System.Linq; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; @@ -6,12 +7,16 @@ namespace Blazor.Diagrams.Core.Positions; public class LinkPathPositionProvider : IPositionProvider { - public LinkPathPositionProvider(double distance) + public LinkPathPositionProvider(double distance, double offsetX = 0, double offsetY = 0) { Distance = distance; + OffsetX = offsetX; + OffsetY = offsetY; } public double Distance { get; } + public double OffsetX { get; } + public double OffsetY { get; } public Point? GetPosition(Model model) { @@ -36,7 +41,7 @@ public LinkPathPositionProvider(double distance) if (length < pathLength) { var pt = path.GetPointAtLength(length); - return new Point(pt.X, pt.Y); + return new Point(pt.X + OffsetX, pt.Y + OffsetY); } length -= pathLength; From b89fd2e4f4ec825b3ef5a4c71187fc2f92ff214a Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 29 Aug 2022 13:22:33 +0100 Subject: [PATCH 065/193] Capture MouseEnter/Leave for links too --- .../Components/Renderers/LinkRenderer.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs index 74d8b64e9..f4e91ea7d 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs @@ -43,8 +43,10 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); builder.AddAttribute(5, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); builder.AddEventStopPropagationAttribute(6, "onpointerup", true); - builder.OpenComponent(7, componentType); - builder.AddAttribute(8, "Link", Link); + builder.AddAttribute(7, "onmouseenter", EventCallback.Factory.Create(this, OnMouseEnter)); + builder.AddAttribute(8, "onmouseleave", EventCallback.Factory.Create(this, OnMouseLeave)); + builder.OpenComponent(9, componentType); + builder.AddAttribute(10, "Link", Link); builder.CloseComponent(); builder.CloseElement(); } @@ -60,5 +62,9 @@ private void OnLinkChanged(Model _) private void OnPointerDown(PointerEventArgs e) => BlazorDiagram.TriggerPointerDown(Link, e.ToCore()); private void OnPointerUp(PointerEventArgs e) => BlazorDiagram.TriggerPointerUp(Link, e.ToCore()); + + private void OnMouseEnter(MouseEventArgs e) => BlazorDiagram.TriggerPointerEnter(Link, e.ToCore()); + + private void OnMouseLeave(MouseEventArgs e) => BlazorDiagram.TriggerPointerLeave(Link, e.ToCore()); } } From 13e785029f506136d2b9111b898d8cbaf482630a Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 29 Aug 2022 14:57:58 +0100 Subject: [PATCH 066/193] Only rerender controls when they are visible --- .../Controls/ControlsContainer.cs | 29 ++++++++++--------- .../Controls/ControlsLayer.cs | 22 ++++++++++---- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs b/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs index ea0da8e6b..5ebc16b46 100644 --- a/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs +++ b/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs @@ -7,9 +7,10 @@ namespace Blazor.Diagrams.Core.Controls; public class ControlsContainer : IReadOnlyList { - private readonly List _actions = new(4); + private readonly List _controls = new(4); - public event Action? Changed; + public event Action? VisibilityChanged; + public event Action? ControlsChanged; public ControlsContainer(Model model, ControlsType type = ControlsType.OnSelection) { @@ -27,7 +28,7 @@ public void Show() return; Visible = true; - Changed?.Invoke(Model); + VisibilityChanged?.Invoke(Model); } public void Hide() @@ -36,28 +37,28 @@ public void Hide() return; Visible = false; - Changed?.Invoke(Model); + VisibilityChanged?.Invoke(Model); } - public ControlsContainer Add(Control action) + public ControlsContainer Add(Control control) { - _actions.Add(action); - Changed?.Invoke(Model); + _controls.Add(control); + ControlsChanged?.Invoke(Model); return this; } - public ControlsContainer Remove(Control action) + public ControlsContainer Remove(Control control) { - if (_actions.Remove(action)) + if (_controls.Remove(control)) { - Changed?.Invoke(Model); + ControlsChanged?.Invoke(Model); } return this; } - public int Count => _actions.Count; - public Control this[int index] => _actions[index]; - public IEnumerator GetEnumerator() => _actions.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => _actions.GetEnumerator(); + public int Count => _controls.Count; + public Control this[int index] => _controls[index]; + public IEnumerator GetEnumerator() => _controls.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _controls.GetEnumerator(); } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Controls/ControlsLayer.cs b/src/Blazor.Diagrams.Core/Controls/ControlsLayer.cs index ed9ff74d4..39a331d76 100644 --- a/src/Blazor.Diagrams.Core/Controls/ControlsLayer.cs +++ b/src/Blazor.Diagrams.Core/Controls/ControlsLayer.cs @@ -23,8 +23,9 @@ public ControlsContainer AddFor(Model model, ControlsType type = ControlsType.On return _containers[model]; var container = new ControlsContainer(model, type); - container.Changed += OnChanged; - model.Changed += OnChanged; + container.VisibilityChanged += OnVisibilityChanged; + container.ControlsChanged += RefreshIfVisible; + model.Changed += RefreshIfVisible; _containers.Add(model, container); return container; } @@ -39,12 +40,23 @@ public bool RemoveFor(Model model) if (!_containers.TryGetValue(model, out var container)) return false; - container.Changed -= OnChanged; - model.Changed -= OnChanged; + container.VisibilityChanged -= OnVisibilityChanged; + container.ControlsChanged -= RefreshIfVisible; + model.Changed -= RefreshIfVisible; _containers.Remove(model); ChangeCaused?.Invoke(model); return true; } - private void OnChanged(Model cause) => ChangeCaused?.Invoke(cause); + public bool AreVisibleFor(Model model) => GetFor(model)?.Visible ?? false; + + private void RefreshIfVisible(Model cause) + { + if (!AreVisibleFor(cause)) + return; + + ChangeCaused?.Invoke(cause); + } + + private void OnVisibilityChanged(Model cause) => ChangeCaused?.Invoke(cause); } \ No newline at end of file From 2fd10f2197a98e8ae64032d9df7b475676008fde Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 30 Aug 2022 00:14:52 +0100 Subject: [PATCH 067/193] Fix link not refreshing after vertex is created (vertex shows out of link) --- src/Blazor.Diagrams/Components/LinkWidget.razor.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs index 8d92d2378..982f2387a 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs @@ -27,6 +27,7 @@ private LinkVertexModel CreateVertex(double clientX, double clientY, int index) var rPt = BlazorDiagram.GetRelativeMousePoint(clientX, clientY); var vertex = new LinkVertexModel(Link, rPt); Link.Vertices.Insert(index, vertex); + Link.Refresh(); return vertex; } } From 22c4db1ed69fdb7f3af8e1c80ba9159b8d5735a5 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 31 Aug 2022 16:54:12 +0100 Subject: [PATCH 068/193] Add ILinkable interface for models that can have links to make link creation more generic Add TargetAnchorFactory option Options (links etc) are now readonly, but their properties are mutable --- docs/Layouts/Pages/Index.razor | 4 +- .../Demos/CustomPort/ColoredPort.cs | 10 +++- .../LinksReconnectionAlgorithms.cs | 4 +- src/Blazor.Diagrams.Core/Anchors/Anchor.cs | 18 ++---- .../Anchors/Dynamic/DynamicAnchor.cs | 11 ++-- .../Anchors/ShapeIntersectionAnchor.cs | 13 ++-- .../Anchors/SinglePortAnchor.cs | 33 +++++------ .../Behaviors/DragNewLinkBehavior.cs | 59 ++++++++++--------- .../Controls/Default/DragNewLinkControl.cs | 35 +++++++++++ .../Controls/Default/RemoveControl.cs | 12 +++- .../Controls/ExecutableControl.cs | 3 +- src/Blazor.Diagrams.Core/Delegates.cs | 4 +- src/Blazor.Diagrams.Core/Layers/LinkLayer.cs | 39 +++--------- .../Models/Base/ILinkable.cs | 16 +++++ src/Blazor.Diagrams.Core/Models/NodeModel.cs | 27 ++++++--- src/Blazor.Diagrams.Core/Models/PortModel.cs | 18 +++--- .../Options/DiagramLinkOptions.cs | 17 +++++- .../Options/DiagramOptions.cs | 8 +-- .../PathGenerators/PathGenerators.Smooth.cs | 2 +- src/Blazor.Diagrams/BlazorDiagram.cs | 3 +- .../Controls/ControlsLayerRenderer.razor.cs | 2 +- .../Controls/DragNewLinkControlWidget.razor | 23 ++++++++ .../Controls/RemoveControlWidget.razor | 2 +- .../Options/BlazorDiagramOptions.cs | 8 +-- .../Models/Base/BaseLinkModelTests.cs | 4 +- 25 files changed, 232 insertions(+), 143 deletions(-) create mode 100644 src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs create mode 100644 src/Blazor.Diagrams.Core/Models/Base/ILinkable.cs create mode 100644 src/Blazor.Diagrams/Components/Controls/DragNewLinkControlWidget.razor diff --git a/docs/Layouts/Pages/Index.razor b/docs/Layouts/Pages/Index.razor index 3fd6e0b9e..b731dcf96 100644 --- a/docs/Layouts/Pages/Index.razor +++ b/docs/Layouts/Pages/Index.razor @@ -91,8 +91,8 @@ or it will not be rendered. var edges = _diagram.Links.OfType() .Select(lm => { - var source = nodes.Single(dn => dn.Id == lm.Source.Node.Id); - var target = nodes.Single(dn => dn.Id == lm?.Target?.Node?.Id); + var source = nodes.Single(dn => dn.Id == lm.Source.Model.Id); + var target = nodes.Single(dn => dn.Id == lm?.Target?.Model?.Id); return new QG.Edge(source, target); }) .ToList(); diff --git a/samples/SharedDemo/Demos/CustomPort/ColoredPort.cs b/samples/SharedDemo/Demos/CustomPort/ColoredPort.cs index 420d5f434..9d4696707 100644 --- a/samples/SharedDemo/Demos/CustomPort/ColoredPort.cs +++ b/samples/SharedDemo/Demos/CustomPort/ColoredPort.cs @@ -1,4 +1,5 @@ using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; namespace SharedDemo.Demos.CustomPort { @@ -11,14 +12,17 @@ public ColoredPort(NodeModel parent, PortAlignment alignment, bool isRed) : base public bool IsRed { get; set; } - public override bool CanAttachTo(PortModel port) + public override bool CanAttachTo(ILinkable other) { - // Checks for same-node/port attachements + if (other is not PortModel port) + return false; + + // Checks for same-node/port attachments if (!base.CanAttachTo(port)) return false; // Only able to attach to the same port type - if (!(port is ColoredPort cp)) + if (port is not ColoredPort cp) return false; return IsRed == cp.IsRed; diff --git a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs index a367ba52a..249d28372 100644 --- a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs +++ b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs @@ -21,8 +21,8 @@ public static void ReconnectLinksToClosestPorts(this Diagram diagram) if (link.Source is not SinglePortAnchor spa1 || link.Target is not SinglePortAnchor spa2) continue; - var sourcePorts = spa1.Node.Ports; - var targetPorts = spa2.Node.Ports; + var sourcePorts = spa1.Port.Parent.Ports; + var targetPorts = spa2.Port.Parent.Ports; // Find the ports with minimal distance var minDistance = double.MaxValue; diff --git a/src/Blazor.Diagrams.Core/Anchors/Anchor.cs b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs index 0ab53f15c..0eaa75dd6 100644 --- a/src/Blazor.Diagrams.Core/Anchors/Anchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs @@ -1,25 +1,25 @@ 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.Anchors.Dynamic; namespace Blazor.Diagrams.Core.Anchors { public abstract class Anchor { - public Anchor(NodeModel node, Point? offset = null) + public Anchor(ILinkable model, Point? offset = null) { - Node = node; + Model = model; Offset = offset ?? Point.Zero; } - public NodeModel Node { get; } + public ILinkable Model { get; } public Point Offset { get; } public abstract Point? GetPosition(BaseLinkModel link, Point[] route); + public abstract Point? GetPlainPosition(); + public Point? GetPosition(BaseLinkModel link) => GetPosition(link, Array.Empty()); protected static Point? GetOtherPosition(BaseLinkModel link, bool isTarget) @@ -28,13 +28,7 @@ public Anchor(NodeModel node, Point? offset = null) return link.OnGoingPosition; var anchor = isTarget ? link.Source : link.Target!; - return anchor switch - { - SinglePortAnchor spa => spa.Port.MiddlePosition, - ShapeIntersectionAnchor sia => sia.Node.GetBounds()?.Center ?? null, - DynamicAnchor da => da.Node.GetBounds()?.Center ?? null, - _ => throw new DiagramsException($"Unhandled Anchor type {anchor.GetType().Name} when trying to find intersection") - }; + return anchor.GetPlainPosition(); } protected static Point? GetClosestPointTo(IEnumerable points, Point point) diff --git a/src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs index 01d4d3150..676d4ac82 100644 --- a/src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; @@ -12,8 +11,7 @@ namespace Blazor.Diagrams.Core.Anchors.Dynamic // Generic? public class DynamicAnchor : Anchor { - public DynamicAnchor(NodeModel node, IPositionProvider[] providers, Point? offset = null) - : base(node, offset) + public DynamicAnchor(NodeModel model, IPositionProvider[] providers, Point? offset = null) : base(model, offset) { if (providers.Length == 0) throw new InvalidOperationException("No providers provided"); @@ -25,13 +23,16 @@ public DynamicAnchor(NodeModel node, IPositionProvider[] providers, Point? offse public override Point? GetPosition(BaseLinkModel link, Point[] route) { - if (Node.Size == null) + var node = (Model as NodeModel)!; + if (node.Size == null) return null; var isTarget = link.Target == this; var pt = route.Length > 0 ? route[isTarget ? ^1 : 0] : GetOtherPosition(link, isTarget); - var positions = Providers.Select(e => e.GetPosition(Node)); + var positions = Providers.Select(e => e.GetPosition(node)); return pt is null ? null : GetClosestPointTo(positions, pt); } + + public override Point? GetPlainPosition() => (Model as NodeModel)!.GetBounds()?.Center ?? null; } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs index e2aa9314b..4a5c1ffe5 100644 --- a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs @@ -6,15 +6,16 @@ namespace Blazor.Diagrams.Core.Anchors { public class ShapeIntersectionAnchor : Anchor { - public ShapeIntersectionAnchor(NodeModel node, Point? offset = null) : base(node, offset) { } + public ShapeIntersectionAnchor(NodeModel model, Point? offset = null) : base(model, offset) { } public override Point? GetPosition(BaseLinkModel link, Point[] route) { - if (Node.Size == null) + var node = (Model as NodeModel)!; + if (node.Size == null) return null; var isTarget = link.Target == this; - var nodeCenter = Node.GetBounds()!.Center; + var nodeCenter = node.GetBounds()!.Center; Point? pt; if (route.Length > 0) { @@ -28,8 +29,10 @@ public ShapeIntersectionAnchor(NodeModel node, Point? offset = null) : base(node if (pt is null) return null; var line = new Line(pt, nodeCenter); - var intersections = Node.GetShape().GetIntersectionsWithLine(line); - return GetClosestPointTo(intersections, pt); + var intersections = node.GetShape().GetIntersectionsWithLine(line); + return GetClosestPointTo(intersections, pt); // Todo: use Offset } + + public override Point? GetPlainPosition() => (Model as NodeModel)!.GetBounds()?.Center ?? null; } } diff --git a/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs index 5d44c2e93..d754761fb 100644 --- a/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs @@ -6,11 +6,11 @@ namespace Blazor.Diagrams.Core.Anchors { public class SinglePortAnchor : Anchor { - public SinglePortAnchor(PortModel port, Point? offset = null) : base(port.Parent, offset) + public SinglePortAnchor(PortModel port, Point? offset = null) : base(port, offset) { Port = port; } - + public PortModel Port { get; } public bool MiddleIfNoMarker { get; set; } = false; public bool UseShapeAndAlignment { get; set; } = true; @@ -22,8 +22,7 @@ public SinglePortAnchor(PortModel port, Point? offset = null) : base(port.Parent if (MiddleIfNoMarker && ((link.Source == this && link.SourceMarker is null) || (link.Target == this && link.TargetMarker is null))) return Port.MiddlePosition; - - + var pt = Port.Position; if (UseShapeAndAlignment) { @@ -40,20 +39,20 @@ public SinglePortAnchor(PortModel port, Point? offset = null) : base(port.Parent _ => null, }; } - else + + return Port.Alignment switch { - return Port.Alignment switch - { - PortAlignment.Top => new Point(pt.X + Port.Size.Width / 2, pt.Y), - PortAlignment.TopRight => new Point(pt.X + Port.Size.Width, pt.Y), - PortAlignment.Right => new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height / 2), - PortAlignment.BottomRight => new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height), - PortAlignment.Bottom => new Point(pt.X + Port.Size.Width / 2, pt.Y + Port.Size.Height), - PortAlignment.BottomLeft => new Point(pt.X, pt.Y + Port.Size.Height), - PortAlignment.Left => new Point(pt.X, pt.Y + Port.Size.Height / 2), - _ => pt, - }; - } + PortAlignment.Top => new Point(pt.X + Port.Size.Width / 2, pt.Y), + PortAlignment.TopRight => new Point(pt.X + Port.Size.Width, pt.Y), + PortAlignment.Right => new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height / 2), + PortAlignment.BottomRight => new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height), + PortAlignment.Bottom => new Point(pt.X + Port.Size.Width / 2, pt.Y + Port.Size.Height), + PortAlignment.BottomLeft => new Point(pt.X, pt.Y + Port.Size.Height), + PortAlignment.Left => new Point(pt.X, pt.Y + Port.Size.Height / 2), + _ => pt, + }; } + + public override Point? GetPlainPosition() => (Model as PortModel)!.MiddlePosition; } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index c2ae120a5..924d1bbd8 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core.Models; +using System; +using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Events; using System.Linq; @@ -17,25 +18,27 @@ public DragNewLinkBehavior(Diagram diagram) : base(diagram) Diagram.PointerUp += OnPointerUp; } - private void OnPointerDown(Model? model, MouseEventArgs e) + public void StartFrom(Anchor source, double clientX, double clientY) { - if (e.Button != (int)MouseEventButton.Left) + if (_ongoingLink != null) return; - - Start(model, e.ClientX, e.ClientY); + + //_ongoingLink = Diagram.Options.Links.Factory(Diagram, port); + _ongoingLink = new LinkModel(source); + _ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5); + Diagram.Links.Add(_ongoingLink); } - private void OnPointerMove(Model? model, MouseEventArgs e) => Move(model, e.ClientX, e.ClientY); - - private void OnPointerUp(Model? model, MouseEventArgs e) => End(model); - - private void Start(Model? model, double clientX, double clientY) + private void OnPointerDown(Model? model, MouseEventArgs e) { + if (e.Button != (int)MouseEventButton.Left) + return; + if (model is PortModel port) { if (port.Locked) return; _ongoingLink = Diagram.Options.Links.Factory(Diagram, port); - _ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5); + _ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5); } else { @@ -45,12 +48,12 @@ private void Start(Model? model, double clientX, double clientY) Diagram.Links.Add(_ongoingLink); } - private void Move(Model? model, double clientX, double clientY) + private void OnPointerMove(Model? model, MouseEventArgs e) { if (_ongoingLink == null || model != null) return; - _ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5); + _ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5); if (Diagram.Options.Links.EnableSnapping) { @@ -64,7 +67,7 @@ private void Move(Model? model, double clientX, double clientY) _ongoingLink.Refresh(); } - private void End(Model? model) + private void OnPointerUp(Model? model, MouseEventArgs e) { if (_ongoingLink == null) return; @@ -75,31 +78,31 @@ private void End(Model? model) return; } - var sourcePort = (_ongoingLink.Source as SinglePortAnchor)!.Port; // Assumption for now - - if (model is not PortModel port || !sourcePort.CanAttachTo(port)) + if (model is ILinkable linkable && _ongoingLink.Source.Model.CanAttachTo(linkable)) + { + var targetAnchor = Diagram.Options.Links.TargetAnchorFactory(Diagram, _ongoingLink, linkable); + _ongoingLink.OnGoingPosition = null; + _ongoingLink.SetTarget(targetAnchor); + _ongoingLink.Refresh(); + } + else { + // Todo: support un-attached links Diagram.Links.Remove(_ongoingLink); - _ongoingLink = null; - return; } - _ongoingLink.OnGoingPosition = null; - _ongoingLink.SetTarget(new SinglePortAnchor(port)); - _ongoingLink.Refresh(); - sourcePort.Parent.Group?.Refresh(); - port?.Parent.Group?.Refresh(); _ongoingLink = null; } private PortModel? FindNearPortToAttachTo() { - var sourcePort = (_ongoingLink!.Source as SinglePortAnchor)!.Port; // Assumption for now - foreach (var port in Diagram.Nodes.SelectMany(n => n.Ports)) { - if (_ongoingLink!.OnGoingPosition!.DistanceTo(port.MiddlePosition) < Diagram.Options.Links.SnappingRadius && sourcePort.CanAttachTo(port)) + if (_ongoingLink!.OnGoingPosition!.DistanceTo(port.MiddlePosition) < Diagram.Options.Links.SnappingRadius + && _ongoingLink.Source.Model.CanAttachTo(port)) + { return port; + } } return null; @@ -112,4 +115,4 @@ public override void Dispose() Diagram.PointerUp -= OnPointerUp; } } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs new file mode 100644 index 000000000..084b2a9ad --- /dev/null +++ b/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs @@ -0,0 +1,35 @@ +using System.Threading.Tasks; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Positions; + +namespace Blazor.Diagrams.Core.Controls.Default; + +public class DragNewLinkControl : ExecutableControl +{ + public DragNewLinkControl(double x, double y, double offsetX = 0, double offsetY = 0) + : base(new BoundsBasedPositionProvider(x, y, offsetX, offsetY)) + { + } + + public DragNewLinkControl(IPositionProvider positionProvider) : base(positionProvider) + { + } + + public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) + { + if (model is not NodeModel node || node.Locked) + return ValueTask.CompletedTask; + + var behavior = diagram.GetBehavior(); + if (behavior == null) + throw new DiagramsException($"DragNewLinkBehavior was not found"); + + // Todo: use factory from options + behavior.StartFrom(new ShapeIntersectionAnchor(node), e.ClientX, e.ClientY); + return ValueTask.CompletedTask; + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs index 00b4beb6f..b0e9739e2 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Positions; @@ -16,11 +17,16 @@ public RemoveControl(IPositionProvider positionProvider) : base(positionProvider { } - public override ValueTask Execute(Diagram diagram, Model model) + public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs _) { - if (model is NodeModel node) + switch (model) { - diagram.Nodes.Remove(node); + case NodeModel node: + diagram.Nodes.Remove(node); + break; + case BaseLinkModel link: + diagram.Links.Remove(link); + break; } return ValueTask.CompletedTask; diff --git a/src/Blazor.Diagrams.Core/Controls/ExecutableControl.cs b/src/Blazor.Diagrams.Core/Controls/ExecutableControl.cs index 4aad9780f..56ef783d6 100644 --- a/src/Blazor.Diagrams.Core/Controls/ExecutableControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/ExecutableControl.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Positions; @@ -16,5 +17,5 @@ protected ExecutableControl(IPositionProvider positionProvider) public override Point? GetPosition(Model model) => PositionProvider.GetPosition(model); - public abstract ValueTask Execute(Diagram diagram, Model model); + public abstract ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e); } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Delegates.cs b/src/Blazor.Diagrams.Core/Delegates.cs index e1c2afd7a..83edb84b8 100644 --- a/src/Blazor.Diagrams.Core/Delegates.cs +++ b/src/Blazor.Diagrams.Core/Delegates.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; @@ -9,6 +10,7 @@ namespace Blazor.Diagrams.Core public delegate PathGeneratorResult PathGenerator(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target); public delegate BaseLinkModel LinkFactory(Diagram diagram, PortModel sourcePort); + public delegate Anchor AnchorFactory(Diagram diagram, BaseLinkModel link, ILinkable model); public delegate GroupModel GroupFactory(Diagram diagram, NodeModel[] children); } diff --git a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs index 3b7df38d0..224a51b32 100644 --- a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs @@ -1,5 +1,4 @@ using Blazor.Diagrams.Core.Anchors; -using Blazor.Diagrams.Core.Anchors.Dynamic; using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Core.Layers @@ -15,9 +14,6 @@ protected override void OnItemAdded(BaseLinkModel link) if (link.Target != null) HandleAnchor(link, link.Target, true); link.Refresh(); - link.Source.Node.Group?.Refresh(); - link.Target?.Node.Group?.Refresh(); - link.SourceChanged += OnLinkSourceChanged; link.TargetChanged += OnLinkTargetChanged; } @@ -29,22 +25,19 @@ protected override void OnItemRemoved(BaseLinkModel link) if (link.Target != null) HandleAnchor(link, link.Target, false); link.Refresh(); - link.Source.Node.Group?.Refresh(); - link.Target?.Node.Group?.Refresh(); - link.SourceChanged -= OnLinkSourceChanged; link.TargetChanged -= OnLinkTargetChanged; Diagram.Controls.RemoveFor(link); } - private void OnLinkSourceChanged(BaseLinkModel link, Anchor old, Anchor @new) + private static void OnLinkSourceChanged(BaseLinkModel link, Anchor old, Anchor @new) { HandleAnchor(link, old, add: false); HandleAnchor(link, @new, add: true); } - private void OnLinkTargetChanged(BaseLinkModel link, Anchor? old, Anchor? @new) + private static void OnLinkTargetChanged(BaseLinkModel link, Anchor? old, Anchor? @new) { if (old != null) HandleAnchor(link, old, add: false); if (@new != null) HandleAnchor(link, @new, add: true); @@ -52,34 +45,16 @@ private void OnLinkTargetChanged(BaseLinkModel link, Anchor? old, Anchor? @new) private static void HandleAnchor(BaseLinkModel link, Anchor anchor, bool add) { - if (anchor is SinglePortAnchor spa) - { - if (add) - { - spa.Port.AddLink(link); - } - else - { - spa.Port.RemoveLink(link); - } - - spa.Port.Refresh(); - } - else if (anchor is ShapeIntersectionAnchor or DynamicAnchor) + if (add) { - if (add) - { - anchor.Node.AddLink(link); - } - else - { - anchor.Node.RemoveLink(link); - } + anchor.Model.AddLink(link); } else { - throw new DiagramsException($"Unhandled Anchor type {anchor.GetType().Name}"); + anchor.Model.RemoveLink(link); } + + anchor.Model.Refresh(); } } } diff --git a/src/Blazor.Diagrams.Core/Models/Base/ILinkable.cs b/src/Blazor.Diagrams.Core/Models/Base/ILinkable.cs new file mode 100644 index 000000000..273bf73fa --- /dev/null +++ b/src/Blazor.Diagrams.Core/Models/Base/ILinkable.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; + +namespace Blazor.Diagrams.Core.Models.Base; + +public interface ILinkable +{ + public IReadOnlyList Links { get; } + + public bool CanAttachTo(ILinkable other); + + public void Refresh(); + + internal void AddLink(BaseLinkModel link); + + internal void RemoveLink(BaseLinkModel link); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs index d97127331..eea80db96 100644 --- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs +++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs @@ -6,7 +6,7 @@ namespace Blazor.Diagrams.Core.Models { - public class NodeModel : MovableModel, IHasBounds, IHasShape + public class NodeModel : MovableModel, IHasBounds, IHasShape, ILinkable { private readonly List _ports = new(); private readonly List _links = new(); @@ -15,9 +15,13 @@ public class NodeModel : MovableModel, IHasBounds, IHasShape public event Action? SizeChanged; public event Action? Moving; - public NodeModel(Point? position = null) : base(position) { } + public NodeModel(Point? position = null) : base(position) + { + } - public NodeModel(string id, Point? position = null) : base(id, position) { } + public NodeModel(string id, Point? position = null) : base(id, position) + { + } public Size? Size { @@ -31,6 +35,7 @@ public Size? Size SizeChanged?.Invoke(this); } } + public GroupModel? Group { get; internal set; } public string? Title { get; set; } @@ -120,10 +125,12 @@ public virtual void UpdatePositionSilently(double deltaX, double deltaY) var left = leftPort == null ? Position.X : Math.Min(Position.X, leftPort.Position.X); var top = topPort == null ? Position.Y : Math.Min(Position.Y, topPort.Position.Y); - var right = rightPort == null ? Position.X + Size!.Width : - Math.Max(rightPort.Position.X + rightPort.Size.Width, Position.X + Size!.Width); - var bottom = bottomPort == null ? Position.Y + Size!.Height : - Math.Max(bottomPort.Position.Y + bottomPort.Size.Height, Position.Y + Size!.Height); + var right = rightPort == null + ? Position.X + Size!.Width + : Math.Max(rightPort.Position.X + rightPort.Size.Width, Position.X + Size!.Width); + var bottom = bottomPort == null + ? Position.Y + Size!.Height + : Math.Max(bottomPort.Position.Y + bottomPort.Size.Height, Position.Y + Size!.Height); return new Rectangle(left, top, right, bottom); } @@ -140,8 +147,10 @@ private void UpdatePortPositions(double deltaX, double deltaY) } } - internal void AddLink(BaseLinkModel link) => _links.Add(link); + public virtual bool CanAttachTo(ILinkable other) => other is not PortModel; + + void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); - internal void RemoveLink(BaseLinkModel link) => _links.Remove(link); + void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs index e20c2fe38..a04e3e175 100644 --- a/src/Blazor.Diagrams.Core/Models/PortModel.cs +++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs @@ -1,10 +1,11 @@ -using Blazor.Diagrams.Core.Geometry; +using System; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; using System.Collections.Generic; namespace Blazor.Diagrams.Core.Models { - public class PortModel : Model, IHasBounds, IHasShape + public class PortModel : Model, IHasBounds, IHasShape, ILinkable { private readonly List _links = new(4); @@ -47,15 +48,18 @@ public void RefreshAll() public T GetParent() where T : NodeModel => (T)Parent; - public virtual bool CanAttachTo(PortModel port) - => port != this && !port.Locked && Parent != port.Parent; // Todo: remove in order to support same node links - public Rectangle GetBounds() => new(Position, Size); public virtual IShape GetShape() => Shapes.Circle(this); - internal void AddLink(BaseLinkModel link) => _links.Add(link); + public virtual bool CanAttachTo(ILinkable other) + { + // Todo: remove in order to support same node links + return other is PortModel port && port != this && !port.Locked && Parent != port.Parent; + } + + void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); - internal void RemoveLink(BaseLinkModel link) => _links.Remove(link); + void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link); } } diff --git a/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs index 6c7a4364d..d8a9e7b9e 100644 --- a/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs +++ b/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs @@ -1,4 +1,5 @@ using System; +using Blazor.Diagrams.Core.Anchors; using Blazor.Diagrams.Core.Models; namespace Blazor.Diagrams.Core.Options; @@ -6,10 +7,11 @@ namespace Blazor.Diagrams.Core.Options; public class DiagramLinkOptions { private double _snappingRadius = 50; - + public Router DefaultRouter { get; set; } = Routers.Normal; public PathGenerator DefaultPathGenerator { get; set; } = PathGenerators.Smooth; public bool EnableSnapping { get; set; } = false; + public double SnappingRadius { get => _snappingRadius; @@ -17,9 +19,20 @@ public double SnappingRadius { if (value <= 0) throw new ArgumentException($"SnappingRadius must be greater than zero"); - + _snappingRadius = value; } } + public LinkFactory Factory { get; set; } = (diagram, sourcePort) => new LinkModel(sourcePort); + + public AnchorFactory TargetAnchorFactory { get; set; } = (diagram, link, model) => + { + return model switch + { + NodeModel node => new ShapeIntersectionAnchor(node), + PortModel port => new SinglePortAnchor(port), + _ => throw new ArgumentOutOfRangeException(nameof(model), model, null) + }; + }; } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs index 81270d511..90de94d82 100644 --- a/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs +++ b/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs @@ -7,8 +7,8 @@ public class DiagramOptions public bool AllowPanning { get; set; } = true; public bool EnableVirtualization { get; set; } = true; // Todo: behavior - public DiagramZoomOptions Zoom { get; set; } = new(); - public DiagramLinkOptions Links { get; set; } = new(); - public DiagramGroupOptions Groups { get; set; } = new(); - public DiagramConstraintsOptions Constraints { get; set; } = new(); + public virtual DiagramZoomOptions Zoom { get; } = new(); + public virtual DiagramLinkOptions Links { get; } = new(); + public virtual DiagramGroupOptions Groups { get; } = new(); + public virtual DiagramConstraintsOptions Constraints { get; } = new(); } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs index 90df06520..5d1599de2 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs @@ -87,7 +87,7 @@ private static Point GetCurvePoint(Point[] route, Anchor? anchor, double pX, dou { return GetCurvePoint(pX, pY, cX, cY, spa.Port.Alignment); } - else if (anchor is ShapeIntersectionAnchor || anchor is DynamicAnchor) + else if (anchor is ShapeIntersectionAnchor or DynamicAnchor) { if (Math.Abs(route[0].X - route[1].X) >= Math.Abs(route[0].Y - route[1].Y)) { diff --git a/src/Blazor.Diagrams/BlazorDiagram.cs b/src/Blazor.Diagrams/BlazorDiagram.cs index 7623a99e4..4b09d5a0d 100644 --- a/src/Blazor.Diagrams/BlazorDiagram.cs +++ b/src/Blazor.Diagrams/BlazorDiagram.cs @@ -18,7 +18,8 @@ public BlazorDiagram(BlazorDiagramOptions? options = null) _componentByModelMapping = new Dictionary { [typeof(RemoveControl)] = typeof(RemoveControlWidget), - [typeof(BoundaryControl)] = typeof(BoundaryControlWidget) + [typeof(BoundaryControl)] = typeof(BoundaryControlWidget), + [typeof(DragNewLinkControl)] = typeof(DragNewLinkControlWidget), }; Options = options ?? new BlazorDiagramOptions(); diff --git a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs index 5422473fc..7e1d97cb6 100644 --- a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs +++ b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs @@ -83,7 +83,7 @@ private async Task OnPointerDown(PointerEventArgs e, Model model, ExecutableCont { if (e.Button == 0 || e.Buttons == 1) { - await control.Execute(BlazorDiagram, model); + await control.OnPointerDown(BlazorDiagram, model, e.ToCore()); } } diff --git a/src/Blazor.Diagrams/Components/Controls/DragNewLinkControlWidget.razor b/src/Blazor.Diagrams/Components/Controls/DragNewLinkControlWidget.razor new file mode 100644 index 000000000..c50e720d9 --- /dev/null +++ b/src/Blazor.Diagrams/Components/Controls/DragNewLinkControlWidget.razor @@ -0,0 +1,23 @@ +@if (Model is SvgNodeModel) +{ + + +} +else +{ + + + + +} + +@code +{ + [Parameter] + public DragNewLinkControl Control { get; set; } = null!; + + [Parameter] + public Model Model { get; set; } = null!; +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Controls/RemoveControlWidget.razor b/src/Blazor.Diagrams/Components/Controls/RemoveControlWidget.razor index cc9ac84cd..d5159c68c 100644 --- a/src/Blazor.Diagrams/Components/Controls/RemoveControlWidget.razor +++ b/src/Blazor.Diagrams/Components/Controls/RemoveControlWidget.razor @@ -14,7 +14,7 @@ else @code { [Parameter] - public Control Control { get; set; } = null!; + public RemoveControl Control { get; set; } = null!; [Parameter] public Model Model { get; set; } = null!; diff --git a/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs b/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs index 755978b3a..b81153525 100644 --- a/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs +++ b/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs @@ -7,8 +7,8 @@ public class BlazorDiagramOptions : DiagramOptions public int LinksLayerOrder { get; set; } = 0; public int NodesLayerOrder { get; set; } = 0; - public new BlazorDiagramZoomOptions Zoom { get; set; } = new(); - public new BlazorDiagramLinkOptions Links { get; set; } = new(); - public new BlazorDiagramGroupOptions Groups { get; set; } = new(); - public new BlazorDiagramConstraintsOptions Constraints { get; set; } = new(); + public override BlazorDiagramZoomOptions Zoom { get; } = new(); + public override BlazorDiagramLinkOptions Links { get; } = new(); + public override BlazorDiagramGroupOptions Groups { get; } = new(); + public override BlazorDiagramConstraintsOptions Constraints { get; } = new(); } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs index b3879355c..99cd33bad 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs @@ -37,7 +37,7 @@ public void SetSource_ShouldChangePropertiesAndTriggerEvent() oldSp.Should().NotBeNull(); newSp.Should().BeSameAs(sp); linkInstance.Should().BeSameAs(link); - link.Source.Node.Should().BeSameAs(parent); + link.Source.Model.Should().BeSameAs(parent); } [Fact] @@ -69,7 +69,7 @@ public void SetTarget_ShouldChangePropertiesAndTriggerEvent() oldTp.Should().BeNull(); newTp.Should().BeSameAs(tp); linkInstance.Should().BeSameAs(link); - link.Target!.Node.Should().BeSameAs(parent); + link.Target!.Model.Should().BeSameAs(parent); } } } From 1ce17afba6b82c34dab7569448d85bf827d91c1a Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 31 Aug 2022 18:52:07 +0100 Subject: [PATCH 069/193] Update BaseLinkModelTests.cs --- .../Models/Base/BaseLinkModelTests.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs index 99cd33bad..1f35b40fd 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs @@ -14,7 +14,8 @@ public void SetSource_ShouldChangePropertiesAndTriggerEvent() // Arrange var link = new LinkModel(sourcePort: new PortModel(null), targetPort: null); var parent = new NodeModel(); - var sp = new SinglePortAnchor(new PortModel(parent)); + var port = new PortModel(parent); + var sp = new SinglePortAnchor(port); var eventsTriggered = 0; Anchor? oldSp = null; Anchor? newSp = null; @@ -37,7 +38,7 @@ public void SetSource_ShouldChangePropertiesAndTriggerEvent() oldSp.Should().NotBeNull(); newSp.Should().BeSameAs(sp); linkInstance.Should().BeSameAs(link); - link.Source.Model.Should().BeSameAs(parent); + link.Source.Model.Should().BeSameAs(port); } [Fact] @@ -46,7 +47,8 @@ public void SetTarget_ShouldChangePropertiesAndTriggerEvent() // Arrange var link = new LinkModel(sourcePort: new PortModel(null), targetPort: null); var parent = new NodeModel(); - var tp = new SinglePortAnchor(new PortModel(parent)); + var port = new PortModel(parent); + var tp = new SinglePortAnchor(port); var eventsTriggered = 0; Anchor? oldTp = null; Anchor? newTp = null; @@ -69,7 +71,7 @@ public void SetTarget_ShouldChangePropertiesAndTriggerEvent() oldTp.Should().BeNull(); newTp.Should().BeSameAs(tp); linkInstance.Should().BeSameAs(link); - link.Target!.Model.Should().BeSameAs(parent); + link.Target!.Model.Should().BeSameAs(port); } } } From be75ddf5b6259746257d8bed00c0d366ffbe9931 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 31 Aug 2022 18:58:03 +0100 Subject: [PATCH 070/193] Fix docs --- docs/CustomNodesLinks/Pages/Index.razor | 8 +++++--- docs/Diagram-Demo/Pages/Diagrams.razor | 11 +++++++---- docs/Layouts/Pages/Index.razor | 19 +++++++++---------- .../Models/Base/ILinkable.cs | 1 + 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/docs/CustomNodesLinks/Pages/Index.razor b/docs/CustomNodesLinks/Pages/Index.razor index 39e69e95e..70d79a890 100644 --- a/docs/CustomNodesLinks/Pages/Index.razor +++ b/docs/CustomNodesLinks/Pages/Index.razor @@ -7,6 +7,8 @@ @using Blazor.Diagrams.Components +@using Blazor.Diagrams.Core.Options +@using Blazor.Diagrams.Options @using CustomNodesLinks.Models @using CustomNodesLinks.Widgets @@ -32,13 +34,13 @@ or it will not be rendered. { base.OnInitialized(); - var options = new DiagramOptions + var options = new BlazorDiagramOptions { AllowMultiSelection = true, // Whether to allow multi selection using CTRL - Links = new DiagramLinkOptions + Links = { }, - Zoom = new DiagramZoomOptions + Zoom = { Minimum = 0.5, // Minimum zoom value Inverse = false, // Whether to inverse the direction of the zoom when using the wheel diff --git a/docs/Diagram-Demo/Pages/Diagrams.razor b/docs/Diagram-Demo/Pages/Diagrams.razor index 3ce1f8bbe..2c56d7ef7 100644 --- a/docs/Diagram-Demo/Pages/Diagrams.razor +++ b/docs/Diagram-Demo/Pages/Diagrams.razor @@ -6,6 +6,9 @@ @using Blazor.Diagrams.Components +@using Blazor.Diagrams.Core.Options +@using Blazor.Diagrams.Options +@using Blazor.Diagrams

    Z Blazor Diagrams

    @@ -31,19 +34,19 @@ or it will not be rendered. { base.OnInitialized(); - var options = new DiagramOptions + var options = new BlazorDiagramOptions { AllowMultiSelection = true, // Whether to allow multi selection using CTRL - Links = new DiagramLinkOptions + Links = { }, - Zoom = new DiagramZoomOptions + Zoom = { Minimum = 0.5, // Minimum zoom value Inverse = false, // Whether to inverse the direction of the zoom when using the wheel } }; - Diagram = new Diagram(options); + Diagram = new BlazorDiagram(options); Setup(); } diff --git a/docs/Layouts/Pages/Index.razor b/docs/Layouts/Pages/Index.razor index b731dcf96..5098c0e67 100644 --- a/docs/Layouts/Pages/Index.razor +++ b/docs/Layouts/Pages/Index.razor @@ -7,7 +7,10 @@ @using Blazor.Diagrams.Components +@using Blazor.Diagrams.Core.Options @using GraphShape.Algorithms.Layout +@using Blazor.Diagrams +@using Blazor.Diagrams.Options

    Hello, World of Layouts!

    @@ -44,19 +47,15 @@ or it will not be rendered. { base.OnInitialized(); - var options = new DiagramOptions + var options = new BlazorDiagramOptions { - AllowMultiSelection = true, // Whether to allow multi selection using CTRL - Links = new DiagramLinkOptions + AllowMultiSelection = true, + Zoom = { - }, - Zoom = new DiagramZoomOptions - { - Minimum = 0.5, // Minimum zoom value - Inverse = false, // Whether to inverse the direction of the zoom when using the wheel - } + Minimum = 0.5 + } // Whether to allow multi selection using CTRL }; - _diagram = new Diagram(options); + _diagram = new BlazorDiagram(options); Setup(); } diff --git a/src/Blazor.Diagrams.Core/Models/Base/ILinkable.cs b/src/Blazor.Diagrams.Core/Models/Base/ILinkable.cs index 273bf73fa..90adf0be4 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/ILinkable.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/ILinkable.cs @@ -4,6 +4,7 @@ namespace Blazor.Diagrams.Core.Models.Base; public interface ILinkable { + public string Id { get; } public IReadOnlyList Links { get; } public bool CanAttachTo(ILinkable other); From cd7b6dd6be4cc38ff7aefdb58d971fa29b0b77ad Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 31 Aug 2022 21:15:41 +0100 Subject: [PATCH 071/193] Add SinglePortAnchor unit tests --- .../Anchors/SinglePortAnchorTests.cs | 163 ++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 tests/Blazor.Diagrams.Core.Tests/Anchors/SinglePortAnchorTests.cs diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/SinglePortAnchorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/SinglePortAnchorTests.cs new file mode 100644 index 000000000..93bcb5752 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/SinglePortAnchorTests.cs @@ -0,0 +1,163 @@ +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using FluentAssertions; +using Moq; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Anchors; + +public class SinglePortAnchorTests +{ + [Fact] + public void GetPlainPosition_ShouldReturnMiddlePosition() + { + // Arrange + var parent = new NodeModel(); + var port = new PortModel(parent) + { + Size = new Size(20, 10), + Position = new Point(100, 50) + }; + var anchor = new SinglePortAnchor(port); + + // Act + var position = anchor.GetPlainPosition()!; + + // Assert + var mp = port.MiddlePosition; + position.X.Should().Be(mp.X); + position.Y.Should().Be(mp.Y); + } + + [Fact] + public void GetPosition_ShouldReturnNull_WhenPortNotInitialized() + { + // Arrange + var parent = new NodeModel(); + var port = new PortModel(parent) + { + Size = new Size(20, 10), + Position = new Point(100, 50) + }; + var anchor = new SinglePortAnchor(port); + var link = new LinkModel(anchor); + + // Act + var position = anchor.GetPosition(link); + + // Assert + position.Should().BeNull(); + } + + [Fact] + public void GetPosition_ShouldReturnMiddlePosition_WhenMiddleIfNoMarker() + { + // Arrange + var parent = new NodeModel(); + var port = new PortModel(parent) + { + Size = new Size(20, 10), + Position = new Point(100, 50), + Initialized = true + }; + var anchor = new SinglePortAnchor(port) + { + MiddleIfNoMarker = true + }; + var link = new LinkModel(anchor); + + // Act + var position = anchor.GetPosition(link)!; + + // Assert + var mp = port.MiddlePosition; + position.X.Should().Be(mp.X); + position.Y.Should().Be(mp.Y); + } + + [Theory] + [InlineData(PortAlignment.Top, 110, 50)] + [InlineData(PortAlignment.TopRight, 120, 50)] + [InlineData(PortAlignment.Right, 120, 55)] + [InlineData(PortAlignment.BottomRight, 120, 60)] + [InlineData(PortAlignment.Bottom, 110, 60)] + [InlineData(PortAlignment.BottomLeft, 100, 60)] + [InlineData(PortAlignment.Left, 100, 55)] + [InlineData(PortAlignment.TopLeft, 100, 50)] + public void GetPosition_ShouldReturnAlignmentBasedPosition_WhenUseShapeAndAlignmentIsFalse(PortAlignment alignment, double x, double y) + { + // Arrange + var parent = new NodeModel(); + var port = new PortModel(parent, alignment) + { + Size = new Size(20, 10), + Position = new Point(100, 50), + Initialized = true + }; + var anchor = new SinglePortAnchor(port) + { + MiddleIfNoMarker = false, + UseShapeAndAlignment = false + }; + var link = new LinkModel(anchor); + + // Act + var position = anchor.GetPosition(link)!; + + // Assert + position.X.Should().Be(x); + position.Y.Should().Be(y); + } + + [Theory] + [InlineData(PortAlignment.Top, 270)] + [InlineData(PortAlignment.TopRight, 315)] + [InlineData(PortAlignment.Right, 0)] + [InlineData(PortAlignment.BottomRight, 45)] + [InlineData(PortAlignment.Bottom, 90)] + [InlineData(PortAlignment.BottomLeft, 135)] + [InlineData(PortAlignment.Left, 180)] + [InlineData(PortAlignment.TopLeft, 225)] + public void GetPosition_ShouldUsePointAtAngle_WhenUseShapeAndAlignmentIsTrue(PortAlignment alignment, double angle) + { + // Arrange + var parent = new NodeModel(); + var shapeMock = new Mock(); + var port = new CustomPortModel(shapeMock.Object, parent, alignment) + { + Size = new Size(20, 10), + Position = new Point(100, 50), + Initialized = true + }; + var anchor = new SinglePortAnchor(port) + { + MiddleIfNoMarker = false, + UseShapeAndAlignment = true + }; + var link = new LinkModel(anchor); + + // Act + var position = anchor.GetPosition(link)!; + + // Assert + shapeMock.Verify(s => s.GetPointAtAngle(angle), Times.Once); + } + + private class CustomPortModel : PortModel + { + private readonly IShape _shape; + + public CustomPortModel(IShape shape, NodeModel parent, PortAlignment alignment = PortAlignment.Bottom, Point? position = null, Size? size = null) : base(parent, alignment, position, size) + { + _shape = shape; + } + + public CustomPortModel(IShape shape, string id, NodeModel parent, PortAlignment alignment = PortAlignment.Bottom, Point? position = null, Size? size = null) : base(id, parent, alignment, position, size) + { + _shape = shape; + } + + public override IShape GetShape() => _shape; + } +} \ No newline at end of file From ef886cda5d0f11926a9a7a84293ade96059ce086 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 1 Sep 2022 11:26:28 +0100 Subject: [PATCH 072/193] Add unit tests for ShapeIntersectionAnchor --- .../Anchors/ShapeIntersectionAnchor.cs | 16 +- .../Anchors/ShapeIntersectionAnchorTests.cs | 167 ++++++++++++++++++ .../Blazor.Diagrams.Core.Tests.csproj | 4 - 3 files changed, 177 insertions(+), 10 deletions(-) create mode 100644 tests/Blazor.Diagrams.Core.Tests/Anchors/ShapeIntersectionAnchorTests.cs diff --git a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs index 4a5c1ffe5..786d0160c 100644 --- a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs @@ -6,16 +6,20 @@ namespace Blazor.Diagrams.Core.Anchors { public class ShapeIntersectionAnchor : Anchor { - public ShapeIntersectionAnchor(NodeModel model, Point? offset = null) : base(model, offset) { } + public ShapeIntersectionAnchor(NodeModel model, Point? offset = null) : base(model, offset) + { + Node = model; + } + + public NodeModel Node { get; } public override Point? GetPosition(BaseLinkModel link, Point[] route) { - var node = (Model as NodeModel)!; - if (node.Size == null) + if (Node.Size == null) return null; var isTarget = link.Target == this; - var nodeCenter = node.GetBounds()!.Center; + var nodeCenter = Node.GetBounds()!.Center; Point? pt; if (route.Length > 0) { @@ -29,10 +33,10 @@ public ShapeIntersectionAnchor(NodeModel model, Point? offset = null) : base(mod if (pt is null) return null; var line = new Line(pt, nodeCenter); - var intersections = node.GetShape().GetIntersectionsWithLine(line); + var intersections = Node.GetShape().GetIntersectionsWithLine(line); return GetClosestPointTo(intersections, pt); // Todo: use Offset } - public override Point? GetPlainPosition() => (Model as NodeModel)!.GetBounds()?.Center ?? null; + public override Point? GetPlainPosition() => Node.GetBounds()?.Center ?? null; } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/ShapeIntersectionAnchorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/ShapeIntersectionAnchorTests.cs new file mode 100644 index 000000000..658b0ff11 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/ShapeIntersectionAnchorTests.cs @@ -0,0 +1,167 @@ +using System.Collections.Generic; +using System.Linq; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using FluentAssertions; +using Moq; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Anchors; + +public class ShapeIntersectionAnchorTests +{ + [Fact] + public void GetPlainPosition_ShouldReturnNodeCenter() + { + // Arrange + var node = new NodeModel + { + Size = new Size(100, 80), + Position = new Point(60, 60) + }; + var anchor = new ShapeIntersectionAnchor(node); + + // Act + var position = anchor.GetPlainPosition()!; + + // Assert + var center = node.GetBounds()!.Center; + position.X.Should().Be(center.X); + position.Y.Should().Be(center.Y); + } + + [Fact] + public void GetPosition_ShouldReturnNull_WhenNodeSizeIsNull() + { + // Arrange + var node = new NodeModel(new Point(60, 60)); + var anchor = new ShapeIntersectionAnchor(node); + var link = new LinkModel(anchor); + + // Act + var position = anchor.GetPosition(link); + + // Assert + position.Should().BeNull(); + } + + [Fact] + public void GetPosition_ShouldUseRouteToFindOtherPositionForIntersection_WhenSource() + { + // Arrange + var shapeMock = new Mock(); + var args = new List(); + shapeMock.Setup(s => s.GetIntersectionsWithLine(Capture.In(args))); + var node = new CustomNodeModel(shapeMock.Object) + { + Size = new Size(100, 80), + Position = new Point(60, 60) + }; + var source = new ShapeIntersectionAnchor(node); + var link = new LinkModel(source); + var route = new[] { new Point(170, 100), new Point(180, 110) }; + + // Act + source.GetPosition(link, route); + + // Assert + var line = args.Single(); + line.Start.Should().BeEquivalentTo(route[0]); + line.End.Should().BeEquivalentTo(node.GetBounds()!.Center); + } + + [Fact] + public void GetPosition_ShouldUseRouteToFindOtherPositionForIntersection_WhenTarget() + { + // Arrange + var shapeMock = new Mock(); + var args = new List(); + shapeMock.Setup(s => s.GetIntersectionsWithLine(Capture.In(args))); + var node = new CustomNodeModel(shapeMock.Object) + { + Size = new Size(100, 80), + Position = new Point(60, 60) + }; + var source = new ShapeIntersectionAnchor(node); + var target = new ShapeIntersectionAnchor(node); + var link = new LinkModel(source, target); + var route = new[] { new Point(170, 100), new Point(180, 110) }; + + // Act + target.GetPosition(link, route); + + // Assert + var line = args.Single(); + line.Start.Should().BeEquivalentTo(route[^1]); + line.End.Should().BeEquivalentTo(node.GetBounds()!.Center); + } + + [Fact] + public void GetPosition_ShouldCallOtherGetPlainPosition_WhenNoRoute() + { + // Arrange + var shapeMock = new Mock(); + var args = new List(); + shapeMock.Setup(s => s.GetIntersectionsWithLine(Capture.In(args))); + var node = new CustomNodeModel(shapeMock.Object) + { + Size = new Size(100, 80), + Position = new Point(60, 60) + }; + var source = new ShapeIntersectionAnchor(node); + var targetMock = new Mock(node, Point.Zero); + var pt = new Point(-55, -55); + targetMock.Setup(t => t.GetPlainPosition()).Returns(pt); + var link = new LinkModel(source, targetMock.Object); + + // Act + source.GetPosition(link); + + // Assert + var line = args.Single(); + line.Start.Should().BeEquivalentTo(pt); + line.End.Should().BeEquivalentTo(node.GetBounds()!.Center); + } + + [Fact] + public void GetPosition_ShouldReturnNull_WhenOtherPositionIsNull() + { + // Arrange + var shapeMock = new Mock(); + var args = new List(); + shapeMock.Setup(s => s.GetIntersectionsWithLine(Capture.In(args))); + var node = new CustomNodeModel(shapeMock.Object) + { + Size = new Size(100, 80), + Position = new Point(60, 60) + }; + var source = new ShapeIntersectionAnchor(node); + var targetMock = new Mock(node, Point.Zero); + targetMock.Setup(t => t.GetPlainPosition()).Returns((Point?)null); + var link = new LinkModel(source, targetMock.Object); + + // Act + var position = source.GetPosition(link); + + // Assert + position.Should().BeNull(); + } + + private class CustomNodeModel : NodeModel + { + private readonly IShape _shape; + + public CustomNodeModel(IShape shape, Point? position = null) : base(position) + { + _shape = shape; + } + + public CustomNodeModel(IShape shape, string id, Point? position = null) : base(id, position) + { + _shape = shape; + } + + public override IShape GetShape() => _shape; + } +} \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj index e38fcaff0..85db1b8cb 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj +++ b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj @@ -25,8 +25,4 @@ - - - - From 4e0dfe123f2e7091698a455a22c726e609ce3817 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 1 Sep 2022 22:38:14 +0100 Subject: [PATCH 073/193] Update Blazor.Diagrams.Tests.csproj --- .../Blazor.Diagrams.Tests.csproj | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj index ade3cbc69..b6f8cf0ab 100644 --- a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj +++ b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj @@ -8,12 +8,18 @@ - - + + + - - - + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + From 331076d588bb9e9d922935a0c76d3318b3bde275 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 1 Sep 2022 22:45:02 +0100 Subject: [PATCH 074/193] Update Blazor.Diagrams.csproj --- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index 5a2ac0301..af8c89959 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -3,6 +3,7 @@ net6.0 enable + true zHaytam MIT 2.1.6 @@ -17,8 +18,7 @@ Z.Blazor.Diagrams ZBD.png - - + From ea47d8a0d015d89b96cd171db839428fe7138f06 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 1 Sep 2022 23:09:39 +0100 Subject: [PATCH 075/193] Add new Blazor.Diagrams.Widgets namespace --- samples/SharedDemo/Demos/Simple.razor | 1 + samples/SharedDemo/_Imports.razor | 1 + src/Blazor.Diagrams/Blazor.Diagrams.csproj | 1 - .../{ => Widgets}/NavigatorWidget.razor | 0 .../{ => Widgets}/NavigatorWidget.razor.cs | 2 +- .../{ => Widgets}/SelectionBoxWidget.razor | 0 .../{ => Widgets}/SelectionBoxWidget.razor.cs | 44 ++++++++++--------- 7 files changed, 27 insertions(+), 22 deletions(-) rename src/Blazor.Diagrams/Components/{ => Widgets}/NavigatorWidget.razor (100%) rename src/Blazor.Diagrams/Components/{ => Widgets}/NavigatorWidget.razor.cs (99%) rename src/Blazor.Diagrams/Components/{ => Widgets}/SelectionBoxWidget.razor (100%) rename src/Blazor.Diagrams/Components/{ => Widgets}/SelectionBoxWidget.razor.cs (62%) diff --git a/samples/SharedDemo/Demos/Simple.razor b/samples/SharedDemo/Demos/Simple.razor index 0fcfe137c..a4019cf51 100644 --- a/samples/SharedDemo/Demos/Simple.razor +++ b/samples/SharedDemo/Demos/Simple.razor @@ -33,6 +33,7 @@ + diff --git a/samples/SharedDemo/_Imports.razor b/samples/SharedDemo/_Imports.razor index f86ed02bf..2488d2214 100644 --- a/samples/SharedDemo/_Imports.razor +++ b/samples/SharedDemo/_Imports.razor @@ -8,4 +8,5 @@ @using Blazor.Diagrams.Core.Extensions; @using Blazor.Diagrams.Core.Geometry; @using Blazor.Diagrams.Components.Renderers; +@using Blazor.Diagrams.Components.Widgets; @using SharedDemo.Components; \ No newline at end of file diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index af8c89959..118b4654d 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -3,7 +3,6 @@ net6.0 enable - true zHaytam MIT 2.1.6 diff --git a/src/Blazor.Diagrams/Components/NavigatorWidget.razor b/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor similarity index 100% rename from src/Blazor.Diagrams/Components/NavigatorWidget.razor rename to src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor diff --git a/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs similarity index 99% rename from src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs rename to src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs index 471135591..29c81e14d 100644 --- a/src/Blazor.Diagrams/Components/NavigatorWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs @@ -7,7 +7,7 @@ using System.Linq; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Components +namespace Blazor.Diagrams.Components.Widgets { public partial class NavigatorWidget : IDisposable { diff --git a/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor b/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor similarity index 100% rename from src/Blazor.Diagrams/Components/SelectionBoxWidget.razor rename to src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor diff --git a/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor.cs similarity index 62% rename from src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs rename to src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor.cs index e76a322fc..b50c7889f 100644 --- a/src/Blazor.Diagrams/Components/SelectionBoxWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor.cs @@ -1,23 +1,20 @@ -using Blazor.Diagrams.Core; -using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Events; using Microsoft.AspNetCore.Components; using System; -namespace Blazor.Diagrams.Components +namespace Blazor.Diagrams.Components.Widgets { public partial class SelectionBoxWidget : IDisposable { - private Point _initialClientPoint; - private Point _selectionBoxTopLeft; - private Size _selectionBoxSize; + private Point? _initialClientPoint; + private Point? _selectionBoxTopLeft; // Todo: remove unneeded instantiations + private Size? _selectionBoxSize; // Todo: remove unneeded instantiations - [CascadingParameter] - public BlazorDiagram BlazorDiagram { get; set; } + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; - [Parameter] - public string Background { get; set; } = "rgb(110 159 212 / 25%);"; + [Parameter] public string Background { get; set; } = "rgb(110 159 212 / 25%);"; protected override void OnInitialized() { @@ -27,9 +24,12 @@ protected override void OnInitialized() } private string GenerateStyle() - => FormattableString.Invariant($"position: absolute; background: {Background}; top: {_selectionBoxTopLeft.Y}px; left: {_selectionBoxTopLeft.X}px; width: {_selectionBoxSize.Width}px; height: {_selectionBoxSize.Height}px;"); + { + return FormattableString.Invariant( + $"position: absolute; background: {Background}; top: {_selectionBoxTopLeft!.Y}px; left: {_selectionBoxTopLeft.X}px; width: {_selectionBoxSize!.Width}px; height: {_selectionBoxSize.Height}px;"); + } - private void OnPointerDown(Model model, MouseEventArgs e) + private void OnPointerDown(Model? model, MouseEventArgs e) { if (model != null || !e.ShiftKey) return; @@ -37,7 +37,7 @@ private void OnPointerDown(Model model, MouseEventArgs e) _initialClientPoint = new Point(e.ClientX, e.ClientY); } - private void OnPointerMove(Model model, MouseEventArgs e) + private void OnPointerMove(Model? model, MouseEventArgs e) { if (_initialClientPoint == null) return; @@ -46,13 +46,17 @@ private void OnPointerMove(Model model, MouseEventArgs e) var start = BlazorDiagram.GetRelativeMousePoint(_initialClientPoint.X, _initialClientPoint.Y); var end = BlazorDiagram.GetRelativeMousePoint(e.ClientX, e.ClientY); - (var sX, var sY) = (Math.Min(start.X, end.X), Math.Min(start.Y, end.Y)); - (var eX, var eY) = (Math.Max(start.X, end.X), Math.Max(start.Y, end.Y)); + 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 BlazorDiagram.Nodes) { - if (bounds.Overlap(node.GetBounds())) + var nodeBounds = node.GetBounds(); + if (nodeBounds == null) + continue; + + if (bounds.Overlap(nodeBounds)) { BlazorDiagram.SelectModel(node, false); } @@ -67,7 +71,7 @@ private void OnPointerMove(Model model, MouseEventArgs e) private void SetSelectionBoxInformation(MouseEventArgs e) { - var start = BlazorDiagram.GetRelativePoint(_initialClientPoint.X, _initialClientPoint.Y); + var start = BlazorDiagram.GetRelativePoint(_initialClientPoint!.X, _initialClientPoint.Y); var end = BlazorDiagram.GetRelativePoint(e.ClientX, e.ClientY); (var sX, var sY) = (Math.Min(start.X, end.X), Math.Min(start.Y, end.Y)); (var eX, var eY) = (Math.Max(start.X, end.X), Math.Max(start.Y, end.Y)); @@ -75,7 +79,7 @@ private void SetSelectionBoxInformation(MouseEventArgs e) _selectionBoxSize = new Size(eX - sX, eY - sY); } - private void OnPointerUp(Model model, MouseEventArgs e) + private void OnPointerUp(Model? model, MouseEventArgs e) { _initialClientPoint = null; _selectionBoxTopLeft = null; @@ -90,4 +94,4 @@ public void Dispose() BlazorDiagram.PointerUp -= OnPointerUp; } } -} +} \ No newline at end of file From e6eacbfd00cdd698c40f446b348e23540a0fa31d Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 1 Sep 2022 23:21:38 +0100 Subject: [PATCH 076/193] Refactor --- samples/SharedDemo/Demos/Simple.razor | 1 + src/Blazor.Diagrams/Blazor.Diagrams.csproj | 80 ++--- src/Blazor.Diagrams/BlazorDiagram.cs | 94 +++--- .../Controls/ControlsLayerRenderer.razor.cs | 21 +- .../Controls/DragNewLinkControlWidget.razor | 6 +- .../Components/DefaultGroupWidget.razor | 6 +- .../Components/DefaultLinkLabelWidget.razor | 10 +- .../Components/DiagramCanvas.razor | 14 +- .../Components/DiagramCanvas.razor.cs | 143 ++++---- .../Components/GroupNodes.razor | 3 +- .../Components/LinkVertexWidget.razor | 2 +- .../Components/LinkVertexWidget.razor.cs | 100 +++--- .../Components/LinkWidget.razor | 4 +- .../Components/LinkWidget.razor.cs | 45 ++- .../Components/NodeWidget.razor | 2 + .../Components/Renderers/GroupRenderer.cs | 193 +++++------ .../Components/Renderers/LinkLabelRenderer.cs | 102 +++--- .../Components/Renderers/LinkRenderer.cs | 110 +++--- .../Components/Renderers/NodeRenderer.cs | 300 +++++++++-------- .../Components/Renderers/PortRenderer.cs | 238 +++++++------ .../Components/SvgNodeWidget.razor | 4 +- .../Components/Widgets/NavigatorWidget.razor | 4 +- .../Widgets/NavigatorWidget.razor.cs | 312 ++++++++---------- .../Widgets/SelectionBoxWidget.razor.cs | 150 ++++----- .../Extensions/EventsExtensions.cs | 47 ++- .../Extensions/JSRuntimeExtensions.cs | 47 ++- .../Extensions/StringBuilderExtensions.cs | 18 +- src/Blazor.Diagrams/Models/SvgGroupModel.cs | 13 +- src/Blazor.Diagrams/Models/SvgNodeModel.cs | 13 +- .../Options/BlazorDiagramOptions.cs | 2 +- .../wwwroot/default.styles.css | 135 ++++---- src/Blazor.Diagrams/wwwroot/script.js | 2 +- 32 files changed, 1116 insertions(+), 1105 deletions(-) diff --git a/samples/SharedDemo/Demos/Simple.razor b/samples/SharedDemo/Demos/Simple.razor index a4019cf51..75b653063 100644 --- a/samples/SharedDemo/Demos/Simple.razor +++ b/samples/SharedDemo/Demos/Simple.razor @@ -34,6 +34,7 @@ + diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index 118b4654d..4195dd2a5 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -1,48 +1,48 @@  - - net6.0 - enable - zHaytam - MIT - 2.1.6 - 2.1.6 - https://github.com/zHaytam/Blazor.Diagrams - A fully customizable and extensible all-purpose diagrams library for Blazor - 2.1.6 - true - blazor diagrams diagramming svg drag - Z.Blazor.Diagrams - https://blazor-diagrams.zhaytam.com/ - Z.Blazor.Diagrams - ZBD.png - - - - - - + + net6.0 + enable + zHaytam + MIT + 2.1.6 + 2.1.6 + https://github.com/zHaytam/Blazor.Diagrams + A fully customizable and extensible all-purpose diagrams library for Blazor + 2.1.6 + true + blazor diagrams diagramming svg drag + Z.Blazor.Diagrams + https://blazor-diagrams.zhaytam.com/ + Z.Blazor.Diagrams + ZBD.png + - - - + + + + - - - True - - - + + + - - - - - - + + + True + + + - - - + + + + + + + + + + diff --git a/src/Blazor.Diagrams/BlazorDiagram.cs b/src/Blazor.Diagrams/BlazorDiagram.cs index 4b09d5a0d..8bcd9ef7e 100644 --- a/src/Blazor.Diagrams/BlazorDiagram.cs +++ b/src/Blazor.Diagrams/BlazorDiagram.cs @@ -1,69 +1,65 @@ -using Blazor.Diagrams.Core; -using Blazor.Diagrams.Core.Models.Base; -using Microsoft.AspNetCore.Components; -using System; +using System; using System.Collections.Generic; using Blazor.Diagrams.Components.Controls; +using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Options; +using Microsoft.AspNetCore.Components; + +namespace Blazor.Diagrams; -namespace Blazor.Diagrams +public class BlazorDiagram : Diagram { - public class BlazorDiagram : Diagram - { - private readonly Dictionary _componentByModelMapping; + private readonly Dictionary _componentByModelMapping; - public BlazorDiagram(BlazorDiagramOptions? options = null) + public BlazorDiagram(BlazorDiagramOptions? options = null) + { + _componentByModelMapping = new Dictionary { - _componentByModelMapping = new Dictionary - { - [typeof(RemoveControl)] = typeof(RemoveControlWidget), - [typeof(BoundaryControl)] = typeof(BoundaryControlWidget), - [typeof(DragNewLinkControl)] = typeof(DragNewLinkControlWidget), - }; + [typeof(RemoveControl)] = typeof(RemoveControlWidget), + [typeof(BoundaryControl)] = typeof(BoundaryControlWidget), + [typeof(DragNewLinkControl)] = typeof(DragNewLinkControlWidget) + }; - Options = options ?? new BlazorDiagramOptions(); - } + Options = options ?? new BlazorDiagramOptions(); + } - public override BlazorDiagramOptions Options { get; } + public override BlazorDiagramOptions Options { get; } - public void RegisterModelComponent(bool replace = false) - where TModel : Model where TComponent : ComponentBase - { - RegisterModelComponent(typeof(TModel), typeof(TComponent), replace); - } + public void RegisterModelComponent(bool replace = false) + where TModel : Model where TComponent : ComponentBase + { + RegisterModelComponent(typeof(TModel), typeof(TComponent), replace); + } - public void RegisterModelComponent(Type modelType, Type componentType, bool replace = false) - { - if (!replace && _componentByModelMapping.ContainsKey(modelType)) - throw new Exception($"Component already registered for model '{modelType.Name}'."); + public void RegisterModelComponent(Type modelType, Type componentType, bool replace = false) + { + if (!replace && _componentByModelMapping.ContainsKey(modelType)) + throw new Exception($"Component already registered for model '{modelType.Name}'."); - _componentByModelMapping[modelType] = componentType; - } + _componentByModelMapping[modelType] = componentType; + } - public Type? GetComponentForModel(Type modelType, bool checkSubclasses = true) - { - if (_componentByModelMapping.ContainsKey(modelType)) - { - return _componentByModelMapping[modelType]; - } + public Type? GetComponentForModel(Type modelType, bool checkSubclasses = true) + { + if (_componentByModelMapping.ContainsKey(modelType)) return _componentByModelMapping[modelType]; - if (checkSubclasses) - { - foreach (var rmt in _componentByModelMapping.Keys) - { - if (modelType.IsSubclassOf(rmt)) - return _componentByModelMapping[rmt]; - } - } + if (checkSubclasses) + foreach (var rmt in _componentByModelMapping.Keys) + if (modelType.IsSubclassOf(rmt)) + return _componentByModelMapping[rmt]; - return null; - } + return null; + } - public Type? GetComponentForModel(bool checkSubclasses = true) where TModel : Model - => GetComponentForModel(typeof(TModel), checkSubclasses); + public Type? GetComponentForModel(bool checkSubclasses = true) where TModel : Model + { + return GetComponentForModel(typeof(TModel), checkSubclasses); + } - public Type? GetComponentForModel(Model model, bool checkSubclasses = true) - => GetComponentForModel(model.GetType(), checkSubclasses); + public Type? GetComponentForModel(Model model, bool checkSubclasses = true) + { + return GetComponentForModel(model.GetType(), checkSubclasses); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs index 7e1d97cb6..9fc2e0f48 100644 --- a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs +++ b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs @@ -1,9 +1,9 @@ using System; using System.Threading.Tasks; +using Blazor.Diagrams.Core.Controls; using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; -using Blazor.Diagrams.Core.Controls; using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; @@ -18,6 +18,11 @@ public partial class ControlsLayerRenderer : IDisposable [Parameter] public bool Svg { get; set; } + public void Dispose() + { + BlazorDiagram.Controls.ChangeCaused -= OnControlsChangeCaused; + } + protected override void OnInitialized() { BlazorDiagram.Controls.ChangeCaused += OnControlsChangeCaused; @@ -54,15 +59,11 @@ private RenderFragment RenderControl(Model model, Control control, Point positio builder.AddAttribute(1, "class", $"{(control is ExecutableControl ? "executable " : "")}control {control.GetType().Name}"); if (svg) - { builder.AddAttribute(2, "transform", $"translate({position.X.ToInvariantString()} {position.Y.ToInvariantString()})"); - } else - { builder.AddAttribute(2, "style", $"top: {position.Y.ToInvariantString()}px; left: {position.X.ToInvariantString()}px"); - } if (control is ExecutableControl ec) { @@ -81,14 +82,6 @@ private RenderFragment RenderControl(Model model, Control control, Point positio private async Task OnPointerDown(PointerEventArgs e, Model model, ExecutableControl control) { - if (e.Button == 0 || e.Buttons == 1) - { - await control.OnPointerDown(BlazorDiagram, model, e.ToCore()); - } - } - - public void Dispose() - { - BlazorDiagram.Controls.ChangeCaused -= OnControlsChangeCaused; + if (e.Button == 0 || e.Buttons == 1) await control.OnPointerDown(BlazorDiagram, model, e.ToCore()); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Controls/DragNewLinkControlWidget.razor b/src/Blazor.Diagrams/Components/Controls/DragNewLinkControlWidget.razor index c50e720d9..35c85b2ed 100644 --- a/src/Blazor.Diagrams/Components/Controls/DragNewLinkControlWidget.razor +++ b/src/Blazor.Diagrams/Components/Controls/DragNewLinkControlWidget.razor @@ -2,14 +2,16 @@ { + fill="black"> + } else { + fill="black"> + } diff --git a/src/Blazor.Diagrams/Components/DefaultGroupWidget.razor b/src/Blazor.Diagrams/Components/DefaultGroupWidget.razor index 0d04b2789..3e40af066 100644 --- a/src/Blazor.Diagrams/Components/DefaultGroupWidget.razor +++ b/src/Blazor.Diagrams/Components/DefaultGroupWidget.razor @@ -1,11 +1,13 @@ @code { + [Parameter] public GroupModel Group { get; set; } = null!; + } - + @foreach (var port in Group.Ports) { -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor b/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor index 292d3dc0e..43f2b27c0 100644 --- a/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor +++ b/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor @@ -1,9 +1,13 @@ @((MarkupString)$"") @code { - [Parameter] public LinkLabelModel Label { get; set; } - [Parameter] public Point Position { get; set; } + + [Parameter] + public LinkLabelModel Label { get; set; } + + [Parameter] + public Point Position { get; set; } private double X => Position.X + (Label.Offset?.X ?? 0); private double Y => Position.Y + (Label.Offset?.Y ?? 0); -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor b/src/Blazor.Diagrams/Components/DiagramCanvas.razor index f89ae47f5..790404eca 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor @@ -13,19 +13,19 @@ @foreach (var node in BlazorDiagram.Nodes.OfType().Where(n => n.Group == null)) { - + } @foreach (var group in BlazorDiagram.Groups.OfType().Where(n => n.Group == null)) { - + } @foreach (var link in BlazorDiagram.Links) { - + } - + @@ -34,14 +34,14 @@ @foreach (var group in BlazorDiagram.Groups.Where(n => n is not SvgGroupModel)) { if (group.Group != null) continue; - + } @foreach (var node in BlazorDiagram.Nodes.Where(n => n is not SvgNodeModel && n.Group == null)) { - + } - +
    diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index d01914d4d..b8c60b3fb 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -1,93 +1,108 @@ -using Blazor.Diagrams.Core.Geometry; +using System; +using System.Threading.Tasks; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; using Microsoft.JSInterop; -using System; -using System.Threading.Tasks; -namespace Blazor.Diagrams.Components +namespace Blazor.Diagrams.Components; + +public partial class DiagramCanvas : IDisposable { - public partial class DiagramCanvas : IDisposable - { - [CascadingParameter] - public BlazorDiagram BlazorDiagram { get; set; } = null!; + private DotNetObjectReference? _reference; + private bool _shouldRender; - [Parameter] - public RenderFragment? Widgets { get; set; } + protected ElementReference elementReference; - [Parameter] - public string? Class { get; set; } + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; - [Inject] - public IJSRuntime JSRuntime { get; set; } = null!; + [Parameter] public RenderFragment? Widgets { get; set; } - protected ElementReference elementReference; - private DotNetObjectReference? _reference; - private bool _shouldRender; + [Parameter] public string? Class { get; set; } - private string GetLayerStyle(int order) - { - return FormattableString.Invariant($"transform: translate({BlazorDiagram.Pan.X}px, {BlazorDiagram.Pan.Y}px) scale({BlazorDiagram.Zoom}); z-index: {order};"); - } + [Inject] public IJSRuntime JSRuntime { get; set; } = null!; - protected override void OnInitialized() - { - base.OnInitialized(); + public void Dispose() + { + BlazorDiagram.Changed -= OnDiagramChanged; - _reference = DotNetObjectReference.Create(this); - BlazorDiagram.Changed += OnDiagramChanged; - } + if (_reference == null) + return; - protected override async Task OnAfterRenderAsync(bool firstRender) - { - await base.OnAfterRenderAsync(firstRender); + if (elementReference.Id != null) + _ = JSRuntime.UnobserveResizes(elementReference); - if (firstRender) - { - BlazorDiagram.SetContainer(await JSRuntime.GetBoundingClientRect(elementReference)); - await JSRuntime.ObserveResizes(elementReference, _reference!); - } - } + _reference.Dispose(); + } + + private string GetLayerStyle(int order) + { + return FormattableString.Invariant( + $"transform: translate({BlazorDiagram.Pan.X}px, {BlazorDiagram.Pan.Y}px) scale({BlazorDiagram.Zoom}); z-index: {order};"); + } + + protected override void OnInitialized() + { + base.OnInitialized(); - [JSInvokable] - public void OnResize(Rectangle rect) => BlazorDiagram.SetContainer(rect); + _reference = DotNetObjectReference.Create(this); + BlazorDiagram.Changed += OnDiagramChanged; + } - protected override bool ShouldRender() + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + + if (firstRender) { - if (!_shouldRender) return false; - - _shouldRender = false; - return true; + BlazorDiagram.SetContainer(await JSRuntime.GetBoundingClientRect(elementReference)); + await JSRuntime.ObserveResizes(elementReference, _reference!); } + } - private void OnPointerDown(PointerEventArgs e) => BlazorDiagram.TriggerPointerDown(null, e.ToCore()); - - private void OnPointerMove(PointerEventArgs e) => BlazorDiagram.TriggerPointerMove(null, e.ToCore()); + [JSInvokable] + public void OnResize(Rectangle rect) + { + BlazorDiagram.SetContainer(rect); + } - private void OnPointerUp(PointerEventArgs e) => BlazorDiagram.TriggerPointerUp(null, e.ToCore()); + protected override bool ShouldRender() + { + if (!_shouldRender) return false; - private void OnKeyDown(KeyboardEventArgs e) => BlazorDiagram.TriggerKeyDown(e.ToCore()); + _shouldRender = false; + return true; + } - private void OnWheel(WheelEventArgs e) => BlazorDiagram.TriggerWheel(e.ToCore()); + private void OnPointerDown(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerDown(null, e.ToCore()); + } - private void OnDiagramChanged() - { - _shouldRender = true; - InvokeAsync(StateHasChanged); - } + private void OnPointerMove(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerMove(null, e.ToCore()); + } - public void Dispose() - { - BlazorDiagram.Changed -= OnDiagramChanged; + private void OnPointerUp(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerUp(null, e.ToCore()); + } - if (_reference == null) - return; + private void OnKeyDown(KeyboardEventArgs e) + { + BlazorDiagram.TriggerKeyDown(e.ToCore()); + } - if (elementReference.Id != null) - _ = JSRuntime.UnobserveResizes(elementReference); + private void OnWheel(WheelEventArgs e) + { + BlazorDiagram.TriggerWheel(e.ToCore()); + } - _reference.Dispose(); - } + private void OnDiagramChanged() + { + _shouldRender = true; + InvokeAsync(StateHasChanged); } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/GroupNodes.razor b/src/Blazor.Diagrams/Components/GroupNodes.razor index 8261369db..d545c4d35 100644 --- a/src/Blazor.Diagrams/Components/GroupNodes.razor +++ b/src/Blazor.Diagrams/Components/GroupNodes.razor @@ -1,4 +1,5 @@ @code { + [Parameter] public GroupModel Group { get; set; } = null!; @@ -36,4 +37,4 @@ else } } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor index 19febf708..3ed0177ee 100644 --- a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor +++ b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor @@ -7,4 +7,4 @@ @onpointerdown="OnPointerDown" @onpointerup="OnPointerUp" @onpointerdown:stopPropagation - @onpointerup:stopPropagation /> \ No newline at end of file + @onpointerup:stopPropagation/> \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs index 9cc31f941..60e0beec8 100644 --- a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs @@ -1,56 +1,60 @@ -using Blazor.Diagrams.Core; +using System; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; -using System; -using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Components +namespace Blazor.Diagrams.Components; + +public partial class LinkVertexWidget : IDisposable { - public partial class LinkVertexWidget : IDisposable + private bool _shouldRender = true; + + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; + [Parameter] public LinkVertexModel Vertex { get; set; } = null!; + [Parameter] public string? Color { get; set; } + [Parameter] public string? SelectedColor { get; set; } + + private string? ColorToUse => Vertex.Selected ? SelectedColor : Color; + + public void Dispose() + { + Vertex.Changed -= OnVertexChanged; + } + + protected override void OnInitialized() + { + Vertex.Changed += OnVertexChanged; + } + + protected override bool ShouldRender() + { + if (!_shouldRender) return false; + + _shouldRender = false; + return true; + } + + private void OnVertexChanged(Model _) + { + _shouldRender = true; + InvokeAsync(StateHasChanged); + } + + private void OnPointerDown(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerDown(Vertex, e.ToCore()); + } + + private void OnPointerUp(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerUp(Vertex, e.ToCore()); + } + + private void OnDoubleClick(MouseEventArgs e) { - private bool _shouldRender = true; - - [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; - [Parameter] public LinkVertexModel Vertex { get; set; } = null!; - [Parameter] public string? Color { get; set; } - [Parameter] public string? SelectedColor { get; set; } - - private string? ColorToUse => Vertex.Selected ? SelectedColor : Color; - - public void Dispose() - { - Vertex.Changed -= OnVertexChanged; - } - - protected override void OnInitialized() - { - Vertex.Changed += OnVertexChanged; - } - - protected override bool ShouldRender() - { - if (!_shouldRender) return false; - - _shouldRender = false; - return true; - } - - private void OnVertexChanged(Model _) - { - _shouldRender = true; - InvokeAsync(StateHasChanged); - } - - private void OnPointerDown(PointerEventArgs e) => BlazorDiagram.TriggerPointerDown(Vertex, e.ToCore()); - - private void OnPointerUp(PointerEventArgs e) => BlazorDiagram.TriggerPointerUp(Vertex, e.ToCore()); - - private void OnDoubleClick(MouseEventArgs e) - { - Vertex.Parent.Vertices.Remove(Vertex); - Vertex.Parent.Refresh(); - } + Vertex.Parent.Vertices.Remove(Vertex); + Vertex.Parent.Refresh(); } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor b/src/Blazor.Diagrams/Components/LinkWidget.razor index 1cd6d3987..54ca618f3 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor @@ -1,5 +1,5 @@ @{ - var color = Link.Selected ? (Link.SelectedColor ?? BlazorDiagram.Options.Links.DefaultSelectedColor) : (Link.Color ?? BlazorDiagram.Options.Links.DefaultColor); + var color = Link.Selected ? Link.SelectedColor ?? BlazorDiagram.Options.Links.DefaultSelectedColor : Link.Color ?? BlazorDiagram.Options.Links.DefaultColor; var result = Link.GeneratedPathResult; } @@ -22,7 +22,7 @@ stroke-opacity="0" fill="none" @onpointerdown="e => OnPointerDown(e, index)" - @onpointerdown:stopPropagation="@Link.Segmentable" /> + @onpointerdown:stopPropagation="@Link.Segmentable"/> } } diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs index 982f2387a..f03979dbe 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs @@ -1,34 +1,31 @@ using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; -using Blazor.Diagrams.Extensions; -namespace Blazor.Diagrams.Components +namespace Blazor.Diagrams.Components; + +public partial class LinkWidget { - public partial class LinkWidget - { - [CascadingParameter] - public BlazorDiagram BlazorDiagram { get; set; } = null!; + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; - [Parameter] - public LinkModel Link { get; set; } = null!; + [Parameter] public LinkModel Link { get; set; } = null!; - private void OnPointerDown(PointerEventArgs e, int index) - { - if (!Link.Segmentable) - return; + private void OnPointerDown(PointerEventArgs e, int index) + { + if (!Link.Segmentable) + return; - var vertex = CreateVertex(e.ClientX, e.ClientY, index); - BlazorDiagram.TriggerPointerDown(vertex, e.ToCore()); - } + var vertex = CreateVertex(e.ClientX, e.ClientY, index); + BlazorDiagram.TriggerPointerDown(vertex, e.ToCore()); + } - private LinkVertexModel CreateVertex(double clientX, double clientY, int index) - { - var rPt = BlazorDiagram.GetRelativeMousePoint(clientX, clientY); - var vertex = new LinkVertexModel(Link, rPt); - Link.Vertices.Insert(index, vertex); - Link.Refresh(); - return vertex; - } + private LinkVertexModel CreateVertex(double clientX, double clientY, int index) + { + var rPt = BlazorDiagram.GetRelativeMousePoint(clientX, clientY); + var vertex = new LinkVertexModel(Link, rPt); + Link.Vertices.Insert(index, vertex); + Link.Refresh(); + return vertex; } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/NodeWidget.razor b/src/Blazor.Diagrams/Components/NodeWidget.razor index ef1a8fbcc..37ff3da8d 100644 --- a/src/Blazor.Diagrams/Components/NodeWidget.razor +++ b/src/Blazor.Diagrams/Components/NodeWidget.razor @@ -7,6 +7,8 @@ @code { + [Parameter] public NodeModel Node { get; set; } = null!; + } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs index 5200fec68..efc6dc249 100644 --- a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs @@ -1,126 +1,127 @@ -using Blazor.Diagrams.Core.Geometry; +using System; +using System.Text; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Extensions; using Blazor.Diagrams.Models; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; -using System; -using System.Text; -using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Components.Renderers +namespace Blazor.Diagrams.Components.Renderers; + +public class GroupRenderer : ComponentBase, IDisposable { - public class GroupRenderer : ComponentBase, IDisposable + private bool _isSvg; + private Size? _lastSize; + private bool _shouldRender = true; + + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; + + [Parameter] public GroupModel Group { get; set; } = null!; + + public void Dispose() { - private bool _shouldRender = true; - private Size? _lastSize; - private bool _isSvg; + Group.Changed -= OnGroupChanged; + } - [CascadingParameter] - public BlazorDiagram BlazorDiagram { get; set; } = null!; + protected override void OnInitialized() + { + Group.Changed += OnGroupChanged; + } - [Parameter] - public GroupModel Group { get; set; } = null!; + protected override void OnParametersSet() + { + base.OnParametersSet(); - public void Dispose() - { - Group.Changed -= OnGroupChanged; - } + _isSvg = Group is SvgGroupModel; + } - protected override void OnInitialized() + protected override bool ShouldRender() + { + if (_shouldRender) { - Group.Changed += OnGroupChanged; + _shouldRender = false; + return true; } - protected override void OnParametersSet() - { - base.OnParametersSet(); - - _isSvg = Group is SvgGroupModel; - } + return false; + } - protected override bool ShouldRender() - { - if (_shouldRender) - { - _shouldRender = false; - return true; - } + protected override void OnAfterRender(bool firstRender) + { + if (Size.Zero.Equals(Group.Size)) + return; - return false; - } + // Update the port positions (and links) when the size of the group changes + // This will save us some JS trips as well as useless rerenders - protected override void OnAfterRender(bool firstRender) + if (_lastSize == null || !_lastSize.Equals(Group.Size)) { - if (Size.Zero.Equals(Group.Size)) - return; - - // Update the port positions (and links) when the size of the group changes - // This will save us some JS trips as well as useless rerenders - - if (_lastSize == null || !_lastSize.Equals(Group.Size)) - { - Group.ReinitializePorts(); - Group.RefreshLinks(); - _lastSize = Group.Size; - } + Group.ReinitializePorts(); + Group.RefreshLinks(); + _lastSize = Group.Size; } + } - private void OnGroupChanged(Model _) - { - _shouldRender = true; - InvokeAsync(StateHasChanged); - } + private void OnGroupChanged(Model _) + { + _shouldRender = true; + InvokeAsync(StateHasChanged); + } - private static string GenerateStyle(double top, double left, double width, double height) - { - return FormattableString.Invariant($"top: {top}px; left: {left}px; width: {width}px; height: {height}px"); - } + private static string GenerateStyle(double top, double left, double width, double height) + { + return FormattableString.Invariant($"top: {top}px; left: {left}px; width: {width}px; height: {height}px"); + } - protected override void BuildRenderTree(RenderTreeBuilder builder) + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + var componentType = BlazorDiagram.GetComponentForModel(Group) ?? typeof(DefaultGroupWidget); + var classes = new StringBuilder("group") + .AppendIf(" locked", Group.Locked) + .AppendIf(" selected", Group.Selected) + .AppendIf(" default", componentType == typeof(DefaultGroupWidget)); + + builder.OpenElement(0, _isSvg ? "g" : "div"); + builder.AddAttribute(1, "class", classes.ToString()); + builder.AddAttribute(2, "data-group-id", Group.Id); + + if (_isSvg) + builder.AddAttribute(3, "transform", + FormattableString.Invariant($"translate({Group.Position.X} {Group.Position.Y})")); + else + builder.AddAttribute(3, "style", + GenerateStyle(Group.Position.Y, Group.Position.X, Group.Size!.Width, Group.Size.Height)); + + builder.AddAttribute(4, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); + builder.AddEventStopPropagationAttribute(5, "onpointerdown", true); + builder.AddAttribute(6, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); + builder.AddEventStopPropagationAttribute(7, "onpointerup", true); + + if (_isSvg) { - var componentType = BlazorDiagram.GetComponentForModel(Group) ?? typeof(DefaultGroupWidget); - var classes = new StringBuilder("group") - .AppendIf(" locked", Group.Locked) - .AppendIf(" selected", Group.Selected) - .AppendIf(" default", componentType == typeof(DefaultGroupWidget)); - - builder.OpenElement(0, _isSvg ? "g" : "div"); - builder.AddAttribute(1, "class", classes.ToString()); - builder.AddAttribute(2, "data-group-id", Group.Id); - - if (_isSvg) - { - builder.AddAttribute(3, "transform", FormattableString.Invariant($"translate({Group.Position.X} {Group.Position.Y})")); - } - else - { - builder.AddAttribute(3, "style", GenerateStyle(Group.Position.Y, Group.Position.X, Group.Size!.Width, Group.Size.Height)); - } - - builder.AddAttribute(4, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); - builder.AddEventStopPropagationAttribute(5, "onpointerdown", true); - builder.AddAttribute(6, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); - builder.AddEventStopPropagationAttribute(7, "onpointerup", true); - - if (_isSvg) - { - builder.OpenElement(8, "rect"); - builder.AddAttribute(9, "width", Group.Size!.Width); - builder.AddAttribute(10, "height", Group.Size.Height); - builder.AddAttribute(11, "fill", "none"); - builder.CloseElement(); - } - - builder.OpenComponent(12, componentType); - builder.AddAttribute(13, "Group", Group); - builder.CloseComponent(); + builder.OpenElement(8, "rect"); + builder.AddAttribute(9, "width", Group.Size!.Width); + builder.AddAttribute(10, "height", Group.Size.Height); + builder.AddAttribute(11, "fill", "none"); builder.CloseElement(); } - private void OnPointerDown(PointerEventArgs e) => BlazorDiagram.TriggerPointerDown(Group, e.ToCore()); + builder.OpenComponent(12, componentType); + builder.AddAttribute(13, "Group", Group); + builder.CloseComponent(); + builder.CloseElement(); + } - private void OnPointerUp(PointerEventArgs e) => BlazorDiagram.TriggerPointerUp(Group, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerDown(Group, e.ToCore()); + } + + private void OnPointerUp(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerUp(Group, e.ToCore()); } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs index dbcb5dbaf..8b32104c6 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs @@ -1,70 +1,72 @@ -using Blazor.Diagrams.Core.Geometry; +using System; +using System.Linq; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using SvgPathProperties; -using System; -using System.Linq; -using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Components.Renderers +namespace Blazor.Diagrams.Components.Renderers; + +public class LinkLabelRenderer : ComponentBase, IDisposable { - public class LinkLabelRenderer : ComponentBase, IDisposable + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; + [Parameter] public LinkLabelModel Label { get; set; } = null!; + [Parameter] public SvgPath[] Paths { get; set; } = null!; + + public void Dispose() { - [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; - [Parameter] public LinkLabelModel Label { get; set; } = null!; - [Parameter] public SvgPath[] Paths { get; set; } = null!; + Label.Changed -= OnLabelChanged; + } - public void Dispose() - { - Label.Changed -= OnLabelChanged; - } + protected override void OnInitialized() + { + Label.Changed += OnLabelChanged; + } - protected override void OnInitialized() - { - Label.Changed += OnLabelChanged; - } + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + var component = BlazorDiagram.GetComponentForModel(Label) ?? typeof(DefaultLinkLabelWidget); + var position = FindPosition(); + if (position == null) + return; - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - var component = BlazorDiagram.GetComponentForModel(Label) ?? typeof(DefaultLinkLabelWidget); - var position = FindPosition(); - if (position == null) - return; + builder.OpenComponent(0, component); + builder.AddAttribute(1, "Label", Label); + builder.AddAttribute(2, "Position", position); + builder.CloseComponent(); + } - builder.OpenComponent(0, component); - builder.AddAttribute(1, "Label", Label); - builder.AddAttribute(2, "Position", position); - builder.CloseComponent(); - } + private void OnLabelChanged(Model _) + { + InvokeAsync(StateHasChanged); + } - private void OnLabelChanged(Model _) => InvokeAsync(StateHasChanged); + private Point? FindPosition() + { + var totalLength = Paths.Sum(p => p.Length); - private Point? FindPosition() + var length = Label.Distance switch { - var totalLength = Paths.Sum(p => p.Length); - - var length = Label.Distance switch - { - var d when d >= 0 && d <= 1 => Label.Distance.Value * totalLength, - var d when d > 1 => Label.Distance.Value, - var d when d < 0 => totalLength + Label.Distance.Value, - _ => totalLength * (Label.Parent.Labels.IndexOf(Label) + 1) / (Label.Parent.Labels.Count + 1) - }; + var d when d >= 0 && d <= 1 => Label.Distance.Value * totalLength, + var d when d > 1 => Label.Distance.Value, + var d when d < 0 => totalLength + Label.Distance.Value, + _ => totalLength * (Label.Parent.Labels.IndexOf(Label) + 1) / (Label.Parent.Labels.Count + 1) + }; - foreach (var path in Paths) + foreach (var path in Paths) + { + var pathLength = path.Length; + if (length < pathLength) { - var pathLength = path.Length; - if (length < pathLength) - { - var pt = path.GetPointAtLength(length); - return new Point(pt.X, pt.Y); - } - - length -= pathLength; + var pt = path.GetPointAtLength(length); + return new Point(pt.X, pt.Y); } - return null; + length -= pathLength; } + + return null; } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs index f4e91ea7d..ca01d7b37 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs @@ -1,70 +1,84 @@ -using Blazor.Diagrams.Core; +using System; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; -using System; -namespace Blazor.Diagrams.Components.Renderers +namespace Blazor.Diagrams.Components.Renderers; + +public class LinkRenderer : ComponentBase, IDisposable { - public class LinkRenderer : ComponentBase, IDisposable - { - private bool _shouldRender = true; + private bool _shouldRender = true; - [CascadingParameter] - public BlazorDiagram BlazorDiagram { get; set; } + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } - [Parameter] - public BaseLinkModel Link { get; set; } + [Parameter] public BaseLinkModel Link { get; set; } - public void Dispose() - { - Link.Changed -= OnLinkChanged; - } + public void Dispose() + { + Link.Changed -= OnLinkChanged; + } - protected override void OnInitialized() - { - base.OnInitialized(); + protected override void OnInitialized() + { + base.OnInitialized(); - Link.Changed += OnLinkChanged; - } + Link.Changed += OnLinkChanged; + } - protected override bool ShouldRender() => _shouldRender; + protected override bool ShouldRender() + { + return _shouldRender; + } - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - var componentType = BlazorDiagram.GetComponentForModel(Link) ?? typeof(LinkWidget); + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + var componentType = BlazorDiagram.GetComponentForModel(Link) ?? typeof(LinkWidget); - builder.OpenElement(0, "g"); - builder.AddAttribute(1, "class", "link"); - builder.AddAttribute(2, "data-link-id", Link.Id); - builder.AddAttribute(3, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); - builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); - builder.AddAttribute(5, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); - builder.AddEventStopPropagationAttribute(6, "onpointerup", true); - builder.AddAttribute(7, "onmouseenter", EventCallback.Factory.Create(this, OnMouseEnter)); - builder.AddAttribute(8, "onmouseleave", EventCallback.Factory.Create(this, OnMouseLeave)); - builder.OpenComponent(9, componentType); - builder.AddAttribute(10, "Link", Link); - builder.CloseComponent(); - builder.CloseElement(); - } + builder.OpenElement(0, "g"); + builder.AddAttribute(1, "class", "link"); + builder.AddAttribute(2, "data-link-id", Link.Id); + builder.AddAttribute(3, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); + builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); + builder.AddAttribute(5, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); + builder.AddEventStopPropagationAttribute(6, "onpointerup", true); + builder.AddAttribute(7, "onmouseenter", EventCallback.Factory.Create(this, OnMouseEnter)); + builder.AddAttribute(8, "onmouseleave", EventCallback.Factory.Create(this, OnMouseLeave)); + builder.OpenComponent(9, componentType); + builder.AddAttribute(10, "Link", Link); + builder.CloseComponent(); + builder.CloseElement(); + } - protected override void OnAfterRender(bool firstRender) => _shouldRender = false; + protected override void OnAfterRender(bool firstRender) + { + _shouldRender = false; + } - private void OnLinkChanged(Model _) - { - _shouldRender = true; - InvokeAsync(StateHasChanged); - } + private void OnLinkChanged(Model _) + { + _shouldRender = true; + InvokeAsync(StateHasChanged); + } - private void OnPointerDown(PointerEventArgs e) => BlazorDiagram.TriggerPointerDown(Link, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerDown(Link, e.ToCore()); + } - private void OnPointerUp(PointerEventArgs e) => BlazorDiagram.TriggerPointerUp(Link, e.ToCore()); + private void OnPointerUp(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerUp(Link, e.ToCore()); + } - private void OnMouseEnter(MouseEventArgs e) => BlazorDiagram.TriggerPointerEnter(Link, e.ToCore()); + private void OnMouseEnter(MouseEventArgs e) + { + BlazorDiagram.TriggerPointerEnter(Link, e.ToCore()); + } - private void OnMouseLeave(MouseEventArgs e) => BlazorDiagram.TriggerPointerLeave(Link, e.ToCore()); + private void OnMouseLeave(MouseEventArgs e) + { + BlazorDiagram.TriggerPointerLeave(Link, e.ToCore()); } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index 6f53700bc..e2cf965fc 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -1,183 +1,193 @@ -using Blazor.Diagrams.Core.Extensions; +using System; +using System.Text; +using System.Threading.Tasks; +using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Extensions; using Blazor.Diagrams.Models; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Rendering; using Microsoft.AspNetCore.Components.Web; using Microsoft.JSInterop; -using System; -using System.Text; -using System.Threading.Tasks; -using Blazor.Diagrams.Core; -using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Components.Renderers +namespace Blazor.Diagrams.Components.Renderers; + +public class NodeRenderer : ComponentBase, IDisposable { - public class NodeRenderer : ComponentBase, IDisposable + private bool _becameVisible; + private ElementReference _element; + private bool _isSvg; + private bool _isVisible = true; + private DotNetObjectReference? _reference; + private bool _shouldRender; + + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; + + [Parameter] public NodeModel Node { get; set; } = null!; + + [Inject] private IJSRuntime JsRuntime { get; set; } = null!; + + public void Dispose() { - private bool _shouldRender; - private bool _isVisible = true; - private bool _becameVisible; - private ElementReference _element; - private DotNetObjectReference? _reference; - private bool _isSvg; + BlazorDiagram.PanChanged -= CheckVisibility; + BlazorDiagram.ZoomChanged -= CheckVisibility; + BlazorDiagram.ContainerChanged -= CheckVisibility; + Node.Changed -= OnNodeChanged; - [CascadingParameter] - public BlazorDiagram BlazorDiagram { get; set; } = null!; + if (_element.Id != null) + _ = JsRuntime.UnobserveResizes(_element); - [Parameter] - public NodeModel Node { get; set; } = null!; + _reference?.Dispose(); + } - [Inject] - private IJSRuntime JsRuntime { get; set; } = null!; + [JSInvokable] + public void OnResize(Size size) + { + // When the node becomes invisible (a.k.a unrendered), the size is zero + if (Size.Zero.Equals(size)) + return; + + size = new Size(size.Width / BlazorDiagram.Zoom, size.Height / BlazorDiagram.Zoom); + if (Node.Size != null && Node.Size.Width.AlmostEqualTo(size.Width) && + Node.Size.Height.AlmostEqualTo(size.Height)) + return; + + Node.Size = size; + Node.Refresh(); + Node.RefreshLinks(); + Node.ReinitializePorts(); + } - public void Dispose() - { - BlazorDiagram.PanChanged -= CheckVisibility; - BlazorDiagram.ZoomChanged -= CheckVisibility; - BlazorDiagram.ContainerChanged -= CheckVisibility; - Node.Changed -= OnNodeChanged; + protected override void OnInitialized() + { + base.OnInitialized(); - if (_element.Id != null) - _ = JsRuntime.UnobserveResizes(_element); + _reference = DotNetObjectReference.Create(this); + BlazorDiagram.PanChanged += CheckVisibility; + BlazorDiagram.ZoomChanged += CheckVisibility; + BlazorDiagram.ContainerChanged += CheckVisibility; + Node.Changed += OnNodeChanged; + } - _reference?.Dispose(); - } + protected override void OnParametersSet() + { + base.OnParametersSet(); - [JSInvokable] - public void OnResize(Size size) - { - // When the node becomes invisible (a.k.a unrendered), the size is zero - if (Size.Zero.Equals(size)) - return; - - size = new Size(size.Width / BlazorDiagram.Zoom, size.Height / BlazorDiagram.Zoom); - if (Node.Size != null && Node.Size.Width.AlmostEqualTo(size.Width) && Node.Size.Height.AlmostEqualTo(size.Height)) - return; - - Node.Size = size; - Node.Refresh(); - Node.RefreshLinks(); - Node.ReinitializePorts(); - } + _isSvg = Node is SvgNodeModel; + } - protected override void OnInitialized() - { - base.OnInitialized(); + protected override bool ShouldRender() + { + if (!_shouldRender) + return false; - _reference = DotNetObjectReference.Create(this); - BlazorDiagram.PanChanged += CheckVisibility; - BlazorDiagram.ZoomChanged += CheckVisibility; - BlazorDiagram.ContainerChanged += CheckVisibility; - Node.Changed += OnNodeChanged; - } + _shouldRender = false; + return true; + } - protected override void OnParametersSet() - { - base.OnParametersSet(); + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + if (!_isVisible) + return; + + var componentType = BlazorDiagram.GetComponentForModel(Node) ?? + (_isSvg ? typeof(SvgNodeWidget) : typeof(NodeWidget)); + var classes = new StringBuilder("node") + .AppendIf(" locked", Node.Locked) + .AppendIf(" selected", Node.Selected) + .AppendIf(" grouped", Node.Group != null); + + builder.OpenElement(0, _isSvg ? "g" : "div"); + builder.AddAttribute(1, "class", classes.ToString()); + builder.AddAttribute(2, "data-node-id", Node.Id); + + if (_isSvg) + builder.AddAttribute(3, "transform", + $"translate({Node.Position.X.ToInvariantString()} {Node.Position.Y.ToInvariantString()})"); + else + builder.AddAttribute(3, "style", + $"top: {Node.Position.Y.ToInvariantString()}px; left: {Node.Position.X.ToInvariantString()}px"); + + builder.AddAttribute(4, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); + builder.AddEventStopPropagationAttribute(5, "onpointerdown", true); + builder.AddAttribute(6, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); + builder.AddEventStopPropagationAttribute(7, "onpointerup", true); + builder.AddAttribute(8, "onmouseenter", EventCallback.Factory.Create(this, OnMouseEnter)); + builder.AddAttribute(9, "onmouseleave", EventCallback.Factory.Create(this, OnMouseLeave)); + builder.AddElementReferenceCapture(10, value => _element = value); + builder.OpenComponent(11, componentType); + builder.AddAttribute(12, "Node", Node); + builder.CloseComponent(); + + builder.CloseElement(); + } - _isSvg = Node is SvgNodeModel; - } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); - protected override bool ShouldRender() + if (firstRender || _becameVisible) { - if (!_shouldRender) - return false; - - _shouldRender = false; - return true; + _becameVisible = false; + await JsRuntime.ObserveResizes(_element, _reference); } + } - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - if (!_isVisible) - return; - - var componentType = BlazorDiagram.GetComponentForModel(Node) ?? (_isSvg ? typeof(SvgNodeWidget) : typeof(NodeWidget)); - var classes = new StringBuilder("node") - .AppendIf(" locked", Node.Locked) - .AppendIf(" selected", Node.Selected) - .AppendIf(" grouped", Node.Group != null); - - builder.OpenElement(0, _isSvg ? "g" : "div"); - builder.AddAttribute(1, "class", classes.ToString()); - builder.AddAttribute(2, "data-node-id", Node.Id); - - if (_isSvg) - { - builder.AddAttribute(3, "transform", $"translate({Node.Position.X.ToInvariantString()} {Node.Position.Y.ToInvariantString()})"); - } - else - { - builder.AddAttribute(3, "style", $"top: {Node.Position.Y.ToInvariantString()}px; left: {Node.Position.X.ToInvariantString()}px"); - } - - builder.AddAttribute(4, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); - builder.AddEventStopPropagationAttribute(5, "onpointerdown", true); - builder.AddAttribute(6, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); - builder.AddEventStopPropagationAttribute(7, "onpointerup", true); - builder.AddAttribute(8, "onmouseenter", EventCallback.Factory.Create(this, OnMouseEnter)); - builder.AddAttribute(9, "onmouseleave", EventCallback.Factory.Create(this, OnMouseLeave)); - builder.AddElementReferenceCapture(10, value => _element = value); - builder.OpenComponent(11, componentType); - builder.AddAttribute(12, "Node", Node); - builder.CloseComponent(); - - builder.CloseElement(); - } + private void CheckVisibility() + { + // _isVisible must be true in case virtualization gets disabled and some nodes are hidden + if (!BlazorDiagram.Options.EnableVirtualization && _isVisible) + return; - protected override async Task OnAfterRenderAsync(bool firstRender) - { - await base.OnAfterRenderAsync(firstRender); + if (Node.Size == null) + return; - if (firstRender || _becameVisible) - { - _becameVisible = false; - await JsRuntime.ObserveResizes(_element, _reference); - } - } + var left = Node.Position.X * BlazorDiagram.Zoom + BlazorDiagram.Pan.X; + var top = Node.Position.Y * BlazorDiagram.Zoom + BlazorDiagram.Pan.Y; + var right = left + Node.Size.Width * BlazorDiagram.Zoom; + var bottom = top + Node.Size.Height * BlazorDiagram.Zoom; - private void CheckVisibility() - { - // _isVisible must be true in case virtualization gets disabled and some nodes are hidden - if (!BlazorDiagram.Options.EnableVirtualization && _isVisible) - return; - - if (Node.Size == null) - return; - - var left = Node.Position.X * BlazorDiagram.Zoom + BlazorDiagram.Pan.X; - var top = Node.Position.Y * BlazorDiagram.Zoom + BlazorDiagram.Pan.Y; - var right = left + Node.Size.Width * BlazorDiagram.Zoom; - var bottom = top + Node.Size.Height * BlazorDiagram.Zoom; - - var isVisible = right > 0 && left < BlazorDiagram.Container.Width && bottom > 0 && - top < BlazorDiagram.Container.Height; - - if (_isVisible != isVisible) - { - _isVisible = isVisible; - _becameVisible = isVisible; - ReRender(); - } - } + var isVisible = right > 0 && left < BlazorDiagram.Container.Width && bottom > 0 && + top < BlazorDiagram.Container.Height; - private void OnNodeChanged(Model _) => ReRender(); - - private void ReRender() + if (_isVisible != isVisible) { - _shouldRender = true; - InvokeAsync(StateHasChanged); + _isVisible = isVisible; + _becameVisible = isVisible; + ReRender(); } + } - private void OnPointerDown(PointerEventArgs e) => BlazorDiagram.TriggerPointerDown(Node, e.ToCore()); + private void OnNodeChanged(Model _) + { + ReRender(); + } - private void OnPointerUp(PointerEventArgs e) => BlazorDiagram.TriggerPointerUp(Node, e.ToCore()); + private void ReRender() + { + _shouldRender = true; + InvokeAsync(StateHasChanged); + } - private void OnMouseEnter(MouseEventArgs e) => BlazorDiagram.TriggerPointerEnter(Node, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerDown(Node, e.ToCore()); + } - private void OnMouseLeave(MouseEventArgs e) => BlazorDiagram.TriggerPointerLeave(Node, e.ToCore()); + private void OnPointerUp(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerUp(Node, e.ToCore()); + } + + private void OnMouseEnter(MouseEventArgs e) + { + BlazorDiagram.TriggerPointerEnter(Node, e.ToCore()); + } + + private void OnMouseLeave(MouseEventArgs e) + { + BlazorDiagram.TriggerPointerLeave(Node, e.ToCore()); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs index 05eaaa77c..ae4c79cc0 100644 --- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs @@ -1,156 +1,152 @@ -using Blazor.Diagrams.Core.Models; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Components; -using Microsoft.JSInterop; -using System; +using System; +using System.Linq; using System.Threading.Tasks; -using Blazor.Diagrams.Extensions; using Blazor.Diagrams.Core.Geometry; -using Microsoft.AspNetCore.Components.Rendering; -using System.Linq; +using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Extensions; using Blazor.Diagrams.Models; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.JSInterop; + +namespace Blazor.Diagrams.Components.Renderers; -namespace Blazor.Diagrams.Components.Renderers +public class PortRenderer : ComponentBase, IDisposable { - public class PortRenderer : ComponentBase, IDisposable - { - private bool _shouldRender = true; - private ElementReference _element; - private bool _updatingDimensions; - private bool _shouldRefreshPort; - private bool _isParentSvg; + private ElementReference _element; + private bool _isParentSvg; + private bool _shouldRefreshPort; + private bool _shouldRender = true; + private bool _updatingDimensions; - [CascadingParameter] - public BlazorDiagram BlazorDiagram { get; set; } = null!; + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; - [Inject] - private IJSRuntime JSRuntime { get; set; } = null!; + [Inject] private IJSRuntime JSRuntime { get; set; } = null!; - [Parameter] - public PortModel Port { get; set; } = null!; + [Parameter] public PortModel Port { get; set; } = null!; - [Parameter] - public string? Class { get; set; } + [Parameter] public string? Class { get; set; } - [Parameter] - public RenderFragment? ChildContent { get; set; } + [Parameter] public RenderFragment? ChildContent { get; set; } - public void Dispose() - { - Port.Changed -= OnPortChanged; - } + public void Dispose() + { + Port.Changed -= OnPortChanged; + } - protected override void OnInitialized() - { - base.OnInitialized(); + protected override void OnInitialized() + { + base.OnInitialized(); - Port.Changed += OnPortChanged; - } + Port.Changed += OnPortChanged; + } - protected override void OnParametersSet() - { - base.OnParametersSet(); + protected override void OnParametersSet() + { + base.OnParametersSet(); - _isParentSvg = Port.Parent is SvgNodeModel; - } + _isParentSvg = Port.Parent is SvgNodeModel; + } - protected override bool ShouldRender() => _shouldRender; + protected override bool ShouldRender() + { + return _shouldRender; + } - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - builder.OpenElement(0, _isParentSvg ? "g" : "div"); - builder.AddAttribute(1, "class", "port" + " " + (Port.Alignment.ToString().ToLower()) + " " + (Port.Links.Count > 0 ? "has-links" : "") + " " + (Class)); - builder.AddAttribute(2, "data-port-id", Port.Id); - builder.AddAttribute(3, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); - builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); - builder.AddAttribute(5, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); - builder.AddEventStopPropagationAttribute(6, "onpointerup", true); - builder.AddElementReferenceCapture(12, (__value) => { _element = __value; }); - builder.AddContent(13, ChildContent); - builder.CloseElement(); - } + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + builder.OpenElement(0, _isParentSvg ? "g" : "div"); + builder.AddAttribute(1, "class", + "port" + " " + Port.Alignment.ToString().ToLower() + " " + (Port.Links.Count > 0 ? "has-links" : "") + " " + + Class); + builder.AddAttribute(2, "data-port-id", Port.Id); + builder.AddAttribute(3, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); + builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); + builder.AddAttribute(5, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); + builder.AddEventStopPropagationAttribute(6, "onpointerup", true); + builder.AddElementReferenceCapture(12, __value => { _element = __value; }); + builder.AddContent(13, ChildContent); + builder.CloseElement(); + } - protected override async Task OnAfterRenderAsync(bool firstRender) - { - await base.OnAfterRenderAsync(firstRender); - _shouldRender = false; + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + _shouldRender = false; - if (!Port.Initialized) - { - await UpdateDimensions(); - } - } + if (!Port.Initialized) await UpdateDimensions(); + } - private void OnPointerDown(PointerEventArgs e) => BlazorDiagram.TriggerPointerDown(Port, e.ToCore()); + private void OnPointerDown(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerDown(Port, e.ToCore()); + } - private void OnPointerUp(PointerEventArgs e) + private void OnPointerUp(PointerEventArgs e) + { + var model = e.PointerType == "mouse" ? Port : FindPortOn(e.ClientX, e.ClientY); + BlazorDiagram.TriggerPointerUp(model, e.ToCore()); + } + + private PortModel? FindPortOn(double clientX, double clientY) + { + var allPorts = BlazorDiagram.Nodes.SelectMany(n => n.Ports) + .Union(BlazorDiagram.Groups.SelectMany(g => g.Ports)); + + foreach (var port in allPorts) { - var model = e.PointerType == "mouse" ? Port : FindPortOn(e.ClientX, e.ClientY); - BlazorDiagram.TriggerPointerUp(model, e.ToCore()); + if (!port.Initialized) + continue; + + var relativePt = BlazorDiagram.GetRelativeMousePoint(clientX, clientY); + if (port.GetBounds().ContainsPoint(relativePt)) + return port; } - private PortModel? FindPortOn(double clientX, double clientY) - { - var allPorts = BlazorDiagram.Nodes.SelectMany(n => n.Ports) - .Union(BlazorDiagram.Groups.SelectMany(g => g.Ports)); + return null; + } - foreach (var port in allPorts) - { - if (!port.Initialized) - continue; + private async Task UpdateDimensions() + { + _updatingDimensions = true; + var zoom = BlazorDiagram.Zoom; + var pan = BlazorDiagram.Pan; + var rect = await JSRuntime.GetBoundingClientRect(_element); - var relativePt = BlazorDiagram.GetRelativeMousePoint(clientX, clientY); - if (port.GetBounds().ContainsPoint(relativePt)) - return port; - } + Port.Size = new Size(rect.Width / zoom, rect.Height / zoom); + Port.Position = new Point((rect.Left - BlazorDiagram.Container.Left - pan.X) / zoom, + (rect.Top - BlazorDiagram.Container.Top - pan.Y) / zoom); - return null; - } + Port.Initialized = true; + _updatingDimensions = false; - private async Task UpdateDimensions() + if (_shouldRefreshPort) { - _updatingDimensions = true; - var zoom = BlazorDiagram.Zoom; - var pan = BlazorDiagram.Pan; - var rect = await JSRuntime.GetBoundingClientRect(_element); - - Port.Size = new Size(rect.Width / zoom, rect.Height / zoom); - Port.Position = new Point((rect.Left - BlazorDiagram.Container.Left - pan.X) / zoom, - (rect.Top - BlazorDiagram.Container.Top - pan.Y) / zoom); - - Port.Initialized = true; - _updatingDimensions = false; - - if (_shouldRefreshPort) - { - _shouldRefreshPort = false; - Port.RefreshAll(); - } - else - { - Port.RefreshLinks(); - } + _shouldRefreshPort = false; + Port.RefreshAll(); } + else + { + Port.RefreshLinks(); + } + } + + private async void OnPortChanged(Model _) + { + // If an update is ongoing and the port is refreshed again, + // it's highly likely the port needs to be refreshed (e.g. link added) + if (_updatingDimensions) _shouldRefreshPort = true; - private async void OnPortChanged(Model _) + if (Port.Initialized) + { + _shouldRender = true; + await InvokeAsync(StateHasChanged); + } + else { - // If an update is ongoing and the port is refreshed again, - // it's highly likely the port needs to be refreshed (e.g. link added) - if (_updatingDimensions) - { - _shouldRefreshPort = true; - } - - if (Port.Initialized) - { - _shouldRender = true; - await InvokeAsync(StateHasChanged); - } - else - { - await UpdateDimensions(); - } + await UpdateDimensions(); } } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/SvgNodeWidget.razor b/src/Blazor.Diagrams/Components/SvgNodeWidget.razor index cda931456..c1b7af8d9 100644 --- a/src/Blazor.Diagrams/Components/SvgNodeWidget.razor +++ b/src/Blazor.Diagrams/Components/SvgNodeWidget.razor @@ -1,6 +1,8 @@ - + @code { + [Parameter] public NodeModel Node { get; set; } + } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor b/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor index 123633d79..3baa3c1fc 100644 --- a/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor +++ b/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor @@ -20,8 +20,8 @@ @foreach (var node in BlazorDiagram.Nodes.Where(n => n.Size != null)) { - var left = ((node.Position.X * BlazorDiagram.Zoom) + addedNodeX) * XFactor; - var top = ((node.Position.Y * BlazorDiagram.Zoom) + addedNodeY) * YFactor; + var left = (node.Position.X * BlazorDiagram.Zoom + addedNodeX) * XFactor; + var top = (node.Position.Y * BlazorDiagram.Zoom + addedNodeY) * YFactor; var width = node.Size.Width * BlazorDiagram.Zoom * XFactor; var height = node.Size.Height * BlazorDiagram.Zoom * YFactor; diff --git a/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs index 29c81e14d..0ca85380a 100644 --- a/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs @@ -1,209 +1,189 @@ -using Blazor.Diagrams.Core; +using System; +using System.Linq; using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -using Microsoft.AspNetCore.Components; -using System; -using System.Linq; using Blazor.Diagrams.Core.Models.Base; +using Microsoft.AspNetCore.Components; + +namespace Blazor.Diagrams.Components.Widgets; -namespace Blazor.Diagrams.Components.Widgets +public partial class NavigatorWidget : IDisposable { - public partial class NavigatorWidget : IDisposable - { - [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } - [Parameter] public double Width { get; set; } - [Parameter] public double Height { get; set; } - [Parameter] public string FillColor { get; set; } = "#40babd"; - [Parameter] public bool DefaultStyle { get; set; } + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } + [Parameter] public double Width { get; set; } + [Parameter] public double Height { get; set; } + [Parameter] public string FillColor { get; set; } = "#40babd"; + [Parameter] public bool DefaultStyle { get; set; } - private Point NodePositionAdjustment { get; set; } - private double XFactor { get; set; } - private double YFactor { get; set; } + private Point NodePositionAdjustment { get; set; } + private double XFactor { get; set; } + private double YFactor { get; set; } - protected override void OnParametersSet() + public void Dispose() + { + if (BlazorDiagram != null) { - base.OnParametersSet(); - if (BlazorDiagram != null) - { - foreach (var node in BlazorDiagram.Nodes) - node.Changed += Refresh; + BlazorDiagram.Changed -= Diagram_Changed; + BlazorDiagram.Nodes.Added -= Diagram_NodesAdded; + BlazorDiagram.Nodes.Removed -= Diagram_NodesRemoved; + foreach (var node in BlazorDiagram.Nodes) + node.Changed -= Refresh; + + foreach (var group in BlazorDiagram.Groups) + group.Changed -= Refresh; + } + } - foreach (var group in BlazorDiagram.Groups) - group.Changed += Refresh; + protected override void OnParametersSet() + { + base.OnParametersSet(); + if (BlazorDiagram != null) + { + foreach (var node in BlazorDiagram.Nodes) + node.Changed += Refresh; - BlazorDiagram.Changed += Diagram_Changed; - BlazorDiagram.Nodes.Added += Diagram_NodesAdded; - BlazorDiagram.Nodes.Removed += Diagram_NodesRemoved; - BlazorDiagram.GroupAdded += Diagram_GroupAdded; - BlazorDiagram.GroupRemoved += Diagram_GroupRemoved; - } + foreach (var group in BlazorDiagram.Groups) + group.Changed += Refresh; + BlazorDiagram.Changed += Diagram_Changed; + BlazorDiagram.Nodes.Added += Diagram_NodesAdded; + BlazorDiagram.Nodes.Removed += Diagram_NodesRemoved; + BlazorDiagram.GroupAdded += Diagram_GroupAdded; + BlazorDiagram.GroupRemoved += Diagram_GroupRemoved; } + } + + private void Diagram_Changed() + { + Refresh(null); + } + + private void Diagram_NodesAdded(NodeModel node) + { + node.Changed += Refresh; + } + + private void Diagram_NodesRemoved(NodeModel node) + { + node.Changed -= Refresh; + } - private void Diagram_Changed() => Refresh(null); + private void Diagram_GroupAdded(GroupModel group) + { + group.Changed += Refresh; + } + + private void Diagram_GroupRemoved(GroupModel group) + { + group.Changed -= Refresh; + } - private void Diagram_NodesAdded(NodeModel node) + private void Refresh(Model? _) + { + if (BlazorDiagram != null) { - node.Changed += Refresh; + var nodes = BlazorDiagram.Nodes + .Union(BlazorDiagram.Groups) + .Where(n => n.Size?.Equals(Size.Zero) == false).ToList(); + + if (nodes.Count == 0) + return; + + var bounds = nodes.GetBounds(); + var nodesMinX = bounds.Left * BlazorDiagram.Zoom; + var nodesMaxX = bounds.Right * BlazorDiagram.Zoom; + var nodesMinY = bounds.Top * BlazorDiagram.Zoom; + var nodesMaxY = bounds.Bottom * BlazorDiagram.Zoom; + + (var fullSizeWidth, var fullSizeHeight) = GetFullSize(nodesMaxX, nodesMaxY); + AdjustFullSizeWithNodesRect(nodesMinX, nodesMinY, ref fullSizeWidth, ref fullSizeHeight); + + NodePositionAdjustment = new Point(nodesMinX < 0 ? Math.Abs(nodesMinX) : 0, + nodesMinY < 0 ? Math.Abs(nodesMinY) : 0); + XFactor = Width / fullSizeWidth; + YFactor = Height / fullSizeHeight; + InvokeAsync(StateHasChanged); } + } - private void Diagram_NodesRemoved(NodeModel node) + private void AdjustFullSizeWithNodesRect(double nodesMinX, double nodesMinY, ref double fullSizeWidth, + ref double fullSizeHeight) + { + // Width + if (nodesMinX < 0) { - node.Changed -= Refresh; + var temp = nodesMinX + BlazorDiagram.Pan.X; + if (BlazorDiagram.Pan.X > 0 && temp < 0) + fullSizeWidth += Math.Abs(temp); + else if (BlazorDiagram.Pan.X <= 0) fullSizeWidth += Math.Abs(nodesMinX); } - private void Diagram_GroupAdded(GroupModel group) => group.Changed += Refresh; + // Height + if (nodesMinY < 0) + { + var temp = nodesMinY + BlazorDiagram.Pan.Y; + if (BlazorDiagram.Pan.Y > 0 && temp < 0) + fullSizeHeight += Math.Abs(temp); + else if (BlazorDiagram.Pan.Y <= 0) fullSizeHeight += Math.Abs(nodesMinY); + } + } - private void Diagram_GroupRemoved(GroupModel group) => group.Changed -= Refresh; + private (double width, double height) GetFullSize(double nodesMaxX, double nodesMaxY) + { + var nodesLayerWidth = Math.Max(BlazorDiagram.Container.Width * BlazorDiagram.Zoom, nodesMaxX); + var nodesLayerHeight = Math.Max(BlazorDiagram.Container.Height * BlazorDiagram.Zoom, nodesMaxY); + double fullWidth; + double fullHeight; - private void Refresh(Model? _) + if (BlazorDiagram.Zoom == 1) { - if (BlazorDiagram != null) - { - var nodes = BlazorDiagram.Nodes - .Union(BlazorDiagram.Groups) - .Where(n => n.Size?.Equals(Size.Zero) == false).ToList(); - - if (nodes.Count == 0) - return; - - var bounds = nodes.GetBounds(); - var nodesMinX = bounds.Left * BlazorDiagram.Zoom; - var nodesMaxX = bounds.Right * BlazorDiagram.Zoom; - var nodesMinY = bounds.Top * BlazorDiagram.Zoom; - var nodesMaxY = bounds.Bottom * BlazorDiagram.Zoom; - - (double fullSizeWidth, double fullSizeHeight) = GetFullSize(nodesMaxX, nodesMaxY); - AdjustFullSizeWithNodesRect(nodesMinX, nodesMinY, ref fullSizeWidth, ref fullSizeHeight); - - NodePositionAdjustment = new Point(nodesMinX < 0 ? Math.Abs(nodesMinX) : 0, nodesMinY < 0 ? Math.Abs(nodesMinY) : 0); - XFactor = Width / fullSizeWidth; - YFactor = Height / fullSizeHeight; - InvokeAsync(StateHasChanged); - } - + fullWidth = BlazorDiagram.Container.Width + Math.Abs(BlazorDiagram.Pan.X); + fullHeight = BlazorDiagram.Container.Height + Math.Abs(BlazorDiagram.Pan.Y); } - - private void AdjustFullSizeWithNodesRect(double nodesMinX, double nodesMinY, ref double fullSizeWidth, - ref double fullSizeHeight) + else if (BlazorDiagram.Zoom > 1) { // Width - if (nodesMinX < 0) + if (BlazorDiagram.Pan.X < 0) { - var temp = nodesMinX + BlazorDiagram.Pan.X; - if (BlazorDiagram.Pan.X > 0 && temp < 0) - { - fullSizeWidth += Math.Abs(temp); - } - else if (BlazorDiagram.Pan.X <= 0) - { - fullSizeWidth += Math.Abs(nodesMinX); - } + if (nodesLayerWidth + BlazorDiagram.Pan.X < BlazorDiagram.Container.Width) + fullWidth = BlazorDiagram.Container.Width + Math.Abs(BlazorDiagram.Pan.X); + else + fullWidth = nodesLayerWidth; } - - // Height - if (nodesMinY < 0) + else { - var temp = nodesMinY + BlazorDiagram.Pan.Y; - if (BlazorDiagram.Pan.Y > 0 && temp < 0) - { - fullSizeHeight += Math.Abs(temp); - } - else if (BlazorDiagram.Pan.Y <= 0) - { - fullSizeHeight += Math.Abs(nodesMinY); - } + fullWidth = nodesLayerWidth + BlazorDiagram.Pan.X; } - } - private (double width, double height) GetFullSize(double nodesMaxX, double nodesMaxY) - { - var nodesLayerWidth = Math.Max(BlazorDiagram.Container.Width * BlazorDiagram.Zoom, nodesMaxX); - var nodesLayerHeight = Math.Max(BlazorDiagram.Container.Height * BlazorDiagram.Zoom, nodesMaxY); - double fullWidth; - double fullHeight; - - if (BlazorDiagram.Zoom == 1) - { - fullWidth = BlazorDiagram.Container.Width + Math.Abs(BlazorDiagram.Pan.X); - fullHeight = BlazorDiagram.Container.Height + Math.Abs(BlazorDiagram.Pan.Y); - } - else if (BlazorDiagram.Zoom > 1) + // Height + if (BlazorDiagram.Pan.Y < 0) { - // Width - if (BlazorDiagram.Pan.X < 0) - { - if (nodesLayerWidth + BlazorDiagram.Pan.X < BlazorDiagram.Container.Width) - { - fullWidth = BlazorDiagram.Container.Width + Math.Abs(BlazorDiagram.Pan.X); - } - else - { - fullWidth = nodesLayerWidth; - } - } - else - { - fullWidth = nodesLayerWidth + BlazorDiagram.Pan.X; - } - - // Height - if (BlazorDiagram.Pan.Y < 0) - { - if (nodesLayerHeight + BlazorDiagram.Pan.Y < BlazorDiagram.Container.Height) - { - fullHeight = BlazorDiagram.Container.Height + Math.Abs(BlazorDiagram.Pan.Y); - } - else - { - fullHeight = nodesLayerHeight; - } - } + if (nodesLayerHeight + BlazorDiagram.Pan.Y < BlazorDiagram.Container.Height) + fullHeight = BlazorDiagram.Container.Height + Math.Abs(BlazorDiagram.Pan.Y); else - { - fullHeight = nodesLayerHeight + BlazorDiagram.Pan.Y; - } + fullHeight = nodesLayerHeight; } else { - // Width - if (BlazorDiagram.Pan.X > 0) - { - fullWidth = Math.Max(nodesLayerWidth + BlazorDiagram.Pan.X, BlazorDiagram.Container.Width); - } - else - { - fullWidth = BlazorDiagram.Container.Width + Math.Abs(BlazorDiagram.Pan.X); - } - - // Height - if (BlazorDiagram.Pan.Y > 0) - { - fullHeight = Math.Max(nodesLayerHeight + BlazorDiagram.Pan.Y, BlazorDiagram.Container.Height); - } - else - { - fullHeight = BlazorDiagram.Container.Height + Math.Abs(BlazorDiagram.Pan.Y); - } + fullHeight = nodesLayerHeight + BlazorDiagram.Pan.Y; } - - return (fullWidth, fullHeight); } - - public void Dispose() + else { - if (BlazorDiagram != null) - { - BlazorDiagram.Changed -= Diagram_Changed; - BlazorDiagram.Nodes.Added -= Diagram_NodesAdded; - BlazorDiagram.Nodes.Removed -= Diagram_NodesRemoved; - foreach (var node in BlazorDiagram.Nodes) - node.Changed -= Refresh; - - foreach (var group in BlazorDiagram.Groups) - group.Changed -= Refresh; - } + // Width + if (BlazorDiagram.Pan.X > 0) + fullWidth = Math.Max(nodesLayerWidth + BlazorDiagram.Pan.X, BlazorDiagram.Container.Width); + else + fullWidth = BlazorDiagram.Container.Width + Math.Abs(BlazorDiagram.Pan.X); + + // Height + if (BlazorDiagram.Pan.Y > 0) + fullHeight = Math.Max(nodesLayerHeight + BlazorDiagram.Pan.Y, BlazorDiagram.Container.Height); + else + fullHeight = BlazorDiagram.Container.Height + Math.Abs(BlazorDiagram.Pan.Y); } + + return (fullWidth, fullHeight); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor.cs index b50c7889f..dbf3f24a3 100644 --- a/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor.cs @@ -1,97 +1,91 @@ -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models.Base; +using System; using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; using Microsoft.AspNetCore.Components; -using System; -namespace Blazor.Diagrams.Components.Widgets +namespace Blazor.Diagrams.Components.Widgets; + +public partial class SelectionBoxWidget : IDisposable { - public partial class SelectionBoxWidget : IDisposable + private Point? _initialClientPoint; + private Size? _selectionBoxSize; // Todo: remove unneeded instantiations + private Point? _selectionBoxTopLeft; // Todo: remove unneeded instantiations + + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; + + [Parameter] public string Background { get; set; } = "rgb(110 159 212 / 25%);"; + + public void Dispose() { - private Point? _initialClientPoint; - private Point? _selectionBoxTopLeft; // Todo: remove unneeded instantiations - private Size? _selectionBoxSize; // Todo: remove unneeded instantiations + BlazorDiagram.PointerDown -= OnPointerDown; + BlazorDiagram.PointerMove -= OnPointerMove; + BlazorDiagram.PointerUp -= OnPointerUp; + } - [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; + protected override void OnInitialized() + { + BlazorDiagram.PointerDown += OnPointerDown; + BlazorDiagram.PointerMove += OnPointerMove; + BlazorDiagram.PointerUp += OnPointerUp; + } - [Parameter] public string Background { get; set; } = "rgb(110 159 212 / 25%);"; + private string GenerateStyle() + { + return FormattableString.Invariant( + $"position: absolute; background: {Background}; top: {_selectionBoxTopLeft!.Y}px; left: {_selectionBoxTopLeft.X}px; width: {_selectionBoxSize!.Width}px; height: {_selectionBoxSize.Height}px;"); + } - protected override void OnInitialized() - { - BlazorDiagram.PointerDown += OnPointerDown; - BlazorDiagram.PointerMove += OnPointerMove; - BlazorDiagram.PointerUp += OnPointerUp; - } + private void OnPointerDown(Model? model, MouseEventArgs e) + { + if (model != null || !e.ShiftKey) + return; - private string GenerateStyle() - { - return FormattableString.Invariant( - $"position: absolute; background: {Background}; top: {_selectionBoxTopLeft!.Y}px; left: {_selectionBoxTopLeft.X}px; width: {_selectionBoxSize!.Width}px; height: {_selectionBoxSize.Height}px;"); - } + _initialClientPoint = new Point(e.ClientX, e.ClientY); + } - private void OnPointerDown(Model? model, MouseEventArgs e) - { - if (model != null || !e.ShiftKey) - return; + private void OnPointerMove(Model? model, MouseEventArgs e) + { + if (_initialClientPoint == null) + return; - _initialClientPoint = new Point(e.ClientX, e.ClientY); - } + SetSelectionBoxInformation(e); - private void OnPointerMove(Model? model, MouseEventArgs e) - { - if (_initialClientPoint == null) - return; - - SetSelectionBoxInformation(e); - - var start = BlazorDiagram.GetRelativeMousePoint(_initialClientPoint.X, _initialClientPoint.Y); - var end = BlazorDiagram.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); - - foreach (var node in BlazorDiagram.Nodes) - { - var nodeBounds = node.GetBounds(); - if (nodeBounds == null) - continue; - - if (bounds.Overlap(nodeBounds)) - { - BlazorDiagram.SelectModel(node, false); - } - else if (node.Selected) - { - BlazorDiagram.UnselectModel(node); - } - } - - InvokeAsync(StateHasChanged); - } + var start = BlazorDiagram.GetRelativeMousePoint(_initialClientPoint.X, _initialClientPoint.Y); + var end = BlazorDiagram.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); - private void SetSelectionBoxInformation(MouseEventArgs e) + foreach (var node in BlazorDiagram.Nodes) { - var start = BlazorDiagram.GetRelativePoint(_initialClientPoint!.X, _initialClientPoint.Y); - var end = BlazorDiagram.GetRelativePoint(e.ClientX, e.ClientY); - (var sX, var sY) = (Math.Min(start.X, end.X), Math.Min(start.Y, end.Y)); - (var eX, var eY) = (Math.Max(start.X, end.X), Math.Max(start.Y, end.Y)); - _selectionBoxTopLeft = new Point(sX, sY); - _selectionBoxSize = new Size(eX - sX, eY - sY); - } + var nodeBounds = node.GetBounds(); + if (nodeBounds == null) + continue; - private void OnPointerUp(Model? model, MouseEventArgs e) - { - _initialClientPoint = null; - _selectionBoxTopLeft = null; - _selectionBoxSize = null; - InvokeAsync(StateHasChanged); + if (bounds.Overlap(nodeBounds)) + BlazorDiagram.SelectModel(node, false); + else if (node.Selected) BlazorDiagram.UnselectModel(node); } - public void Dispose() - { - BlazorDiagram.PointerDown -= OnPointerDown; - BlazorDiagram.PointerMove -= OnPointerMove; - BlazorDiagram.PointerUp -= OnPointerUp; - } + InvokeAsync(StateHasChanged); + } + + private void SetSelectionBoxInformation(MouseEventArgs e) + { + var start = BlazorDiagram.GetRelativePoint(_initialClientPoint!.X, _initialClientPoint.Y); + var end = BlazorDiagram.GetRelativePoint(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)); + _selectionBoxTopLeft = new Point(sX, sY); + _selectionBoxSize = new Size(eX - sX, eY - sY); + } + + private void OnPointerUp(Model? model, MouseEventArgs e) + { + _initialClientPoint = null; + _selectionBoxTopLeft = null; + _selectionBoxSize = null; + InvokeAsync(StateHasChanged); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Extensions/EventsExtensions.cs b/src/Blazor.Diagrams/Extensions/EventsExtensions.cs index d597ed371..377853cba 100644 --- a/src/Blazor.Diagrams/Extensions/EventsExtensions.cs +++ b/src/Blazor.Diagrams/Extensions/EventsExtensions.cs @@ -1,31 +1,30 @@ using Blazor.Diagrams.Core.Events; -using System.Linq; -using Web = Microsoft.AspNetCore.Components.Web; +using MouseEventArgs = Microsoft.AspNetCore.Components.Web.MouseEventArgs; -namespace Blazor.Diagrams.Extensions +namespace Blazor.Diagrams.Extensions; + +public static class EventsExtensions { - public static class EventsExtensions + public static PointerEventArgs ToCore(this Microsoft.AspNetCore.Components.Web.PointerEventArgs e) + { + return new PointerEventArgs(e.ClientX, e.ClientY, e.Button, e.Buttons, e.CtrlKey, e.ShiftKey, e.AltKey, + e.PointerId, e.Width, e.Height, e.Pressure, e.TiltX, e.TiltY, e.PointerType, e.IsPrimary); + } + + public static PointerEventArgs ToCore(this MouseEventArgs e) { - public static PointerEventArgs ToCore(this Web.PointerEventArgs e) - { - return new PointerEventArgs(e.ClientX, e.ClientY, e.Button, e.Buttons, e.CtrlKey, e.ShiftKey, e.AltKey, - e.PointerId, e.Width, e.Height, e.Pressure, e.TiltX, e.TiltY, e.PointerType, e.IsPrimary); - } - - public static PointerEventArgs ToCore(this Web.MouseEventArgs e) - { - return new PointerEventArgs(e.ClientX, e.ClientY, e.Button, e.Buttons, e.CtrlKey, e.ShiftKey, e.AltKey, - 0, 0, 0, 0, 0, 0, string.Empty, false); - } + return new PointerEventArgs(e.ClientX, e.ClientY, e.Button, e.Buttons, e.CtrlKey, e.ShiftKey, e.AltKey, + 0, 0, 0, 0, 0, 0, string.Empty, false); + } - public static KeyboardEventArgs ToCore(this Web.KeyboardEventArgs e) - { - return new KeyboardEventArgs(e.Key, e.Code, e.Location, e.CtrlKey, e.ShiftKey, e.AltKey); - } + public static KeyboardEventArgs ToCore(this Microsoft.AspNetCore.Components.Web.KeyboardEventArgs e) + { + return new KeyboardEventArgs(e.Key, e.Code, e.Location, e.CtrlKey, e.ShiftKey, e.AltKey); + } - public static WheelEventArgs ToCore(this Web.WheelEventArgs e) - { - return new WheelEventArgs(e.ClientX, e.ClientY, e.Button, e.Buttons, e.CtrlKey, e.ShiftKey, e.AltKey, e.DeltaX, e.DeltaY, e.DeltaZ, e.DeltaMode); - } + public static WheelEventArgs ToCore(this Microsoft.AspNetCore.Components.Web.WheelEventArgs e) + { + return new WheelEventArgs(e.ClientX, e.ClientY, e.Button, e.Buttons, e.CtrlKey, e.ShiftKey, e.AltKey, e.DeltaX, + e.DeltaY, e.DeltaZ, e.DeltaMode); } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs index b17d6ba2b..459456863 100644 --- a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs +++ b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs @@ -1,38 +1,33 @@ -using Blazor.Diagrams.Core.Geometry; +using System; +using System.Threading.Tasks; +using Blazor.Diagrams.Core.Geometry; using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; -using System; -using System.Threading.Tasks; -namespace Blazor.Diagrams.Extensions +namespace Blazor.Diagrams.Extensions; + +public static class JSRuntimeExtensions { - public static class JSRuntimeExtensions + public static async Task GetBoundingClientRect(this IJSRuntime jsRuntime, ElementReference element) { - public static async Task GetBoundingClientRect(this IJSRuntime jsRuntime, ElementReference element) - { - return await jsRuntime.InvokeAsync("ZBlazorDiagrams.getBoundingClientRect", element); - } + return await jsRuntime.InvokeAsync("ZBlazorDiagrams.getBoundingClientRect", element); + } - public static async Task ObserveResizes(this IJSRuntime jsRuntime, ElementReference element, - DotNetObjectReference reference) where T : class + public static async Task ObserveResizes(this IJSRuntime jsRuntime, ElementReference element, + DotNetObjectReference reference) where T : class + { + try { - try - { - await jsRuntime.InvokeVoidAsync("ZBlazorDiagrams.observe", element, reference, element.Id); - } - catch (ObjectDisposedException) - { - // Ignore, DotNetObjectReference was likely disposed - } - catch - { - throw; - } + await jsRuntime.InvokeVoidAsync("ZBlazorDiagrams.observe", element, reference, element.Id); } - - public static async Task UnobserveResizes(this IJSRuntime jsRuntime, ElementReference element) + catch (ObjectDisposedException) { - await jsRuntime.InvokeVoidAsync("ZBlazorDiagrams.unobserve", element, element.Id); + // Ignore, DotNetObjectReference was likely disposed } } + + public static async Task UnobserveResizes(this IJSRuntime jsRuntime, ElementReference element) + { + await jsRuntime.InvokeVoidAsync("ZBlazorDiagrams.unobserve", element, element.Id); + } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Extensions/StringBuilderExtensions.cs b/src/Blazor.Diagrams/Extensions/StringBuilderExtensions.cs index 40f2c7bb5..85045c9b7 100644 --- a/src/Blazor.Diagrams/Extensions/StringBuilderExtensions.cs +++ b/src/Blazor.Diagrams/Extensions/StringBuilderExtensions.cs @@ -1,17 +1,13 @@ using System.Text; -namespace Blazor.Diagrams.Extensions +namespace Blazor.Diagrams.Extensions; + +public static class StringBuilderExtensions { - public static class StringBuilderExtensions + public static StringBuilder AppendIf(this StringBuilder builder, string str, bool condition) { - public static StringBuilder AppendIf(this StringBuilder builder, string str, bool condition) - { - if (condition) - { - builder.Append(str); - } + if (condition) builder.Append(str); - return builder; - } + return builder; } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Models/SvgGroupModel.cs b/src/Blazor.Diagrams/Models/SvgGroupModel.cs index 1d994454f..d470541e5 100644 --- a/src/Blazor.Diagrams/Models/SvgGroupModel.cs +++ b/src/Blazor.Diagrams/Models/SvgGroupModel.cs @@ -1,10 +1,11 @@ -using Blazor.Diagrams.Core.Models; -using System.Collections.Generic; +using System.Collections.Generic; +using Blazor.Diagrams.Core.Models; -namespace Blazor.Diagrams.Models +namespace Blazor.Diagrams.Models; + +public class SvgGroupModel : GroupModel { - public class SvgGroupModel : GroupModel + public SvgGroupModel(IEnumerable children, byte padding = 30) : base(children, padding) { - public SvgGroupModel(IEnumerable children, byte padding = 30) : base(children, padding) { } } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Models/SvgNodeModel.cs b/src/Blazor.Diagrams/Models/SvgNodeModel.cs index b7ab8ef05..8ed9804dc 100644 --- a/src/Blazor.Diagrams/Models/SvgNodeModel.cs +++ b/src/Blazor.Diagrams/Models/SvgNodeModel.cs @@ -1,12 +1,15 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -namespace Blazor.Diagrams.Models +namespace Blazor.Diagrams.Models; + +public class SvgNodeModel : NodeModel { - public class SvgNodeModel : NodeModel + public SvgNodeModel(Point? position = null) : base(position) { - public SvgNodeModel(Point? position = null) : base(position) { } + } - public SvgNodeModel(string id, Point? position = null) : base(id, position) { } + public SvgNodeModel(string id, Point? position = null) : base(id, position) + { } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs b/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs index b81153525..c608edf12 100644 --- a/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs +++ b/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs @@ -6,7 +6,7 @@ public class BlazorDiagramOptions : DiagramOptions { public int LinksLayerOrder { get; set; } = 0; public int NodesLayerOrder { get; set; } = 0; - + public override BlazorDiagramZoomOptions Zoom { get; } = new(); public override BlazorDiagramLinkOptions Links { get; } = new(); public override BlazorDiagramGroupOptions Groups { get; } = new(); diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.css b/src/Blazor.Diagrams/wwwroot/default.styles.css index fe1e2bd04..1dd96c814 100644 --- a/src/Blazor.Diagrams/wwwroot/default.styles.css +++ b/src/Blazor.Diagrams/wwwroot/default.styles.css @@ -4,8 +4,8 @@ border-radius: 10px; background-color: #f5f5f5; border: 1px solid #e8e8e8; - -webkit-box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0,0,0,.12); - box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0,0,0,.12); + -webkit-box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, .12); + box-shadow: 0px 2px 1px -1px rgba(0, 0, 0, 0.2), 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 1px 3px 0px rgba(0, 0, 0, .12); position: relative; display: -webkit-box; display: -ms-flexbox; @@ -18,68 +18,68 @@ justify-content: center; } - .default-node.selected { - border: 1px solid #6e9fd4; - } - - .default-node.selected .port { - border: 1px solid #6e9fd4; - } - - .default-node .port, .default.group .port { - width: 20px; - height: 20px; - margin: -10px; - border-radius: 50%; - background-color: #f5f5f5; - border: 1px solid #d4d4d4; - cursor: pointer; - position: absolute; - } - - .default-node .port:hover, .default-node .port.has-links, .default.group .port.has-links { - background-color: black; - } - - .default-node .port.bottom, .default.group .port.bottom { - bottom: 0px; - left: 50%; - } - - .default-node .port.bottomleft, .default.group .port.bottomleft { - bottom: 0px; - left: 0px; - } - - .default-node .port.bottomright, .default.group .port.bottomright { - bottom: 0px; - right: 0px; - } - - .default-node .port.top, .default.group .port.top { - top: 0px; - left: 50%; - } - - .default-node .port.topleft, .default.group .port.topleft { - top: 0px; - left: 0px; - } - - .default-node .port.topright, .default.group .port.topright { - top: 0px; - right: 0px; - } - - .default-node .port.left, .default.group .port.left { - left: 0px; - top: 50%; - } - - .default-node .port.right, .default.group .port.right { - right: 0px; - top: 50%; - } +.default-node.selected { + border: 1px solid #6e9fd4; +} + +.default-node.selected .port { + border: 1px solid #6e9fd4; +} + +.default-node .port, .default.group .port { + width: 20px; + height: 20px; + margin: -10px; + border-radius: 50%; + background-color: #f5f5f5; + border: 1px solid #d4d4d4; + cursor: pointer; + position: absolute; +} + +.default-node .port:hover, .default-node .port.has-links, .default.group .port.has-links { + background-color: black; +} + +.default-node .port.bottom, .default.group .port.bottom { + bottom: 0px; + left: 50%; +} + +.default-node .port.bottomleft, .default.group .port.bottomleft { + bottom: 0px; + left: 0px; +} + +.default-node .port.bottomright, .default.group .port.bottomright { + bottom: 0px; + right: 0px; +} + +.default-node .port.top, .default.group .port.top { + top: 0px; + left: 50%; +} + +.default-node .port.topleft, .default.group .port.topleft { + top: 0px; + left: 0px; +} + +.default-node .port.topright, .default.group .port.topright { + top: 0px; + right: 0px; +} + +.default-node .port.left, .default.group .port.left { + left: 0px; + top: 50%; +} + +.default-node .port.right, .default.group .port.right { + right: 0px; + top: 50%; +} .diagram-navigator.default { position: absolute; @@ -96,9 +96,9 @@ div.group.default { background: rgb(198, 198, 198); } - div.group.default.selected { - outline: 2px solid #6e9fd4; - } +div.group.default.selected { + outline: 2px solid #6e9fd4; +} g.group.default rect { outline: 2px solid black; @@ -126,4 +126,5 @@ g.group.default.selected > rect { -ms-transform: translate(-50%, -50%); transform: translate(-50%, -50%); } + /*# sourceMappingURL=wwwroot\default.styles.css.map */ diff --git a/src/Blazor.Diagrams/wwwroot/script.js b/src/Blazor.Diagrams/wwwroot/script.js index c47a5dfb2..840a619b5 100644 --- a/src/Blazor.Diagrams/wwwroot/script.js +++ b/src/Blazor.Diagrams/wwwroot/script.js @@ -52,4 +52,4 @@ window.addEventListener('scroll', () => { canvas.ref.invokeMethodAsync('OnResize', canvas.lastBounds); } }); -s.mo.observe(document.body, { childList: true, subtree: true }); \ No newline at end of file +s.mo.observe(document.body, {childList: true, subtree: true}); \ No newline at end of file From 7511b4bc5245694b1e2d70ffab57fffb8eed5481 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 2 Sep 2022 19:46:35 +0100 Subject: [PATCH 077/193] Add GridWidget (Thanks to @xevoryn) --- samples/SharedDemo/Demos/Simple.razor | 1 - samples/SharedDemo/Demos/SnapToGrid.razor | 3 +- .../Components/Widgets/GridWidget.razor | 8 ++ .../Components/Widgets/GridWidget.razor.cs | 79 +++++++++++++++++++ .../Components/Widgets/GridWidget.razor.css | 0 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 src/Blazor.Diagrams/Components/Widgets/GridWidget.razor create mode 100644 src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs create mode 100644 src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.css diff --git a/samples/SharedDemo/Demos/Simple.razor b/samples/SharedDemo/Demos/Simple.razor index 75b653063..a4019cf51 100644 --- a/samples/SharedDemo/Demos/Simple.razor +++ b/samples/SharedDemo/Demos/Simple.razor @@ -34,7 +34,6 @@ -
    diff --git a/samples/SharedDemo/Demos/SnapToGrid.razor b/samples/SharedDemo/Demos/SnapToGrid.razor index 992c3cc21..1d449b21d 100644 --- a/samples/SharedDemo/Demos/SnapToGrid.razor +++ b/samples/SharedDemo/Demos/SnapToGrid.razor @@ -16,9 +16,10 @@ } - + + diff --git a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor new file mode 100644 index 000000000..0fc8bdd43 --- /dev/null +++ b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor @@ -0,0 +1,8 @@ +@if (_visible) +{ +
    +} +else +{ +
    +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs new file mode 100644 index 000000000..596f985ff --- /dev/null +++ b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs @@ -0,0 +1,79 @@ +using System; +using System.Text; +using Blazor.Diagrams.Core.Extensions; +using Microsoft.AspNetCore.Components; + +namespace Blazor.Diagrams.Components.Widgets; + +public partial class GridWidget : IDisposable +{ + private bool _visible; + 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; + [Parameter] public GridMode Mode { get; set; } = GridMode.Line; + [Parameter] public string BackgroundColor { get; set; } = "rgb(241 241 241)"; + + public void Dispose() + { + BlazorDiagram.PanChanged -= RefreshPosition; + BlazorDiagram.ZoomChanged -= RefreshPosition; + } + + protected override void OnInitialized() + { + BlazorDiagram.PanChanged += RefreshPosition; + BlazorDiagram.ZoomChanged += RefreshPosition; + } + + protected override void OnParametersSet() + { + _posX = BlazorDiagram.Pan.X; + _posY = BlazorDiagram.Pan.Y; + _scaledSize = Size * BlazorDiagram.Zoom; + _visible = BlazorDiagram.Zoom > ZoomThreshold; + } + + private void RefreshPosition() + { + _posX = BlazorDiagram.Pan.X; + _posY = BlazorDiagram.Pan.Y; + _scaledSize = Size * BlazorDiagram.Zoom; + _visible = BlazorDiagram.Zoom > ZoomThreshold; + InvokeAsync(StateHasChanged); + } + + private string GenerateStyle() + { + var sb = new StringBuilder(); + + sb.Append($"background-color: {BackgroundColor};"); + sb.Append($"background-size: {_scaledSize.ToInvariantString()}px {_scaledSize.ToInvariantString()}px;"); + sb.Append($"background-position-x: {_posX.ToInvariantString()}px;"); + sb.Append($"background-position-y: {_posY.ToInvariantString()}px;"); + + switch (Mode) + { + case GridMode.Line: + sb.Append("background-image: linear-gradient(rgb(211, 211, 211) 1px, transparent 1px), linear-gradient(90deg, rgb(211, 211, 211) 1px, transparent 1px);"); + break; + case GridMode.Point: + sb.Append("background-image: radial-gradient(circle at 0 0, rgb(129, 129, 129) 1px, transparent 1px);"); + break; + default: + throw new ArgumentOutOfRangeException(); + } + + return sb.ToString(); + } +} + +public enum GridMode +{ + Line, + Point +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.css b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.css new file mode 100644 index 000000000..e69de29bb From e0985107d7c041508cd83b18ba37e7a74216ddc8 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 3 Sep 2022 21:30:06 +0100 Subject: [PATCH 078/193] Rewrite Navigator to be lighter, faster & can take node shape into account (rect/ellipse for now) --- .../SharedDemo/Demos/Groups/Grouping.razor | 5 +- .../Demos/Nodes/PortlessLinks.razor | 9 +- .../Demos/Nodes/PortlessLinks.razor.cs | 27 +- .../SharedDemo/Demos/Nodes/SvgDemo.razor.cs | 10 +- samples/SharedDemo/Demos/Simple.razor | 17 +- .../Components/Widgets/NavigatorWidget.razor | 86 +++--- .../Widgets/NavigatorWidget.razor.cs | 258 ++++++++---------- 7 files changed, 204 insertions(+), 208 deletions(-) diff --git a/samples/SharedDemo/Demos/Groups/Grouping.razor b/samples/SharedDemo/Demos/Groups/Grouping.razor index ef0b3d17d..9ba1eded5 100644 --- a/samples/SharedDemo/Demos/Groups/Grouping.razor +++ b/samples/SharedDemo/Demos/Groups/Grouping.razor @@ -18,7 +18,10 @@ - + + diff --git a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor index ac6606a18..1836088ce 100644 --- a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor +++ b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor @@ -3,5 +3,12 @@ @inject LayoutData LayoutData - + + + + + + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs index e518567f0..1fa3d35b2 100644 --- a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs @@ -2,6 +2,7 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams; using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Controls.Default; namespace SharedDemo.Demos.Nodes { @@ -15,7 +16,7 @@ protected override void OnInitialized() LayoutData.Title = "Portless Links"; LayoutData.Info = "Starting from 2.0, you can create links between nodes directly! " + - "All you need to specify is the shape of your nodes in order to calculate the connection points."; + "All you need to specify is the shape of your nodes in order to calculate the connection points."; LayoutData.DataChanged(); InitializeDiagram(); @@ -38,22 +39,40 @@ private void InitializeDiagram() TargetMarker = LinkMarker.Arrow, Segmentable = true }); - _blazorDiagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node2), new SinglePortAnchor(node3.GetPort(PortAlignment.Left))) + _blazorDiagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node2), + new SinglePortAnchor(node3.GetPort(PortAlignment.Left))) { SourceMarker = LinkMarker.Arrow, TargetMarker = LinkMarker.Arrow, Segmentable = true }); + + _blazorDiagram.Controls.AddFor(node1) + .Add(new RemoveControl(1, 0)) + .Add(new DragNewLinkControl(1, 0.5, 20)) + .Add(new BoundaryControl()); + + _blazorDiagram.Controls.AddFor(node2) + .Add(new RemoveControl(1, 0)) + .Add(new DragNewLinkControl(1, 0.5, 20)) + .Add(new BoundaryControl()); + + _blazorDiagram.Controls.AddFor(node3) + .Add(new RemoveControl(1, 0)) + .Add(new DragNewLinkControl(1, 0.5, 20)) + .Add(new BoundaryControl()); } } class RoundedNode : NodeModel { - public RoundedNode(Point position = null) : base(position) { } + public RoundedNode(Point position = null) : base(position) + { + } public override IShape GetShape() { return Shapes.Circle(this); } } -} +} \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs index 2bff55fde..9eb015231 100644 --- a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs @@ -2,6 +2,7 @@ using Blazor.Diagrams.Components; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Controls.Default; using Blazor.Diagrams.Models; namespace SharedDemo.Demos.Nodes @@ -32,6 +33,10 @@ private void InitializeDiagram() var node3 = NewNode(500, 100); var node4 = NewNode(700, 350); _blazorDiagram.Nodes.Add(new[] { node1, node2, node3, node4 }); + + var controls1 = _blazorDiagram.Controls.AddFor(node4); + controls1.Add(new RemoveControl(1, 0)); + controls1.Add(new BoundaryControl()); _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); @@ -40,7 +45,10 @@ private void InitializeDiagram() var group1 = _blazorDiagram.AddGroup(new SvgGroupModel(new[] { node1, node2 })); var group2 = _blazorDiagram.AddGroup(new SvgGroupModel(new[] { group1, node3 })); - _blazorDiagram.Links.Add(new LinkModel(group2, node4)); + var link = _blazorDiagram.Links.Add(new LinkModel(group2, node4)); + var controls2 = _blazorDiagram.Controls.AddFor(link); + controls2.Add(new RemoveControl(1, 0)); + controls2.Add(new BoundaryControl()); } private NodeModel NewNode(double x, double y, bool svg = true) diff --git a/samples/SharedDemo/Demos/Simple.razor b/samples/SharedDemo/Demos/Simple.razor index a4019cf51..9fa10df97 100644 --- a/samples/SharedDemo/Demos/Simple.razor +++ b/samples/SharedDemo/Demos/Simple.razor @@ -4,6 +4,7 @@ @inject LayoutData LayoutData @code { + protected override void OnInitialized() { base.OnInitialized(); @@ -12,19 +13,20 @@ LayoutData.Info = "A simple example of Blazor.Diagrams."; LayoutData.DataChanged(); } + }
    - +
    - +
    - +
    @@ -32,8 +34,11 @@ - - + + + - +
    \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor b/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor index 3baa3c1fc..a72c12a1e 100644 --- a/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor +++ b/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor @@ -1,54 +1,40 @@ -@if (BlazorDiagram?.Container != null) -{ - var addedNodeX = Math.Max(0, BlazorDiagram.Pan.X) + NodePositionAdjustment.X; - var addedNodeY = Math.Max(0, BlazorDiagram.Pan.Y) + NodePositionAdjustment.Y; - var addedCurrentViewX = (Math.Abs(Math.Min(0, BlazorDiagram.Pan.X)) + NodePositionAdjustment.X) * XFactor; - var addedCurrentViewY = (Math.Abs(Math.Min(0, BlazorDiagram.Pan.Y)) + NodePositionAdjustment.Y) * YFactor; - var currentViewWidth = BlazorDiagram.Container.Width * XFactor; - var currentViewHeight = BlazorDiagram.Container.Height * YFactor; + -
    -
    -
    -
    -
    - - @foreach (var node in BlazorDiagram.Nodes.Where(n => n.Size != null)) - { - var left = (node.Position.X * BlazorDiagram.Zoom + addedNodeX) * XFactor; - var top = (node.Position.Y * BlazorDiagram.Zoom + addedNodeY) * YFactor; - var width = node.Size.Width * BlazorDiagram.Zoom * XFactor; - var height = node.Size.Height * BlazorDiagram.Zoom * YFactor; + @foreach (var group in BlazorDiagram.Groups) + { + if (group.Size == null) + continue; - - - - - } + + + } - @foreach (var group in BlazorDiagram.Groups.Where(g => !Size.Zero.Equals(g.Size))) - { - var left = (Math.Max(0, group.Position.X * BlazorDiagram.Zoom) + addedNodeX) * XFactor; - var top = (Math.Max(0, group.Position.Y * BlazorDiagram.Zoom) + addedNodeY) * YFactor; - var width = group.Size.Width * BlazorDiagram.Zoom * XFactor; - var height = group.Size.Height * BlazorDiagram.Zoom * YFactor; + @foreach (var node in BlazorDiagram.Nodes) + { + if (node.Size == null) + continue; - - - - - } - -
    -} \ No newline at end of file + @GetNodeRenderFragment(node) + } + + + + + \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs index 0ca85380a..7c91809cb 100644 --- a/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs @@ -5,185 +5,153 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Rendering; namespace Blazor.Diagrams.Components.Widgets; public partial class NavigatorWidget : IDisposable { - [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } + private double _x; + private double _y; + private double _width; + private double _height; + private double _scaledMargin; + private double _vX; + private double _vY; + private double _vWidth; + private double _vHeight; + + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; + [Parameter] public bool UseNodeShape { get; set; } = true; [Parameter] public double Width { get; set; } [Parameter] public double Height { get; set; } - [Parameter] public string FillColor { get; set; } = "#40babd"; - [Parameter] public bool DefaultStyle { get; set; } - - private Point NodePositionAdjustment { get; set; } - private double XFactor { get; set; } - private double YFactor { get; set; } + [Parameter] public double Margin { get; set; } = 5; + [Parameter] public string NodeColor { get; set; } = "#40babd"; + [Parameter] public string GroupColor { get; set; } = "#9fd0d1"; + [Parameter] public string ViewStrokeColor { get; set; } = "#40babd"; + [Parameter] public int ViewStrokeWidth { get; set; } = 4; + [Parameter] public string? Class { get; set; } + [Parameter] public string? Style { get; set; } public void Dispose() { - if (BlazorDiagram != null) - { - BlazorDiagram.Changed -= Diagram_Changed; - BlazorDiagram.Nodes.Added -= Diagram_NodesAdded; - BlazorDiagram.Nodes.Removed -= Diagram_NodesRemoved; - foreach (var node in BlazorDiagram.Nodes) - node.Changed -= Refresh; - - foreach (var group in BlazorDiagram.Groups) - group.Changed -= Refresh; - } + BlazorDiagram.Changed -= Refresh; + BlazorDiagram.Nodes.Added -= OnNodeAdded; + BlazorDiagram.Nodes.Removed -= OnNodeRemoved; + BlazorDiagram.GroupAdded -= OnNodeAdded; + BlazorDiagram.GroupRemoved -= OnNodeRemoved; + + foreach (var node in BlazorDiagram.Nodes) + node.Changed -= OnNodeChanged; + + foreach (var group in BlazorDiagram.Groups) + group.Changed -= OnNodeChanged; } - protected override void OnParametersSet() + protected override void OnInitialized() { - base.OnParametersSet(); - if (BlazorDiagram != null) - { - foreach (var node in BlazorDiagram.Nodes) - node.Changed += Refresh; + BlazorDiagram.Changed += Refresh; + BlazorDiagram.Nodes.Added += OnNodeAdded; + BlazorDiagram.Nodes.Removed += OnNodeRemoved; + BlazorDiagram.GroupAdded += OnNodeAdded; + BlazorDiagram.GroupRemoved += OnNodeRemoved; - foreach (var group in BlazorDiagram.Groups) - group.Changed += Refresh; + foreach (var node in BlazorDiagram.Nodes) + node.Changed += OnNodeChanged; - BlazorDiagram.Changed += Diagram_Changed; - BlazorDiagram.Nodes.Added += Diagram_NodesAdded; - BlazorDiagram.Nodes.Removed += Diagram_NodesRemoved; - BlazorDiagram.GroupAdded += Diagram_GroupAdded; - BlazorDiagram.GroupRemoved += Diagram_GroupRemoved; - } + foreach (var group in BlazorDiagram.Groups) + group.Changed += OnNodeChanged; } - private void Diagram_Changed() - { - Refresh(null); - } + private void OnNodeAdded(NodeModel node) => node.Changed += OnNodeChanged; - private void Diagram_NodesAdded(NodeModel node) - { - node.Changed += Refresh; - } + private void OnNodeRemoved(NodeModel node) => node.Changed -= OnNodeChanged; - private void Diagram_NodesRemoved(NodeModel node) - { - node.Changed -= Refresh; - } + private void OnNodeChanged(Model _) => Refresh(); - private void Diagram_GroupAdded(GroupModel group) + private void Refresh() { - group.Changed += Refresh; - } + if (BlazorDiagram.Container == null) + return; - private void Diagram_GroupRemoved(GroupModel group) - { - group.Changed -= Refresh; - } + _vX = -BlazorDiagram.Pan.X / BlazorDiagram.Zoom; + _vY = -BlazorDiagram.Pan.Y / BlazorDiagram.Zoom; + _vWidth = BlazorDiagram.Container.Width / BlazorDiagram.Zoom; + _vHeight = BlazorDiagram.Container.Height / BlazorDiagram.Zoom; - private void Refresh(Model? _) - { - if (BlazorDiagram != null) - { - var nodes = BlazorDiagram.Nodes - .Union(BlazorDiagram.Groups) - .Where(n => n.Size?.Equals(Size.Zero) == false).ToList(); - - if (nodes.Count == 0) - return; - - var bounds = nodes.GetBounds(); - var nodesMinX = bounds.Left * BlazorDiagram.Zoom; - var nodesMaxX = bounds.Right * BlazorDiagram.Zoom; - var nodesMinY = bounds.Top * BlazorDiagram.Zoom; - var nodesMaxY = bounds.Bottom * BlazorDiagram.Zoom; - - (var fullSizeWidth, var fullSizeHeight) = GetFullSize(nodesMaxX, nodesMaxY); - AdjustFullSizeWithNodesRect(nodesMinX, nodesMinY, ref fullSizeWidth, ref fullSizeHeight); - - NodePositionAdjustment = new Point(nodesMinX < 0 ? Math.Abs(nodesMinX) : 0, - nodesMinY < 0 ? Math.Abs(nodesMinY) : 0); - XFactor = Width / fullSizeWidth; - YFactor = Height / fullSizeHeight; - InvokeAsync(StateHasChanged); - } - } + var minX = _vX; + var minY = _vY; + var maxX = _vX + _vWidth; + var maxY = _vY + _vHeight; - private void AdjustFullSizeWithNodesRect(double nodesMinX, double nodesMinY, ref double fullSizeWidth, - ref double fullSizeHeight) - { - // Width - if (nodesMinX < 0) + foreach (var node in BlazorDiagram.Nodes.Union(BlazorDiagram.Groups)) { - var temp = nodesMinX + BlazorDiagram.Pan.X; - if (BlazorDiagram.Pan.X > 0 && temp < 0) - fullSizeWidth += Math.Abs(temp); - else if (BlazorDiagram.Pan.X <= 0) fullSizeWidth += Math.Abs(nodesMinX); - } + if (node.Size == null) + continue; - // Height - if (nodesMinY < 0) - { - var temp = nodesMinY + BlazorDiagram.Pan.Y; - if (BlazorDiagram.Pan.Y > 0 && temp < 0) - fullSizeHeight += Math.Abs(temp); - else if (BlazorDiagram.Pan.Y <= 0) fullSizeHeight += Math.Abs(nodesMinY); + minX = Math.Min(minX, node.Position.X); + minY = Math.Min(minY, node.Position.Y); + maxX = Math.Max(maxX, node.Position.X + node.Size.Width); + maxY = Math.Max(maxY, node.Position.Y + node.Size.Height); } + + var width = maxX - minX; + var height = maxY - minY; + var scaledWidth = width / Width; + var scaledHeight = height / Height; + var scale = Math.Max(scaledWidth, scaledHeight); + var viewWidth = scale * Width; + var viewHeight = scale * Height; + + _scaledMargin = Margin * scale; + _x = minX - (viewWidth - width) / 2 - _scaledMargin; + _y = minY - (viewHeight - height) / 2 - _scaledMargin; + _width = viewWidth + _scaledMargin * 2; + _height = viewHeight + _scaledMargin * 2; + InvokeAsync(StateHasChanged); } - private (double width, double height) GetFullSize(double nodesMaxX, double nodesMaxY) + private RenderFragment GetNodeRenderFragment(NodeModel node) { - var nodesLayerWidth = Math.Max(BlazorDiagram.Container.Width * BlazorDiagram.Zoom, nodesMaxX); - var nodesLayerHeight = Math.Max(BlazorDiagram.Container.Height * BlazorDiagram.Zoom, nodesMaxY); - double fullWidth; - double fullHeight; - - if (BlazorDiagram.Zoom == 1) + return builder => { - fullWidth = BlazorDiagram.Container.Width + Math.Abs(BlazorDiagram.Pan.X); - fullHeight = BlazorDiagram.Container.Height + Math.Abs(BlazorDiagram.Pan.Y); - } - else if (BlazorDiagram.Zoom > 1) - { - // Width - if (BlazorDiagram.Pan.X < 0) + if (UseNodeShape) { - if (nodesLayerWidth + BlazorDiagram.Pan.X < BlazorDiagram.Container.Width) - fullWidth = BlazorDiagram.Container.Width + Math.Abs(BlazorDiagram.Pan.X); - else - fullWidth = nodesLayerWidth; - } - else - { - fullWidth = nodesLayerWidth + BlazorDiagram.Pan.X; + var shape = node.GetShape(); + if (shape is Ellipse ellipse) + { + RenderEllipse(node, builder, ellipse); + return; + } } + + RenderRect(node, builder); + }; + } - // Height - if (BlazorDiagram.Pan.Y < 0) - { - if (nodesLayerHeight + BlazorDiagram.Pan.Y < BlazorDiagram.Container.Height) - fullHeight = BlazorDiagram.Container.Height + Math.Abs(BlazorDiagram.Pan.Y); - else - fullHeight = nodesLayerHeight; - } - else - { - fullHeight = nodesLayerHeight + BlazorDiagram.Pan.Y; - } - } - else - { - // Width - if (BlazorDiagram.Pan.X > 0) - fullWidth = Math.Max(nodesLayerWidth + BlazorDiagram.Pan.X, BlazorDiagram.Container.Width); - else - fullWidth = BlazorDiagram.Container.Width + Math.Abs(BlazorDiagram.Pan.X); - - // Height - if (BlazorDiagram.Pan.Y > 0) - fullHeight = Math.Max(nodesLayerHeight + BlazorDiagram.Pan.Y, BlazorDiagram.Container.Height); - else - fullHeight = BlazorDiagram.Container.Height + Math.Abs(BlazorDiagram.Pan.Y); - } + private void RenderRect(NodeModel node, RenderTreeBuilder builder) + { + builder.OpenElement(0, "rect"); + builder.SetKey(node); + builder.AddAttribute(1, "class", "navigator-node"); + builder.AddAttribute(2, "fill", NodeColor); + builder.AddAttribute(2, "x", node.Position.X.ToInvariantString()); + builder.AddAttribute(2, "y", node.Position.Y.ToInvariantString()); + builder.AddAttribute(2, "width", node.Size!.Width.ToInvariantString()); + builder.AddAttribute(2, "height", node.Size.Height.ToInvariantString()); + builder.CloseElement(); + } - return (fullWidth, fullHeight); + private void RenderEllipse(NodeModel node, RenderTreeBuilder builder, Ellipse ellipse) + { + builder.OpenElement(0, "ellipse"); + builder.SetKey(node); + builder.AddAttribute(1, "class", "navigator-node"); + builder.AddAttribute(2, "fill", NodeColor); + builder.AddAttribute(2, "cx", ellipse.Cx.ToInvariantString()); + builder.AddAttribute(2, "cy", ellipse.Cy.ToInvariantString()); + builder.AddAttribute(2, "rx",ellipse.Rx.ToInvariantString()); + builder.AddAttribute(2, "ry", ellipse.Ry.ToInvariantString()); + builder.CloseElement(); } } \ No newline at end of file From 160baee28d158bd02ce25c4df87bbba2ea6c3899 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 4 Sep 2022 11:42:38 +0100 Subject: [PATCH 079/193] Add dynamic anchor test page --- samples/SharedDemo/Demos/DevTests.razor | 71 +++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 samples/SharedDemo/Demos/DevTests.razor diff --git a/samples/SharedDemo/Demos/DevTests.razor b/samples/SharedDemo/Demos/DevTests.razor new file mode 100644 index 000000000..b7e04b54c --- /dev/null +++ b/samples/SharedDemo/Demos/DevTests.razor @@ -0,0 +1,71 @@ +@page "/demos/dynamic-anchor" +@layout DemoLayout +@using Blazor.Diagrams +@using Blazor.Diagrams.Core.Anchors.Dynamic +@using Blazor.Diagrams.Core.Controls +@using Blazor.Diagrams.Core.Positions +@using Blazor.Diagrams.Core.Controls.Default + + + + + +@code { + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() + { + var node1 = NewNode(100, 100); + var node2 = NewNode(300, 100); + + var providers1 = new IPositionProvider[] + { + new BoundsBasedPositionProvider(0.5, 0.0, 0, -10), // top + new BoundsBasedPositionProvider(0.0, 0.5, -10, 0), // left + new BoundsBasedPositionProvider(1.0, 0.5, 10, 0), // right + new BoundsBasedPositionProvider(0.5, 1.0, 0, 10), // bottom + }; + + var providers2 = new IPositionProvider[] + { + new ShapeAnglePositionProvider(45, 10, 10), // bottom right + new ShapeAnglePositionProvider(135, -10, 10), // bottom left + new ShapeAnglePositionProvider(225, -10, -10), // top left + new ShapeAnglePositionProvider(315, 10, -10), // top right + }; + + var link1 = new LinkModel(new DynamicAnchor(node1, providers1), new DynamicAnchor(node2, providers2)) + { + PathGenerator = PathGenerators.Straight, + SourceMarker = LinkMarker.Arrow, + TargetMarker = LinkMarker.Arrow + }; + + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Links.Add(link1); + + _blazorDiagram.Controls.AddFor(node1) + .Add(new RemoveControl(1, 0)) + .Add(new DragNewLinkControl(1, 0.5, 20)) + .Add(new BoundaryControl()); + + _blazorDiagram.Controls.AddFor(node2) + .Add(new RemoveControl(1, 0)) + .Add(new DragNewLinkControl(1, 0.5, 20)) + .Add(new BoundaryControl()); + + _blazorDiagram.Controls.AddFor(link1) + .Add(new RemoveControl(new LinkPathPositionProvider(0.1))) + .Add(new BoundaryControl()); + } + + private static NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; + } +} \ No newline at end of file From ead6b77b4f7b1a1d63b4928f723ed81d7879b77a Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 4 Sep 2022 12:26:15 +0100 Subject: [PATCH 080/193] Update versions and changelog --- CHANGELOG.md | 64 ++++++++++++++++--- .../Blazor.Diagrams.Core.csproj | 8 +-- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 8 +-- 3 files changed, 63 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c342c683..88a5b9fd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,16 +1,24 @@ + # Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Diagrams (3.0.0) - 2022-X-X +## Diagrams (3.0.0-preview.1) - 2022-09-04 + +.NET 6! + +A lot of things changed in this version, a lot of breaking changes were introduced but I believe it was necessary. +Many changes were required to make everything clearer, customizable and cleaner (code wise). +I'm aiming to completely decouple the Core library from the UI, because I'm thinking of giving MAUI Diagrams a try very soon! ### Added -- `Diagram` class (inherits `DiagramBase`) to the blazor package to replace the old Core one +- `BlazorDiagram` class (inherits `Diagram`) to the blazor package to replace the old Core one +- `BlazorDiagramOptions` that inherit from the other diagram options to add Blazor (UI) specific options - `Blazor.Diagrams.Models.SvgNodeModel` class to represent a node that needs to be rendered in the SVG layer -- `GetBehavior` method to `DiagramBase` in order to retrieve a registered behavior +- `GetBehavior` method to `Diagram` in order to retrieve a registered behavior - `KeyboardShortcutsBehavior` class which handles keyboard shortcuts/actions: - `SetShortcut`: sets an action (`Func`) to be executed whenever the specified combination is pressed - `RemoveShortcut`: removes a defined action (if it exists) @@ -20,23 +28,59 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Instead of links requiring the source and target to be either both nodes or both ports, there is now only one `Source` and `Target` of type `Anchor` - This lets the link not worry about the details of from/to where its going, as long as the anchor provides it with its position when asked for - Current implementations: - - `SinglePortAnchor`: Specifies that the connection point is a specific port (replaces a link) - - `ShapeIntersectionAnchor`: Specifies that the connection point is the intersection of the line with the node's shape - -- Lot of unit tests + - `SinglePortAnchor`: Specifies that the connection point is a specific port (supports shape & alignment) + - `ShapeIntersectionAnchor`: Specifies that the connection point is the intersection of a line with the node's shape + - `DynamicAnchor`: Specifies that the connection point is one of the given positions (closest) +- Virtual `IShape GetShape()` method on nodes (default `Rectangle`) and ports (default `Circle`) +- `Options.LinksLayerOrder` to indicate the order of the links layer (svg for blazor) +- `Options.NodesLayerOrder` to indicate the order of the nodes layer (html for blazor) +- Support for SVG groups that also represent children as a hierarchy (`SvgGroupModel`) +- Node renderer will now append, in addition to `node locked`, the classes `selected grouped` +- `IHasBounds` and `IHasShape` interfaces to both nodes and ports +- `IPositionProvider` to encapsulate how certain positions are calculated given a model + - They are used for dynamic anchors and controls for now + - `BoundsBasedPositionProvider` returns the position based on the bounds of the model (e.g. (0.5, 0.5) would be the center) + - `ShapeAnglePositionProvider` returns the position as the point at the angle of the shape + - `LinkPathPositionProvider` returns the position based on the link's path (`getPointAtLength`) +- Links have a reference to `Diagram` now +- `PointerEnter` and `PointerLeave` events for nodes and links for now +- `GeneratedPathResult` and `Paths` to `BaseLinkModel` to always have access to the actual paths +- `Controls` feature (beta): + - They are things that can show up on top of nodes/links and can even be clicked to be executed + - Their UI is also picked up from the registered components + - `Control` designates a control that has a position and will be rendered if visible + - `ExecutableControl` designates a control that has a position and will be executed when pressed (PointerDown event) + - Default controls for now are: + - `BoundaryControl` shows the model's boundary + - `RemoveControl` shows a button that when clicked, removes the model from the diagram + - `DragNewLink` shows a button that when clicked, starts a new link dragging from that node +- `GridWidget` a background grid that moves with the diagram instead of being fixed like in the Snap to grid example +- More unit tests ### Changed - Core package changes: - Web dependency was removed from the Core package - - `Diagram` in the Core package was renamed to `DiagramBase` - - These changes were done to decouple the core from the rendering, in the future we might have a MAUI renderer + - `Diagram` is now abstract + - These changes were done to decouple the core from the rendering, in the future we might have a MAUI renderer - `Diagram.GetComponentForModel` now accepts a `checkSubclasses` argument (default `true`) - Constraints now must return a `ValueTask` instead of a simple `bool` +- Renamed `AllLinks` to `PortLinks` for more clarity on which links, since `Links` contains the others +- Dragging links from ports will now follow the mouse at the same pace minus 5 pixels so that it doesn't go on top of the link it self or other ports +- How groups are rendered + - `GroupRenderer` will take care of rendering the group with the appropriate style and classes + - Only `GroupNodes` is required, `GroupLinks` was deleted because all links are shown in the svg layer (with appropriate order) +- `Diagram.AddGroup` will now return the added group +- All `Mouse` events have been converted to `Pointer` events +- `PathGenerator` now return `SvgPath` instead of just strings +- `NavigatorWidget` was rewritten to be faster, lighter, WORKING and customizable + - It now can also take the shape of the nodes into account (rect and ellipse for now) ### Fixed - Virtualization throwing a JSException (#155) +- Ports not rendering correctly because of the missing `@key` (#220) +- Link not refreshing when a new vertex is created, which was showing out of link ### Removed @@ -46,6 +90,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `GroupingBehavior` since there is a new keyboard shortcuts system - `BaseLinkModelExtensions` since it was Obselete - Unnecessary port refreshes when dragging a link ends or when link snapping +- `ShapeDefiner` delegate and constructor arguments on nodes since delegates can't be serialized +- `TouchX` events ## Diagrams (2.1.6) - 2021-10-31 diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index a73af7b82..9dc3ad661 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -7,10 +7,10 @@ MIT zHaytam A fully customizable and extensible all-purpose diagrams library for Blazor - 2.1.6 - 2.1.6 - https://github.com/zHaytam/Blazor.Diagrams - 2.1.6 + 3.0.0 + 3.0.0-beta.1 + https://github.com/Blazor-Diagrams/Blazor.Diagrams + 3.0.0-beta.1 Z.Blazor.Diagrams.Core blazor diagrams diagramming svg drag Z.Blazor.Diagrams.Core diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index 4195dd2a5..dfe7f6f4d 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -5,11 +5,11 @@ enable zHaytam MIT - 2.1.6 - 2.1.6 - https://github.com/zHaytam/Blazor.Diagrams + 3.0.0 + 3.0.0-beta.1 + https://github.com/Blazor-Diagrams/Blazor.Diagrams A fully customizable and extensible all-purpose diagrams library for Blazor - 2.1.6 + 3.0.0-beta.1 true blazor diagrams diagramming svg drag Z.Blazor.Diagrams From b9c3544875275dae9d88fa73411c4c01e5c24690 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 4 Sep 2022 12:31:39 +0100 Subject: [PATCH 081/193] Update build.yml --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index da20c898a..83ca7e4d6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -2,6 +2,8 @@ name: Build on: push: branches: + - master + - develop - 3.0.0 pull_request: types: [opened, synchronize, reopened] From 60de7b04fbccc9373d2bb2850314b5b0c4fb1c36 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 6 Sep 2022 11:22:44 +0100 Subject: [PATCH 082/193] WIP --- Blazor.Diagrams.sln | 9 + site/Site/App.razor | 12 + .../Components/Landing/AddNodeWidget.razor | 27 + .../Landing/LandingShowcaseDiagram.razor | 27 + .../Landing/LandingShowcaseDiagram.razor.cs | 79 + .../Landing/LandingShowcaseDiagram.razor.css | 10 + .../Components/Landing/NumberNodeWidget.razor | 21 + site/Site/Models/Landing/AddNodeModel.cs | 43 + site/Site/Models/Landing/BaseOperation.cs | 29 + .../Models/Landing/CalculatorPortModel.cs | 23 + site/Site/Models/Landing/NumberNodeModel.cs | 11 + site/Site/Pages/Index.razor | 498 +++++++ site/Site/Program.cs | 11 + site/Site/Properties/launchSettings.json | 30 + site/Site/Shared/LandingLayout.razor | 112 ++ site/Site/Shared/LandingLayout.razor.css | 0 site/Site/Site.csproj | 30 + site/Site/_Imports.razor | 21 + site/Site/tailwind.config.js | 8 + site/Site/wwwroot/css/app.css | 1316 +++++++++++++++++ site/Site/wwwroot/css/input.css | 30 + site/Site/wwwroot/favicon.ico | Bin 0 -> 15406 bytes site/Site/wwwroot/icon-192.png | Bin 0 -> 2626 bytes site/Site/wwwroot/img/ZBD.png | Bin 0 -> 7497 bytes site/Site/wwwroot/index.html | 68 + .../Components/Renderers/PortRenderer.cs | 23 +- 26 files changed, 2425 insertions(+), 13 deletions(-) create mode 100644 site/Site/App.razor create mode 100644 site/Site/Components/Landing/AddNodeWidget.razor create mode 100644 site/Site/Components/Landing/LandingShowcaseDiagram.razor create mode 100644 site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs create mode 100644 site/Site/Components/Landing/LandingShowcaseDiagram.razor.css create mode 100644 site/Site/Components/Landing/NumberNodeWidget.razor create mode 100644 site/Site/Models/Landing/AddNodeModel.cs create mode 100644 site/Site/Models/Landing/BaseOperation.cs create mode 100644 site/Site/Models/Landing/CalculatorPortModel.cs create mode 100644 site/Site/Models/Landing/NumberNodeModel.cs create mode 100644 site/Site/Pages/Index.razor create mode 100644 site/Site/Program.cs create mode 100644 site/Site/Properties/launchSettings.json create mode 100644 site/Site/Shared/LandingLayout.razor create mode 100644 site/Site/Shared/LandingLayout.razor.css create mode 100644 site/Site/Site.csproj create mode 100644 site/Site/_Imports.razor create mode 100644 site/Site/tailwind.config.js create mode 100644 site/Site/wwwroot/css/app.css create mode 100644 site/Site/wwwroot/css/input.css create mode 100644 site/Site/wwwroot/favicon.ico create mode 100644 site/Site/wwwroot/icon-192.png create mode 100644 site/Site/wwwroot/img/ZBD.png create mode 100644 site/Site/wwwroot/index.html diff --git a/Blazor.Diagrams.sln b/Blazor.Diagrams.sln index 491396633..6ae5eb041 100644 --- a/Blazor.Diagrams.sln +++ b/Blazor.Diagrams.sln @@ -41,6 +41,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Layouts", "docs\Layouts\Lay EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Diagrams.Tests", "tests\Blazor.Diagrams.Tests\Blazor.Diagrams.Tests.csproj", "{ED3B0D8F-F29A-4C66-A167-C36824A76902}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "site", "site", "{F1E6F4C0-3EC7-4CFF-834A-0CF207CCFF3E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Site", "site\Site\Site.csproj", "{F26307EC-C188-44BD-B3E5-960318F43C0C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -91,6 +95,10 @@ Global {ED3B0D8F-F29A-4C66-A167-C36824A76902}.Debug|Any CPU.Build.0 = Debug|Any CPU {ED3B0D8F-F29A-4C66-A167-C36824A76902}.Release|Any CPU.ActiveCfg = Release|Any CPU {ED3B0D8F-F29A-4C66-A167-C36824A76902}.Release|Any CPU.Build.0 = Release|Any CPU + {F26307EC-C188-44BD-B3E5-960318F43C0C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F26307EC-C188-44BD-B3E5-960318F43C0C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F26307EC-C188-44BD-B3E5-960318F43C0C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F26307EC-C188-44BD-B3E5-960318F43C0C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -107,6 +115,7 @@ Global {3D104DB4-C7F0-42CA-9D78-AB2C8A8AE3D5} = {A9FC9B20-A9F1-4066-8B59-83BD26D3B1C8} {78C85C89-B464-4083-8829-78BA52BB4780} = {A9FC9B20-A9F1-4066-8B59-83BD26D3B1C8} {ED3B0D8F-F29A-4C66-A167-C36824A76902} = {CEEAE4C2-CE68-4FC3-9E0F-D4781B91F7F4} + {F26307EC-C188-44BD-B3E5-960318F43C0C} = {F1E6F4C0-3EC7-4CFF-834A-0CF207CCFF3E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {969540A2-8162-4063-A4E3-B488F69BD582} diff --git a/site/Site/App.razor b/site/Site/App.razor new file mode 100644 index 000000000..11850a587 --- /dev/null +++ b/site/Site/App.razor @@ -0,0 +1,12 @@ + + + + + + + Not found + +

    Sorry, there's nothing at this address.

    +
    +
    +
    \ No newline at end of file diff --git a/site/Site/Components/Landing/AddNodeWidget.razor b/site/Site/Components/Landing/AddNodeWidget.razor new file mode 100644 index 000000000..37fc0c457 --- /dev/null +++ b/site/Site/Components/Landing/AddNodeWidget.razor @@ -0,0 +1,27 @@ +
    +
    + + + + Add +
    +
    + @Node.Value + + + +
    +
    + +@code { + + [Parameter] + public AddNodeModel Node { get; set; } = null!; + +} \ No newline at end of file diff --git a/site/Site/Components/Landing/LandingShowcaseDiagram.razor b/site/Site/Components/Landing/LandingShowcaseDiagram.razor new file mode 100644 index 000000000..f794cb908 --- /dev/null +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor @@ -0,0 +1,27 @@ +
    +
    + + + + + + + + + +
    +

    + Blazor Diagrams +

    +

    + A fully customizable, extensible and all-purpose
    open-source diagrams library for Blazor +

    + +
    +
    +
    \ No newline at end of file diff --git a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs new file mode 100644 index 000000000..9078a28c8 --- /dev/null +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs @@ -0,0 +1,79 @@ +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using Microsoft.AspNetCore.Components; +using Site.Models.Landing; + +namespace Site.Components.Landing; + +public partial class LandingShowcaseDiagram +{ + private readonly BlazorDiagram _diagram = new(); + + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.GridSize = 20; + _diagram.RegisterModelComponent(); + _diagram.RegisterModelComponent(); + + _diagram.Nodes.Added += OnNodeAdded; + _diagram.Nodes.Removed += OnNodeRemoved; + _diagram.Links.Added += OnLinkAdded; + _diagram.Links.Removed += OnLinkRemoved; + + var n1 = _diagram.Nodes.Add(new NumberNodeModel(new Point(200, 100))); + var n2 = _diagram.Nodes.Add(new NumberNodeModel(new Point(200, 260))); + var n3 = _diagram.Nodes.Add(new NumberNodeModel(new Point(480, 260))); + var a1 = _diagram.Nodes.Add(new AddNodeModel(new Point(500, 100))); + var a2 = _diagram.Nodes.Add(new AddNodeModel(new Point(750, 200))); + + _diagram.Links.Add(new LinkModel(n1.Ports[0], a1.Ports[0])); + _diagram.Links.Add(new LinkModel(n2.Ports[0], a1.Ports[1])); + _diagram.Links.Add(new LinkModel(a1.Ports[2], a2.Ports[0])); + _diagram.Links.Add(new LinkModel(n3.Ports[0], a2.Ports[1])); + } + + private void OnNodeAdded(NodeModel node) + { + (node as BaseOperation)!.ValueChanged += OnValueChanged; + } + + private void OnNodeRemoved(NodeModel node) + { + (node as BaseOperation)!.ValueChanged -= OnValueChanged; + } + + private void OnValueChanged(BaseOperation op) + { + foreach (var link in _diagram.Links) + { + var sp = (link.Source as SinglePortAnchor)!; + var tp = (link.Target as SinglePortAnchor)!; + var otherNode = sp.Port.Parent == op ? tp.Port.Parent : sp.Port.Parent; + otherNode.Refresh(); + } + } + + private void OnLinkAdded(BaseLinkModel link) + { + link.TargetChanged += OnLinKTargetChanged; + } + + private void OnLinKTargetChanged(BaseLinkModel link, Anchor? oldTarget, Anchor? newTarget) + { + if (oldTarget == null && newTarget != null) // First attach + { + (newTarget.Model as PortModel)!.Parent.Refresh(); + } + } + + private void OnLinkRemoved(BaseLinkModel link) + { + (link.Source.Model as PortModel)!.Parent.Refresh(); + if (link.Target != null) (link.Target.Model as PortModel)!.Parent.Refresh(); + link.TargetChanged -= OnLinKTargetChanged; + } +} \ No newline at end of file diff --git a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.css b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.css new file mode 100644 index 000000000..633ec3a91 --- /dev/null +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.css @@ -0,0 +1,10 @@ +::deep g.link path:not(.selection-helper) { + stroke-dasharray: 5; + animation: dash .5s linear infinite; +} + +@keyframes dash { + to { + stroke-dashoffset: -10; + } +} \ No newline at end of file diff --git a/site/Site/Components/Landing/NumberNodeWidget.razor b/site/Site/Components/Landing/NumberNodeWidget.razor new file mode 100644 index 000000000..527eee13a --- /dev/null +++ b/site/Site/Components/Landing/NumberNodeWidget.razor @@ -0,0 +1,21 @@ +
    +
    + + + + Number +
    +
    + + +
    +
    + +@code { + + [Parameter] + public NumberNodeModel Node { get; set; } = null!; + +} \ No newline at end of file diff --git a/site/Site/Models/Landing/AddNodeModel.cs b/site/Site/Models/Landing/AddNodeModel.cs new file mode 100644 index 000000000..6bfd2c63f --- /dev/null +++ b/site/Site/Models/Landing/AddNodeModel.cs @@ -0,0 +1,43 @@ +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Site.Models.Landing; + +public class AddNodeModel : BaseOperation +{ + public AddNodeModel(Point position) : base(position) + { + AddPort(new CalculatorPortModel(this, true)); + AddPort(new CalculatorPortModel(this, true)); + AddPort(new CalculatorPortModel(this, false)); + } + + public override void Refresh() + { + var i1 = Ports[0]; + var i2 = Ports[1]; + + if (i1.Links.Count > 0 && i2.Links.Count > 0) + { + var l1 = i1.Links[0]; + var l2 = i2.Links[0]; + Value = GetInputValue(i1, l1) + GetInputValue(i2, l2); + } + else + { + Value = 0; + } + + base.Refresh(); + } + + private static double GetInputValue(PortModel port, BaseLinkModel link) + { + var sp = (link.Source as SinglePortAnchor)!; + var tp = (link.Source as SinglePortAnchor)!; + var p = sp.Port == port ? tp : sp; + return (p.Port.Parent as BaseOperation)!.Value; + } +} \ No newline at end of file diff --git a/site/Site/Models/Landing/BaseOperation.cs b/site/Site/Models/Landing/BaseOperation.cs new file mode 100644 index 000000000..2861076ef --- /dev/null +++ b/site/Site/Models/Landing/BaseOperation.cs @@ -0,0 +1,29 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; + +namespace Site.Models.Landing; + +public abstract class BaseOperation : NodeModel +{ + private double _value; + + public event Action? ValueChanged; + + protected BaseOperation(Point position) : base(position) + { + + } + + public double Value + { + get => _value; + set + { + if (_value == value) + return; + + _value = value; + ValueChanged?.Invoke(this); + } + } +} \ No newline at end of file diff --git a/site/Site/Models/Landing/CalculatorPortModel.cs b/site/Site/Models/Landing/CalculatorPortModel.cs new file mode 100644 index 000000000..e57926220 --- /dev/null +++ b/site/Site/Models/Landing/CalculatorPortModel.cs @@ -0,0 +1,23 @@ +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Site.Models.Landing; + +public class CalculatorPortModel : PortModel +{ + public CalculatorPortModel(NodeModel parent, bool input) : base(parent, + input ? PortAlignment.Left : PortAlignment.Right) + { + Input = input; + } + + public bool Input { get; } + + public override bool CanAttachTo(ILinkable other) + { + if (other is not CalculatorPortModel port) + return false; + + return port.Input != Input; + } +} \ No newline at end of file diff --git a/site/Site/Models/Landing/NumberNodeModel.cs b/site/Site/Models/Landing/NumberNodeModel.cs new file mode 100644 index 000000000..f3b221d60 --- /dev/null +++ b/site/Site/Models/Landing/NumberNodeModel.cs @@ -0,0 +1,11 @@ +using Blazor.Diagrams.Core.Geometry; + +namespace Site.Models.Landing; + +public class NumberNodeModel : BaseOperation +{ + public NumberNodeModel(Point position) : base(position) + { + AddPort(new CalculatorPortModel(this, false)); + } +} \ No newline at end of file diff --git a/site/Site/Pages/Index.razor b/site/Site/Pages/Index.razor new file mode 100644 index 000000000..a90241bba --- /dev/null +++ b/site/Site/Pages/Index.razor @@ -0,0 +1,498 @@ +@page "/" + +Index + + + + + + + + + @* configurable *@ + + + + + + + + + + + + + + +
    +
    +

    + Title +

    +
    +
    +
    +
    +
    +

    + Lorem ipsum dolor sit amet +

    +

    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at ipsum eu nunc commodo posuere et sit amet ligula. +
    +
    + + Images from: + + undraw.co +

    +
    +
    + + travel booking + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + + connected world + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +

    + Lorem ipsum dolor sit amet +

    +

    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at ipsum eu nunc commodo posuere et sit amet ligula. +
    +
    + Images from: + + undraw.co +

    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +

    + Pricing +

    +
    +
    +
    +
    +
    +
    +
    + Free +
    +
      +
    • Thing
    • +
    • Thing
    • +
    • Thing
    • +
    +
    +
    +
    + £0 + for one user +
    +
    + +
    +
    +
    +
    +
    +
    Basic
    +
    +
      +
    • Thing
    • +
    • Thing
    • +
    • Thing
    • +
    • Thing
    • +
    +
    +
    +
    + £x.99 + / per user +
    +
    + +
    +
    +
    +
    +
    +
    + Pro +
    +
      +
    • Thing
    • +
    • Thing
    • +
    • Thing
    • +
    +
    +
    +
    + £x.99 + / per user +
    +
    + +
    +
    +
    +
    +
    +
    + + + + + + + + + + + + + + + + + + + +
    +

    + Call to Action +

    +
    +
    +
    +

    + Main Hero Message to sell yourself! +

    + +
    \ No newline at end of file diff --git a/site/Site/Program.cs b/site/Site/Program.cs new file mode 100644 index 000000000..41906bb16 --- /dev/null +++ b/site/Site/Program.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using Site; + +var builder = WebAssemblyHostBuilder.CreateDefault(args); +builder.RootComponents.Add("#app"); +builder.RootComponents.Add("head::after"); + +builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + +await builder.Build().RunAsync(); \ No newline at end of file diff --git a/site/Site/Properties/launchSettings.json b/site/Site/Properties/launchSettings.json new file mode 100644 index 000000000..6e91e69c3 --- /dev/null +++ b/site/Site/Properties/launchSettings.json @@ -0,0 +1,30 @@ +{ + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:40851", + "sslPort": 44376 + } + }, + "profiles": { + "Site": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:7025;http://localhost:5037", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/site/Site/Shared/LandingLayout.razor b/site/Site/Shared/LandingLayout.razor new file mode 100644 index 000000000..91c3e6f96 --- /dev/null +++ b/site/Site/Shared/LandingLayout.razor @@ -0,0 +1,112 @@ +@inherits LayoutComponentBase + + + + +@Body + + \ No newline at end of file diff --git a/site/Site/Shared/LandingLayout.razor.css b/site/Site/Shared/LandingLayout.razor.css new file mode 100644 index 000000000..e69de29bb diff --git a/site/Site/Site.csproj b/site/Site/Site.csproj new file mode 100644 index 000000000..5d16d2168 --- /dev/null +++ b/site/Site/Site.csproj @@ -0,0 +1,30 @@ + + + + net6.0 + enable + enable + + + + + + + + + <_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css" /> + <_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css.map" /> + <_ContentIncludedByDefault Remove="wwwroot\sample-data\weather.json" /> + + + + + + + + + + + + + diff --git a/site/Site/_Imports.razor b/site/Site/_Imports.razor new file mode 100644 index 000000000..fbc1f3d8a --- /dev/null +++ b/site/Site/_Imports.razor @@ -0,0 +1,21 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using Site +@using Site.Shared +@using Site.Components +@using Site.Components.Landing +@using Site.Models +@using Site.Models.Landing +@using Blazor.Diagrams.Core; +@using Blazor.Diagrams.Core.Models; +@using Blazor.Diagrams.Components; +@using Blazor.Diagrams.Core.Extensions; +@using Blazor.Diagrams.Core.Geometry; +@using Blazor.Diagrams.Components.Renderers; +@using Blazor.Diagrams.Components.Widgets; \ No newline at end of file diff --git a/site/Site/tailwind.config.js b/site/Site/tailwind.config.js new file mode 100644 index 000000000..88376e1e0 --- /dev/null +++ b/site/Site/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ["./**/*.{razor,html,cshtml}"], + theme: { + extend: {}, + }, + plugins: [], +} diff --git a/site/Site/wwwroot/css/app.css b/site/Site/wwwroot/css/app.css new file mode 100644 index 000000000..bd1eacf9a --- /dev/null +++ b/site/Site/wwwroot/css/app.css @@ -0,0 +1,1316 @@ +/* +! tailwindcss v3.1.8 | MIT License | https://tailwindcss.com +*/ + +/* +1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4) +2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116) +*/ + +*, +::before, +::after { + box-sizing: border-box; + /* 1 */ + border-width: 0; + /* 2 */ + border-style: solid; + /* 2 */ + border-color: #e5e7eb; + /* 2 */ +} + +::before, +::after { + --tw-content: ''; +} + +/* +1. Use a consistent sensible line-height in all browsers. +2. Prevent adjustments of font size after orientation changes in iOS. +3. Use a more readable tab size. +4. Use the user's configured `sans` font-family by default. +*/ + +html { + line-height: 1.5; + /* 1 */ + -webkit-text-size-adjust: 100%; + /* 2 */ + -moz-tab-size: 4; + /* 3 */ + -o-tab-size: 4; + tab-size: 4; + /* 3 */ + font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + /* 4 */ +} + +/* +1. Remove the margin in all browsers. +2. Inherit line-height from `html` so users can set them as a class directly on the `html` element. +*/ + +body { + margin: 0; + /* 1 */ + line-height: inherit; + /* 2 */ +} + +/* +1. Add the correct height in Firefox. +2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655) +3. Ensure horizontal rules are visible by default. +*/ + +hr { + height: 0; + /* 1 */ + color: inherit; + /* 2 */ + border-top-width: 1px; + /* 3 */ +} + +/* +Add the correct text decoration in Chrome, Edge, and Safari. +*/ + +abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; +} + +/* +Remove the default font size and weight for headings. +*/ + +h1, +h2, +h3, +h4, +h5, +h6 { + font-size: inherit; + font-weight: inherit; +} + +/* +Reset links to optimize for opt-in styling instead of opt-out. +*/ + +a { + color: inherit; + text-decoration: inherit; +} + +/* +Add the correct font weight in Edge and Safari. +*/ + +b, +strong { + font-weight: bolder; +} + +/* +1. Use the user's configured `mono` font family by default. +2. Correct the odd `em` font sizing in all browsers. +*/ + +code, +kbd, +samp, +pre { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + /* 1 */ + font-size: 1em; + /* 2 */ +} + +/* +Add the correct font size in all browsers. +*/ + +small { + font-size: 80%; +} + +/* +Prevent `sub` and `sup` elements from affecting the line height in all browsers. +*/ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* +1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297) +2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016) +3. Remove gaps between table borders by default. +*/ + +table { + text-indent: 0; + /* 1 */ + border-color: inherit; + /* 2 */ + border-collapse: collapse; + /* 3 */ +} + +/* +1. Change the font styles in all browsers. +2. Remove the margin in Firefox and Safari. +3. Remove default padding in all browsers. +*/ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; + /* 1 */ + font-size: 100%; + /* 1 */ + font-weight: inherit; + /* 1 */ + line-height: inherit; + /* 1 */ + color: inherit; + /* 1 */ + margin: 0; + /* 2 */ + padding: 0; + /* 3 */ +} + +/* +Remove the inheritance of text transform in Edge and Firefox. +*/ + +button, +select { + text-transform: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Remove default button styles. +*/ + +button, +[type='button'], +[type='reset'], +[type='submit'] { + -webkit-appearance: button; + /* 1 */ + background-color: transparent; + /* 2 */ + background-image: none; + /* 2 */ +} + +/* +Use the modern Firefox focus style for all focusable elements. +*/ + +:-moz-focusring { + outline: auto; +} + +/* +Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737) +*/ + +:-moz-ui-invalid { + box-shadow: none; +} + +/* +Add the correct vertical alignment in Chrome and Firefox. +*/ + +progress { + vertical-align: baseline; +} + +/* +Correct the cursor style of increment and decrement buttons in Safari. +*/ + +::-webkit-inner-spin-button, +::-webkit-outer-spin-button { + height: auto; +} + +/* +1. Correct the odd appearance in Chrome and Safari. +2. Correct the outline style in Safari. +*/ + +[type='search'] { + -webkit-appearance: textfield; + /* 1 */ + outline-offset: -2px; + /* 2 */ +} + +/* +Remove the inner padding in Chrome and Safari on macOS. +*/ + +::-webkit-search-decoration { + -webkit-appearance: none; +} + +/* +1. Correct the inability to style clickable types in iOS and Safari. +2. Change font properties to `inherit` in Safari. +*/ + +::-webkit-file-upload-button { + -webkit-appearance: button; + /* 1 */ + font: inherit; + /* 2 */ +} + +/* +Add the correct display in Chrome and Safari. +*/ + +summary { + display: list-item; +} + +/* +Removes the default spacing and border for appropriate elements. +*/ + +blockquote, +dl, +dd, +h1, +h2, +h3, +h4, +h5, +h6, +hr, +figure, +p, +pre { + margin: 0; +} + +fieldset { + margin: 0; + padding: 0; +} + +legend { + padding: 0; +} + +ol, +ul, +menu { + list-style: none; + margin: 0; + padding: 0; +} + +/* +Prevent resizing textareas horizontally by default. +*/ + +textarea { + resize: vertical; +} + +/* +1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300) +2. Set the default placeholder color to the user's configured gray 400 color. +*/ + +input::-moz-placeholder, textarea::-moz-placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +input::placeholder, +textarea::placeholder { + opacity: 1; + /* 1 */ + color: #9ca3af; + /* 2 */ +} + +/* +Set the default cursor for buttons. +*/ + +button, +[role="button"] { + cursor: pointer; +} + +/* +Make sure disabled buttons don't get the pointer cursor. +*/ + +:disabled { + cursor: default; +} + +/* +1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14) +2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210) + This can trigger a poorly considered lint error in some tools but is included by design. +*/ + +img, +svg, +video, +canvas, +audio, +iframe, +embed, +object { + display: block; + /* 1 */ + vertical-align: middle; + /* 2 */ +} + +/* +Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14) +*/ + +img, +video { + max-width: 100%; + height: auto; +} + +*, ::before, ::after { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +::-webkit-backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +::backdrop { + --tw-border-spacing-x: 0; + --tw-border-spacing-y: 0; + --tw-translate-x: 0; + --tw-translate-y: 0; + --tw-rotate: 0; + --tw-skew-x: 0; + --tw-skew-y: 0; + --tw-scale-x: 1; + --tw-scale-y: 1; + --tw-pan-x: ; + --tw-pan-y: ; + --tw-pinch-zoom: ; + --tw-scroll-snap-strictness: proximity; + --tw-ordinal: ; + --tw-slashed-zero: ; + --tw-numeric-figure: ; + --tw-numeric-spacing: ; + --tw-numeric-fraction: ; + --tw-ring-inset: ; + --tw-ring-offset-width: 0px; + --tw-ring-offset-color: #fff; + --tw-ring-color: rgb(59 130 246 / 0.5); + --tw-ring-offset-shadow: 0 0 #0000; + --tw-ring-shadow: 0 0 #0000; + --tw-shadow: 0 0 #0000; + --tw-shadow-colored: 0 0 #0000; + --tw-blur: ; + --tw-brightness: ; + --tw-contrast: ; + --tw-grayscale: ; + --tw-hue-rotate: ; + --tw-invert: ; + --tw-saturate: ; + --tw-sepia: ; + --tw-drop-shadow: ; + --tw-backdrop-blur: ; + --tw-backdrop-brightness: ; + --tw-backdrop-contrast: ; + --tw-backdrop-grayscale: ; + --tw-backdrop-hue-rotate: ; + --tw-backdrop-invert: ; + --tw-backdrop-opacity: ; + --tw-backdrop-saturate: ; + --tw-backdrop-sepia: ; +} + +.container { + width: 100%; +} + +@media (min-width: 640px) { + .container { + max-width: 640px; + } +} + +@media (min-width: 768px) { + .container { + max-width: 768px; + } +} + +@media (min-width: 1024px) { + .container { + max-width: 1024px; + } +} + +@media (min-width: 1280px) { + .container { + max-width: 1280px; + } +} + +@media (min-width: 1536px) { + .container { + max-width: 1536px; + } +} + +.fixed { + position: fixed; +} + +.absolute { + position: absolute; +} + +.relative { + position: relative; +} + +.top-0 { + top: 0px; +} + +.z-10 { + z-index: 10; +} + +.z-30 { + z-index: 30; +} + +.z-20 { + z-index: 20; +} + +.m-8 { + margin: 2rem; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.my-0 { + margin-top: 0px; + margin-bottom: 0px; +} + +.my-6 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} + +.my-12 { + margin-top: 3rem; + margin-bottom: 3rem; +} + +.my-4 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.mt-6 { + margin-top: 1.5rem; +} + +.mb-5 { + margin-bottom: 1.25rem; +} + +.mt-auto { + margin-top: auto; +} + +.mt-4 { + margin-top: 1rem; +} + +.mb-12 { + margin-bottom: 3rem; +} + +.mt-0 { + margin-top: 0px; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mr-3 { + margin-right: 0.75rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.mr-2 { + margin-right: 0.5rem; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.inline { + display: inline; +} + +.flex { + display: flex; +} + +.hidden { + display: none; +} + +.h-1 { + height: 0.25rem; +} + +.h-8 { + height: 2rem; +} + +.h-6 { + height: 1.5rem; +} + +.w-full { + width: 100%; +} + +.w-64 { + width: 16rem; +} + +.w-5\/6 { + width: 83.333333%; +} + +.w-1\/6 { + width: 16.666667%; +} + +.w-6 { + width: 1.5rem; +} + +.max-w-5xl { + max-width: 64rem; +} + +.flex-1 { + flex: 1 1 0%; +} + +.flex-none { + flex: none; +} + +.flex-shrink { + flex-shrink: 1; +} + +.flex-grow { + flex-grow: 1; +} + +.transform { + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.flex-col { + flex-direction: column; +} + +.flex-col-reverse { + flex-direction: column-reverse; +} + +.flex-wrap { + flex-wrap: wrap; +} + +.items-start { + align-items: flex-start; +} + +.items-center { + align-items: center; +} + +.justify-start { + justify-content: flex-start; +} + +.justify-end { + justify-content: flex-end; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.overflow-hidden { + overflow: hidden; +} + +.rounded-full { + border-radius: 9999px; +} + +.rounded-none { + border-radius: 0px; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-t { + border-top-left-radius: 0.25rem; + border-top-right-radius: 0.25rem; +} + +.rounded-b-none { + border-bottom-right-radius: 0px; + border-bottom-left-radius: 0px; +} + +.rounded-b { + border-bottom-right-radius: 0.25rem; + border-bottom-left-radius: 0.25rem; +} + +.rounded-t-none { + border-top-left-radius: 0px; + border-top-right-radius: 0px; +} + +.border { + border-width: 1px; +} + +.border-b { + border-bottom-width: 1px; +} + +.border-b-4 { + border-bottom-width: 4px; +} + +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + +.bg-gray-100 { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + +.fill-current { + fill: currentColor; +} + +.p-6 { + padding: 1.5rem; +} + +.p-8 { + padding: 2rem; +} + +.p-1 { + padding: 0.25rem; +} + +.p-4 { + padding: 1rem; +} + +.p-2 { + padding: 0.5rem; +} + +.py-8 { + padding-top: 2rem; + padding-bottom: 2rem; +} + +.py-0 { + padding-top: 0px; + padding-bottom: 0px; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.px-8 { + padding-left: 2rem; + padding-right: 2rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.pt-4 { + padding-top: 1rem; +} + +.pb-12 { + padding-bottom: 3rem; +} + +.pt-12 { + padding-top: 3rem; +} + +.pt-6 { + padding-top: 1.5rem; +} + +.pl-4 { + padding-left: 1rem; +} + +.pr-4 { + padding-right: 1rem; +} + +.pr-6 { + padding-right: 1.5rem; +} + +.pt-24 { + padding-top: 6rem; +} + +.text-center { + text-align: center; +} + +.align-middle { + vertical-align: middle; +} + +.text-5xl { + font-size: 3rem; + line-height: 1; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.text-xl { + font-size: 1.25rem; + line-height: 1.75rem; +} + +.text-base { + font-size: 1rem; + line-height: 1.5rem; +} + +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; +} + +.text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; +} + +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; +} + +.font-bold { + font-weight: 700; +} + +.font-semibold { + font-weight: 600; +} + +.uppercase { + text-transform: uppercase; +} + +.leading-tight { + line-height: 1.25; +} + +.leading-none { + line-height: 1; +} + +.leading-normal { + line-height: 1.5; +} + +.tracking-normal { + letter-spacing: 0em; +} + +.text-gray-800 { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity)); +} + +.text-gray-600 { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity)); +} + +.text-pink-500 { + --tw-text-opacity: 1; + color: rgb(236 72 153 / var(--tw-text-opacity)); +} + +.text-white { + --tw-text-opacity: 1; + color: rgb(255 255 255 / var(--tw-text-opacity)); +} + +.text-pink-800 { + --tw-text-opacity: 1; + color: rgb(157 23 77 / var(--tw-text-opacity)); +} + +.text-black { + --tw-text-opacity: 1; + color: rgb(0 0 0 / var(--tw-text-opacity)); +} + +.text-pink-600 { + --tw-text-opacity: 1; + color: rgb(219 39 119 / var(--tw-text-opacity)); +} + +.text-gray-500 { + --tw-text-opacity: 1; + color: rgb(107 114 128 / var(--tw-text-opacity)); +} + +.underline { + -webkit-text-decoration-line: underline; + text-decoration-line: underline; +} + +.no-underline { + -webkit-text-decoration-line: none; + text-decoration-line: none; +} + +.opacity-25 { + opacity: 0.25; +} + +.opacity-75 { + opacity: 0.75; +} + +.shadow { + --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.shadow-lg { + --tw-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color); + box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); +} + +.drop-shadow-lg { + --tw-drop-shadow: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1)); + filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); +} + +.transition { + transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-text-decoration-color, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color, -webkit-backdrop-filter; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 150ms; +} + +.duration-300 { + transition-duration: 300ms; +} + +.ease-in-out { + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + +#blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; +} + +.bg-main { + background-color: #40BABD; +} + +.text-main { + color: #40BABD; +} + +.hover\:scale-105:hover { + --tw-scale-x: 1.05; + --tw-scale-y: 1.05; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.hover\:text-gray-900:hover { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + +.hover\:text-gray-800:hover { + --tw-text-opacity: 1; + color: rgb(31 41 55 / var(--tw-text-opacity)); +} + +.hover\:text-pink-500:hover { + --tw-text-opacity: 1; + color: rgb(236 72 153 / var(--tw-text-opacity)); +} + +.hover\:underline:hover { + -webkit-text-decoration-line: underline; + text-decoration-line: underline; +} + +.hover\:no-underline:hover { + -webkit-text-decoration-line: none; + text-decoration-line: none; +} + +.focus\:outline-none:focus { + outline: 2px solid transparent; + outline-offset: 2px; +} + +@media (min-width: 640px) { + .sm\:my-4 { + margin-top: 1rem; + margin-bottom: 1rem; + } + + .sm\:-mt-6 { + margin-top: -1.5rem; + } + + .sm\:h-64 { + height: 16rem; + } + + .sm\:w-1\/2 { + width: 50%; + } + + .sm\:flex-row { + flex-direction: row; + } +} + +@media (min-width: 768px) { + .md\:mb-6 { + margin-bottom: 1.5rem; + } + + .md\:mr-0 { + margin-right: 0px; + } + + .md\:block { + display: block; + } + + .md\:w-1\/3 { + width: 33.333333%; + } + + .md\:w-2\/5 { + width: 40%; + } + + .md\:flex-row { + flex-direction: row; + } + + .md\:text-left { + text-align: left; + } + + .md\:text-sm { + font-size: 0.875rem; + line-height: 1.25rem; + } +} + +@media (min-width: 1024px) { + .lg\:mx-0 { + margin-left: 0px; + margin-right: 0px; + } + + .lg\:mt-0 { + margin-top: 0px; + } + + .lg\:flex { + display: flex; + } + + .lg\:hidden { + display: none; + } + + .lg\:w-1\/4 { + width: 25%; + } + + .lg\:w-1\/3 { + width: 33.333333%; + } + + .lg\:w-auto { + width: auto; + } + + .lg\:items-center { + align-items: center; + } + + .lg\:rounded-l-lg { + border-top-left-radius: 0.5rem; + border-bottom-left-radius: 0.5rem; + } + + .lg\:bg-transparent { + background-color: transparent; + } + + .lg\:p-0 { + padding: 0px; + } + + .lg\:text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; + } +} \ No newline at end of file diff --git a/site/Site/wwwroot/css/input.css b/site/Site/wwwroot/css/input.css new file mode 100644 index 000000000..b30690de3 --- /dev/null +++ b/site/Site/wwwroot/css/input.css @@ -0,0 +1,30 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + +#blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; +} + +.bg-main { + background-color: #40BABD; +} + +.text-main { + color: #40BABD; +} \ No newline at end of file diff --git a/site/Site/wwwroot/favicon.ico b/site/Site/wwwroot/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..475ad21e27a7292dd32dd012297b191be9119726 GIT binary patch literal 15406 zcmeHO=~q-o7VkMTe}MC0K902P&`r~HL$e62sEA5%8P}+BUl9Q}TnER6sBzzd3vo;k z4Y-SxE)W1V~EKSYMa)J*A_Ookih*VQ$MKZ-k*~_9252g!Wo}>GB@7h6QV*|~b zIo%G&zTHWbVsb4*WMEqoL;jRpsyF z$T5DT#%`Tr-45>SeyoIkLa7Z7U_7_81B1pk$3i>jQhEk(k<}?{?rsrzkHEkO`PP@tW?i<+@;PaX`_C?3UAh(dp zzCj;j{VL!`cH1HSBk1ID`!m4tOgM2Yg|=;pr#?NxWaCxB>qmt~t{b5rr2u@48XD<6F6_jD^n;fCD`XYcp5L9i^ZEvoQIW5NhUeEbIy#ZRCTmc7985Ym$Ju?Ve8zzPR1VCRg%cZpnInfk8_Y>GSH(( z4@vH?si}!pF8$Cg-pIfJ+O#2#)5ltoGiF(1M$*^kzw!vRw{LMaU%h(8bb-k&?$S>! z>oB8>XZZ#Gq~=}t79AEu|El?p^N>F!$0hwv?Sr|lz#dR)YYMzbmHYSZ=JXd9%ypu# z4_DL2>n!Vk?Fwi6Qj9kyLQPm7?jWN}6!q@jl?L|Cv_j6rarFF`UpW04d4sKedvp$= zA^jrglg;sb-m8wL@NZ&zj?cPK>{Uy9SNiPq$u4yDN)^3)`GTH2BkFaZp^HFS%yOP&c zT~tT_6|J|(Qu5CP!WwebPN>_xYbU3_WYGf2c0zkOC2Za!2QBk3dwR4TUSaQ{)P)Ap zQwq17 zZ_wNDUBCnMH_kHj+puO8rz!lwK3Kncg->Y<{IzFx7JMJ@Fn^Ao)6hqR(4MkgOs5@V zb98|6CQXn$e*|3Kmc!qV2+_9(JV?fPc7Y zgNYCWG%CD1@}&&XVQRu%lqY-5ogIfemk$>h$ZQH~>HhG#KVr07o8xot_>`(YdE87J z*Txb26km?55?qLD=45FpXQDLZvhOqUbhMWFbhs;n9SzywU&6=rDlR2JEA5sq$lM{^U;%Zur*ejNh`ImbfV9qF}b|j!+JcUqhRTqjA4ynFHM43NZpD zycsoN^j_CaJhq4Uo;Q%0-kFk&(Td{$rs#IGf43#RfVGs-#S&*x!hb?c6pxkv^xy$` z3lDz0Pc`3OrdiYSSj(NT^-Y<<7BU;LY5aGj6Y(ha>mAf zHN1SuWw>?o28)Hqd*r1v%UwBk#1bz;d)SYIUI=eE~_FEs-Z9EtT%yyJz8_-M@A3Dd$=%3^L>cCQ}AKxYSti?3U?oL;kgIQ*_(yUOeV6UDK7Br#s-d*nV%&DPQ3 z=J-DN$32l!8!pM%j+PY~X#J{KSx~e-lS;O4p^5|h=uGuVI?M8Z1h{nZJUxH@ob%q) z^k2ICO(`v2XkaplaIcQ#_af!YW)SQi@Q(QSut5>*+)%c5(06)|B_Xzs-?-@o1_E>BX z@CjMA7MW-Rn{OvrV`AUNQt4;KRCTP9&YnKS;nKwm)_YK_pPHI-DqLvf>r#vZwm)>v z#L-$FUv3HSf*jb#@GHb~FmI-w;@Mtv%-yoQ2a_o}k`nZ6AG0$LU^W?P)kjPC`Hi`# zy?uk}&jh}A#W=8^aZl_Ez6JhK>KRV>6M_u;*?jcs7N&?!ddFm*n+N{ue*T%;v|<|m zenvc@tkmXDtX>|==Rhvq&dCY%!<{=|pI`&Qrk2~+7wxc)awkSv;|bVflgGQZryT7H z*nhB{U%asB&^zCM&+>hIdmk!CMy&aw^_F}G>}BM{z9;f2;OoqrHG`c4+o@4(NtaLn5u~2zFt9UNj+Y02&512cMUgh*`Ei!+$B>T*UHZ{CB@7M^5DF zEL&`_p3OKPGSh876@1;A@4lsn4}anSzw_#q8g@rMi{~WBq2cc~$aN~;X;t6RA%|Hx9kJrBQO6UbfHWZ{C3)_7U>Gt66+jIG2&@4L$WP==*|(mgT@>9SP@J z#erm=7v<~o(-*wD!-0G7?ZwS`S8zYu$Uh(O2@p=Yjt(tPjCX_EaKfJh)$YtIL2fM4n$obB>TFzQx~>lbL3I_c_6i za|hq2faNwRxdTF5oNEi_>Ai}%I`YuccjTh@3M^q}8=zmI^|Bk(`l$%`fc literal 0 HcmV?d00001 diff --git a/site/Site/wwwroot/icon-192.png b/site/Site/wwwroot/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..166f56da7612ea74df6a297154c8d281a4f28a14 GIT binary patch literal 2626 zcmV-I3cdA-P)v0A9xRwxP|bki~~&uFk>U z#P+PQh zyZ;-jwXKqnKbb6)@RaxQz@vm={%t~VbaZrdbaZrdbaeEeXj>~BG?&`J0XrqR#sSlO zg~N5iUk*15JibvlR1f^^1czzNKWvoJtc!Sj*G37QXbZ8LeD{Fzxgdv#Q{x}ytfZ5q z+^k#NaEp>zX_8~aSaZ`O%B9C&YLHb(mNtgGD&Kezd5S@&C=n~Uy1NWHM`t07VQP^MopUXki{2^#ryd94>UJMYW|(#4qV`kb7eD)Q=~NN zaVIRi@|TJ!Rni8J=5DOutQ#bEyMVr8*;HU|)MEKmVC+IOiDi9y)vz=rdtAUHW$yjt zrj3B7v(>exU=IrzC<+?AE=2vI;%fafM}#ShGDZx=0Nus5QHKdyb9pw&4>4XCpa-o?P(Gnco1CGX|U> z$f+_tA3+V~<{MU^A%eP!8R*-sD9y<>Jc7A(;aC5hVbs;kX9&Sa$JMG!W_BLFQa*hM zri__C@0i0U1X#?)Y=)>JpvTnY6^s;fu#I}K9u>OldV}m!Ch`d1Vs@v9 zb}w(!TvOmSzmMBa9gYvD4xocL2r0ds6%Hs>Z& z#7#o9PGHDmfG%JQq`O5~dt|MAQN@2wyJw_@``7Giyy(yyk(m8U*kk5$X1^;3$a3}N^Lp6hE5!#8l z#~NYHmKAs6IAe&A;bvM8OochRmXN>`D`{N$%#dZCRxp4-dJ?*3P}}T`tYa3?zz5BA zTu7uE#GsDpZ$~j9q=Zq!LYjLbZPXFILZK4?S)C-zE1(dC2d<7nO4-nSCbV#9E|E1MM|V<9>i4h?WX*r*ul1 z5#k6;po8z=fdMiVVz*h+iaTlz#WOYmU^SX5#97H~B32s-#4wk<1NTN#g?LrYieCu> zF7pbOLR;q2D#Q`^t%QcY06*X-jM+ei7%ZuanUTH#9Y%FBi*Z#22({_}3^=BboIsbg zR0#jJ>9QR8SnmtSS6x($?$}6$x+q)697#m${Z@G6Ujf=6iO^S}7P`q8DkH!IHd4lB zDzwxt3BHsPAcXFFY^Fj}(073>NL_$A%v2sUW(CRutd%{G`5ow?L`XYSO*Qu?x+Gzv zBtR}Y6`XF4xX7)Z04D+fH;TMapdQFFameUuHL34NN)r@aF4RO%x&NApeWGtr#mG~M z6sEIZS;Uj1HB1*0hh=O@0q1=Ia@L>-tETu-3n(op+97E z#&~2xggrl(LA|giII;RwBlX2^Q`B{_t}gxNL;iB11gEPC>v` zb4SJ;;BFOB!{chn>?cCeGDKuqI0+!skyWTn*k!WiPNBf=8rn;@y%( znhq%8fj2eAe?`A5mP;TE&iLEmQ^xV%-kmC-8mWao&EUK_^=GW-Y3z ksi~={si~={skwfB0gq6itke#r1ONa407*qoM6N<$g11Kq@c;k- literal 0 HcmV?d00001 diff --git a/site/Site/wwwroot/img/ZBD.png b/site/Site/wwwroot/img/ZBD.png new file mode 100644 index 0000000000000000000000000000000000000000..e641498c88725c185f2f90cad7d8b86906a8df0b GIT binary patch literal 7497 zcmZ`;cQjnlyPe4BBwF-3dM`n=s6)nxHYAAN38E%5j2eO=`Xo9b22n$lAVlvaNQmBB zq8okiCcpQ`d+(38X5BgWo_oKw?)mOM-`RT~l!4wON-}0L002O#4FSXOZ6E$9A|b;6 zH_0Ky@eR;fO;-&7s7fHm*%IQfIqe}ZT>v0}4*&>_001uVU7?!*z)J}L;ExReppXUt zFnDD(8YGx7s_n2erPfkvWaZx(9pdM5wX~V**C_-d^+v*?&&Z6_)L0f-d zW>@~G=bb81&!**8Gjd`4+_<^gVaw`jw|N5@vH>67!^JO>Kh9Auy3yf~xNAx(a;p&F z-5RcCG@YhO>;?QyR(AUpz%2A5G2Hraz;5xuZJ>|nzNHP5!5;RWt2ALDDT45f=`*?5 zy?$q_Jbg~(MV8iu+d5&z;}dSunw(@7#Iv_~!h)X2P*#^?NH=Nw0AYa~XKo*cT}x${ zi6Mac+f0jB!Fh-aASjf-3Ygy<<;1f>Ob^%ywZz9T2C1ao#xaUg#Sw|H;TUxo7)|@n zjcl^U-@8hS*|EMC!x2d^xY6l+9JT0|vZ=ULM)W+pPV1vLS8eO_+iv*=6gw8ox@MqI zULuJpR~>0)hWk^kP?=E|=~Vs~AAGB%GlZ8UBZKe66@D89jk>fDh|`p8-%(hM69K9%qs)DrJcOuP?$ zuvI@&$Zrs{x-N>{{=J2L<5Cfd1n1L%7koRAewq-i~;7vv3dHTq=g%>kkk2$KU|l9NZM zzIr)YweBdzulK&2Ty5nuq@Yu=B&_qQbf1R?!dZ=?y`_W{dcEuMbS9t!oLmnZ@>68mNl>GKP;U6O{wO z5OIF~}a`#@HvzboxB%i@9>_Up}Vf z+*NKL>WgjPoY&jWYWA;loCF!Ub^IDuSwW_gi-BPr(!B zda8m1R?NzpiNPl{J%q_%ws&erqZvxwXe(AV^WHR^?7aJNq*}$N^myyaF88M5l|(5u zm9F3d9tl^_!Mg$KJcDb3bCER%w)B*u8r6Q1E1p{g?dB#rxUgS{l2UgPpt0i!u-?uj%jW*wK&bvF-_B@nEDszDcWe; z#OeCnVM>Ly0t==C5K6>C75iQj|Hih74}azQjA002g>NnJS@!CdF`k7#Xy5p9uQ=0# z!8OE{vZ0WVEeb8kebfgehEw3NtzQFexhR&|E_+~Ybcs1<-GdP60>j22T}UW zyjM(w^oCgXYmew<`$*9+n&A;ZO=3`mQ+Csui&u^K;M}8^{C#Tqy3cUvvqUx6j}wf<*zkXgl@D~~K{+~#-^;N;?-~&)Y;rh2GGpL|?9+gVKl6=d_ z;vZYLLCd+h?Wr#bwSTQ_kKUKp)LM4hJ9DbvcTvJu$dIiX>W+9c(0PdGpjC(>(@kSA zmcjLAGNhiZm?g4}<7z2I>HN&TV>9)Hom9^PaTORcbG?0k_&CZeV0&xvz6_d1V3L zaWB-(Zof{r`(tc6GVhI48G0l%WI@*F;NAeq<8*h$wE`p_KjwAQ3Rcj*vjdZZ_M;Ig zCv0)P)wSpY@zs#)A#2H_j8e&$jf0f07ocd}hX-859{K+R-%*(mf1OhYW%z8-#wC8t9;BTc zM+ey|*W9~bgFbz%w;SG0SGt+KZ~XLWJ{GoYn}^i|@rDVepeh;F`?Z~|`}?-;<6+Qp zy&tZ0vf05X1~D_MK6A69d8=kiaP=*hpQvAv+qqhGr!Cqc6gq#as?|sLWQgT-y!Xi4j`1I0R zUuUjMA}1abgsILulM;rPSp;)gE9e6lut;sc}{p~+{Ovt!^2MEG?v6ghtkR#VZ-OS`u*9>us#lE7Jt|jhAFF8F^#xlH9 zX>OjZ6bG)_Dm{_CeLPRMBVJy`R%{)ha9~znY>?}x(9xz5HC1(VyHGE?Jx%iCQMDu; zneYA&+mqdyN=>_zu?Dw)OEu`a_4&8smnX|q;{``?L)gGG!#n+e$yY0 z)P`5JL{q)~^9KjBc|6XO-6!V}(6_9dge}UNMLuX_n*9A(o*KJ5t>H7y^0Z$&{R0|A zgYr7Yy85$CWl8 z@ZYZ|CwHb56>9K_5c|goPDF%uTLz{<3f2*=?yj}66AfOr5P7o~8iJbA%xkEgkOWha zjLE7Z{ami}$Ip{~cRmCq(cGMsMps&eQP-xX@FJ00ru8|4RV$A7igg4J&I6UMi1oc@ zlDqslzQY%K3SbI+Rk4ab_t5P|bE!Ts=lg3ssoV{p#Me4Ub(mUgpJIMG3!aCZ{iId8 z{>^U)_DB zDUwX!J%5i4P0TyK`TP3Y{33H2$#E@h^4sj?XO#<}(3kMqItVY*33Q}`yphuV+dvuk z>M3S#sj1=4@;8VQyB&O1>+It1_FkA6?{$@ZvHyi&7F9Gseq>vWDSA{{{E%D)dwKPi z`ANcvB0`*sf~dH(98*#<_DUoVZRhJozNMH-#qM=y>42)Fq-{T7XTdj&H__rwx*m5E z5)Up(^O;^t7p}>Pn8&ZoW?pG%!$Y#~pBqgnCsv$&(zUI zAFHC?#U{VQNbaNKLg-kB96ya9>Em22{MxdE$qJ;#;Q4o^2w9XV9mQ)j#o)+2bpxX( z`0p#&s9T7}ZG_#<(&a0UiO-#54il%VYcYA#7fBxmm~Z5qzfi!lA1-7-Z;rgq-h9wM z+PSM9`%6ZcdT;sdKU(`7{mv&Df_C_$5DIi6CYWW5U8c2;*Ax<_H8wGkm%?6LH~5yR z_^j%L%+T(yP`(jFaH=d~93wh_g}S^Fj#3Tg3@OuJQg?S0dvg?>ui^(D>Kth1(`jkG zdFL}GS6DzOHT>7{r6xMf)`BJqUtOcST#8G5d7S@@SBPCNh2PzT6p5FUivYu12=jl- zQc}IGFU;87(IK@A5yjKZ0z3m*%@8-eO)B;a>YciGg30PM5#tXGAMz;>Q@!;hvW@$I ztouC3ng$|3SKvL!hzkxG_4o@ZI-l8@@xR1wiQgMOdD9MDJ|QNE+#?ctYY^SqNeV2| z*G%VYSa<48ufPg*1z||k{0>CH^s}a81f*nbhovC*BDW8K zYjKOTJeGLDNc55Uk9Oxu{1n4oBKg_r{`7%@Jdy-rYk`Q;3mGUE#ck^qMuHT6`#S3y z0wjYWYZ3$%(vA?~@F&p+S&dma1Pk;Y#xS-F_>n(%EBW-P;#G|z?a^iWGs~e%=aI1- zJ!`wzLB|8&YexUUNjgf*ey1>rUT!4Rxi?GR&Ho;Q-*+-`8qEGrW258E$pabL*CM)M z5SxsJ9*qOC{Kx}LRUTMLPa~*VnEt`9hz>9=xGq~h{L{~kU=3{pGHbcznC??vv^tOg zSg5B3z?1VFURYrD)1hDxQJm(5_%pqZI) zemeFRm5GJus8UJcH~RsoIe$f=RPOU`Pu8F|dOf`UbYl(o#0rc(Qt{p?U{(h>vxZHWq(afSs{b&k0_lIOu4^MWnodS6jNdcwEtQtGJ$S(yhN9h% zd#;N0ysJ!immBEh%oNGjIblvp)QK)8`f~&q`M1yr-{ToQ+Deci4m6gHof?Dw zX;u5XIIw~dX zY6yz()4IJyZ2A{}HWPDm4LMeyI-5FM&J=BhiSi=*xd3BN&+9k~ym0KM$x*5NTF7u* z=^7b9^}4PumR0c;sB#6AMABySV@qCM*s5=BbNYgB$IV&`13M?zBmq4b>M^pktoaR& zE$HlB^Qp1?mNqGARmqS$87nMQh(+ZiXOMm`expO)=wkLKeo?pVPUokWX~OJCxb`oP zGUrU7!CVi!2qD^&o*om=Whw9)uOqrEX8GT!qemG0*6Ka&6yEWr49jI?<9IJ3V-)dj z!+K{Qj*k@c&xvS7Yd_=3&`E+xdAB3@bTiT>y?5BMvDgzikLy%wf}>^4p(#Y6^W zjy$vbD*EAqoX>c8BYIYB*J2!v`Dpyv;3wPi0Z60WL!K)}qE88T$C}{Q!E<7mef_M^ z$>DjK^Nh`HwlNC&ap`|_eb>4cv}b>pT_NkVTcrz(myzF(#HyQ+`|5^do}e&1_ZCe~ ziZ><`7D|)#AbT*B+k+NNaer_qYQ9iV zTRwCQQX?7b(MxPXPh?jTa^f(S$~^TMX@~oJ4bX z%|p|VD_<=3CVkvJ1zoNBIOyeap?lmL_s^BBD`A`#L8p($S0}n292G5G$>!PoaP@`8 z*!Sq~-vl3X3_D!k39~S|znW5F$A}OTB72_VrfHXvTw0maeH3y{A@l1OUi82UV1n1D zH|O#_`dI>O;$u-$qhbB4k`|&sEkDNCXBI>z^J;>;$H%p}{RZcBCHe1DYf162n2^ha zRMVS&BDfyA6{;mj+)*}VVH?S5@Wa+-5>@a${#S0$p1$eVMXjPCL3!C1(}N3FS1QSU zUqwvZKBpBPGl}F^F7`rbP_~SLE2tjR=I8#d$_~W-S8<{^YzU6h!-L_`?`9UY6ch~l zRP1|#r}EX$=(M{`V%1ZuZWAqoHy1Ox-gy|z>T;saKB8>I#?aIaM)s8{(G6^u!FTi` zZQ#pD;7HOHq0YO)LZ{$seeuK6kW2l5fB>jDC)5@g?w>0AWXtb77W}^@uK@mKN%7B2 zU96kqaJg0vr@PInTx4taIP_;l@G}SL>gutw$=~z4;YeeCrX^foSzb#hyIbOIH4TZC_;pp>(|XrL4>iXxX@tdH zfhaS*d0GrzSZJaR&l)AmkC!t3yfGFteYs?sPfRjPkH2V)8U7=3N;zYn$SRd7ShMr z84Ivki+DW!4>+%I0xllYn0*Cs(+`!FI==Etlz-W{H~_DONDqT}+gy!EEfp125;%cd zEBw8+wXR7n{fu~`vC_HQLd(rM+*JB*x=H1GEx#<@CzjK?ST?=f68H25cZ_*gW?#8Fhcz zrG#>d_&|qH!jm&dU{^J6C9%a9>%L(4n_f)#pYiK`Sk7r8v3fXH^25cRz`#+8=Yc2Qel-1gHH#nI8#O2ufv1OP zQsz2s%a)cB>g(~%E2mz{@cFwc`S8izd@^7SM7l#QB)K>=J_|fm&k3~zB7~}@n+fx*In9H0{^`*^PU64O_(xDMgqs^g=_{GO z?(u%}8yzMtgDF-^^SWo3-<%JH{hyK4K(f>Y-+HB;mE1e)Nth(KB|+f;zuOm=xp}c; z9*;~6u7R+sBbqyR8^eFp`a<~)#CiVP>rJ)JgP-P%teee-(xYevG|viIP+~3em51_< z+Sa(yCTEW9$`-AYL3xP?Hz2_?o~}pG*Y_*9TDI9E7F2u)L7mP_&6fBoj{mB~IaWQ9 z{*@8*+LB2$c*?wdTG?J)%$V*C$(3_(PR>bqHF`BM|XRgU=L5IYN)j(pxV9}wucmz_XDSJ4OfCJf0is$L3^i?qM zgC*tFT5`aTW?%B-w;aWMf->X5J@b%{e*t zdTP{lhmBw&woPCtH9SozNRR2E3><_Dju>%!(k{l2R@cVcg_v;)3t^ZRyAd)O^g$|p zWTAMiTa^o)Btev~2@~o6eDIbI${#T1!m1t%{&-G?$8AQX>i?X!0quydo_WFO*XR_}#$N15$~Mup;*Yf&N9H*4&Gxa57XW>89a_Bc3b_3It)HNE zujUy+oWlM4q7u>y5)z@qz_$Mr;Nj`u>g50b2OOk)3&IBgv^Dj>73wyx{|gT{ BWjp`? literal 0 HcmV?d00001 diff --git a/site/Site/wwwroot/index.html b/site/Site/wwwroot/index.html new file mode 100644 index 000000000..1554481d9 --- /dev/null +++ b/site/Site/wwwroot/index.html @@ -0,0 +1,68 @@ + + + + + + + Site + + + + + + + + + +
    Loading...
    + +
    + An unhandled error has occurred. + Reload + 🗙 +
    + + + + + + diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs index ae4c79cc0..92eb276d7 100644 --- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs @@ -22,13 +22,10 @@ public class PortRenderer : ComponentBase, IDisposable private bool _updatingDimensions; [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; - [Inject] private IJSRuntime JSRuntime { get; set; } = null!; - [Parameter] public PortModel Port { get; set; } = null!; - [Parameter] public string? Class { get; set; } - + [Parameter] public string? Style { get; set; } [Parameter] public RenderFragment? ChildContent { get; set; } public void Dispose() @@ -59,15 +56,15 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) { builder.OpenElement(0, _isParentSvg ? "g" : "div"); builder.AddAttribute(1, "class", - "port" + " " + Port.Alignment.ToString().ToLower() + " " + (Port.Links.Count > 0 ? "has-links" : "") + " " + - Class); - builder.AddAttribute(2, "data-port-id", Port.Id); - builder.AddAttribute(3, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); - builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); - builder.AddAttribute(5, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); - builder.AddEventStopPropagationAttribute(6, "onpointerup", true); - builder.AddElementReferenceCapture(12, __value => { _element = __value; }); - builder.AddContent(13, ChildContent); + $"port {Port.Alignment.ToString().ToLower()} {(Port.Links.Count > 0 ? "has-links" : "")} {Class}"); + builder.AddAttribute(2, "style", Style); + builder.AddAttribute(3, "data-port-id", Port.Id); + builder.AddAttribute(4, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); + builder.AddEventStopPropagationAttribute(5, "onpointerdown", true); + builder.AddAttribute(6, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); + builder.AddEventStopPropagationAttribute(7, "onpointerup", true); + builder.AddElementReferenceCapture(8, __value => { _element = __value; }); + builder.AddContent(9, ChildContent); builder.CloseElement(); } From d5d9f90af86169a1001f90c5c1405341e9d427ee Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 6 Sep 2022 20:05:18 +0100 Subject: [PATCH 083/193] Add Moved event to Movables --- samples/SharedDemo/Demos/Events.razor.cs | 1 + .../Behaviors/DragMovablesBehavior.cs | 64 ++++++++++--------- .../Blazor.Diagrams.Core.csproj | 2 +- .../Models/Base/MovableModel.cs | 10 ++- .../Behaviors/DragMovablesBehaviorTests.cs | 53 +++++++++++++++ 5 files changed, 98 insertions(+), 32 deletions(-) create mode 100644 tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs diff --git a/samples/SharedDemo/Demos/Events.razor.cs b/samples/SharedDemo/Demos/Events.razor.cs index f0d843694..9bd170b95 100644 --- a/samples/SharedDemo/Demos/Events.razor.cs +++ b/samples/SharedDemo/Demos/Events.razor.cs @@ -76,6 +76,7 @@ private NodeModel NewNode(double x, double y) node.AddPort(PortAlignment.Top); node.AddPort(PortAlignment.Left); node.AddPort(PortAlignment.Right); + node.Moved += (m) => events.Add($"Node.Moved, NodeId={node.Id}"); return node; } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 50193ffbb..2a72c2c92 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -2,69 +2,71 @@ using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Events; using System; +using System.Collections.Generic; using System.Linq; namespace Blazor.Diagrams.Core.Behaviors { public class DragMovablesBehavior : Behavior { - private Point[]? _initialPositions; + private readonly Dictionary _initialPositions; private double? _lastClientX; private double? _lastClientY; 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) => Start(model, e.ClientX, e.ClientY); - - private void OnPointerMove(Model? model, PointerEventArgs e) => Move(e.ClientX, e.ClientY); - - private void OnPointerUp(Model? model, PointerEventArgs e) => End(); - - private void Start(Model model, double clientX, double clientY) + private void OnPointerDown(Model? model, PointerEventArgs e) { - if (!(model is MovableModel)) + if (model is not MovableModel) return; - // Don't like this linq - _initialPositions = Diagram.GetSelectedModels() - .Where(m => m is MovableModel) - .Select(m => (m as MovableModel)!.Position) - .ToArray(); + _initialPositions.Clear(); + foreach (var sm in Diagram.GetSelectedModels()) + { + if (sm is not MovableModel movable || movable.Locked) + continue; + + _initialPositions.Add(movable, movable.Position); + } - _lastClientX = clientX; - _lastClientY = clientY; + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; } - private void Move(double clientX, double clientY) + private void OnPointerMove(Model? model, PointerEventArgs e) { - if (_initialPositions == null || _lastClientX == null || _lastClientY == null) + if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) return; - var deltaX = (clientX - _lastClientX.Value) / Diagram.Zoom; - var deltaY = (clientY - _lastClientY.Value) / Diagram.Zoom; - var i = 0; + var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; + var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; - foreach (var sm in Diagram.GetSelectedModels()) + foreach (var (movable, initialPosition) in _initialPositions) { - if (sm is not MovableModel node || node.Locked) - continue; - - var initialPosition = _initialPositions[i]; var ndx = ApplyGridSize(deltaX + initialPosition.X); var ndy = ApplyGridSize(deltaY + initialPosition.Y); - node.SetPosition(ndx, ndy); - i++; + movable.SetPosition(ndx, ndy); } } - private void End() + private void OnPointerUp(Model? model, PointerEventArgs e) { - _initialPositions = null; + if (_initialPositions.Count == 0) + return; + + foreach (var (movable, _) in _initialPositions) + { + movable.TriggerMoved(); + } + + _initialPositions.Clear(); _lastClientX = null; _lastClientY = null; } @@ -84,6 +86,8 @@ private double ApplyGridSize(double n) public override void Dispose() { + _initialPositions.Clear(); + Diagram.PointerDown -= OnPointerDown; Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index 9dc3ad661..fb3fdb4a1 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -26,7 +26,7 @@
    - + diff --git a/src/Blazor.Diagrams.Core/Models/Base/MovableModel.cs b/src/Blazor.Diagrams.Core/Models/Base/MovableModel.cs index cb43afcd1..f1aed7f34 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/MovableModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/MovableModel.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core.Geometry; +using System; +using Blazor.Diagrams.Core.Geometry; namespace Blazor.Diagrams.Core.Models.Base { @@ -6,6 +7,8 @@ namespace Blazor.Diagrams.Core.Models.Base // I believe it makes sense since if you click to move something then you're also selecting public abstract class MovableModel : SelectableModel { + public event Action? Moved; + public MovableModel(Point? position = null) { Position = position ?? Point.Zero; @@ -19,5 +22,10 @@ public MovableModel(string id, Point? position = null) : base(id) public Point Position { get; set; } public virtual void SetPosition(double x, double y) => Position = new Point(x, y); + + /// + /// Only use this if you know what you're doing + /// + public void TriggerMoved() => Moved?.Invoke(this); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs new file mode 100644 index 000000000..ac3901d4e --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -0,0 +1,53 @@ +using System.Linq; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using FluentAssertions; +using Moq; +using Xunit; + +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); + } + + [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(); + } +} \ No newline at end of file From 8f67e2a26fd476b53809638a702334e8f29a2bb2 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 8 Sep 2022 14:46:31 +0100 Subject: [PATCH 084/193] Rename RegisterModelComponent and GetComponentForModel to remove Model notion since its used in Controls --- docs/CustomNodesLinks/Pages/Index.razor | 4 +-- .../Demos/CustomGroup/Demo.razor.cs | 2 +- .../SharedDemo/Demos/CustomLink/Demo.razor.cs | 2 +- samples/SharedDemo/Demos/CustomNode.razor.cs | 2 +- .../SharedDemo/Demos/CustomPort/Demo.razor.cs | 2 +- .../Demos/CustomSvgGroup/Demo.razor.cs | 4 +-- samples/SharedDemo/Demos/DragAndDrop.razor.cs | 2 +- .../Demos/Nodes/PortlessLinks.razor.cs | 2 +- .../SharedDemo/Demos/Nodes/SvgDemo.razor.cs | 6 ++-- src/Blazor.Diagrams/BlazorDiagram.cs | 31 +++++++++---------- .../Controls/ControlsLayerRenderer.razor.cs | 2 +- .../Components/Renderers/GroupRenderer.cs | 2 +- .../Components/Renderers/LinkLabelRenderer.cs | 2 +- .../Components/Renderers/LinkRenderer.cs | 2 +- .../Components/Renderers/NodeRenderer.cs | 2 +- tests/Blazor.Diagrams.Tests/DiagramTests.cs | 20 ++++++------ 16 files changed, 43 insertions(+), 44 deletions(-) diff --git a/docs/CustomNodesLinks/Pages/Index.razor b/docs/CustomNodesLinks/Pages/Index.razor index 70d79a890..65c4336b1 100644 --- a/docs/CustomNodesLinks/Pages/Index.razor +++ b/docs/CustomNodesLinks/Pages/Index.razor @@ -49,8 +49,8 @@ or it will not be rendered. BlazorDiagram = new BlazorDiagram(options); // connect node/link to renderer - BlazorDiagram.RegisterModelComponent(); - BlazorDiagram.RegisterModelComponent(); + BlazorDiagram.RegisterComponent(); + BlazorDiagram.RegisterComponent(); Setup(); } diff --git a/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs b/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs index 538cac3dc..2d4d3f8c9 100644 --- a/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs @@ -18,7 +18,7 @@ protected override void OnInitialized() _blazorDiagram.Options.LinksLayerOrder = 2; _blazorDiagram.Options.NodesLayerOrder = 1; - _blazorDiagram.RegisterModelComponent(); + _blazorDiagram.RegisterComponent(); var node1 = NewNode(50, 50); var node2 = NewNode(300, 300); diff --git a/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs b/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs index e8169b9a8..5f3691e90 100644 --- a/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs @@ -16,7 +16,7 @@ protected override void OnInitialized() LayoutData.Info = "Creating your own custom links is very easy!"; LayoutData.DataChanged(); - _blazorDiagram.RegisterModelComponent(); + _blazorDiagram.RegisterComponent(); // Also usable: _diagram.Options.Links.DefaultLinkComponent = typeof(ThickLink); var node1 = NewNode(50, 50); diff --git a/samples/SharedDemo/Demos/CustomNode.razor.cs b/samples/SharedDemo/Demos/CustomNode.razor.cs index e4343600a..9e41e433a 100644 --- a/samples/SharedDemo/Demos/CustomNode.razor.cs +++ b/samples/SharedDemo/Demos/CustomNode.razor.cs @@ -13,7 +13,7 @@ protected override void OnInitialized() { base.OnInitialized(); - BlazorDiagram.RegisterModelComponent(); + BlazorDiagram.RegisterComponent(); var node = new NodeModel(new Point(20, 20)); node.AddPort(PortAlignment.Top); diff --git a/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs b/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs index bd0871759..dca336305 100644 --- a/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs @@ -17,7 +17,7 @@ protected override void OnInitialized() "In this example, you can only attach links from/to ports with the same color."; LayoutData.DataChanged(); - _blazorDiagram.RegisterModelComponent(replace: true); + _blazorDiagram.RegisterComponent(replace: true); var node1 = NewNode(50, 50); var node2 = NewNode(300, 300); diff --git a/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs index 733c64df2..a955e145f 100644 --- a/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs @@ -18,8 +18,8 @@ protected override void OnInitialized() LayoutData.Info = "Creating your own custom svg groups is very easy!"; LayoutData.DataChanged(); - _blazorDiagram.RegisterModelComponent(); - _blazorDiagram.RegisterModelComponent(); + _blazorDiagram.RegisterComponent(); + _blazorDiagram.RegisterComponent(); var node1 = NewNode(50, 50); var node2 = NewNode(300, 300); diff --git a/samples/SharedDemo/Demos/DragAndDrop.razor.cs b/samples/SharedDemo/Demos/DragAndDrop.razor.cs index 7968545dd..ff054494b 100644 --- a/samples/SharedDemo/Demos/DragAndDrop.razor.cs +++ b/samples/SharedDemo/Demos/DragAndDrop.razor.cs @@ -17,7 +17,7 @@ protected override void OnInitialized() LayoutData.Info = "A very simple drag & drop implementation using the HTML5 events."; LayoutData.DataChanged(); - _blazorDiagram.RegisterModelComponent(); + _blazorDiagram.RegisterComponent(); } private void OnDragStart(int key) diff --git a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs index 1fa3d35b2..f70d7401a 100644 --- a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs @@ -24,7 +24,7 @@ protected override void OnInitialized() private void InitializeDiagram() { - _blazorDiagram.RegisterModelComponent(); + _blazorDiagram.RegisterComponent(); var node1 = new NodeModel(new Point(80, 80)); var node2 = new RoundedNode(new Point(280, 150)); diff --git a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs index 9eb015231..173998f61 100644 --- a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs @@ -24,9 +24,9 @@ protected override void OnInitialized() private void InitializeDiagram() { - _blazorDiagram.RegisterModelComponent(); - _blazorDiagram.RegisterModelComponent(); - _blazorDiagram.RegisterModelComponent(); + _blazorDiagram.RegisterComponent(); + _blazorDiagram.RegisterComponent(); + _blazorDiagram.RegisterComponent(); var node1 = NewNode(50, 50); var node2 = NewNode(250, 250); diff --git a/src/Blazor.Diagrams/BlazorDiagram.cs b/src/Blazor.Diagrams/BlazorDiagram.cs index 8bcd9ef7e..4b4fc5ce1 100644 --- a/src/Blazor.Diagrams/BlazorDiagram.cs +++ b/src/Blazor.Diagrams/BlazorDiagram.cs @@ -11,11 +11,11 @@ namespace Blazor.Diagrams; public class BlazorDiagram : Diagram { - private readonly Dictionary _componentByModelMapping; + private readonly Dictionary _componentsMapping; public BlazorDiagram(BlazorDiagramOptions? options = null) { - _componentByModelMapping = new Dictionary + _componentsMapping = new Dictionary { [typeof(RemoveControl)] = typeof(RemoveControlWidget), [typeof(BoundaryControl)] = typeof(BoundaryControlWidget), @@ -27,39 +27,38 @@ public BlazorDiagram(BlazorDiagramOptions? options = null) public override BlazorDiagramOptions Options { get; } - public void RegisterModelComponent(bool replace = false) - where TModel : Model where TComponent : ComponentBase + public void RegisterComponent(bool replace = false) { - RegisterModelComponent(typeof(TModel), typeof(TComponent), replace); + RegisterComponent(typeof(TModel), typeof(TComponent), replace); } - public void RegisterModelComponent(Type modelType, Type componentType, bool replace = false) + public void RegisterComponent(Type modelType, Type componentType, bool replace = false) { - if (!replace && _componentByModelMapping.ContainsKey(modelType)) + if (!replace && _componentsMapping.ContainsKey(modelType)) throw new Exception($"Component already registered for model '{modelType.Name}'."); - _componentByModelMapping[modelType] = componentType; + _componentsMapping[modelType] = componentType; } - public Type? GetComponentForModel(Type modelType, bool checkSubclasses = true) + public Type? GetComponent(Type modelType, bool checkSubclasses = true) { - if (_componentByModelMapping.ContainsKey(modelType)) return _componentByModelMapping[modelType]; + if (_componentsMapping.ContainsKey(modelType)) return _componentsMapping[modelType]; if (checkSubclasses) - foreach (var rmt in _componentByModelMapping.Keys) + foreach (var rmt in _componentsMapping.Keys) if (modelType.IsSubclassOf(rmt)) - return _componentByModelMapping[rmt]; + return _componentsMapping[rmt]; return null; } - public Type? GetComponentForModel(bool checkSubclasses = true) where TModel : Model + public Type? GetComponent(bool checkSubclasses = true) { - return GetComponentForModel(typeof(TModel), checkSubclasses); + return GetComponent(typeof(TModel), checkSubclasses); } - public Type? GetComponentForModel(Model model, bool checkSubclasses = true) + public Type? GetComponent(Model model, bool checkSubclasses = true) { - return GetComponentForModel(model.GetType(), checkSubclasses); + return GetComponent(model.GetType(), checkSubclasses); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs index 9fc2e0f48..afb34664b 100644 --- a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs +++ b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor.cs @@ -48,7 +48,7 @@ private void OnControlsChangeCaused(Model cause) private RenderFragment RenderControl(Model model, Control control, Point position, bool svg) { - var componentType = BlazorDiagram.GetComponentForModel(control.GetType()); + var componentType = BlazorDiagram.GetComponent(control.GetType()); if (componentType == null) throw new BlazorDiagramsException( $"A component couldn't be found for the user action {control.GetType().Name}"); diff --git a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs index efc6dc249..612aad901 100644 --- a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs @@ -78,7 +78,7 @@ private static string GenerateStyle(double top, double left, double width, doubl protected override void BuildRenderTree(RenderTreeBuilder builder) { - var componentType = BlazorDiagram.GetComponentForModel(Group) ?? typeof(DefaultGroupWidget); + var componentType = BlazorDiagram.GetComponent(Group) ?? typeof(DefaultGroupWidget); var classes = new StringBuilder("group") .AppendIf(" locked", Group.Locked) .AppendIf(" selected", Group.Selected) diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs index 8b32104c6..618b2fc88 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs @@ -27,7 +27,7 @@ protected override void OnInitialized() protected override void BuildRenderTree(RenderTreeBuilder builder) { - var component = BlazorDiagram.GetComponentForModel(Label) ?? typeof(DefaultLinkLabelWidget); + var component = BlazorDiagram.GetComponent(Label) ?? typeof(DefaultLinkLabelWidget); var position = FindPosition(); if (position == null) return; diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs index ca01d7b37..4ab86fb79 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs @@ -34,7 +34,7 @@ protected override bool ShouldRender() protected override void BuildRenderTree(RenderTreeBuilder builder) { - var componentType = BlazorDiagram.GetComponentForModel(Link) ?? typeof(LinkWidget); + var componentType = BlazorDiagram.GetComponent(Link) ?? typeof(LinkWidget); builder.OpenElement(0, "g"); builder.AddAttribute(1, "class", "link"); diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index e2cf965fc..0d12f882d 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -92,7 +92,7 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) if (!_isVisible) return; - var componentType = BlazorDiagram.GetComponentForModel(Node) ?? + var componentType = BlazorDiagram.GetComponent(Node) ?? (_isSvg ? typeof(SvgNodeWidget) : typeof(NodeWidget)); var classes = new StringBuilder("node") .AppendIf(" locked", Node.Locked) diff --git a/tests/Blazor.Diagrams.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Tests/DiagramTests.cs index 852355aa6..90a93fa91 100644 --- a/tests/Blazor.Diagrams.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Tests/DiagramTests.cs @@ -14,10 +14,10 @@ public void GetComponentForModel_ShouldReturnComponentType_WhenModelTypeWasRegis { // Arrange var diagram = new BlazorDiagram(); - diagram.RegisterModelComponent(); + diagram.RegisterComponent(); // Act - var componentType = diagram.GetComponentForModel(); + var componentType = diagram.GetComponent(); // Assert componentType.Should().Be(typeof(NodeWidget)); @@ -30,7 +30,7 @@ public void GetComponentForModel_ShouldReturnNull_WhenModelTypeWasNotRegistered( var diagram = new BlazorDiagram(); // Act - var componentType = diagram.GetComponentForModel(); + var componentType = diagram.GetComponent(); // Assert componentType.Should().BeNull(); @@ -41,10 +41,10 @@ public void GetComponentForModel_ShouldReturnComponentType_WhenInheritedModelTyp { // Arrange var diagram = new BlazorDiagram(); - diagram.RegisterModelComponent(); + diagram.RegisterComponent(); // Act - var componentType = diagram.GetComponentForModel(); + var componentType = diagram.GetComponent(); // Assert componentType.Should().Be(typeof(NodeWidget)); @@ -55,11 +55,11 @@ public void GetComponentForModel_ShouldReturnSpecificComponentType_WhenInherited { // Arrange var diagram = new BlazorDiagram(); - diagram.RegisterModelComponent(); - diagram.RegisterModelComponent(); + diagram.RegisterComponent(); + diagram.RegisterComponent(); // Act - var componentType = diagram.GetComponentForModel(); + var componentType = diagram.GetComponent(); // Assert componentType.Should().Be(typeof(CustomWidget)); @@ -70,10 +70,10 @@ public void GetComponentForModel_ShouldReturnNull_WhenCheckSubclassesIsFalse() { // Arrange var diagram = new BlazorDiagram(); - diagram.RegisterModelComponent(); + diagram.RegisterComponent(); // Act - var componentType = diagram.GetComponentForModel(false); + var componentType = diagram.GetComponent(false); // Assert componentType.Should().BeNull(); From e1d1f0f2db6985a388b6bc19a052afa7063e17a3 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 8 Sep 2022 17:27:28 +0100 Subject: [PATCH 085/193] Update BlazorDiagram.cs --- src/Blazor.Diagrams/BlazorDiagram.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Blazor.Diagrams/BlazorDiagram.cs b/src/Blazor.Diagrams/BlazorDiagram.cs index 4b4fc5ce1..30dfa88b4 100644 --- a/src/Blazor.Diagrams/BlazorDiagram.cs +++ b/src/Blazor.Diagrams/BlazorDiagram.cs @@ -12,6 +12,7 @@ namespace Blazor.Diagrams; public class BlazorDiagram : Diagram { private readonly Dictionary _componentsMapping; + private readonly Dictionary _fragmentsMapping; public BlazorDiagram(BlazorDiagramOptions? options = null) { @@ -22,6 +23,8 @@ public BlazorDiagram(BlazorDiagramOptions? options = null) [typeof(DragNewLinkControl)] = typeof(DragNewLinkControlWidget) }; + _fragmentsMapping = new Dictionary(); + Options = options ?? new BlazorDiagramOptions(); } From 062adea01163faae8b2ff0e0d8945b2571505a38 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 9 Sep 2022 23:01:10 +0100 Subject: [PATCH 086/193] Add VirtualizationBehavior and VirtualizationOptions and Visible property on Model --- samples/SharedDemo/Demos/Simple.razor.cs | 2 +- .../Behaviors/VirtualizationBehavior.cs | 69 +++++++++++++++++++ src/Blazor.Diagrams.Core/Diagram.cs | 1 + .../Models/Base/BaseLinkModel.cs | 2 +- src/Blazor.Diagrams.Core/Models/Base/Model.cs | 15 ++++ .../Options/DiagramOptions.cs | 2 +- .../Options/DiagramVirtualizationOptions.cs | 9 +++ src/Blazor.Diagrams/Blazor.Diagrams.csproj | 10 +-- .../Components/DiagramCanvas.razor | 6 +- .../Components/Renderers/GroupRenderer.cs | 5 ++ .../Components/Renderers/LinkRenderer.cs | 16 +++-- .../Components/Renderers/NodeRenderer.cs | 44 +++--------- .../Components/Renderers/PortRenderer.cs | 23 +++++-- .../Options/BlazorDiagramOptions.cs | 1 + .../BlazorDiagramVirtualizationOptions.cs | 7 ++ 15 files changed, 155 insertions(+), 57 deletions(-) create mode 100644 src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs create mode 100644 src/Blazor.Diagrams.Core/Options/DiagramVirtualizationOptions.cs create mode 100644 src/Blazor.Diagrams/Options/BlazorDiagramVirtualizationOptions.cs diff --git a/samples/SharedDemo/Demos/Simple.razor.cs b/samples/SharedDemo/Demos/Simple.razor.cs index 51510afe6..8c88e3bbb 100644 --- a/samples/SharedDemo/Demos/Simple.razor.cs +++ b/samples/SharedDemo/Demos/Simple.razor.cs @@ -38,7 +38,7 @@ protected override void OnInitialized() protected void TogglePanning() => BlazorDiagram.Options.AllowPanning = !BlazorDiagram.Options.AllowPanning; protected void ToggleVirtualization() - => BlazorDiagram.Options.EnableVirtualization = !BlazorDiagram.Options.EnableVirtualization; + => BlazorDiagram.Options.Virtualization.Enabled = !BlazorDiagram.Options.Virtualization.Enabled; private NodeModel NewNode(double x, double y) { diff --git a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs new file mode 100644 index 000000000..acd4dd7b0 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs @@ -0,0 +1,69 @@ +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Behaviors; + +public class VirtualizationBehavior : Behavior +{ + public VirtualizationBehavior(Diagram diagram) : base(diagram) + { + Diagram.ZoomChanged += CheckVisibility; + Diagram.PanChanged += CheckVisibility; + Diagram.ContainerChanged += CheckVisibility; + } + + private void CheckVisibility() + { + if (!Diagram.Options.Virtualization.Enabled) + return; + + if (Diagram.Container == null) + return; + + if (Diagram.Options.Virtualization.OnNodes) + { + foreach (var node in Diagram.Nodes) + { + CheckVisibility(node); + } + } + + if (Diagram.Options.Virtualization.OnGroups) + { + foreach (var group in Diagram.Groups) + { + CheckVisibility(group); + } + } + + if (Diagram.Options.Virtualization.OnLinks) + { + foreach (var link in Diagram.Links) + { + CheckVisibility(link); + } + } + } + + 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; + var bottom = top + bounds.Height * Diagram.Zoom; + model.Visible = right > 0 && left < Diagram.Container!.Width && bottom > 0 && top < Diagram.Container.Height; + } + + public override void Dispose() + { + Diagram.ZoomChanged -= CheckVisibility; + Diagram.PanChanged -= CheckVisibility; + Diagram.ContainerChanged -= CheckVisibility; + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 930de19eb..ab420f24f 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -60,6 +60,7 @@ protected Diagram() RegisterBehavior(new EventsBehavior(this)); RegisterBehavior(new KeyboardShortcutsBehavior(this)); RegisterBehavior(new ControlsBehavior(this)); + RegisterBehavior(new VirtualizationBehavior(this)); } public abstract DiagramOptions Options { get; } diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index ac6da75a9..1d947e5c2 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -69,7 +69,7 @@ public void SetTarget(Anchor? anchor) public Rectangle? GetBounds() { if (Paths.Length == 0) - return Rectangle.Zero; + return null; var minX = double.PositiveInfinity; var minY = double.PositiveInfinity; diff --git a/src/Blazor.Diagrams.Core/Models/Base/Model.cs b/src/Blazor.Diagrams.Core/Models/Base/Model.cs index cca4356ec..f700748ab 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/Model.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/Model.cs @@ -4,6 +4,8 @@ namespace Blazor.Diagrams.Core.Models.Base { public abstract class Model { + private bool _visible = true; + public Model() : this(Guid.NewGuid().ToString()) { } public Model(string id) @@ -12,9 +14,22 @@ public Model(string id) } public event Action? Changed; + public event Action? VisibilityChanged; public string Id { get; } public bool Locked { get; set; } + public bool Visible + { + get => _visible; + set + { + if (_visible == value) + return; + + _visible = value; + VisibilityChanged?.Invoke(this); + } + } public virtual void Refresh() => Changed?.Invoke(this); } diff --git a/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs index 90de94d82..ec0fc584a 100644 --- a/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs +++ b/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs @@ -5,10 +5,10 @@ public class DiagramOptions public int? GridSize { get; set; } public bool AllowMultiSelection { get; set; } = true; public bool AllowPanning { get; set; } = true; - public bool EnableVirtualization { get; set; } = true; // Todo: behavior public virtual DiagramZoomOptions Zoom { get; } = new(); public virtual DiagramLinkOptions Links { get; } = new(); public virtual DiagramGroupOptions Groups { get; } = new(); public virtual DiagramConstraintsOptions Constraints { get; } = new(); + public virtual DiagramVirtualizationOptions Virtualization { get; } = new(); } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Options/DiagramVirtualizationOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramVirtualizationOptions.cs new file mode 100644 index 000000000..3d29717b9 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Options/DiagramVirtualizationOptions.cs @@ -0,0 +1,9 @@ +namespace Blazor.Diagrams.Core.Options; + +public class DiagramVirtualizationOptions +{ + public bool Enabled { get; set; } + public bool OnNodes { get; set; } = true; + public bool OnGroups { get; set; } + public bool OnLinks { get; set; } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index dfe7f6f4d..b2498315c 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -19,12 +19,12 @@ - - + + - + @@ -37,12 +37,12 @@ - + - + diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor b/src/Blazor.Diagrams/Components/DiagramCanvas.razor index 790404eca..2a6c1a951 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor @@ -9,7 +9,6 @@ @onpointermove:preventDefault @onwheel:stopPropagation> - @* Links *@ @foreach (var node in BlazorDiagram.Nodes.OfType().Where(n => n.Group == null)) { @@ -29,11 +28,12 @@ - @* Nodes *@
    @foreach (var group in BlazorDiagram.Groups.Where(n => n is not SvgGroupModel)) { - if (group.Group != null) continue; + if (group.Group != null) + continue; + } diff --git a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs index 612aad901..8bf4c9bd1 100644 --- a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs @@ -24,11 +24,13 @@ public class GroupRenderer : ComponentBase, IDisposable public void Dispose() { Group.Changed -= OnGroupChanged; + Group.VisibilityChanged -= OnGroupChanged; } protected override void OnInitialized() { Group.Changed += OnGroupChanged; + Group.VisibilityChanged += OnGroupChanged; } protected override void OnParametersSet() @@ -78,6 +80,9 @@ private static string GenerateStyle(double top, double left, double width, doubl protected override void BuildRenderTree(RenderTreeBuilder builder) { + if (!Group.Visible) + return; + var componentType = BlazorDiagram.GetComponent(Group) ?? typeof(DefaultGroupWidget); var classes = new StringBuilder("group") .AppendIf(" locked", Group.Locked) diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs index 4ab86fb79..0d1d21ff2 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs @@ -18,6 +18,7 @@ public class LinkRenderer : ComponentBase, IDisposable public void Dispose() { Link.Changed -= OnLinkChanged; + Link.VisibilityChanged -= OnLinkChanged; } protected override void OnInitialized() @@ -25,15 +26,23 @@ protected override void OnInitialized() base.OnInitialized(); Link.Changed += OnLinkChanged; + Link.VisibilityChanged += OnLinkChanged; } protected override bool ShouldRender() { - return _shouldRender; + if (!_shouldRender) + return false; + + _shouldRender = false; + return true; } protected override void BuildRenderTree(RenderTreeBuilder builder) { + if (!Link.Visible) + return; + var componentType = BlazorDiagram.GetComponent(Link) ?? typeof(LinkWidget); builder.OpenElement(0, "g"); @@ -51,11 +60,6 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.CloseElement(); } - protected override void OnAfterRender(bool firstRender) - { - _shouldRender = false; - } - private void OnLinkChanged(Model _) { _shouldRender = true; diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index 0d12f882d..a1c934190 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -19,7 +19,6 @@ public class NodeRenderer : ComponentBase, IDisposable private bool _becameVisible; private ElementReference _element; private bool _isSvg; - private bool _isVisible = true; private DotNetObjectReference? _reference; private bool _shouldRender; @@ -31,10 +30,8 @@ public class NodeRenderer : ComponentBase, IDisposable public void Dispose() { - BlazorDiagram.PanChanged -= CheckVisibility; - BlazorDiagram.ZoomChanged -= CheckVisibility; - BlazorDiagram.ContainerChanged -= CheckVisibility; Node.Changed -= OnNodeChanged; + Node.VisibilityChanged -= OnVisibilityChanged; if (_element.Id != null) _ = JsRuntime.UnobserveResizes(_element); @@ -65,10 +62,8 @@ protected override void OnInitialized() base.OnInitialized(); _reference = DotNetObjectReference.Create(this); - BlazorDiagram.PanChanged += CheckVisibility; - BlazorDiagram.ZoomChanged += CheckVisibility; - BlazorDiagram.ContainerChanged += CheckVisibility; Node.Changed += OnNodeChanged; + Node.VisibilityChanged += OnVisibilityChanged; } protected override void OnParametersSet() @@ -89,7 +84,7 @@ protected override bool ShouldRender() protected override void BuildRenderTree(RenderTreeBuilder builder) { - if (!_isVisible) + if (!Node.Visible) return; var componentType = BlazorDiagram.GetComponent(Node) ?? @@ -104,11 +99,15 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.AddAttribute(2, "data-node-id", Node.Id); if (_isSvg) + { builder.AddAttribute(3, "transform", $"translate({Node.Position.X.ToInvariantString()} {Node.Position.Y.ToInvariantString()})"); + } else + { builder.AddAttribute(3, "style", $"top: {Node.Position.Y.ToInvariantString()}px; left: {Node.Position.X.ToInvariantString()}px"); + } builder.AddAttribute(4, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); builder.AddEventStopPropagationAttribute(5, "onpointerdown", true); @@ -126,8 +125,6 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) protected override async Task OnAfterRenderAsync(bool firstRender) { - await base.OnAfterRenderAsync(firstRender); - if (firstRender || _becameVisible) { _becameVisible = false; @@ -135,33 +132,14 @@ protected override async Task OnAfterRenderAsync(bool firstRender) } } - private void CheckVisibility() + private void OnNodeChanged(Model _) { - // _isVisible must be true in case virtualization gets disabled and some nodes are hidden - if (!BlazorDiagram.Options.EnableVirtualization && _isVisible) - return; - - if (Node.Size == null) - return; - - var left = Node.Position.X * BlazorDiagram.Zoom + BlazorDiagram.Pan.X; - var top = Node.Position.Y * BlazorDiagram.Zoom + BlazorDiagram.Pan.Y; - var right = left + Node.Size.Width * BlazorDiagram.Zoom; - var bottom = top + Node.Size.Height * BlazorDiagram.Zoom; - - var isVisible = right > 0 && left < BlazorDiagram.Container.Width && bottom > 0 && - top < BlazorDiagram.Container.Height; - - if (_isVisible != isVisible) - { - _isVisible = isVisible; - _becameVisible = isVisible; - ReRender(); - } + ReRender(); } - private void OnNodeChanged(Model _) + private void OnVisibilityChanged(Model _) { + _becameVisible = Node.Visible; ReRender(); } diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs index ae4c79cc0..baf5c21f6 100644 --- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs @@ -34,6 +34,7 @@ public class PortRenderer : ComponentBase, IDisposable public void Dispose() { Port.Changed -= OnPortChanged; + Port.VisibilityChanged -= OnPortChanged; } protected override void OnInitialized() @@ -41,6 +42,7 @@ protected override void OnInitialized() base.OnInitialized(); Port.Changed += OnPortChanged; + Port.VisibilityChanged += OnPortChanged; } protected override void OnParametersSet() @@ -52,11 +54,18 @@ protected override void OnParametersSet() protected override bool ShouldRender() { - return _shouldRender; + if (!_shouldRender) + return false; + + _shouldRender = false; + return true; } protected override void BuildRenderTree(RenderTreeBuilder builder) { + if (!Port.Visible) + return; + builder.OpenElement(0, _isParentSvg ? "g" : "div"); builder.AddAttribute(1, "class", "port" + " " + Port.Alignment.ToString().ToLower() + " " + (Port.Links.Count > 0 ? "has-links" : "") + " " + @@ -66,17 +75,17 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); builder.AddAttribute(5, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); builder.AddEventStopPropagationAttribute(6, "onpointerup", true); - builder.AddElementReferenceCapture(12, __value => { _element = __value; }); - builder.AddContent(13, ChildContent); + builder.AddElementReferenceCapture(7, __value => { _element = __value; }); + builder.AddContent(8, ChildContent); builder.CloseElement(); } protected override async Task OnAfterRenderAsync(bool firstRender) { - await base.OnAfterRenderAsync(firstRender); - _shouldRender = false; - - if (!Port.Initialized) await UpdateDimensions(); + if (!Port.Initialized) + { + await UpdateDimensions(); + } } private void OnPointerDown(PointerEventArgs e) diff --git a/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs b/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs index c608edf12..b7ea0e63e 100644 --- a/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs +++ b/src/Blazor.Diagrams/Options/BlazorDiagramOptions.cs @@ -11,4 +11,5 @@ public class BlazorDiagramOptions : DiagramOptions public override BlazorDiagramLinkOptions Links { get; } = new(); public override BlazorDiagramGroupOptions Groups { get; } = new(); public override BlazorDiagramConstraintsOptions Constraints { get; } = new(); + public override BlazorDiagramVirtualizationOptions Virtualization { get; } = new(); } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Options/BlazorDiagramVirtualizationOptions.cs b/src/Blazor.Diagrams/Options/BlazorDiagramVirtualizationOptions.cs new file mode 100644 index 000000000..c3d2c0f9f --- /dev/null +++ b/src/Blazor.Diagrams/Options/BlazorDiagramVirtualizationOptions.cs @@ -0,0 +1,7 @@ +using Blazor.Diagrams.Core.Options; + +namespace Blazor.Diagrams.Options; + +public class BlazorDiagramVirtualizationOptions : DiagramVirtualizationOptions +{ +} \ No newline at end of file From 582773a38c2944aa543d88c562e6f8ec17f1eb23 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 10 Sep 2022 11:59:08 +0100 Subject: [PATCH 087/193] Render link labels without foreignObejct in widget no MarkupString --- .../Components/DefaultLinkLabelWidget.razor | 11 +++---- .../Components/Renderers/LinkLabelRenderer.cs | 27 +++++++++++++----- .../wwwroot/default.styles.css | 2 +- .../wwwroot/default.styles.min.css | 2 +- .../wwwroot/default.styles.min.css.gz | Bin 666 -> 668 bytes src/Blazor.Diagrams/wwwroot/style.css | 2 +- src/Blazor.Diagrams/wwwroot/style.min.css | 2 +- src/Blazor.Diagrams/wwwroot/style.min.css.gz | Bin 430 -> 436 bytes 8 files changed, 28 insertions(+), 18 deletions(-) diff --git a/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor b/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor index 43f2b27c0..7f2a1c0a7 100644 --- a/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor +++ b/src/Blazor.Diagrams/Components/DefaultLinkLabelWidget.razor @@ -1,13 +1,10 @@ -@((MarkupString)$"") + @code { [Parameter] - public LinkLabelModel Label { get; set; } + public LinkLabelModel Label { get; set; } = null!; - [Parameter] - public Point Position { get; set; } - - private double X => Position.X + (Label.Offset?.X ?? 0); - private double Y => Position.Y + (Label.Offset?.Y ?? 0); } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs index 618b2fc88..eb7b3e1fb 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs @@ -1,5 +1,6 @@ using System; using System.Linq; +using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; @@ -18,24 +19,36 @@ public class LinkLabelRenderer : ComponentBase, IDisposable public void Dispose() { Label.Changed -= OnLabelChanged; + Label.VisibilityChanged -= OnLabelChanged; } protected override void OnInitialized() { Label.Changed += OnLabelChanged; + Label.VisibilityChanged += OnLabelChanged; } protected override void BuildRenderTree(RenderTreeBuilder builder) { - var component = BlazorDiagram.GetComponent(Label) ?? typeof(DefaultLinkLabelWidget); + if (!Label.Visible) + return; + var position = FindPosition(); if (position == null) return; + + var componentType = BlazorDiagram.GetComponent(Label) ?? typeof(DefaultLinkLabelWidget); - builder.OpenComponent(0, component); - builder.AddAttribute(1, "Label", Label); - builder.AddAttribute(2, "Position", position); + builder.OpenElement(0, "foreignObject"); + builder.AddAttribute(1, "class", "link-label"); + builder.AddAttribute(2, "x", (position.X + (Label.Offset?.X ?? 0)).ToInvariantString()); + builder.AddAttribute(3, "y", (position.Y + (Label.Offset?.Y ?? 0)).ToInvariantString()); + + builder.OpenComponent(4, componentType); + builder.AddAttribute(5, "Label", Label); builder.CloseComponent(); + + builder.CloseElement(); } private void OnLabelChanged(Model _) @@ -49,9 +62,9 @@ private void OnLabelChanged(Model _) var length = Label.Distance switch { - var d when d >= 0 && d <= 1 => Label.Distance.Value * totalLength, - var d when d > 1 => Label.Distance.Value, - var d when d < 0 => totalLength + Label.Distance.Value, + <= 1 and >= 0 => Label.Distance.Value * totalLength, + > 1 => Label.Distance.Value, + < 0 => totalLength + Label.Distance.Value, _ => totalLength * (Label.Parent.Labels.IndexOf(Label) + 1) / (Label.Parent.Labels.Count + 1) }; diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.css b/src/Blazor.Diagrams/wwwroot/default.styles.css index 1dd96c814..bcb3061dd 100644 --- a/src/Blazor.Diagrams/wwwroot/default.styles.css +++ b/src/Blazor.Diagrams/wwwroot/default.styles.css @@ -109,7 +109,7 @@ g.group.default.selected > rect { outline: 2px solid green; } -.link .link-label { +.link div.link-label { display: inline-block; color: #fff; background-color: rgb(110, 159, 212); diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css b/src/Blazor.Diagrams/wwwroot/default.styles.min.css index 76dc92a09..010c8a863 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 .port{border:1px solid #6e9fd4;}.default-node .port,.default.group .port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .port:hover,.default-node .port.has-links,.default.group .port.has-links{background-color:#000;}.default-node .port.bottom,.default.group .port.bottom{bottom:0;left:50%;}.default-node .port.bottomleft,.default.group .port.bottomleft{bottom:0;left:0;}.default-node .port.bottomright,.default.group .port.bottomright{bottom:0;right:0;}.default-node .port.top,.default.group .port.top{top:0;left:50%;}.default-node .port.topleft,.default.group .port.topleft{top:0;left:0;}.default-node .port.topright,.default.group .port.topright{top:0;right:0;}.default-node .port.left,.default.group .port.left{left:0;top:50%;}.default-node .port.right,.default.group .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.group.default{outline:2px solid #000;background:#c6c6c6;}div.group.default.selected{outline:2px solid #6e9fd4;}g.group.default rect{outline:2px solid #000;fill:#c6c632;}g.group.default.selected>rect{outline:2px solid #008000;}.link .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 .port{border:1px solid #6e9fd4;}.default-node .port,.default.group .port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .port:hover,.default-node .port.has-links,.default.group .port.has-links{background-color:#000;}.default-node .port.bottom,.default.group .port.bottom{bottom:0;left:50%;}.default-node .port.bottomleft,.default.group .port.bottomleft{bottom:0;left:0;}.default-node .port.bottomright,.default.group .port.bottomright{bottom:0;right:0;}.default-node .port.top,.default.group .port.top{top:0;left:50%;}.default-node .port.topleft,.default.group .port.topleft{top:0;left:0;}.default-node .port.topright,.default.group .port.topright{top:0;right:0;}.default-node .port.left,.default.group .port.left{left:0;top:50%;}.default-node .port.right,.default.group .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.group.default{outline:2px solid #000;background:#c6c6c6;}div.group.default.selected{outline:2px solid #6e9fd4;}g.group.default rect{outline:2px solid #000;fill:#c6c632;}g.group.default.selected>rect{outline:2px solid #008000;}.link div.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 diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz b/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz index e9f866f0a82ea904bf517b461a45cecc4eb7f1f3..eb44349f88e06e3bdab2e1bd12db4cc310e1e5e3 100644 GIT binary patch literal 668 zcmV;N0%QFjiwFP!000003bj>hj@mE~z6uhmv`9D#0d`qO>Rp_85?mZRvK?R-akBpC zk$Q#Nd5~oDp#4!<0ec>wzZuWizkmMnC?urd1_?>wc45>U@y7S_Yj^~f9t}RYc_bAD zg%qKz&{pq6BtE4|7J{N!a;fk#*}GBO;!S>CXvrB}EaBirh%OMFm_d=eB7G!OUa-Fi zteG=K?nk8&dGXh7c)@Dzmm2r^J=JE{=(a;$u(AyP^9H%p%rGgi0!|D&Lr9s}GX z|J%P5B;C0Y#)Z<>E|;uK#N$*+WbFzAqm=PcoR{K6d*)4RNSC*!B0q(RCBVNd)}X-1$s6 zHxzLhpTbUVh8e3P=%?BX_6o8`vg@URtLZ-ziM`&DW}kq)!&_<7yb^`BXjLp&NCEbl zke_2xJroWE855|wsJGocj3PoOQf0WD+2;{nkgdwK`&pIPPfVVu?|%Te3jtNN2mkhZsQ;jzDlHMrA7*t<1}rGrM(L_V}}g}3vik?>dE%U z9%-+zfbH0>AKm@2LVhrhk8g$<`2F)&Kp`U)H%Ledw^v5Z3Gc$Nyv8SB`N`m;`%k2z zppYVzRod#Eh@|IS$x2X^N-h;%X9qVLTf8f;D=j&rt2G?mh|wz~XJ$|$uSlN=m9IEl zMb^xjBKJ~xLLS1c8$q<$hF;^IaG=^fwz~aT7i}zq|GYpcH8V^Ktbh~4E)Y|uOHST! zFUC$*Xq0id_GNB9NC@F97dQoBK*g=cQ0ga$87TDKu!m6E4H*ISg+D57SoUVO%T9%9 z#UFzb2sCg=4ba_7bclXB2WezE4P_NK`+OOr zwV4mFQTBo6c`U@$Y8#-%i00XNDh&w-G#k?blbS|LpohXpu1J9dxv-oVsao&bxW=2E z@l91D_4*6%yQXhOdGY^({suQeWT@?G+53#gjE2WDnj=P)?SI z9;sL8z_K*jb)`P$3G>^Y`Tn2b_s6f&pzebnRHuE?aXz78TR&CRx5fhYmM`m(A~KS% z131l?prMH!dZ;~lc}Wlsj!v~jW574v6e5LMouEA;D{0VPwRO!jbyn$aExA^B-$7#P zmD8^f9(T#|;4a$CG}cw);K1?-D`3^M#7u)Mc1+zyaub6n=ox~71fJui*AW=cU{3}zd{>q!3SDFF%}zHG$h)|j4>zc zqDSf#N+*sxNK~N9Sw;C0$;a>i`{%FPq3M$ulr?cSc{!nD-#lymx%Gg(=j(Z-jEoeU z1i>)Q(9*;KBQ%ks{Fxyg0-YL<&OvPLlrp78ouE4+D;dz;w9A=kvR)Z`X?f}Jwu9uk zSHZkMdfFw=L%3=S)7a=Xz=7o_T>y345i<>P*fC9)DNGEaU}Q)NW{8}xvu;?NOw0#L zgCgmKvC)-o)sV{Ze|LDQ_Z6293>?Wpf$zpLWr55P!14w;&}&J} zkK%){Wn&PGdjm`e8%!O@9>Fc!wBceARlE!I?{VP9W4WJl#8jF1|? znz3X~bOc4CvB)n?t-rp@Xf!kShAAzt3ZsLwxLCTbY8jjaNX-bRf}0&BCnLX%=SmcG z)hDgjPJN Date: Sat, 10 Sep 2022 17:30:09 +0100 Subject: [PATCH 088/193] Only trigger Moved event when movable actually moved --- .../Behaviors/DragMovablesBehavior.cs | 10 ++++++++-- .../Behaviors/DragMovablesBehaviorTests.cs | 20 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 2a72c2c92..7df4bb2af 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -12,6 +12,7 @@ public class DragMovablesBehavior : Behavior private readonly Dictionary _initialPositions; private double? _lastClientX; private double? _lastClientY; + private bool _moved; public DragMovablesBehavior(Diagram diagram) : base(diagram) { @@ -38,6 +39,7 @@ private void OnPointerDown(Model? model, PointerEventArgs e) _lastClientX = e.ClientX; _lastClientY = e.ClientY; + _moved = false; } private void OnPointerMove(Model? model, PointerEventArgs e) @@ -45,6 +47,7 @@ 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; @@ -61,9 +64,12 @@ private void OnPointerUp(Model? model, PointerEventArgs e) if (_initialPositions.Count == 0) return; - foreach (var (movable, _) in _initialPositions) + if (_moved) { - movable.TriggerMoved(); + foreach (var (movable, _) in _initialPositions) + { + movable.TriggerMoved(); + } } _initialPositions.Clear(); diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs index ac3901d4e..9161ff9f8 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -50,4 +50,24 @@ public void Behavior_ShouldTriggerMoved() // 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(); + } } \ No newline at end of file From 2bc24d436598f4f8dd4718ff02dcebb4e737f959 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 10 Sep 2022 18:26:53 +0100 Subject: [PATCH 089/193] Add PointerEnter/Leave events to groups --- .../Components/Renderers/GroupRenderer.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs index 8bf4c9bd1..041a993f9 100644 --- a/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/GroupRenderer.cs @@ -104,18 +104,20 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) builder.AddEventStopPropagationAttribute(5, "onpointerdown", true); builder.AddAttribute(6, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); builder.AddEventStopPropagationAttribute(7, "onpointerup", true); + builder.AddAttribute(8, "onmouseenter", EventCallback.Factory.Create(this, OnMouseEnter)); + builder.AddAttribute(9, "onmouseleave", EventCallback.Factory.Create(this, OnMouseLeave)); if (_isSvg) { - builder.OpenElement(8, "rect"); - builder.AddAttribute(9, "width", Group.Size!.Width); - builder.AddAttribute(10, "height", Group.Size.Height); - builder.AddAttribute(11, "fill", "none"); + builder.OpenElement(10, "rect"); + builder.AddAttribute(11, "width", Group.Size!.Width); + builder.AddAttribute(12, "height", Group.Size.Height); + builder.AddAttribute(13, "fill", "none"); builder.CloseElement(); } - builder.OpenComponent(12, componentType); - builder.AddAttribute(13, "Group", Group); + builder.OpenComponent(14, componentType); + builder.AddAttribute(15, "Group", Group); builder.CloseComponent(); builder.CloseElement(); } @@ -129,4 +131,14 @@ private void OnPointerUp(PointerEventArgs e) { BlazorDiagram.TriggerPointerUp(Group, e.ToCore()); } + + private void OnMouseEnter(MouseEventArgs e) + { + BlazorDiagram.TriggerPointerEnter(Group, e.ToCore()); + } + + private void OnMouseLeave(MouseEventArgs e) + { + BlazorDiagram.TriggerPointerLeave(Group, e.ToCore()); + } } \ No newline at end of file From 7774f74ba38b06d171cd0343ff45d9765158e82e Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 11 Sep 2022 22:26:28 +0100 Subject: [PATCH 090/193] Add experimental Link to Link --- samples/SharedDemo/Demos/DevTests.razor | 17 ++++++++++---- .../Anchors/LinkAnchor.cs | 22 +++++++++++++++++++ .../Models/Base/BaseLinkModel.cs | 19 +++++++++++++++- src/Blazor.Diagrams.Core/Models/NodeModel.cs | 3 ++- src/Blazor.Diagrams.Core/Models/PortModel.cs | 9 +++++++- .../Positions/LinkPathPositionProvider.cs | 1 - 6 files changed, 63 insertions(+), 8 deletions(-) create mode 100644 src/Blazor.Diagrams.Core/Anchors/LinkAnchor.cs diff --git a/samples/SharedDemo/Demos/DevTests.razor b/samples/SharedDemo/Demos/DevTests.razor index b7e04b54c..505a78011 100644 --- a/samples/SharedDemo/Demos/DevTests.razor +++ b/samples/SharedDemo/Demos/DevTests.razor @@ -1,6 +1,7 @@ @page "/demos/dynamic-anchor" @layout DemoLayout @using Blazor.Diagrams +@using Blazor.Diagrams.Core.Anchors @using Blazor.Diagrams.Core.Anchors.Dynamic @using Blazor.Diagrams.Core.Controls @using Blazor.Diagrams.Core.Positions @@ -17,6 +18,7 @@ { var node1 = NewNode(100, 100); var node2 = NewNode(300, 100); + var node3 = NewNode(500, 250); var providers1 = new IPositionProvider[] { @@ -41,19 +43,25 @@ TargetMarker = LinkMarker.Arrow }; - _blazorDiagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); _blazorDiagram.Links.Add(link1); - + _blazorDiagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node3), new LinkAnchor(link1, 0.5)) + { + PathGenerator = PathGenerators.Straight, + SourceMarker = LinkMarker.Arrow, + TargetMarker = LinkMarker.Arrow + }); + _blazorDiagram.Controls.AddFor(node1) .Add(new RemoveControl(1, 0)) .Add(new DragNewLinkControl(1, 0.5, 20)) .Add(new BoundaryControl()); - + _blazorDiagram.Controls.AddFor(node2) .Add(new RemoveControl(1, 0)) .Add(new DragNewLinkControl(1, 0.5, 20)) .Add(new BoundaryControl()); - + _blazorDiagram.Controls.AddFor(link1) .Add(new RemoveControl(new LinkPathPositionProvider(0.1))) .Add(new BoundaryControl()); @@ -68,4 +76,5 @@ node.AddPort(PortAlignment.Right); return node; } + } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Anchors/LinkAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/LinkAnchor.cs new file mode 100644 index 000000000..d05dc0d2c --- /dev/null +++ b/src/Blazor.Diagrams.Core/Anchors/LinkAnchor.cs @@ -0,0 +1,22 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Positions; + +namespace Blazor.Diagrams.Core.Anchors; + +public class LinkAnchor : Anchor +{ + private readonly LinkPathPositionProvider _positionProvider; + + public LinkAnchor(BaseLinkModel link, double distance, double offsetX = 0, double offsetY = 0) : base(link) + { + _positionProvider = new LinkPathPositionProvider(distance, offsetX, offsetY); + Link = link; + } + + public BaseLinkModel Link { get; } + + public override Point? GetPosition(BaseLinkModel link, Point[] route) => _positionProvider.GetPosition(Link); + + public override Point? GetPlainPosition() => _positionProvider.GetPosition(Link); +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index 1d947e5c2..0a8194aec 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -6,8 +6,10 @@ namespace Blazor.Diagrams.Core.Models.Base { - public abstract class BaseLinkModel : SelectableModel, IHasBounds + public abstract class BaseLinkModel : SelectableModel, IHasBounds, ILinkable { + private readonly List _links = new(); + public event Action? SourceChanged; public event Action? TargetChanged; @@ -37,6 +39,7 @@ protected BaseLinkModel(string id, Anchor source, Anchor? target = null) : base( public bool Segmentable { get; set; } = false; public List Vertices { get; } = new(); public List Labels { get; } = new(); + public IReadOnlyList Links => _links; public override void Refresh() { @@ -44,6 +47,14 @@ public override void Refresh() base.Refresh(); } + public void RefreshLinks() + { + foreach (var link in Links) + { + link.Refresh(); + } + } + public void SetSource(Anchor anchor) { ArgumentNullException.ThrowIfNull(anchor, nameof(anchor)); @@ -88,6 +99,8 @@ public void SetTarget(Anchor? anchor) return new Rectangle(minX, minY, maxX, maxY); } + public bool CanAttachTo(ILinkable other) => true; + private void GeneratePath() { if (Diagram != null) @@ -106,5 +119,9 @@ private void GeneratePath() GeneratedPathResult = PathGeneratorResult.Empty; } + + void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); + + void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link); } } diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs index eea80db96..b94d69761 100644 --- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs +++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs @@ -75,6 +75,7 @@ public void RefreshLinks() foreach (var link in Links) { link.Refresh(); + link.RefreshLinks(); } } @@ -147,7 +148,7 @@ private void UpdatePortPositions(double deltaX, double deltaY) } } - public virtual bool CanAttachTo(ILinkable other) => other is not PortModel; + public virtual bool CanAttachTo(ILinkable other) => other is not PortModel && other is not BaseLinkModel; void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs index a04e3e175..e5502432f 100644 --- a/src/Blazor.Diagrams.Core/Models/PortModel.cs +++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs @@ -44,7 +44,14 @@ public void RefreshAll() RefreshLinks(); } - public void RefreshLinks() => _links.ForEach(l => l.Refresh()); + public void RefreshLinks() + { + foreach (var link in Links) + { + link.Refresh(); + link.RefreshLinks(); + } + } public T GetParent() where T : NodeModel => (T)Parent; diff --git a/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs b/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs index 51f959c13..64dbb5f1f 100644 --- a/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs @@ -1,4 +1,3 @@ -using System; using System.Linq; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; From 2a1f7d03d7392a0f26f436e542ccd0d7750222ac Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 11 Sep 2022 22:46:17 +0100 Subject: [PATCH 091/193] Update Versions & CHANGELOG --- .../Blazor.Diagrams.Algorithms.csproj | 6 +++--- .../Blazor.Diagrams.Core.csproj | 4 ++-- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 4 ++-- src/Blazor.Diagrams/BlazorDiagram.cs | 19 ++++++++++--------- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj index 4bcfa56fa..51cb42316 100644 --- a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj +++ b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj @@ -7,10 +7,10 @@ MIT zHaytam Algorithms for Z.Blazor.Diagrams - 0.1.0 - 0.1.0 + 3.0.0 + 3.0.0 https://github.com/zHaytam/Blazor.Diagrams - 0.1.0 + 3.0.0-beta.2 Z.Blazor.Diagrams.Algorithms blazor diagrams diagramming svg drag algorithms layouts Z.Blazor.Diagrams.Algorithms diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index fb3fdb4a1..c649bac51 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -8,9 +8,9 @@ zHaytam A fully customizable and extensible all-purpose diagrams library for Blazor 3.0.0 - 3.0.0-beta.1 + 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams - 3.0.0-beta.1 + 3.0.0-beta.2 Z.Blazor.Diagrams.Core blazor diagrams diagramming svg drag Z.Blazor.Diagrams.Core diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index b2498315c..6d2445e74 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -6,10 +6,10 @@ zHaytam MIT 3.0.0 - 3.0.0-beta.1 + 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams A fully customizable and extensible all-purpose diagrams library for Blazor - 3.0.0-beta.1 + 3.0.0-beta.2 true blazor diagrams diagramming svg drag Z.Blazor.Diagrams diff --git a/src/Blazor.Diagrams/BlazorDiagram.cs b/src/Blazor.Diagrams/BlazorDiagram.cs index 30dfa88b4..3b0a6c9bd 100644 --- a/src/Blazor.Diagrams/BlazorDiagram.cs +++ b/src/Blazor.Diagrams/BlazorDiagram.cs @@ -5,14 +5,12 @@ using Blazor.Diagrams.Core.Controls.Default; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Options; -using Microsoft.AspNetCore.Components; namespace Blazor.Diagrams; public class BlazorDiagram : Diagram { private readonly Dictionary _componentsMapping; - private readonly Dictionary _fragmentsMapping; public BlazorDiagram(BlazorDiagramOptions? options = null) { @@ -23,8 +21,6 @@ public BlazorDiagram(BlazorDiagramOptions? options = null) [typeof(DragNewLinkControl)] = typeof(DragNewLinkControlWidget) }; - _fragmentsMapping = new Dictionary(); - Options = options ?? new BlazorDiagramOptions(); } @@ -45,12 +41,17 @@ public void RegisterComponent(Type modelType, Type componentType, bool replace = public Type? GetComponent(Type modelType, bool checkSubclasses = true) { - if (_componentsMapping.ContainsKey(modelType)) return _componentsMapping[modelType]; + if (_componentsMapping.ContainsKey(modelType)) + return _componentsMapping[modelType]; - if (checkSubclasses) - foreach (var rmt in _componentsMapping.Keys) - if (modelType.IsSubclassOf(rmt)) - return _componentsMapping[rmt]; + if (!checkSubclasses) + return null; + + foreach (var rmt in _componentsMapping.Keys) + { + if (modelType.IsSubclassOf(rmt)) + return _componentsMapping[rmt]; + } return null; } From 3646b0e3c9e25fea23baa4e0437342dedf63f81c Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 11 Sep 2022 22:46:21 +0100 Subject: [PATCH 092/193] Update CHANGELOG.md --- CHANGELOG.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 88a5b9fd2..7e304bf5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Diagrams (3.0.0-preview.2) - 2022-09-14 + +### Added + +- `Moved` event to Movables +- `Visible` property and `VisbilityChanged` event to models +- `Options.Virtualization` (of type `[Diagram]VirtualizationOptions`) for virtualization options +- `PointerEnter/Leave` events for groups as well +- **Experimental Link to Link** (using `LinkAnchor`) + +### Changed + +- Rename `RegisterModelComponent` to `RegisterComponent` +- Rename `GetComponentForModel` to `GetComponent` +- Virtualization is now handled by a behavior instead of NodeRender + - This means that it works for almost all models (nodes, groups and links) +- Render link labels without foreignObject in widget nor MarkupString (Thank you .NET 6) +- Custom link labels only need to contain relevant content, they don't need to handle positioning anymore + +### Removed + +- `EnableVirtualization` option (see added alternative) + ## Diagrams (3.0.0-preview.1) - 2022-09-04 .NET 6! From f26285bb33868721a5665ab812cd320f2cee9b65 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 11 Sep 2022 22:46:41 +0100 Subject: [PATCH 093/193] Fix date in changelog --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e304bf5d..2cc6938e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Diagrams (3.0.0-preview.2) - 2022-09-14 +## Diagrams (3.0.0-preview.2) - 2022-09-11 ### Added From e7d29c34da0b1b2c5967156e3378e9ef116e3f72 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 11 Sep 2022 22:50:48 +0100 Subject: [PATCH 094/193] Update CHANGELOG.md --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cc6938e9..04ee606eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## Diagrams (3.0.0-preview.2) - 2022-09-11 +## Diagrams (3.0.0-beta.2) - 2022-09-11 ### Added @@ -28,7 +28,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `EnableVirtualization` option (see added alternative) -## Diagrams (3.0.0-preview.1) - 2022-09-04 +## Diagrams (3.0.0-beta.1) - 2022-09-04 .NET 6! From 11b81432714c536c64f00789d9c441e1b1205d99 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 13 Sep 2022 08:57:25 +0100 Subject: [PATCH 095/193] Add README to packages --- src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj | 7 ++++++- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index c649bac51..bc726717a 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -16,12 +16,17 @@ Z.Blazor.Diagrams.Core ZBD.png https://blazor-diagrams.zhaytam.com/ + README.md True - + \ + + + True + \ diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index 6d2445e74..c5328a8a3 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -16,6 +16,7 @@ https://blazor-diagrams.zhaytam.com/ Z.Blazor.Diagrams ZBD.png + README.md @@ -30,7 +31,11 @@ True - + \ + + + True + \ From a4e46b587d09ab41547fce21952c51e8b2f7c5f2 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 13 Sep 2022 09:56:39 +0100 Subject: [PATCH 096/193] Add support for LinkFactory to return null in order to not create an ongoing link --- .../Behaviors/DragNewLinkBehavior.cs | 14 +++++------ src/Blazor.Diagrams.Core/Delegates.cs | 3 ++- .../Behaviors/DragNewLinkBehaviorTests.cs | 25 +++++++++++++++++++ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 924d1bbd8..d2b34f044 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -36,16 +36,16 @@ private void OnPointerDown(Model? model, MouseEventArgs e) if (model is PortModel port) { - if (port.Locked) return; + if (port.Locked) + return; + _ongoingLink = Diagram.Options.Links.Factory(Diagram, port); + if (_ongoingLink == null) + return; + _ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5); + Diagram.Links.Add(_ongoingLink); } - else - { - return; - } - - Diagram.Links.Add(_ongoingLink); } private void OnPointerMove(Model? model, MouseEventArgs e) diff --git a/src/Blazor.Diagrams.Core/Delegates.cs b/src/Blazor.Diagrams.Core/Delegates.cs index 83edb84b8..20a0c1adf 100644 --- a/src/Blazor.Diagrams.Core/Delegates.cs +++ b/src/Blazor.Diagrams.Core/Delegates.cs @@ -9,7 +9,8 @@ namespace Blazor.Diagrams.Core public delegate PathGeneratorResult PathGenerator(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target); - public delegate BaseLinkModel LinkFactory(Diagram diagram, PortModel sourcePort); + public delegate BaseLinkModel? LinkFactory(Diagram diagram, PortModel sourcePort); + public delegate Anchor AnchorFactory(Diagram diagram, BaseLinkModel link, ILinkable model); public delegate GroupModel GroupFactory(Diagram diagram, NodeModel[] children); diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index 5a48319d4..99b8eeba5 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -340,5 +340,30 @@ public void Behavior_ShouldSetTarget_WhenMouseUp() 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, sp) => 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); + } } } \ No newline at end of file From 1b2d56c56f8a84b4619905623a5d45a1febd039d Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 14 Sep 2022 09:13:07 +0100 Subject: [PATCH 097/193] Remove PositionProvider param from ExecutableControl for more freedom --- .../Controls/Default/DragNewLinkControl.cs | 10 ++++++++-- .../Controls/Default/RemoveControl.cs | 10 ++++++++-- .../Controls/ExecutableControl.cs | 13 +------------ 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs index 084b2a9ad..7b6532476 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs @@ -2,6 +2,7 @@ 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 Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Positions; @@ -10,15 +11,20 @@ namespace Blazor.Diagrams.Core.Controls.Default; public class DragNewLinkControl : ExecutableControl { + private readonly IPositionProvider _positionProvider; + public DragNewLinkControl(double x, double y, double offsetX = 0, double offsetY = 0) - : base(new BoundsBasedPositionProvider(x, y, offsetX, offsetY)) + : this(new BoundsBasedPositionProvider(x, y, offsetX, offsetY)) { } - public DragNewLinkControl(IPositionProvider positionProvider) : base(positionProvider) + public DragNewLinkControl(IPositionProvider positionProvider) { + _positionProvider = positionProvider; } + public override Point? GetPosition(Model model) => _positionProvider.GetPosition(model); + public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) { if (model is not NodeModel node || node.Locked) diff --git a/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs index b0e9739e2..1e1704256 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs @@ -1,5 +1,6 @@ using System.Threading.Tasks; using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Positions; @@ -8,15 +9,20 @@ namespace Blazor.Diagrams.Core.Controls.Default; public class RemoveControl : ExecutableControl { + private readonly IPositionProvider _positionProvider; + public RemoveControl(double x, double y, double offsetX = 0, double offsetY = 0) - : base(new BoundsBasedPositionProvider(x, y, offsetX, offsetY)) + : this(new BoundsBasedPositionProvider(x, y, offsetX, offsetY)) { } - public RemoveControl(IPositionProvider positionProvider) : base(positionProvider) + public RemoveControl(IPositionProvider positionProvider) { + _positionProvider = positionProvider; } + public override Point? GetPosition(Model model) => _positionProvider.GetPosition(model); + public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs _) { switch (model) diff --git a/src/Blazor.Diagrams.Core/Controls/ExecutableControl.cs b/src/Blazor.Diagrams.Core/Controls/ExecutableControl.cs index 56ef783d6..5176ced7e 100644 --- a/src/Blazor.Diagrams.Core/Controls/ExecutableControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/ExecutableControl.cs @@ -1,21 +1,10 @@ -using System.Threading.Tasks; using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; -using Blazor.Diagrams.Core.Positions; +using System.Threading.Tasks; namespace Blazor.Diagrams.Core.Controls; public abstract class ExecutableControl : Control { - public IPositionProvider PositionProvider { get; } - - protected ExecutableControl(IPositionProvider positionProvider) - { - PositionProvider = positionProvider; - } - - public override Point? GetPosition(Model model) => PositionProvider.GetPosition(model); - public abstract ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e); } \ No newline at end of file From f22a85a17b4b56aaaa992ac8c1de6950fb2747d9 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 14 Sep 2022 09:13:45 +0100 Subject: [PATCH 098/193] Fix LinkPathPositionProvider not working with maxlength ratios --- src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs b/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs index 64dbb5f1f..606cb40b1 100644 --- a/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs @@ -37,7 +37,7 @@ public LinkPathPositionProvider(double distance, double offsetX = 0, double offs foreach (var path in link.Paths) { var pathLength = path.Length; - if (length < pathLength) + if (length <= pathLength) { var pt = path.GetPointAtLength(length); return new Point(pt.X + OffsetX, pt.Y + OffsetY); From 4e4f655f7fbd8720efb5573786c149f6d4d3a13a Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 14 Sep 2022 13:22:05 +0100 Subject: [PATCH 099/193] Add support for free links (no target required) --- .../Behaviors/DragNewLinkBehavior.cs | 15 +++++--- .../Models/Base/BaseLinkModel.cs | 2 ++ .../Options/DiagramLinkOptions.cs | 1 + .../Components/LinkWidget.razor | 35 +++++++++---------- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index d2b34f044..3d629940e 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -22,9 +22,17 @@ public void StartFrom(Anchor source, double clientX, double clientY) { if (_ongoingLink != null) return; - + //_ongoingLink = Diagram.Options.Links.Factory(Diagram, port); - _ongoingLink = new LinkModel(source); + StartFrom(new LinkModel(source), clientX, clientY); + } + + public void StartFrom(BaseLinkModel link, double clientX, double clientY) + { + if (_ongoingLink != null) + return; + + _ongoingLink = link; _ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5); Diagram.Links.Add(_ongoingLink); } @@ -85,9 +93,8 @@ private void OnPointerUp(Model? model, MouseEventArgs e) _ongoingLink.SetTarget(targetAnchor); _ongoingLink.Refresh(); } - else + else if (Diagram.Options.Links.RequireTarget) { - // Todo: support un-attached links Diagram.Links.Remove(_ongoingLink); } diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index 0a8194aec..c77301449 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -101,6 +101,8 @@ public void SetTarget(Anchor? anchor) public bool CanAttachTo(ILinkable other) => true; + public Point? GetTargetPosition() => Target?.GetPosition(this) ?? OnGoingPosition; + private void GeneratePath() { if (Diagram != null) diff --git a/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs index d8a9e7b9e..984f13468 100644 --- a/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs +++ b/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs @@ -11,6 +11,7 @@ public class DiagramLinkOptions public Router DefaultRouter { get; set; } = Routers.Normal; public PathGenerator DefaultPathGenerator { get; set; } = PathGenerators.Smooth; public bool EnableSnapping { get; set; } = false; + public bool RequireTarget { get; set; } = false; public double SnappingRadius { diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor b/src/Blazor.Diagrams/Components/LinkWidget.razor index 54ca618f3..6656294e9 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor @@ -8,22 +8,19 @@ var index = i; var d = result.Paths[i].ToString(); + stroke-width="@Link.Width.ToInvariantString()" + fill="none" + stroke="@color" /> - @if (Link.IsAttached) - { - - } + } @if (Link.SourceMarker != null && result.SourceMarkerAngle != null && result.SourceMarkerPosition != null) @@ -48,14 +45,14 @@ @foreach (var vertex in Link.Vertices) { + Vertex="vertex" + Color="@normalColor" + SelectedColor="@selectedColor" /> } } @foreach (var label in Link.Labels) { - + } \ No newline at end of file From 31786a97e6a4bc803a54bd1dd6ab25e4bb39b4ed Mon Sep 17 00:00:00 2001 From: Jeremy Vance <49650352+240026763@users.noreply.github.com> Date: Thu, 15 Sep 2022 12:53:48 -0400 Subject: [PATCH 100/193] Add GridSnapToCenter to DiagramOptions --- src/Blazor.Diagrams.Core/Options/DiagramOptions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs index ec0fc584a..4175eda0e 100644 --- a/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs +++ b/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs @@ -3,6 +3,7 @@ namespace Blazor.Diagrams.Core.Options; public class DiagramOptions { public int? GridSize { get; set; } + public bool GridSnapToCenter { get; set; } public bool AllowMultiSelection { get; set; } = true; public bool AllowPanning { get; set; } = true; From 095e3ceed6a434e98feb2631ccd0e5101c61d70b Mon Sep 17 00:00:00 2001 From: Jeremy Vance <49650352+240026763@users.noreply.github.com> Date: Thu, 15 Sep 2022 12:54:26 -0400 Subject: [PATCH 101/193] Snap to Center of Node --- .../Behaviors/DragMovablesBehavior.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 7df4bb2af..9047889b3 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -3,21 +3,20 @@ using Blazor.Diagrams.Core.Events; using System; using System.Collections.Generic; -using System.Linq; +using Blazor.Diagrams.Core.Models; namespace Blazor.Diagrams.Core.Behaviors { public class DragMovablesBehavior : Behavior { - private readonly Dictionary _initialPositions; + private readonly Dictionary _initialPositions; private double? _lastClientX; private double? _lastClientY; private bool _moved; public DragMovablesBehavior(Diagram diagram) : base(diagram) { - _initialPositions = new Dictionary(); - + _initialPositions = new Dictionary(); Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; Diagram.PointerUp += OnPointerUp; @@ -25,16 +24,21 @@ 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 movable || movable.Locked) continue; - - _initialPositions.Add(movable, movable.Position); + var position = movable.Position; + if (Diagram.Options.GridSnapToCenter) + { + position = new Point(movable.Position.X + movable.Size.Width / 2, + movable.Position.Y + movable.Size.Height / 2); + } + _initialPositions.Add(movable, position); } _lastClientX = e.ClientX; @@ -51,11 +55,14 @@ private void OnPointerMove(Model? model, PointerEventArgs e) var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; - foreach (var (movable, initialPosition) in _initialPositions) + foreach (var (node, initialPosition) in _initialPositions) { var ndx = ApplyGridSize(deltaX + initialPosition.X); var ndy = ApplyGridSize(deltaY + initialPosition.Y); - movable.SetPosition(ndx, ndy); + if (Diagram.Options.GridSnapToCenter) + node.SetPosition(ndx - node.Size.Width / 2, ndy - node.Size.Height / 2); + else + node.SetPosition(ndx, ndy); } } From 81420685e6a55bbd3e6f7b589771f9fa12ec4e90 Mon Sep 17 00:00:00 2001 From: Jeremy Vance <49650352+240026763@users.noreply.github.com> Date: Thu, 15 Sep 2022 15:59:10 -0400 Subject: [PATCH 102/193] Use MovableModel and check for is NodeModel when snapping to center --- .../Behaviors/DragMovablesBehavior.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 9047889b3..82048fc62 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -9,14 +9,14 @@ namespace Blazor.Diagrams.Core.Behaviors { public class DragMovablesBehavior : Behavior { - private readonly Dictionary _initialPositions; + private readonly Dictionary _initialPositions; private double? _lastClientX; private double? _lastClientY; private bool _moved; public DragMovablesBehavior(Diagram diagram) : base(diagram) { - _initialPositions = new Dictionary(); + _initialPositions = new Dictionary(); Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; Diagram.PointerUp += OnPointerUp; @@ -30,13 +30,13 @@ private void OnPointerDown(Model? model, PointerEventArgs e) _initialPositions.Clear(); foreach (var sm in Diagram.GetSelectedModels()) { - if (sm is not NodeModel movable || movable.Locked) + if (sm is not MovableModel movable || movable.Locked) continue; var position = movable.Position; - if (Diagram.Options.GridSnapToCenter) + if (Diagram.Options.GridSnapToCenter && movable is NodeModel node) { - position = new Point(movable.Position.X + movable.Size.Width / 2, - movable.Position.Y + movable.Size.Height / 2); + position = new Point(movable.Position.X + (node.Size?.Width ?? 0) / 2, + movable.Position.Y + (node.Size?.Height ?? 0) / 2); } _initialPositions.Add(movable, position); } @@ -55,14 +55,14 @@ private void OnPointerMove(Model? model, PointerEventArgs e) var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; - foreach (var (node, initialPosition) in _initialPositions) + foreach (var (movable, initialPosition) in _initialPositions) { var ndx = ApplyGridSize(deltaX + initialPosition.X); var ndy = ApplyGridSize(deltaY + initialPosition.Y); - if (Diagram.Options.GridSnapToCenter) - node.SetPosition(ndx - node.Size.Width / 2, ndy - node.Size.Height / 2); + if (Diagram.Options.GridSnapToCenter && movable is NodeModel node) + node.SetPosition(ndx - (node.Size?.Width ?? 0) / 2, ndy - (node.Size?.Height ?? 0) / 2); else - node.SetPosition(ndx, ndy); + movable.SetPosition(ndx, ndy); } } From 921935f9a6d49d1792db6c675b929c66d0e016a2 Mon Sep 17 00:00:00 2001 From: Jeremy Vance <49650352+240026763@users.noreply.github.com> Date: Thu, 15 Sep 2022 16:06:12 -0400 Subject: [PATCH 103/193] Check that it is a MovableModel instead of NodeModel --- src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 82048fc62..c8158beee 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -24,7 +24,7 @@ public DragMovablesBehavior(Diagram diagram) : base(diagram) private void OnPointerDown(Model? model, PointerEventArgs e) { - if (model is not NodeModel) + if (model is not MovableModel) return; _initialPositions.Clear(); @@ -32,6 +32,7 @@ private void OnPointerDown(Model? model, PointerEventArgs e) { if (sm is not MovableModel movable || movable.Locked) continue; + var position = movable.Position; if (Diagram.Options.GridSnapToCenter && movable is NodeModel node) { From 15920a4b9c9aff63cc21f1ec8bed44e066580283 Mon Sep 17 00:00:00 2001 From: Jeremy Vance <49650352+240026763@users.noreply.github.com> Date: Thu, 15 Sep 2022 16:57:44 -0400 Subject: [PATCH 104/193] Add Toggle Snap To Center to Snap to Grid Demo --- samples/SharedDemo/Demos/SnapToGrid.razor | 10 ++++++++-- samples/SharedDemo/Demos/SnapToGrid.razor.cs | 2 +- samples/SharedDemo/Layouts/DemoLayout.razor | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/samples/SharedDemo/Demos/SnapToGrid.razor b/samples/SharedDemo/Demos/SnapToGrid.razor index 1d449b21d..9a2fc71d7 100644 --- a/samples/SharedDemo/Demos/SnapToGrid.razor +++ b/samples/SharedDemo/Demos/SnapToGrid.razor @@ -14,12 +14,18 @@ LayoutData.DataChanged(); } } +
    +
    + + +
    +
    - - + + diff --git a/samples/SharedDemo/Demos/SnapToGrid.razor.cs b/samples/SharedDemo/Demos/SnapToGrid.razor.cs index 2575a67b6..aacbffb2e 100644 --- a/samples/SharedDemo/Demos/SnapToGrid.razor.cs +++ b/samples/SharedDemo/Demos/SnapToGrid.razor.cs @@ -10,7 +10,7 @@ public class SnapToGridComponent : ComponentBase { protected readonly BlazorDiagram BlazorDiagram = new(new BlazorDiagramOptions { - GridSize = 50 + GridSize = 75 }); protected override void OnInitialized() diff --git a/samples/SharedDemo/Layouts/DemoLayout.razor b/samples/SharedDemo/Layouts/DemoLayout.razor index 97e016fd9..4366580a7 100644 --- a/samples/SharedDemo/Layouts/DemoLayout.razor +++ b/samples/SharedDemo/Layouts/DemoLayout.razor @@ -21,7 +21,7 @@ @if (LayoutData.Info != null) { -
    diff --git a/samples/SharedDemo/Demos/CustomLink/ThickLinkWidget.razor b/samples/SharedDemo/Demos/CustomLink/ThickLinkWidget.razor index 2f79d579a..0a0e68fb3 100644 --- a/samples/SharedDemo/Demos/CustomLink/ThickLinkWidget.razor +++ b/samples/SharedDemo/Demos/CustomLink/ThickLinkWidget.razor @@ -7,7 +7,7 @@ Console.WriteLine(Link.Source); Console.WriteLine(Link.Target); var sourcePosition = Link.Source.GetPosition(Link); - var targetPosition = Link.Target is null ? Link.OnGoingPosition : Link.Target.GetPosition(Link); + var targetPosition = Link.Target.GetPosition(Link); if (sourcePosition is null || targetPosition is null) return; diff --git a/samples/SharedDemo/Demos/DynamicInsertions.razor.cs b/samples/SharedDemo/Demos/DynamicInsertions.razor.cs index d80abe22c..8c9e5d39e 100644 --- a/samples/SharedDemo/Demos/DynamicInsertions.razor.cs +++ b/samples/SharedDemo/Demos/DynamicInsertions.razor.cs @@ -21,12 +21,12 @@ protected override void OnInitialized() BlazorDiagram.Nodes.Add(new NodeModel(new Point(300, 50))); BlazorDiagram.Nodes.Add(new NodeModel(new Point(300, 400))); - BlazorDiagram.Options.Links.Factory = (d, sp) => + BlazorDiagram.Options.Links.Factory = (d, s, ta) => { - var link = new LinkModel(new SinglePortAnchor(sp) + var link = new LinkModel(new SinglePortAnchor(s as PortModel) { UseShapeAndAlignment = false - }) + }, ta) { SourceMarker = LinkMarker.Arrow }; diff --git a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs index 249d28372..8cda7518d 100644 --- a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs +++ b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs @@ -15,9 +15,6 @@ public static void ReconnectLinksToClosestPorts(this Diagram diagram) foreach (var link in diagram.Links.ToArray()) { - if (link.Target == null) - continue; - if (link.Source is not SinglePortAnchor spa1 || link.Target is not SinglePortAnchor spa2) continue; diff --git a/src/Blazor.Diagrams.Core/Anchors/PositionAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/PositionAnchor.cs new file mode 100644 index 000000000..ac128dc55 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Anchors/PositionAnchor.cs @@ -0,0 +1,21 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Anchors +{ + public class PositionAnchor : Anchor + { + private Point _position; + + public PositionAnchor(Point position) : base(null) + { + _position = position; + } + + public void SetPosition(Point position) => _position = position; + + public override Point? GetPlainPosition() => _position; + + public override Point? GetPosition(BaseLinkModel link, Point[] route) => _position; + } +} diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 3d629940e..a1fe1b250 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -1,5 +1,4 @@ -using System; -using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Events; using System.Linq; @@ -10,6 +9,7 @@ namespace Blazor.Diagrams.Core.Behaviors public class DragNewLinkBehavior : Behavior { private BaseLinkModel? _ongoingLink; + private PositionAnchor? _targetPositionAnchor; public DragNewLinkBehavior(Diagram diagram) : base(diagram) { @@ -18,22 +18,16 @@ public DragNewLinkBehavior(Diagram diagram) : base(diagram) Diagram.PointerUp += OnPointerUp; } - public void StartFrom(Anchor source, double clientX, double clientY) + public void StartFrom(ILinkable source, double clientX, double clientY) { if (_ongoingLink != null) return; - //_ongoingLink = Diagram.Options.Links.Factory(Diagram, port); - StartFrom(new LinkModel(source), clientX, clientY); - } - - public void StartFrom(BaseLinkModel link, double clientX, double clientY) - { - if (_ongoingLink != null) + _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5)); + _ongoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor); + if (_ongoingLink == null) return; - _ongoingLink = link; - _ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5); Diagram.Links.Add(_ongoingLink); } @@ -42,16 +36,20 @@ 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; - _ongoingLink = Diagram.Options.Links.Factory(Diagram, port); + _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5)); + _ongoingLink = Diagram.Options.Links.Factory(Diagram, port, _targetPositionAnchor); if (_ongoingLink == null) return; - _ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5); + _ongoingLink.SetTarget(_targetPositionAnchor); Diagram.Links.Add(_ongoingLink); } } @@ -61,14 +59,14 @@ private void OnPointerMove(Model? model, MouseEventArgs e) if (_ongoingLink == null || model != null) return; - _ongoingLink.OnGoingPosition = Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5); + _targetPositionAnchor!.SetPosition(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5)); if (Diagram.Options.Links.EnableSnapping) { var nearPort = FindNearPortToAttachTo(); - if (nearPort != null || _ongoingLink.Target != null) + if (nearPort != null || _ongoingLink.Target is not PositionAnchor) { - _ongoingLink.SetTarget(nearPort is null ? null : new SinglePortAnchor(nearPort)); + _ongoingLink.SetTarget(nearPort is null ? _targetPositionAnchor : new SinglePortAnchor(nearPort)); } } @@ -86,10 +84,9 @@ private void OnPointerUp(Model? model, MouseEventArgs e) return; } - if (model is ILinkable linkable && _ongoingLink.Source.Model.CanAttachTo(linkable)) + if (model is ILinkable linkable && (_ongoingLink.Source.Model == null || _ongoingLink.Source.Model.CanAttachTo(linkable))) { var targetAnchor = Diagram.Options.Links.TargetAnchorFactory(Diagram, _ongoingLink, linkable); - _ongoingLink.OnGoingPosition = null; _ongoingLink.SetTarget(targetAnchor); _ongoingLink.Refresh(); } @@ -103,10 +100,11 @@ private void OnPointerUp(Model? model, MouseEventArgs e) private PortModel? FindNearPortToAttachTo() { + var ongoingPosition = _targetPositionAnchor!.GetPosition(_ongoingLink!)!; foreach (var port in Diagram.Nodes.SelectMany(n => n.Ports)) { - if (_ongoingLink!.OnGoingPosition!.DistanceTo(port.MiddlePosition) < Diagram.Options.Links.SnappingRadius - && _ongoingLink.Source.Model.CanAttachTo(port)) + if (ongoingPosition.DistanceTo(port.MiddlePosition) < Diagram.Options.Links.SnappingRadius + && (_ongoingLink!.Source.Model == null || _ongoingLink.Source.Model.CanAttachTo(port))) { return port; } diff --git a/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs index 7b6532476..bb4e6498b 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs @@ -34,8 +34,7 @@ public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEve if (behavior == null) throw new DiagramsException($"DragNewLinkBehavior was not found"); - // Todo: use factory from options - behavior.StartFrom(new ShapeIntersectionAnchor(node), e.ClientX, e.ClientY); + behavior.StartFrom(node, e.ClientX, e.ClientY); return ValueTask.CompletedTask; } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Delegates.cs b/src/Blazor.Diagrams.Core/Delegates.cs index 20a0c1adf..f0cac9bb8 100644 --- a/src/Blazor.Diagrams.Core/Delegates.cs +++ b/src/Blazor.Diagrams.Core/Delegates.cs @@ -9,7 +9,7 @@ namespace Blazor.Diagrams.Core public delegate PathGeneratorResult PathGenerator(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target); - public delegate BaseLinkModel? LinkFactory(Diagram diagram, PortModel sourcePort); + public delegate BaseLinkModel? LinkFactory(Diagram diagram, ILinkable source, Anchor targetAnchor); public delegate Anchor AnchorFactory(Diagram diagram, BaseLinkModel link, ILinkable model); diff --git a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs index d5cb65e78..f7fa012ba 100644 --- a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs @@ -11,7 +11,7 @@ protected override void OnItemAdded(BaseLinkModel link) { link.Diagram = Diagram; HandleAnchor(link, link.Source, true); - if (link.Target != null) HandleAnchor(link, link.Target, true); + HandleAnchor(link, link.Target, true); link.Refresh(); link.SourceChanged += OnLinkSourceChanged; @@ -22,7 +22,7 @@ protected override void OnItemRemoved(BaseLinkModel link) { link.Diagram = null; HandleAnchor(link, link.Source, false); - if (link.Target != null) HandleAnchor(link, link.Target, false); + HandleAnchor(link, link.Target, false); link.Refresh(); link.SourceChanged -= OnLinkSourceChanged; @@ -37,10 +37,10 @@ private static void OnLinkSourceChanged(BaseLinkModel link, Anchor old, Anchor @ HandleAnchor(link, @new, add: true); } - private static void OnLinkTargetChanged(BaseLinkModel link, Anchor? old, Anchor? @new) + private static void OnLinkTargetChanged(BaseLinkModel link, Anchor old, Anchor @new) { - if (old != null) HandleAnchor(link, old, add: false); - if (@new != null) HandleAnchor(link, @new, add: true); + HandleAnchor(link, old, add: false); + HandleAnchor(link, @new, add: true); } private static void HandleAnchor(BaseLinkModel link, Anchor anchor, bool add) diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index c77301449..f6273361f 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -11,27 +11,26 @@ public abstract class BaseLinkModel : SelectableModel, IHasBounds, ILinkable private readonly List _links = new(); public event Action? SourceChanged; - public event Action? TargetChanged; + public event Action? TargetChanged; - protected BaseLinkModel(Anchor source, Anchor? target = null) + protected BaseLinkModel(Anchor source, Anchor target) { Source = source; Target = target; } - protected BaseLinkModel(string id, Anchor source, Anchor? target = null) : base(id) + protected BaseLinkModel(string id, Anchor source, Anchor target) : base(id) { Source = source; Target = target; } public Anchor Source { get; private set; } - public Anchor? Target { get; private set; } + public Anchor Target { get; private set; } public Diagram? Diagram { get; internal set; } public PathGeneratorResult GeneratedPathResult { get; private set; } = PathGeneratorResult.Empty; public SvgPath[] Paths => GeneratedPathResult.Paths; - public bool IsAttached => Target != null; - public Point? OnGoingPosition { get; set; } + public bool IsAttached => Source is not PositionAnchor && Target is not PositionAnchor; public Router? Router { get; set; } public PathGenerator? PathGenerator { get; set; } public LinkMarker? SourceMarker { get; set; } @@ -67,7 +66,7 @@ public void SetSource(Anchor anchor) SourceChanged?.Invoke(this, old, Source); } - public void SetTarget(Anchor? anchor) + public void SetTarget(Anchor anchor) { if (Target == anchor) return; @@ -101,8 +100,6 @@ public void SetTarget(Anchor? anchor) public bool CanAttachTo(ILinkable other) => true; - public Point? GetTargetPosition() => Target?.GetPosition(this) ?? OnGoingPosition; - private void GeneratePath() { if (Diagram != null) @@ -111,7 +108,7 @@ private void GeneratePath() var pathGenerator = PathGenerator ?? Diagram.Options.Links.DefaultPathGenerator; var route = router(Diagram, this); var source = Source.GetPosition(this, route); - var target = Target is null ? OnGoingPosition : Target.GetPosition(this, route); + var target = Target.GetPosition(this, route); if (source != null && target != null) { GeneratedPathResult = pathGenerator(Diagram, this, route, source, target); diff --git a/src/Blazor.Diagrams.Core/Models/LinkModel.cs b/src/Blazor.Diagrams.Core/Models/LinkModel.cs index 9d134a72b..e8814d80b 100644 --- a/src/Blazor.Diagrams.Core/Models/LinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/LinkModel.cs @@ -5,21 +5,21 @@ namespace Blazor.Diagrams.Core.Models { public class LinkModel : BaseLinkModel { - public LinkModel(Anchor source, Anchor? target = null) : base(source, target) { } + public LinkModel(Anchor source, Anchor target) : base(source, target) { } - public LinkModel(string id, Anchor source, Anchor? target = null) : base(id, source, target) { } + public LinkModel(string id, Anchor source, Anchor target) : base(id, source, target) { } - public LinkModel(PortModel sourcePort, PortModel? targetPort = null) - : base(new SinglePortAnchor(sourcePort), targetPort is null ? null : new SinglePortAnchor(targetPort)) { } + public LinkModel(PortModel sourcePort, PortModel targetPort) + : base(new SinglePortAnchor(sourcePort), new SinglePortAnchor(targetPort)) { } - public LinkModel(NodeModel sourceNode, NodeModel? targetNode) - : base(new ShapeIntersectionAnchor(sourceNode), targetNode is null ? null : new ShapeIntersectionAnchor(targetNode)) { } + public LinkModel(NodeModel sourceNode, NodeModel targetNode) + : base(new ShapeIntersectionAnchor(sourceNode), new ShapeIntersectionAnchor(targetNode)) { } - public LinkModel(string id, PortModel sourcePort, PortModel? targetPort = null) - : base(id, new SinglePortAnchor(sourcePort), targetPort is null ? null : new SinglePortAnchor(targetPort)) { } + public LinkModel(string id, PortModel sourcePort, PortModel targetPort) + : base(id, new SinglePortAnchor(sourcePort), new SinglePortAnchor(targetPort)) { } - public LinkModel(string id, NodeModel sourceNode, NodeModel? targetNode) - : base(id, new ShapeIntersectionAnchor(sourceNode), targetNode is null ? null : new ShapeIntersectionAnchor(targetNode)) { } + public LinkModel(string id, NodeModel sourceNode, NodeModel targetNode) + : base(id, new ShapeIntersectionAnchor(sourceNode), new ShapeIntersectionAnchor(targetNode)) { } public string? Color { get; set; } public string? SelectedColor { get; set; } diff --git a/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs index 984f13468..9132348a5 100644 --- a/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs +++ b/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs @@ -11,7 +11,7 @@ public class DiagramLinkOptions public Router DefaultRouter { get; set; } = Routers.Normal; public PathGenerator DefaultPathGenerator { get; set; } = PathGenerators.Smooth; public bool EnableSnapping { get; set; } = false; - public bool RequireTarget { get; set; } = false; + public bool RequireTarget { get; set; } = true; public double SnappingRadius { @@ -25,8 +25,17 @@ public double SnappingRadius } } - public LinkFactory Factory { get; set; } = (diagram, sourcePort) => new LinkModel(sourcePort); - + public LinkFactory Factory { get; set; } = (diagram, source, targetAnchor) => + { + Anchor sourceAnchor = source switch + { + NodeModel node => new ShapeIntersectionAnchor(node), + PortModel port => new SinglePortAnchor(port), + _ => throw new NotImplementedException() + }; + return new LinkModel(sourceAnchor, targetAnchor); + }; + public AnchorFactory TargetAnchorFactory { get; set; } = (diagram, link, model) => { return model switch diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs index 5d1599de2..f32e97506 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs @@ -78,9 +78,9 @@ private static Point[] GetRouteWithCurvePoints(BaseLinkModel link, Point[] route return new[] { route[0], curvePointA, curvePointB, route[1] }; } - private static Point GetCurvePoint(Point[] route, Anchor? anchor, double pX, double pY, double cX, double cY, bool first) + private static Point GetCurvePoint(Point[] route, Anchor anchor, double pX, double pY, double cX, double cY, bool first) { - if (anchor is null) + if (anchor is PositionAnchor) return new Point(cX, cY); if (anchor is SinglePortAnchor spa) diff --git a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs index bd2145753..6b4ee3f27 100644 --- a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs +++ b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs @@ -18,12 +18,10 @@ public static Point[] Orthogonal(Diagram _, BaseLinkModel link) if (link.Source is not SinglePortAnchor spa1) throw new Exception("Orthogonal router doesn't work with port-less links yet"); - if (link.Target is not null && link.Target is not SinglePortAnchor) + if (link.Target is not SinglePortAnchor targetAnchor) throw new Exception("Orthogonal router doesn't work with port-less links yet"); var sourcePort = spa1.Port; - var targetAnchor = (link.Target as SinglePortAnchor); - if (targetAnchor == null || sourcePort.Parent.Size == null || targetAnchor.Port.Parent.Size == null) return Normal(_, link); diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/ShapeIntersectionAnchorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/ShapeIntersectionAnchorTests.cs index 4a81dda26..71726a926 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Anchors/ShapeIntersectionAnchorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/ShapeIntersectionAnchorTests.cs @@ -37,7 +37,7 @@ public void GetPosition_ShouldReturnNull_WhenNodeSizeIsNull() // Arrange var node = new NodeModel(new Point(60, 60)); var anchor = new ShapeIntersectionAnchor(node); - var link = new LinkModel(anchor); + var link = new LinkModel(anchor, new PositionAnchor(Point.Zero)); // Act var position = anchor.GetPosition(link); @@ -59,7 +59,7 @@ public void GetPosition_ShouldUseRouteToFindOtherPositionForIntersection_WhenSou Position = new Point(60, 60) }; var source = new ShapeIntersectionAnchor(node); - var link = new LinkModel(source); + var link = new LinkModel(source, new PositionAnchor(Point.Zero)); var route = new[] { new Point(170, 100), new Point(180, 110) }; // Act diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/SinglePortAnchorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/SinglePortAnchorTests.cs index 93bcb5752..e01d746f7 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Anchors/SinglePortAnchorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/SinglePortAnchorTests.cs @@ -41,7 +41,7 @@ public void GetPosition_ShouldReturnNull_WhenPortNotInitialized() Position = new Point(100, 50) }; var anchor = new SinglePortAnchor(port); - var link = new LinkModel(anchor); + var link = new LinkModel(anchor, new PositionAnchor(Point.Zero)); // Act var position = anchor.GetPosition(link); @@ -65,7 +65,7 @@ public void GetPosition_ShouldReturnMiddlePosition_WhenMiddleIfNoMarker() { MiddleIfNoMarker = true }; - var link = new LinkModel(anchor); + var link = new LinkModel(anchor, new PositionAnchor(Point.Zero)); // Act var position = anchor.GetPosition(link)!; @@ -100,7 +100,7 @@ public void GetPosition_ShouldReturnAlignmentBasedPosition_WhenUseShapeAndAlignm MiddleIfNoMarker = false, UseShapeAndAlignment = false }; - var link = new LinkModel(anchor); + var link = new LinkModel(anchor, new PositionAnchor(Point.Zero)); // Act var position = anchor.GetPosition(link)!; @@ -135,7 +135,7 @@ public void GetPosition_ShouldUsePointAtAngle_WhenUseShapeAndAlignmentIsTrue(Por MiddleIfNoMarker = false, UseShapeAndAlignment = true }; - var link = new LinkModel(anchor); + var link = new LinkModel(anchor, new PositionAnchor(Point.Zero)); // Act var position = anchor.GetPosition(link)!; diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index 99b8eeba5..01134f6ef 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -32,11 +32,10 @@ public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnP var link = diagram.Links.Single(); var source = link.Source as SinglePortAnchor; source.Should().NotBeNull(); - link.Target.Should().BeNull(); source!.Port.Should().BeSameAs(port); - link.OnGoingPosition.Should().NotBeNull(); - link.OnGoingPosition!.X.Should().Be(95); - link.OnGoingPosition.Y.Should().Be(95); + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().Be(95); + ongoingPosition.Y.Should().Be(95); } [Fact] @@ -46,10 +45,10 @@ public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() var diagram = new TestDiagram(); diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); var factoryCalled = false; - diagram.Options.Links.Factory = (d, sp) => + diagram.Options.Links.Factory = (d, s, ta) => { factoryCalled = true; - return new LinkModel(sp); + 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) @@ -68,11 +67,10 @@ public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() var link = diagram.Links.Single(); var source = link.Source as SinglePortAnchor; source.Should().NotBeNull(); - link.Target.Should().BeNull(); source!.Port.Should().BeSameAs(port); - link.OnGoingPosition.Should().NotBeNull(); - link.OnGoingPosition!.X.Should().Be(95); - link.OnGoingPosition.Y.Should().Be(95); + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().Be(95); + ongoingPosition.Y.Should().Be(95); } [Fact] @@ -100,8 +98,9 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() // Assert var source = link.Source as SinglePortAnchor; - link.OnGoingPosition!.X.Should().Be(145); - link.OnGoingPosition.Y.Should().Be(145); + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().Be(145); + ongoingPosition.Y.Should().Be(145); linkRefreshed.Should().BeTrue(); } @@ -131,8 +130,9 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoom // Assert var source = link.Source as SinglePortAnchor; - link.OnGoingPosition!.X.Should().BeApproximately(101.6, 0.1); - link.OnGoingPosition.Y.Should().BeApproximately(101.6, 0.1); + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().BeApproximately(101.6, 0.1); + ongoingPosition.Y.Should().BeApproximately(101.6, 0.1); linkRefreshed.Should().BeTrue(); } @@ -208,7 +208,7 @@ public void Behavior_ShouldNotSnapToPort_WhenSnappingIsEnabledAndPortIsNotInRadi // Assert var link = diagram.Links.Single(); - link.Target.Should().BeNull(); + link.Target.Should().BeOfType(); } [Fact] @@ -334,7 +334,6 @@ public void Behavior_ShouldSetTarget_WhenMouseUp() // Assert var link = diagram.Links.Single(); - link.OnGoingPosition.Should().BeNull(); var target = link.Target as SinglePortAnchor; target.Should().NotBeNull(); target!.Port.Should().BeSameAs(port2); @@ -346,7 +345,7 @@ public void Behavior_ShouldNotCreateOngoingLink_WhenFactoryReturnsNull() { // Arrange var diagram = new TestDiagram(); - diagram.Options.Links.Factory = (d, sp) => null; + 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)); diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs index 1f35b40fd..d9596fc8e 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs @@ -1,4 +1,5 @@ using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using FluentAssertions; @@ -45,7 +46,7 @@ public void SetSource_ShouldChangePropertiesAndTriggerEvent() public void SetTarget_ShouldChangePropertiesAndTriggerEvent() { // Arrange - var link = new LinkModel(sourcePort: new PortModel(null), targetPort: null); + var link = new LinkModel(new SinglePortAnchor(null), new PositionAnchor(Point.Zero)); var parent = new NodeModel(); var port = new PortModel(parent); var tp = new SinglePortAnchor(port); @@ -68,7 +69,7 @@ public void SetTarget_ShouldChangePropertiesAndTriggerEvent() // Assert eventsTriggered.Should().Be(1); link.Target.Should().BeSameAs(tp); - oldTp.Should().BeNull(); + oldTp.Should().BeOfType(); newTp.Should().BeSameAs(tp); linkInstance.Should().BeSameAs(link); link.Target!.Model.Should().BeSameAs(port); From 7a2d5849002c56ca2b35efbe1745f295e87e2169 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 16 Sep 2022 13:32:14 +0100 Subject: [PATCH 110/193] Fix deleting link not deleting other links attached to it --- src/Blazor.Diagrams.Core/Layers/LinkLayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs index f7fa012ba..8d09f786b 100644 --- a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs @@ -1,5 +1,6 @@ using Blazor.Diagrams.Core.Anchors; using Blazor.Diagrams.Core.Models.Base; +using System.Linq; namespace Blazor.Diagrams.Core.Layers { @@ -29,6 +30,7 @@ protected override void OnItemRemoved(BaseLinkModel link) link.TargetChanged -= OnLinkTargetChanged; Diagram.Controls.RemoveFor(link); + Remove(link.Links.ToList()); } private static void OnLinkSourceChanged(BaseLinkModel link, Anchor old, Anchor @new) From 71567e7bedc61d5263e01b2fbbe0a7da5037094b Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 16 Sep 2022 20:42:53 +0100 Subject: [PATCH 111/193] Add ArrowHeadControl to control Source/Target on the fly --- samples/SharedDemo/Demos/DevTests.razor | 23 +++++++- .../Behaviors/DragNewLinkBehavior.cs | 14 +++++ .../Controls/Default/ArrowHeadControl.cs | 59 +++++++++++++++++++ src/Blazor.Diagrams/BlazorDiagram.cs | 3 +- .../Controls/ArrowHeadControlWidget.razor | 12 ++++ 5 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 src/Blazor.Diagrams.Core/Controls/Default/ArrowHeadControl.cs create mode 100644 src/Blazor.Diagrams/Components/Controls/ArrowHeadControlWidget.razor diff --git a/samples/SharedDemo/Demos/DevTests.razor b/samples/SharedDemo/Demos/DevTests.razor index 505a78011..1e205f499 100644 --- a/samples/SharedDemo/Demos/DevTests.razor +++ b/samples/SharedDemo/Demos/DevTests.razor @@ -38,14 +38,14 @@ var link1 = new LinkModel(new DynamicAnchor(node1, providers1), new DynamicAnchor(node2, providers2)) { - PathGenerator = PathGenerators.Straight, + PathGenerator = PathGenerators.Smooth, SourceMarker = LinkMarker.Arrow, TargetMarker = LinkMarker.Arrow }; _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); _blazorDiagram.Links.Add(link1); - _blazorDiagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node3), new LinkAnchor(link1, 0.5)) + var link2 = _blazorDiagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node3), new LinkAnchor(link1, 0.5)) { PathGenerator = PathGenerators.Straight, SourceMarker = LinkMarker.Arrow, @@ -64,7 +64,26 @@ _blazorDiagram.Controls.AddFor(link1) .Add(new RemoveControl(new LinkPathPositionProvider(0.1))) + .Add(new ArrowHeadControl(true, LinkMarker.NewArrow(30, 30))) + .Add(new ArrowHeadControl(false, LinkMarker.NewArrow(30, 30))) + .Add(new BoundaryControl()); + + _blazorDiagram.Controls.AddFor(link2) + .Add(new RemoveControl(new LinkPathPositionProvider(0.8))) + .Add(new ArrowHeadControl(true)) + .Add(new ArrowHeadControl(false)) .Add(new BoundaryControl()); + + _blazorDiagram.Options.Links.RequireTarget = false; + + _blazorDiagram.Links.Added += OnLinkAdded; + } + + private void OnLinkAdded(Blazor.Diagrams.Core.Models.Base.BaseLinkModel link) + { + _blazorDiagram.Controls.AddFor(link) + .Add(new ArrowHeadControl(true)) + .Add(new ArrowHeadControl(false)); } private static NodeModel NewNode(double x, double y) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index a1fe1b250..4171853ef 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -31,6 +31,18 @@ public void StartFrom(ILinkable source, double clientX, double clientY) Diagram.Links.Add(_ongoingLink); } + public void StartFrom(BaseLinkModel link, double clientX, double clientY) + { + if (_ongoingLink != null) + return; + + _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5)); + _ongoingLink = link; + _ongoingLink.SetTarget(_targetPositionAnchor); + _ongoingLink.Refresh(); + _ongoingLink.RefreshLinks(); + } + private void OnPointerDown(Model? model, MouseEventArgs e) { if (e.Button != (int)MouseEventButton.Left) @@ -71,6 +83,7 @@ private void OnPointerMove(Model? model, MouseEventArgs e) } _ongoingLink.Refresh(); + _ongoingLink.RefreshLinks(); } private void OnPointerUp(Model? model, MouseEventArgs e) @@ -89,6 +102,7 @@ private void OnPointerUp(Model? model, MouseEventArgs e) var targetAnchor = Diagram.Options.Links.TargetAnchorFactory(Diagram, _ongoingLink, linkable); _ongoingLink.SetTarget(targetAnchor); _ongoingLink.Refresh(); + _ongoingLink.RefreshLinks(); } else if (Diagram.Options.Links.RequireTarget) { diff --git a/src/Blazor.Diagrams.Core/Controls/Default/ArrowHeadControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ArrowHeadControl.cs new file mode 100644 index 000000000..0fccc3911 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Controls/Default/ArrowHeadControl.cs @@ -0,0 +1,59 @@ +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Positions; +using System; +using System.Threading.Tasks; + +namespace Blazor.Diagrams.Core.Controls.Default +{ + public class ArrowHeadControl : ExecutableControl + { + public ArrowHeadControl(bool source, LinkMarker? marker = null) + { + Source = source; + Marker = marker ?? LinkMarker.NewArrow(20, 20); + } + + public bool Source { get; } + public LinkMarker Marker { get; } + public double Angle { get; private set; } + + public override Point? GetPosition(Model model) + { + if (model is not BaseLinkModel link) + throw new DiagramsException("ArrowHeadControl only works for models of type BaseLinkModel"); + + var dist = Source ? Marker.Width - (link.SourceMarker?.Width ?? 0) : (link.TargetMarker?.Width ?? 0) - Marker.Width; + var pp = new LinkPathPositionProvider(dist); + var p1 = pp.GetPosition(link); + if (p1 is not null) + { + var p2 = Source ? link.Source.GetPosition(link) : link.Target.GetPosition(link); + if (p2 is not null) + { + Angle = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X) * 180 / Math.PI; + } + } + + return p1; + } + + public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) + { + if (model is not BaseLinkModel link) + throw new DiagramsException("ArrowHeadControl only works for models of type BaseLinkModel"); + + var dnlb = diagram.GetBehavior()!; + if (Source) + { + link.SetSource(link.Target); + } + + dnlb.StartFrom(link, e.ClientX, e.ClientY); + return ValueTask.CompletedTask; + } + } +} diff --git a/src/Blazor.Diagrams/BlazorDiagram.cs b/src/Blazor.Diagrams/BlazorDiagram.cs index 3b0a6c9bd..ab61d98a8 100644 --- a/src/Blazor.Diagrams/BlazorDiagram.cs +++ b/src/Blazor.Diagrams/BlazorDiagram.cs @@ -18,7 +18,8 @@ public BlazorDiagram(BlazorDiagramOptions? options = null) { [typeof(RemoveControl)] = typeof(RemoveControlWidget), [typeof(BoundaryControl)] = typeof(BoundaryControlWidget), - [typeof(DragNewLinkControl)] = typeof(DragNewLinkControlWidget) + [typeof(DragNewLinkControl)] = typeof(DragNewLinkControlWidget), + [typeof(ArrowHeadControl)] = typeof(ArrowHeadControlWidget) }; Options = options ?? new BlazorDiagramOptions(); diff --git a/src/Blazor.Diagrams/Components/Controls/ArrowHeadControlWidget.razor b/src/Blazor.Diagrams/Components/Controls/ArrowHeadControlWidget.razor new file mode 100644 index 000000000..5a6280cea --- /dev/null +++ b/src/Blazor.Diagrams/Components/Controls/ArrowHeadControlWidget.razor @@ -0,0 +1,12 @@ + + + + +@code +{ + [Parameter] + public ArrowHeadControl Control { get; set; } = null!; + + [Parameter] + public BaseLinkModel Model { get; set; } = null!; +} \ No newline at end of file From da6facc8039e6e00e94e99d6306f0241d9c27e9b Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 17 Sep 2022 14:50:44 +0100 Subject: [PATCH 112/193] Add attached class to attached links --- src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs index 0d1d21ff2..3e33e9428 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs @@ -1,4 +1,5 @@ using System; +using System.Text; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; @@ -44,9 +45,13 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) return; var componentType = BlazorDiagram.GetComponent(Link) ?? typeof(LinkWidget); + var classes = new StringBuilder() + .Append("link") + .AppendIf(" attached", Link.IsAttached) + .ToString(); builder.OpenElement(0, "g"); - builder.AddAttribute(1, "class", "link"); + builder.AddAttribute(1, "class", classes); builder.AddAttribute(2, "data-link-id", Link.Id); builder.AddAttribute(3, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); From 11d292662ca3168b1661dae593e33359267887b4 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 18 Sep 2022 16:46:39 +0100 Subject: [PATCH 113/193] Move DynamicAnchor back to Anchors namespace and seal all Anchor classes --- .../Anchors/{Dynamic => }/DynamicAnchor.cs | 6 ++---- src/Blazor.Diagrams.Core/Anchors/PositionAnchor.cs | 2 +- src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs | 2 +- src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs | 2 +- .../PathGenerators/PathGenerators.Smooth.cs | 1 - 5 files changed, 5 insertions(+), 8 deletions(-) rename src/Blazor.Diagrams.Core/Anchors/{Dynamic => }/DynamicAnchor.cs (89%) diff --git a/src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs similarity index 89% rename from src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs rename to src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs index 4f55851c9..ef1f8a54c 100644 --- a/src/Blazor.Diagrams.Core/Anchors/Dynamic/DynamicAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs @@ -5,11 +5,9 @@ using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Positions; -namespace Blazor.Diagrams.Core.Anchors.Dynamic +namespace Blazor.Diagrams.Core.Anchors { - // Figure out a better name - // Generic? - public class DynamicAnchor : Anchor + public sealed class DynamicAnchor : Anchor { public DynamicAnchor(NodeModel model, IPositionProvider[] providers) : base(model) { diff --git a/src/Blazor.Diagrams.Core/Anchors/PositionAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/PositionAnchor.cs index ac128dc55..09113bee4 100644 --- a/src/Blazor.Diagrams.Core/Anchors/PositionAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/PositionAnchor.cs @@ -3,7 +3,7 @@ namespace Blazor.Diagrams.Core.Anchors { - public class PositionAnchor : Anchor + public sealed class PositionAnchor : Anchor { private Point _position; diff --git a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs index d24f8ee7c..bdd17bbfd 100644 --- a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs @@ -4,7 +4,7 @@ namespace Blazor.Diagrams.Core.Anchors { - public class ShapeIntersectionAnchor : Anchor + public sealed class ShapeIntersectionAnchor : Anchor { public ShapeIntersectionAnchor(NodeModel model) : base(model) { diff --git a/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs index 8d51fa573..ca2cc2eaa 100644 --- a/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs @@ -4,7 +4,7 @@ namespace Blazor.Diagrams.Core.Anchors { - public class SinglePortAnchor : Anchor + public sealed class SinglePortAnchor : Anchor { public SinglePortAnchor(PortModel port) : base(port) { diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs index f32e97506..4db5942f6 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs @@ -3,7 +3,6 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using System; -using Blazor.Diagrams.Core.Anchors.Dynamic; using SvgPathProperties; namespace Blazor.Diagrams.Core From f5dc8f05258cf5748413cbecef3f3f553f02bfdc Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 19 Sep 2022 18:52:07 +0100 Subject: [PATCH 114/193] Add Batch call when a node is removed --- src/Blazor.Diagrams.Core/Layers/NodeLayer.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs index 9ab67021f..2a3aa297d 100644 --- a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs @@ -14,11 +14,13 @@ public override void Remove(NodeModel node) protected override void OnItemRemoved(NodeModel node) { - // Todo: batch - Diagram.Links.Remove(node.PortLinks.ToList()); - Diagram.Links.Remove(node.Links.ToList()); - node.Group?.RemoveChild(node); - Diagram.Controls.RemoveFor(node); + Diagram.Batch(() => + { + Diagram.Links.Remove(node.PortLinks.ToList()); + Diagram.Links.Remove(node.Links.ToList()); + node.Group?.RemoveChild(node); + Diagram.Controls.RemoveFor(node); + }); } } } From 978e5e77e768a81b47c7ce3c713699b894ea2f8a Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 19 Sep 2022 19:03:22 +0100 Subject: [PATCH 115/193] Remove delete namespace --- samples/SharedDemo/Demos/DevTests.razor | 1 - tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs | 1 - 2 files changed, 2 deletions(-) diff --git a/samples/SharedDemo/Demos/DevTests.razor b/samples/SharedDemo/Demos/DevTests.razor index 1e205f499..725004669 100644 --- a/samples/SharedDemo/Demos/DevTests.razor +++ b/samples/SharedDemo/Demos/DevTests.razor @@ -2,7 +2,6 @@ @layout DemoLayout @using Blazor.Diagrams @using Blazor.Diagrams.Core.Anchors -@using Blazor.Diagrams.Core.Anchors.Dynamic @using Blazor.Diagrams.Core.Controls @using Blazor.Diagrams.Core.Positions @using Blazor.Diagrams.Core.Controls.Default diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs index 47f25b317..7e13fb205 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs @@ -1,5 +1,4 @@ using Blazor.Diagrams.Core.Anchors; -using Blazor.Diagrams.Core.Anchors.Dynamic; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Positions; From c37e43683ba43d8167afbf0e62923c1cd1ffa9fb Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 19 Sep 2022 19:33:40 +0100 Subject: [PATCH 116/193] Update Versions & CHANGELOG --- CHANGELOG.md | 29 +++++++++++++++++++ .../Blazor.Diagrams.Algorithms.csproj | 2 +- .../Blazor.Diagrams.Core.csproj | 2 +- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 2 +- 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04ee606eb..c8b25da4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,35 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Diagrams (3.0.0-beta.3) - 2022-09-18 + +### Added + +- Support for `LinkFactory` to return null in order to not create an ongoing link +- Support for free links (no source/target required) +- `PositionAnchor` which reprensents a simple plain position (mutable) +- `ArrowHeadControl` to control a link's Source/Target on the fly +- `attached` css class to attached links + +### Changed + +- Replace `OngoingPosition` with the new `PositionAnchor` + - `BaseLinkModel.Target` will never be null anymore. An ongoing link will have a position anchor as the target +- `Links.Factory` signature now takes the diagram, source (model) and the target anchor +- Move `DynamicAnchor` back to `Anchors` namespace and seal all `Anchor` classes + +### Fixed + +- Links attached to links not refreshing when the others are +- `LinkPathPositionProvider` not working with maxlength ratios +- Deleting a link not deleting the links attached to it + +### Removed + +- `PositionProvider` argument from `ExecutableControl` for more freedom +- `Id` and `Refresh` from `ILinkable` +- Unused `Offset` from `Anchor` and make `Model` nullable + ## Diagrams (3.0.0-beta.2) - 2022-09-11 ### Added diff --git a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj index 51cb42316..dbb79fba4 100644 --- a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj +++ b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj @@ -10,7 +10,7 @@ 3.0.0 3.0.0 https://github.com/zHaytam/Blazor.Diagrams - 3.0.0-beta.2 + 3.0.0-beta.3 Z.Blazor.Diagrams.Algorithms blazor diagrams diagramming svg drag algorithms layouts Z.Blazor.Diagrams.Algorithms diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index bc726717a..d527e7f34 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -10,7 +10,7 @@ 3.0.0 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams - 3.0.0-beta.2 + 3.0.0-beta.3 Z.Blazor.Diagrams.Core blazor diagrams diagramming svg drag Z.Blazor.Diagrams.Core diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index c5328a8a3..d4d0fb202 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -9,7 +9,7 @@ 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams A fully customizable and extensible all-purpose diagrams library for Blazor - 3.0.0-beta.2 + 3.0.0-beta.3 true blazor diagrams diagramming svg drag Z.Blazor.Diagrams From 2799a9c9f3f42796d72a9037a88015fdaf25cb31 Mon Sep 17 00:00:00 2001 From: Jeremy Vance <49650352+240026763@users.noreply.github.com> Date: Mon, 19 Sep 2022 15:39:53 -0400 Subject: [PATCH 117/193] Add Unit Test for GridSnapToCenter --- .../Behaviors/DragMovablesBehaviorTests.cs | 30 ++++++++++++++++++- .../Blazor.Diagrams.Core.Tests/TestDiagram.cs | 6 ++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs index 9161ff9f8..8b5859fd6 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -2,6 +2,7 @@ using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Options; using FluentAssertions; using Moq; using Xunit; @@ -28,7 +29,34 @@ public void Behavior_ShouldCallSetPosition() // Assert nodeMock.Verify(n => n.SetPosition(50, 50), Times.Once); } - + + [Theory] + [InlineData(false, 45, 45)] + [InlineData(true, 35, 35)] + public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, 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(50, 50); + node.Position = new Point(0, 0); + diagram.SelectModel(node, false); + + // Act + 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() { diff --git a/tests/Blazor.Diagrams.Core.Tests/TestDiagram.cs b/tests/Blazor.Diagrams.Core.Tests/TestDiagram.cs index 1f3355a01..a1319222c 100644 --- a/tests/Blazor.Diagrams.Core.Tests/TestDiagram.cs +++ b/tests/Blazor.Diagrams.Core.Tests/TestDiagram.cs @@ -1,12 +1,14 @@ +#nullable enable +using System; using Blazor.Diagrams.Core.Options; namespace Blazor.Diagrams.Core.Tests; public class TestDiagram : Diagram { - public TestDiagram() + public TestDiagram(DiagramOptions? options = null) { - Options = new DiagramOptions(); + Options = options ?? new DiagramOptions(); } public override DiagramOptions Options { get; } From 95d5942ac5e647753faa8354395031ff0f36b1ba Mon Sep 17 00:00:00 2001 From: Jeremy Vance <49650352+240026763@users.noreply.github.com> Date: Mon, 19 Sep 2022 15:49:17 -0400 Subject: [PATCH 118/193] Additional inlines to help understanding --- .../Behaviors/DragMovablesBehaviorTests.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs index 8b5859fd6..562774d36 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -31,9 +31,11 @@ public void Behavior_ShouldCallSetPosition() } [Theory] - [InlineData(false, 45, 45)] - [InlineData(true, 35, 35)] - public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, double deltaX, double deltaY) + [InlineData(false, 0, 0, 45, 45)] + [InlineData(true, 0, 0, 50, 50)] + [InlineData(false, 3, 3, 45, 45)] + [InlineData(true, 3, 3, 35, 35)] + public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, double initialX, double initialY, double deltaX, double deltaY) { // Arrange var diagram = new TestDiagram(new DiagramOptions @@ -43,11 +45,12 @@ public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, dou }); var nodeMock = new Mock(Point.Zero); var node = diagram.Nodes.Add(nodeMock.Object); - node.Size = new Size(50, 50); - node.Position = new Point(0, 0); + 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, From bdaac87289f5fd435dba98b29d14f1aa4ed46ef9 Mon Sep 17 00:00:00 2001 From: Jeremy Vance <49650352+240026763@users.noreply.github.com> Date: Mon, 19 Sep 2022 15:56:53 -0400 Subject: [PATCH 119/193] Fix reversed deltas --- .../Behaviors/DragMovablesBehaviorTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs index 562774d36..a896f1738 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -32,9 +32,9 @@ public void Behavior_ShouldCallSetPosition() [Theory] [InlineData(false, 0, 0, 45, 45)] - [InlineData(true, 0, 0, 50, 50)] + [InlineData(true, 0, 0, 35, 35)] [InlineData(false, 3, 3, 45, 45)] - [InlineData(true, 3, 3, 35, 35)] + [InlineData(true, 3, 3, 50, 50)] public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, double initialX, double initialY, double deltaX, double deltaY) { // Arrange From 7d31313cdab238855f3688ebf31357e0d72e8262 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 8 Oct 2022 20:58:35 +0100 Subject: [PATCH 120/193] Add GroupLayer & Fix a couple of bugs --- .../Demos/CustomGroup/Demo.razor.cs | 2 +- .../Demos/CustomSvgGroup/Demo.razor.cs | 2 +- .../SharedDemo/Demos/Groups/Dynamic.razor.cs | 2 +- .../SharedDemo/Demos/Groups/Grouping.razor.cs | 4 +- .../SharedDemo/Demos/Nodes/SvgDemo.razor.cs | 4 +- .../Behaviors/DebugEventsBehavior.cs | 16 +--- .../Behaviors/KeyboardShortcutsBehavior.cs | 1 + .../Behaviors/KeyboardShortcutsDefaults.cs | 6 +- src/Blazor.Diagrams.Core/Diagram.cs | 86 +---------------- src/Blazor.Diagrams.Core/Layers/GroupLayer.cs | 50 ++++++++++ src/Blazor.Diagrams.Core/Models/GroupModel.cs | 1 + .../Widgets/NavigatorWidget.razor.cs | 8 +- .../KeyboardShortcutsDefaultsTests.cs | 2 +- .../Layers/GroupLayerTests.cs | 96 +++++++++++++++++++ .../Blazor.Diagrams.Core.Tests/TestDiagram.cs | 2 - 15 files changed, 169 insertions(+), 113 deletions(-) create mode 100644 src/Blazor.Diagrams.Core/Layers/GroupLayer.cs create mode 100644 tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs diff --git a/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs b/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs index 2d4d3f8c9..efb426ae3 100644 --- a/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs @@ -25,7 +25,7 @@ protected override void OnInitialized() var node3 = NewNode(500, 100); _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - _blazorDiagram.AddGroup(new CustomGroupModel(new[] { node2, node3 }, "Group 1")); + _blazorDiagram.Groups.Add(new CustomGroupModel(new[] { node2, node3 }, "Group 1")); _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); diff --git a/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs index a955e145f..5aed0ac85 100644 --- a/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs @@ -26,7 +26,7 @@ protected override void OnInitialized() var node3 = NewNode(500, 100); _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - _blazorDiagram.AddGroup(new CustomSvgGroupModel(new[] { node2, node3 }, "Group 1")); + _blazorDiagram.Groups.Add(new CustomSvgGroupModel(new[] { node2, node3 }, "Group 1")); _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); diff --git a/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs b/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs index e1b3545b7..2c0f88b42 100644 --- a/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs @@ -29,7 +29,7 @@ protected override void OnInitialized() private void AddEmptyGroup() { - _blazorDiagram.AddGroup(new GroupModel(Array.Empty()) + _blazorDiagram.Groups.Add(new GroupModel(Array.Empty()) { Position = new Point(100, 100) }); diff --git a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs index 116db073c..79192914c 100644 --- a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs @@ -26,8 +26,8 @@ protected override void OnInitialized() BlazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); - var group1 = BlazorDiagram.Group(node1, node2); - var group2 = BlazorDiagram.Group(group1, node3); + var group1 = BlazorDiagram.Groups.Group(node1, node2); + var group2 = BlazorDiagram.Groups.Group(group1, node3); BlazorDiagram.Links.Add(new LinkModel(group2, node4)); } diff --git a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs index 173998f61..cb4a111a3 100644 --- a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs @@ -42,8 +42,8 @@ private void InitializeDiagram() _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); - var group1 = _blazorDiagram.AddGroup(new SvgGroupModel(new[] { node1, node2 })); - var group2 = _blazorDiagram.AddGroup(new SvgGroupModel(new[] { group1, node3 })); + var group1 = _blazorDiagram.Groups.Add(new SvgGroupModel(new[] { node1, node2 })); + var group2 = _blazorDiagram.Groups.Add(new SvgGroupModel(new[] { group1, node3 })); var link = _blazorDiagram.Links.Add(new LinkModel(group2, node4)); var controls2 = _blazorDiagram.Controls.AddFor(link); diff --git a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs index 88aaafd41..ff15e342c 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs @@ -1,7 +1,6 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using System; -using System.Linq; namespace Blazor.Diagrams.Core.Behaviors { @@ -16,9 +15,8 @@ public DebugEventsBehavior(Diagram diagram) : base(diagram) Diagram.Nodes.Removed += Nodes_Removed; Diagram.Links.Added += Links_Added; Diagram.Links.Removed += Links_Removed; - Diagram.GroupAdded += Diagram_GroupAdded; - Diagram.GroupRemoved += Diagram_GroupRemoved; - Diagram.GroupUngrouped += Diagram_GroupUngrouped; + Diagram.Groups.Added += Diagram_GroupAdded; + Diagram.Groups.Removed += Diagram_GroupRemoved; Diagram.SelectionChanged += Diagram_SelectionChanged; Diagram.ZoomChanged += Diagram_ZoomChanged; } @@ -33,11 +31,6 @@ private void Diagram_SelectionChanged(SelectableModel obj) Console.WriteLine($"SelectionChanged, Model={obj.GetType().Name}, Selected={obj.Selected}"); } - private void Diagram_GroupUngrouped(GroupModel obj) - { - Console.WriteLine($"GroupUngrouped, Id={obj.Id}"); - } - private void Links_Removed(BaseLinkModel obj) { Console.WriteLine($"Links.Removed, Links=[{obj}]"); @@ -92,9 +85,8 @@ public override void Dispose() Diagram.Nodes.Removed -= Nodes_Removed; Diagram.Links.Added -= Links_Added; Diagram.Links.Removed -= Links_Removed; - Diagram.GroupAdded -= Diagram_GroupAdded; - Diagram.GroupRemoved -= Diagram_GroupRemoved; - Diagram.GroupUngrouped -= Diagram_GroupUngrouped; + Diagram.Groups.Added -= Diagram_GroupAdded; + Diagram.Groups.Removed -= Diagram_GroupRemoved; Diagram.SelectionChanged -= Diagram_SelectionChanged; Diagram.ZoomChanged -= Diagram_ZoomChanged; } diff --git a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs index c48293617..86f6b6a53 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs @@ -34,6 +34,7 @@ public bool RemoveShortcut(string key, bool ctrl, bool shift, bool alt) private async void OnDiagramKeyDown(KeyboardEventArgs e) { var k = KeysUtils.GetStringRepresentation(e.CtrlKey, e.ShiftKey, e.AltKey, e.Key); + Console.WriteLine(k); if (_shortcuts.TryGetValue(k, out var action)) { await action(Diagram); diff --git a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs index 91f0d0727..4c8fa1786 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs @@ -19,7 +19,7 @@ public static async ValueTask DeleteSelection(Diagram diagram) if (sm is GroupModel group && (await diagram.Options.Constraints.ShouldDeleteGroup(group))) { - diagram.RemoveGroup(group); + diagram.Groups.Delete(group); } else if (sm is NodeModel node && (await diagram.Options.Constraints.ShouldDeleteNode(node))) { @@ -53,7 +53,7 @@ public static ValueTask Grouping(Diagram diagram) // Ungroup foreach (var group in nodesWithGroup.GroupBy(n => n.Group!).Select(g => g.Key)) { - diagram.Ungroup(group); + diagram.Groups.Remove(group); } } else @@ -65,7 +65,7 @@ public static ValueTask Grouping(Diagram diagram) if (selectedNodes.Any(n => n.Group != null)) return ValueTask.CompletedTask; - diagram.Group(selectedNodes); + diagram.Groups.Group(selectedNodes); } return ValueTask.CompletedTask; diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index ab420f24f..26dea6c61 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -21,7 +21,6 @@ namespace Blazor.Diagrams.Core public abstract class Diagram { private readonly Dictionary _behaviors; - private readonly List _groups; public event Action? PointerDown; public event Action? PointerMove; @@ -34,10 +33,6 @@ public abstract class Diagram public event Action? PointerDoubleClick; public event Action? SelectionChanged; - public event Action? GroupAdded; - public event Action? GroupUngrouped; - public event Action? GroupRemoved; - public event Action? PanChanged; public event Action? ZoomChanged; public event Action? ContainerChanged; @@ -46,10 +41,10 @@ public abstract class Diagram protected Diagram() { _behaviors = new Dictionary(); - _groups = new List(); Nodes = new NodeLayer(this); Links = new LinkLayer(this); + Groups = new GroupLayer(this); Controls = new ControlsLayer(); RegisterBehavior(new SelectionBehavior(this)); @@ -66,8 +61,8 @@ protected Diagram() public abstract DiagramOptions Options { get; } public NodeLayer Nodes { get; } public LinkLayer Links { get; } + public GroupLayer Groups { get; } public ControlsLayer Controls { get; } - public IReadOnlyList Groups => _groups; public Rectangle? Container { get; private set; } public Point Pan { get; private set; } = Point.Zero; public double Zoom { get; private set; } = 1; @@ -97,83 +92,6 @@ public void Batch(Action action) Refresh(); } - #region Groups - - /// - /// Groups 2 or more children. - /// - /// An array of child nodes. - /// The created group instance. - public GroupModel Group(params NodeModel[] children) - { - var group = Options.Groups.Factory(this, children); - AddGroup(group); - return group; - } - - /// - /// Adds the group to the diagram after validating it. - /// - /// A group instance. - public GroupModel AddGroup(GroupModel group) - { - foreach (var child in group.Children) - { - if (child is GroupModel g) - { - if (!Groups.Contains(g)) - throw new Exception( - "One of the children isn't in the diagram (Groups). Make sure to add all the nodes before creating the group."); - } - else if (child is NodeModel n) - if (!Nodes.Contains(n)) - throw new Exception( - "One of the children isn't in the diagram (Nodes). Make sure to add all the nodes before creating the group."); - } - - _groups.Add(group); - GroupAdded?.Invoke(group); - Refresh(); - return group; - } - - /// - /// Splits up the group by deleting the group and keeping the children. - /// - /// A group instance. - public void Ungroup(GroupModel group) - { - if (!_groups.Remove(group)) - return; - - Batch(() => - { - group.Ungroup(); - Links.Remove(group.PortLinks.ToArray()); - GroupUngrouped?.Invoke(group); - }); - } - - /// - /// Deletes the group and all its children from the diagram. - /// - /// A group instnace. - public void RemoveGroup(GroupModel group) - { - if (!_groups.Remove(group)) - return; - - Batch(() => - { - Nodes.Remove(group.Children.ToArray()); - Links.Remove(group.PortLinks.ToArray()); - group.Ungroup(); - GroupRemoved?.Invoke(group); - }); - } - - #endregion - #region Selection public IEnumerable GetSelectedModels() diff --git a/src/Blazor.Diagrams.Core/Layers/GroupLayer.cs b/src/Blazor.Diagrams.Core/Layers/GroupLayer.cs new file mode 100644 index 000000000..9686085a5 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Layers/GroupLayer.cs @@ -0,0 +1,50 @@ +using Blazor.Diagrams.Core.Models; +using System.Linq; + +namespace Blazor.Diagrams.Core.Layers +{ + public class GroupLayer : BaseLayer + { + public GroupLayer(Diagram diagram) : base(diagram) + { + } + + public GroupModel Group(params NodeModel[] children) + { + return Add(Diagram.Options.Groups.Factory(Diagram, children)); + } + + /// + /// Removes the group AND its children + /// + public void Delete(GroupModel group) + { + var children = group.Children.ToArray(); + + Remove(group); + + foreach (var child in children) + { + if (child is GroupModel g) + { + Delete(g); + } + else + { + Diagram.Nodes.Remove(child); + } + } + } + + protected override void OnItemRemoved(GroupModel group) + { + Diagram.Batch(() => + { + Diagram.Links.Remove(group.PortLinks.ToArray()); + Diagram.Links.Remove(group.Links.ToArray()); + group.Ungroup(); + group.Group?.RemoveChild(group); + }); + } + } +} diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index f530e2653..8dc4b28ba 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -46,6 +46,7 @@ public void RemoveChild(NodeModel child) if (UpdateDimensions()) { Refresh(); + RefreshLinks(); } } diff --git a/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs index 7c91809cb..ca1e36213 100644 --- a/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/NavigatorWidget.razor.cs @@ -38,8 +38,8 @@ public void Dispose() BlazorDiagram.Changed -= Refresh; BlazorDiagram.Nodes.Added -= OnNodeAdded; BlazorDiagram.Nodes.Removed -= OnNodeRemoved; - BlazorDiagram.GroupAdded -= OnNodeAdded; - BlazorDiagram.GroupRemoved -= OnNodeRemoved; + BlazorDiagram.Groups.Added -= OnNodeAdded; + BlazorDiagram.Groups.Removed -= OnNodeRemoved; foreach (var node in BlazorDiagram.Nodes) node.Changed -= OnNodeChanged; @@ -53,8 +53,8 @@ protected override void OnInitialized() BlazorDiagram.Changed += Refresh; BlazorDiagram.Nodes.Added += OnNodeAdded; BlazorDiagram.Nodes.Removed += OnNodeRemoved; - BlazorDiagram.GroupAdded += OnNodeAdded; - BlazorDiagram.GroupRemoved += OnNodeRemoved; + BlazorDiagram.Groups.Added += OnNodeAdded; + BlazorDiagram.Groups.Removed += OnNodeRemoved; foreach (var node in BlazorDiagram.Nodes) node.Changed += OnNodeChanged; diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs index ed6012924..9a1839c52 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs @@ -38,7 +38,7 @@ public async Task DeleteSelection_ShouldTakeIntoAccountGroupConstraint() funcCalled = true; return ValueTask.FromResult(false); }; - diagram.AddGroup(new GroupModel(Array.Empty()) + diagram.Groups.Add(new GroupModel(Array.Empty()) { Selected = true }); diff --git a/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs b/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs new file mode 100644 index 000000000..880a4c4f9 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs @@ -0,0 +1,96 @@ +using Blazor.Diagrams.Core.Models; +using FluentAssertions; +using System; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Layers +{ + public class GroupLayerTests + { + [Fact] + public void Group_ShouldCallFactoryThenAddMethod() + { + // Arrange + var diagram = new TestDiagram(); + var factoryCalled = false; + + diagram.Options.Groups.Factory = (_, children) => + { + factoryCalled = true; + return new GroupModel(children); + }; + + // Act + diagram.Groups.Group(Array.Empty()); + + // Assert + factoryCalled.Should().BeTrue(); + } + + [Fact] + public void Remove_ShouldRemoveAllPortLinks() + { + // Arrange + var diagram = new TestDiagram(); + var group = diagram.Groups.Add(new GroupModel(Array.Empty())); + var groupPort = group.AddPort(PortAlignment.Top); + var node = diagram.Nodes.Add(new NodeModel()); + var nodePort = node.AddPort(PortAlignment.Top); + diagram.Links.Add(new LinkModel(groupPort, nodePort)); + + // Act + diagram.Groups.Remove(group); + + // Assert + diagram.Links.Should().BeEmpty(); + } + + [Fact] + public void Remove_ShouldRemoveAllLinks() + { + // Arrange + var diagram = new TestDiagram(); + var group = diagram.Groups.Add(new GroupModel(Array.Empty())); + var node = diagram.Nodes.Add(new NodeModel()); + diagram.Links.Add(new LinkModel(group, node)); + + // Act + diagram.Groups.Remove(group); + + // Assert + diagram.Links.Should().BeEmpty(); + } + + [Fact] + public void Remove_ShouldUngroup() + { + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel()); + var group = diagram.Groups.Add(new GroupModel(new[] { node })); + + // Act + diagram.Groups.Remove(group); + + // Assert + group.Children.Should().BeEmpty(); + node.Group.Should().BeNull(); + } + + [Fact] + public void Remove_ShouldRemoveItselfFromParentGroup() + { + // Arrange + var diagram = new TestDiagram(); + var group1 = diagram.Groups.Add(new GroupModel(Array.Empty())); + var group2 = diagram.Groups.Add(new GroupModel(new[] { group1 })); + + // Act + diagram.Groups.Remove(group1); + + // Assert + group2.Children.Should().BeEmpty(); + group1.Group.Should().BeNull(); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/TestDiagram.cs b/tests/Blazor.Diagrams.Core.Tests/TestDiagram.cs index a1319222c..7704feef5 100644 --- a/tests/Blazor.Diagrams.Core.Tests/TestDiagram.cs +++ b/tests/Blazor.Diagrams.Core.Tests/TestDiagram.cs @@ -1,5 +1,3 @@ -#nullable enable -using System; using Blazor.Diagrams.Core.Options; namespace Blazor.Diagrams.Core.Tests; From a080152dee03e69563e330d46d8d5c80331c40ca Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 9 Oct 2022 13:17:52 +0100 Subject: [PATCH 121/193] Update GroupLayerTests.cs --- .../Layers/GroupLayerTests.cs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs b/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs index 880a4c4f9..b1dd48de0 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs @@ -92,5 +92,36 @@ public void Remove_ShouldRemoveItselfFromParentGroup() group2.Children.Should().BeEmpty(); group1.Group.Should().BeNull(); } + + [Fact] + public void Delete_ShouldDeleteChildGroup() + { + // Arrange + var diagram = new TestDiagram(); + var group1 = diagram.Groups.Add(new GroupModel(Array.Empty())); + var group2 = diagram.Groups.Add(new GroupModel(new[] { group1 })); + + // Act + diagram.Groups.Delete(group2); + + // Assert + diagram.Groups.Should().BeEmpty(); + } + + [Fact] + public void Delete_ShouldRemoveChild() + { + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel()); + var group = diagram.Groups.Add(new GroupModel(new[] { node })); + + // Act + diagram.Groups.Delete(group); + + // Assert + diagram.Groups.Should().BeEmpty(); + diagram.Nodes.Should().BeEmpty(); + } } } From 8d6d9c789b2c52f876e23d2a6c449503f4680b71 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 15 Oct 2022 16:40:45 +0100 Subject: [PATCH 122/193] Add more unit tests and avoid some refreshes --- src/Blazor.Diagrams.Core/Layers/BaseLayer.cs | 18 +-- src/Blazor.Diagrams.Core/Layers/GroupLayer.cs | 36 +++--- src/Blazor.Diagrams.Core/Layers/NodeLayer.cs | 16 +-- src/Blazor.Diagrams.Core/Models/Base/Model.cs | 4 +- .../Layers/GroupLayerTests.cs | 70 ++++++++++-- .../Layers/NodeLayerTests.cs | 107 ++++++++++++++++++ 6 files changed, 201 insertions(+), 50 deletions(-) create mode 100644 tests/Blazor.Diagrams.Core.Tests/Layers/NodeLayerTests.cs diff --git a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs index 2556a1ec4..0880a7ec8 100644 --- a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs @@ -22,10 +22,12 @@ public virtual T Add(T item) if (item is null) throw new ArgumentNullException(nameof(item)); - _items.Add(item); - OnItemAdded(item); - Added?.Invoke(item); - Diagram.Refresh(); + Diagram.Batch(() => + { + _items.Add(item); + OnItemAdded(item); + Added?.Invoke(item); + }); return item; } @@ -52,9 +54,11 @@ public virtual void Remove(T item) if (_items.Remove(item)) { - OnItemRemoved(item); - Removed?.Invoke(item); - Diagram.Refresh(); + Diagram.Batch(() => + { + OnItemRemoved(item); + Removed?.Invoke(item); + }); } } diff --git a/src/Blazor.Diagrams.Core/Layers/GroupLayer.cs b/src/Blazor.Diagrams.Core/Layers/GroupLayer.cs index 9686085a5..31c89a9f5 100644 --- a/src/Blazor.Diagrams.Core/Layers/GroupLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/GroupLayer.cs @@ -19,32 +19,32 @@ public GroupModel Group(params NodeModel[] children) /// public void Delete(GroupModel group) { - var children = group.Children.ToArray(); + Diagram.Batch(() => + { + var children = group.Children.ToArray(); - Remove(group); + Remove(group); - foreach (var child in children) - { - if (child is GroupModel g) - { - Delete(g); - } - else + foreach (var child in children) { - Diagram.Nodes.Remove(child); + if (child is GroupModel g) + { + Delete(g); + } + else + { + Diagram.Nodes.Remove(child); + } } - } + }); } protected override void OnItemRemoved(GroupModel group) { - Diagram.Batch(() => - { - Diagram.Links.Remove(group.PortLinks.ToArray()); - Diagram.Links.Remove(group.Links.ToArray()); - group.Ungroup(); - group.Group?.RemoveChild(group); - }); + Diagram.Links.Remove(group.PortLinks.ToArray()); + Diagram.Links.Remove(group.Links.ToArray()); + group.Ungroup(); + group.Group?.RemoveChild(group); } } } diff --git a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs index 2a3aa297d..4cdd7b1ca 100644 --- a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs @@ -7,20 +7,12 @@ public class NodeLayer : BaseLayer { public NodeLayer(Diagram diagram) : base(diagram) { } - public override void Remove(NodeModel node) - { - Diagram.Batch(() => base.Remove(node)); - } - protected override void OnItemRemoved(NodeModel node) { - Diagram.Batch(() => - { - Diagram.Links.Remove(node.PortLinks.ToList()); - Diagram.Links.Remove(node.Links.ToList()); - node.Group?.RemoveChild(node); - Diagram.Controls.RemoveFor(node); - }); + Diagram.Links.Remove(node.PortLinks.ToList()); + Diagram.Links.Remove(node.Links.ToList()); + node.Group?.RemoveChild(node); + Diagram.Controls.RemoveFor(node); } } } diff --git a/src/Blazor.Diagrams.Core/Models/Base/Model.cs b/src/Blazor.Diagrams.Core/Models/Base/Model.cs index f700748ab..8d277307f 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/Model.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/Model.cs @@ -6,9 +6,9 @@ public abstract class Model { private bool _visible = true; - public Model() : this(Guid.NewGuid().ToString()) { } + protected Model() : this(Guid.NewGuid().ToString()) { } - public Model(string id) + protected Model(string id) { Id = id; } diff --git a/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs b/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs index b1dd48de0..877d33cc0 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs @@ -61,6 +61,22 @@ public void Remove_ShouldRemoveAllLinks() diagram.Links.Should().BeEmpty(); } + [Fact] + public void Remove_ShouldRemoveItselfFromParentGroup() + { + // Arrange + var diagram = new TestDiagram(); + var group1 = diagram.Groups.Add(new GroupModel(Array.Empty())); + var group2 = diagram.Groups.Add(new GroupModel(new[] { group1 })); + + // Act + diagram.Groups.Remove(group1); + + // Assert + group2.Children.Should().BeEmpty(); + group1.Group.Should().BeNull(); + } + [Fact] public void Remove_ShouldUngroup() { @@ -78,7 +94,7 @@ public void Remove_ShouldUngroup() } [Fact] - public void Remove_ShouldRemoveItselfFromParentGroup() + public void Delete_ShouldDeleteChildGroup() { // Arrange var diagram = new TestDiagram(); @@ -86,42 +102,74 @@ public void Remove_ShouldRemoveItselfFromParentGroup() var group2 = diagram.Groups.Add(new GroupModel(new[] { group1 })); // Act - diagram.Groups.Remove(group1); + diagram.Groups.Delete(group2); // Assert - group2.Children.Should().BeEmpty(); - group1.Group.Should().BeNull(); + diagram.Groups.Should().BeEmpty(); } [Fact] - public void Delete_ShouldDeleteChildGroup() + public void Delete_ShouldRemoveChild() { // Arrange var diagram = new TestDiagram(); - var group1 = diagram.Groups.Add(new GroupModel(Array.Empty())); - var group2 = diagram.Groups.Add(new GroupModel(new[] { group1 })); + var node = diagram.Nodes.Add(new NodeModel()); + var group = diagram.Groups.Add(new GroupModel(new[] { node })); // Act - diagram.Groups.Delete(group2); + diagram.Groups.Delete(group); // Assert diagram.Groups.Should().BeEmpty(); + diagram.Nodes.Should().BeEmpty(); } [Fact] - public void Delete_ShouldRemoveChild() + public void Add_ShouldRefreshDiagramOnce() + { + // Arrange + var diagram = new TestDiagram(); + var refreshes = 0; + diagram.Changed += () => refreshes++; + + // Act + var group = diagram.Groups.Add(new GroupModel(Array.Empty())); + + // Assert + refreshes.Should().Be(1); + } + + [Fact] + public void Remove_ShouldRefreshDiagramOnce() + { + // Arrange + var diagram = new TestDiagram(); + var group = diagram.Groups.Add(new GroupModel(Array.Empty())); + var refreshes = 0; + diagram.Changed += () => refreshes++; + + // Act + diagram.Groups.Remove(group); + + // Assert + refreshes.Should().Be(1); + } + + [Fact] + public void Delete_ShouldRefreshDiagramOnce() { // Arrange var diagram = new TestDiagram(); var node = diagram.Nodes.Add(new NodeModel()); var group = diagram.Groups.Add(new GroupModel(new[] { node })); + var refreshes = 0; + diagram.Changed += () => refreshes++; // Act diagram.Groups.Delete(group); // Assert - diagram.Groups.Should().BeEmpty(); - diagram.Nodes.Should().BeEmpty(); + refreshes.Should().Be(1); } } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Layers/NodeLayerTests.cs b/tests/Blazor.Diagrams.Core.Tests/Layers/NodeLayerTests.cs new file mode 100644 index 000000000..9e2331b0b --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Layers/NodeLayerTests.cs @@ -0,0 +1,107 @@ +using Blazor.Diagrams.Core.Models; +using FluentAssertions; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Layers +{ + public class NodeLayerTests + { + [Fact] + public void Remove_ShouldRemoveAllPortLinks() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); + var nodePort1 = node1.AddPort(PortAlignment.Top); + var node2 = diagram.Nodes.Add(new NodeModel()); + var nodePort2 = node2.AddPort(PortAlignment.Top); + diagram.Links.Add(new LinkModel(nodePort1, nodePort2)); + + // Act + diagram.Nodes.Remove(node1); + + // Assert + diagram.Links.Should().BeEmpty(); + } + + [Fact] + public void Remove_ShouldRemoveAllLinks() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); + var node2 = diagram.Nodes.Add(new NodeModel()); + diagram.Links.Add(new LinkModel(node1, node2)); + + // Act + diagram.Nodes.Remove(node1); + + // Assert + diagram.Links.Should().BeEmpty(); + } + + [Fact] + public void Remove_ShouldRemoveItselfFromParentGroup() + { + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel()); + var group = diagram.Groups.Add(new GroupModel(new[] { node })); + + // Act + diagram.Nodes.Remove(node); + + // Assert + group.Children.Should().BeEmpty(); + node.Group.Should().BeNull(); + } + + [Fact] + public void Add_ShouldRefreshDiagramOnce() + { + // Arrange + var diagram = new TestDiagram(); + var refreshes = 0; + diagram.Changed += () => refreshes++; + + // Act + var node = diagram.Nodes.Add(new NodeModel()); + + // Assert + refreshes.Should().Be(1); + } + + [Fact] + public void Remove_ShouldRefreshDiagramOnce() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); + var node2 = diagram.Nodes.Add(new NodeModel()); + diagram.Links.Add(new LinkModel(node1, node2)); + var refreshes = 0; + diagram.Changed += () => refreshes++; + + // Act + diagram.Nodes.Remove(node1); + + // Assert + refreshes.Should().Be(1); + } + + [Fact] + public void Remove_ShouldRemoveControls() + { + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel()); + var controls = diagram.Controls.AddFor(node); + + // Act + diagram.Nodes.Remove(node); + + // Assert + diagram.Controls.GetFor(node).Should().BeNull(); + } + } +} From e2b6779d181f9cabe0d40dd8ed732b6a8bdbeef2 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 15 Oct 2022 16:41:22 +0100 Subject: [PATCH 123/193] Add initial version of selectables' ordering (nodes, groups and links) --- .../SharedDemo/Demos/Groups/Grouping.razor.cs | 4 +- .../SharedDemo/Demos/Nodes/SvgDemo.razor.cs | 14 +- src/Blazor.Diagrams.Core/Diagram.cs | 90 +++++++++- .../Models/Base/SelectableModel.cs | 24 ++- .../Components/DiagramCanvas.razor | 45 ++--- .../DiagramOrderingTests.cs | 165 ++++++++++++++++++ 6 files changed, 310 insertions(+), 32 deletions(-) create mode 100644 tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs diff --git a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs index 79192914c..1a2a3d2a4 100644 --- a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs @@ -20,7 +20,7 @@ protected override void OnInitialized() var node2 = NewNode(250, 250); var node3 = NewNode(500, 100); var node4 = NewNode(700, 350); - BlazorDiagram.Nodes.Add(new[] { node1, node2, node3, node4 }); + BlazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); BlazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); @@ -29,6 +29,8 @@ protected override void OnInitialized() var group1 = BlazorDiagram.Groups.Group(node1, node2); var group2 = BlazorDiagram.Groups.Group(group1, node3); + BlazorDiagram.Nodes.Add(node4); + BlazorDiagram.Links.Add(new LinkModel(group2, node4)); } diff --git a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs index cb4a111a3..ab9d80078 100644 --- a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs @@ -33,19 +33,19 @@ private void InitializeDiagram() var node3 = NewNode(500, 100); var node4 = NewNode(700, 350); _blazorDiagram.Nodes.Add(new[] { node1, node2, node3, node4 }); - - var controls1 = _blazorDiagram.Controls.AddFor(node4); - controls1.Add(new RemoveControl(1, 0)); - controls1.Add(new BoundaryControl()); + + var group1 = _blazorDiagram.Groups.Add(new SvgGroupModel(new[] { node1, node2 })); + var group2 = _blazorDiagram.Groups.Add(new SvgGroupModel(new[] { group1, node3 })); _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + var link = _blazorDiagram.Links.Add(new LinkModel(group2, node4)); - var group1 = _blazorDiagram.Groups.Add(new SvgGroupModel(new[] { node1, node2 })); - var group2 = _blazorDiagram.Groups.Add(new SvgGroupModel(new[] { group1, node3 })); + var controls1 = _blazorDiagram.Controls.AddFor(node4); + controls1.Add(new RemoveControl(1, 0)); + controls1.Add(new BoundaryControl()); - var link = _blazorDiagram.Links.Add(new LinkModel(group2, node4)); var controls2 = _blazorDiagram.Controls.AddFor(link); controls2.Add(new RemoveControl(1, 0)); controls2.Add(new BoundaryControl()); diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 26dea6c61..502295be7 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -2,7 +2,6 @@ using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Layers; -using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Events; using System; @@ -21,6 +20,8 @@ namespace Blazor.Diagrams.Core public abstract class Diagram { private readonly Dictionary _behaviors; + private readonly List _orderedSelectables; + private bool _suspendSorting; public event Action? PointerDown; public event Action? PointerMove; @@ -41,12 +42,21 @@ public abstract class Diagram protected Diagram() { _behaviors = new Dictionary(); + _orderedSelectables = new List(); Nodes = new NodeLayer(this); Links = new LinkLayer(this); Groups = new GroupLayer(this); Controls = new ControlsLayer(); + Nodes.Added += OnSelectableAdded; + Links.Added += OnSelectableAdded; + Groups.Added += OnSelectableAdded; + + Nodes.Removed += OnSelectableRemoved; + Links.Removed += OnSelectableRemoved; + Groups.Removed += OnSelectableRemoved; + RegisterBehavior(new SelectionBehavior(this)); RegisterBehavior(new DragMovablesBehavior(this)); RegisterBehavior(new DragNewLinkBehavior(this)); @@ -67,6 +77,7 @@ protected Diagram() public Point Pan { get; private set; } = Point.Zero; public double Zoom { get; private set; } = 1; public bool SuspendRefresh { get; set; } + public IReadOnlyList OrderedSelectables => _orderedSelectables; public void Refresh() { @@ -277,6 +288,83 @@ public Point GetScreenPoint(double clientX, double clientY) 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; + } + + private void OnSelectableAdded(SelectableModel model) + { + var maxOrder = GetMaxOrder(); + _orderedSelectables.Add(model); + 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; + + _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); + Refresh(); + } + + #endregion + #region Events public void TriggerPointerDown(Model? model, PointerEventArgs e) => PointerDown?.Invoke(model, e); diff --git a/src/Blazor.Diagrams.Core/Models/Base/SelectableModel.cs b/src/Blazor.Diagrams.Core/Models/Base/SelectableModel.cs index 4cd2846f8..0e3187a13 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/SelectableModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/SelectableModel.cs @@ -1,11 +1,29 @@ -namespace Blazor.Diagrams.Core.Models.Base +using System; + +namespace Blazor.Diagrams.Core.Models.Base { public abstract class SelectableModel : Model { - public SelectableModel() { } + private int _order; + + public event Action? OrderChanged; - public SelectableModel(string id) : base(id) { } + protected SelectableModel() { } + + protected SelectableModel(string id) : base(id) { } public bool Selected { get; internal set; } + public int Order + { + get => _order; + set + { + if (value == Order) + return; + + _order = value; + OrderChanged?.Invoke(this); + } + } } } diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor b/src/Blazor.Diagrams/Components/DiagramCanvas.razor index 2a6c1a951..10e2a8717 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor @@ -10,36 +10,41 @@ @onwheel:stopPropagation> - @foreach (var node in BlazorDiagram.Nodes.OfType().Where(n => n.Group == null)) - { - - } - @foreach (var group in BlazorDiagram.Groups.OfType().Where(n => n.Group == null)) + @foreach (var model in BlazorDiagram.OrderedSelectables) { - - } - - @foreach (var link in BlazorDiagram.Links) - { - + if (model is SvgNodeModel node && node.Group == null) + { + + } + else if (model is SvgGroupModel group && group.Group == null) + { + + } + else if (model is BaseLinkModel link) + { + + } }
    - @foreach (var group in BlazorDiagram.Groups.Where(n => n is not SvgGroupModel)) - { - if (group.Group != null) - continue; - - - } - @foreach (var node in BlazorDiagram.Nodes.Where(n => n is not SvgNodeModel && n.Group == null)) + @foreach (var model in BlazorDiagram.OrderedSelectables) { - + if (model is GroupModel group) + { + if (group.Group == null && group is not SvgGroupModel) + { + + } + } + else if (model is NodeModel node && node.Group == null && node is not SvgNodeModel) + { + + } } diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs new file mode 100644 index 000000000..8add0604e --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs @@ -0,0 +1,165 @@ +using Blazor.Diagrams.Core.Models; +using FluentAssertions; +using System; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests +{ + public class DiagramOrderingTests + { + [Fact] + public void GetMinOrder_ShouldReturnZeroWhenNoModelsHaveBeenAdded() + { + // Arrange + var diagram = new TestDiagram(); + + // Act + var minOrder = diagram.GetMinOrder(); + + // Assert + minOrder.Should().Be(0); + } + + [Fact] + public void GetMinOrder_ShouldReturnCorrectValue() + { + // Arrange + var diagram = new TestDiagram(); + diagram.Nodes.Add(new NodeModel()); + diagram.Groups.Add(new GroupModel(Array.Empty())); + diagram.Links.Add(new LinkModel(diagram.Nodes[0], diagram.Groups[0])); + + // Act + var minOrder = diagram.GetMinOrder(); + + // Assert + minOrder.Should().Be(1); + } + + [Fact] + public void GetMaxOrder_ShouldReturnZeroWhenNoModelsHaveBeenAdded() + { + // Arrange + var diagram = new TestDiagram(); + + // Act + var maxOrder = diagram.GetMaxOrder(); + + // Assert + maxOrder.Should().Be(0); + } + + [Fact] + public void GetMaxOrder_ShouldReturnCorrectValue() + { + // Arrange + var diagram = new TestDiagram(); + diagram.Nodes.Add(new NodeModel()); + diagram.Groups.Add(new GroupModel(Array.Empty())); + diagram.Links.Add(new LinkModel(diagram.Nodes[0], diagram.Groups[0])); + + // Act + var maxOrder = diagram.GetMaxOrder(); + + // Assert + maxOrder.Should().Be(3); + } + + [Fact] + public void Diagram_ShouldReSortWhenModelOrderChanges() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); // 1 + var node2 = diagram.Nodes.Add(new NodeModel()); // 2 + + // Act + node1.Order = 10; + + // Assert + diagram.OrderedSelectables[0].Should().Be(node2); + diagram.OrderedSelectables[1].Should().Be(node1); + } + + [Fact] + public void Diagram_ShouldRefreshOnceWhenModelOrderChanges() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); // 1 + var node2 = diagram.Nodes.Add(new NodeModel()); // 2 + var refreshes = 0; + diagram.Changed += () => refreshes++; + + // Act + node1.Order = 10; + + // Assert + refreshes.Should().Be(1); + } + + [Fact] + public void SendToBack_ShouldInsertAtZeroAndFixOrders() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); + var node2 = diagram.Nodes.Add(new NodeModel()); + var node3 = diagram.Nodes.Add(new NodeModel()); + + // Act + diagram.SendToBack(node3); + + // Assert + diagram.OrderedSelectables[0].Should().Be(node3); + diagram.OrderedSelectables[0].Order.Should().Be(1); + + diagram.OrderedSelectables[1].Should().Be(node1); + diagram.OrderedSelectables[1].Order.Should().Be(2); + + diagram.OrderedSelectables[2].Should().Be(node2); + diagram.OrderedSelectables[2].Order.Should().Be(3); + } + + [Fact] + public void SendToFront_ShouldAddAndFixOrder() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); + var node2 = diagram.Nodes.Add(new NodeModel()); + var node3 = diagram.Nodes.Add(new NodeModel()); + + // Act + diagram.SendToFront(node1); + + // Assert + diagram.OrderedSelectables[0].Should().Be(node2); + diagram.OrderedSelectables[0].Order.Should().Be(2); + + diagram.OrderedSelectables[1].Should().Be(node3); + diagram.OrderedSelectables[1].Order.Should().Be(3); + + diagram.OrderedSelectables[2].Should().Be(node1); + diagram.OrderedSelectables[2].Order.Should().Be(4); + } + + [Fact] + public void Diagram_ShouldRefreshOnceWhenMultipleModelsWereRemoved() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); + var node2 = diagram.Nodes.Add(new NodeModel()); + var link = diagram.Links.Add(new LinkModel(node1, node2)); + var refreshes = 0; + diagram.Changed += () => refreshes++; + + // Act + diagram.Nodes.Remove(node1); + + // Assert + refreshes.Should().Be(1); + } + } +} From 15f6d1d21bec63b7a3690d2c71717c51ea62baca Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 15 Oct 2022 16:43:27 +0100 Subject: [PATCH 124/193] Fix some demo pages --- .../Demos/Algorithms/ReconnectLinksToClosestPorts.razor | 3 ++- samples/SharedDemo/Demos/Groups/CustomShortcut.razor | 3 ++- samples/SharedDemo/Demos/Groups/Dynamic.razor | 3 ++- samples/SharedDemo/Demos/Groups/Factory.razor | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor b/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor index 09fdbd3e3..ad3899a6d 100644 --- a/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor +++ b/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor @@ -21,7 +21,8 @@ - + \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor index 656ee3d22..dac358855 100644 --- a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor +++ b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor @@ -5,7 +5,8 @@ - + diff --git a/samples/SharedDemo/Demos/Groups/Dynamic.razor b/samples/SharedDemo/Demos/Groups/Dynamic.razor index a1661c8e4..0defe27b2 100644 --- a/samples/SharedDemo/Demos/Groups/Dynamic.razor +++ b/samples/SharedDemo/Demos/Groups/Dynamic.razor @@ -11,7 +11,8 @@ - + diff --git a/samples/SharedDemo/Demos/Groups/Factory.razor b/samples/SharedDemo/Demos/Groups/Factory.razor index db02420eb..37973fd2d 100644 --- a/samples/SharedDemo/Demos/Groups/Factory.razor +++ b/samples/SharedDemo/Demos/Groups/Factory.razor @@ -5,7 +5,8 @@ - + From 09c9a8e62fea20ddd657f3ad939c9e7a2d325056 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 16 Oct 2022 10:46:40 +0100 Subject: [PATCH 125/193] Fix bug where moving a single grandchild in groups doesn't move the grandfather as well (confusing explanation?) --- src/Blazor.Diagrams.Core/Models/GroupModel.cs | 14 +++++++++++++- src/Blazor.Diagrams.Core/Models/NodeModel.cs | 7 ++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index 8dc4b28ba..e657f230c 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -1,5 +1,6 @@ using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; +using System; using System.Collections.Generic; using System.Linq; @@ -52,6 +53,7 @@ public void RemoveChild(NodeModel child) public override void SetPosition(double x, double y) { + Console.WriteLine($"({(Group == null ? "Parent" : "Child")}) SetPosition {x:00} {y:00}"); var deltaX = x - Position.X; var deltaY = y - Position.Y; base.SetPosition(x, y); @@ -65,10 +67,13 @@ public override void SetPosition(double x, double y) public override void UpdatePositionSilently(double deltaX, double deltaY) { + Console.WriteLine($"({(Group == null ? "Parent" : "Child")}) UpdatePositionSilently {deltaX:00} {deltaY:00}"); base.UpdatePositionSilently(deltaX, deltaY); foreach (var child in Children) child.UpdatePositionSilently(deltaX, deltaY); + + Refresh(); } public void Ungroup() @@ -113,7 +118,14 @@ private bool UpdateDimensions() return false; var bounds = Children.GetBounds(); - Position = new Point(bounds.Left - Padding, bounds.Top - Padding); + + var newPosition = new Point(bounds.Left - Padding, bounds.Top - Padding); + if (!Position.Equals(newPosition)) + { + Position = newPosition; + TriggerMoving(); + } + Size = new Size(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 b94d69761..be5430a45 100644 --- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs +++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs @@ -138,6 +138,8 @@ public virtual void UpdatePositionSilently(double deltaX, double deltaY) public virtual IShape GetShape() => Shapes.Rectangle(this); + public virtual bool CanAttachTo(ILinkable other) => other is not PortModel && other is not BaseLinkModel; + private void UpdatePortPositions(double deltaX, double deltaY) { // Save some JS calls and update ports directly here @@ -148,7 +150,10 @@ private void UpdatePortPositions(double deltaX, double deltaY) } } - public virtual bool CanAttachTo(ILinkable other) => other is not PortModel && other is not BaseLinkModel; + protected void TriggerMoving() + { + Moving?.Invoke(this); + } void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); From bcd1247708659914a86465bdcef52e901e048b33 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 16 Oct 2022 15:54:50 +0100 Subject: [PATCH 126/193] Update Versions & CHANGELOG --- CHANGELOG.md | 26 +++++++++++++++++++ .../Blazor.Diagrams.Algorithms.csproj | 2 +- .../Blazor.Diagrams.Core.csproj | 2 +- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 2 +- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8b25da4b..3e31467d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Diagrams (3.0.0-beta.4) - 2022-10-16 + +### Added + +- Initial version of Ordering! + - Nodes, groups and links can now be ordered using the new `Order` property or `SendToFront/Back` methods + - `Diagram.OrderedSelectables` returns the ordered selectables/models + - `DiagramCanvas` now uses this new property to render everything +- `GridSnapToCenter` option in order to snap nodes from their center instead of their top/left position (thanks to @[Jeremy Vance](https://github.com/240026763)) +- More unit tests + +### Changed + +- `Groups` is not a list of groups anymore, but a layer instead (just like `Nodes` and `Links`) + +### Fixed + +- Deleting a group doesn't delete links attached to it +- Deleting a group inside of a group doesn't refresh the parent group +- Links not refreshing when a group's dimensions are updated directly (e.g. deleting a child) +- Layers causing more refreshes than intended + +### Removed + +- All group-related methods and events from `Diagram`, please use the new layer from now on + ## Diagrams (3.0.0-beta.3) - 2022-09-18 ### Added diff --git a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj index dbb79fba4..0b6719914 100644 --- a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj +++ b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj @@ -10,7 +10,7 @@ 3.0.0 3.0.0 https://github.com/zHaytam/Blazor.Diagrams - 3.0.0-beta.3 + 3.0.0-beta.4 Z.Blazor.Diagrams.Algorithms blazor diagrams diagramming svg drag algorithms layouts Z.Blazor.Diagrams.Algorithms diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index d527e7f34..c7449a49c 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -10,7 +10,7 @@ 3.0.0 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams - 3.0.0-beta.3 + 3.0.0-beta.4 Z.Blazor.Diagrams.Core blazor diagrams diagramming svg drag Z.Blazor.Diagrams.Core diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index d4d0fb202..a9c9e0d8d 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -9,7 +9,7 @@ 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams A fully customizable and extensible all-purpose diagrams library for Blazor - 3.0.0-beta.3 + 3.0.0-beta.4 true blazor diagrams diagramming svg drag Z.Blazor.Diagrams From a239bbe0764e56c2baa2d9df3942b12ab14099dd Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 2 Nov 2022 21:37:06 +0100 Subject: [PATCH 127/193] Add AdditionalSvg and AdditionalHtml render fragments to DiagramCanvas --- src/Blazor.Diagrams/Components/DiagramCanvas.razor | 4 ++++ src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor b/src/Blazor.Diagrams/Components/DiagramCanvas.razor index 10e2a8717..769058441 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor @@ -28,6 +28,8 @@ } + + @AdditionalSvg
    @@ -48,6 +50,8 @@ } + + @AdditionalHtml
    @Widgets diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index b8c60b3fb..8c723e043 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -19,6 +19,10 @@ public partial class DiagramCanvas : IDisposable [Parameter] public RenderFragment? Widgets { get; set; } + [Parameter] public RenderFragment? AdditionalSvg { get; set; } + + [Parameter] public RenderFragment? AdditionalHtml { get; set; } + [Parameter] public string? Class { get; set; } [Inject] public IJSRuntime JSRuntime { get; set; } = null!; From cf88214b8dce30dcf1ca6a1d662e8f2002957b9d Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 2 Nov 2022 22:54:16 +0100 Subject: [PATCH 128/193] Optimize Orthogonal router by using custom astar --- .../Routers/Routers.Orthogonal.cs | 501 ++++++------------ 1 file changed, 167 insertions(+), 334 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs index 6b4ee3f27..9b66518de 100644 --- a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs +++ b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs @@ -1,25 +1,26 @@ using Blazor.Diagrams.Core.Anchors; -using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using System; -using System.Collections.Generic; using System.Linq; +using System.Collections.Generic; -// Implementation taken from the JS version: https://gist.github.com/menendezpoo/4a8894c152383b9d7a870c24a04447e4 -// Todo: Make it more c#, Benchmark A* vs Dijkstra, Add more options +// Originally based from https://gist.github.com/menendezpoo/4a8894c152383b9d7a870c24a04447e4 with some tweaks (especially the use of a*) namespace Blazor.Diagrams.Core { public static partial class Routers { public static Point[] Orthogonal(Diagram _, BaseLinkModel link) { + if (!link.IsAttached) + return Routers.Normal(_, link); + if (link.Source is not SinglePortAnchor spa1) - throw new Exception("Orthogonal router doesn't work with port-less links yet"); + throw new DiagramsException("Orthogonal router doesn't work with port-less links yet"); if (link.Target is not SinglePortAnchor targetAnchor) - throw new Exception("Orthogonal router doesn't work with port-less links yet"); + throw new DiagramsException("Orthogonal router doesn't work with port-less links yet"); var sourcePort = spa1.Port; if (targetAnchor == null || sourcePort.Parent.Size == null || targetAnchor.Port.Parent.Size == null) @@ -29,7 +30,7 @@ public static Point[] Orthogonal(Diagram _, BaseLinkModel link) var shapeMargin = 10; var globalBoundsMargin = 50; - var spots = new List(); + var spots = new HashSet(); var verticals = new List(); var horizontals = new List(); var sideA = sourcePort.Alignment; @@ -80,19 +81,56 @@ public static Point[] Orthogonal(Diagram _, BaseLinkModel link) var gridPoints = GridToSpots(grid, new[] { inflatedA, inflatedB }); // Add to spots - spots.AddRange(gridPoints); + spots.UnionWith(gridPoints); + + var ys = spots.Select(p => p.Y).Distinct().ToList(); + var xs = spots.Select(p => p.X).Distinct().ToList(); + ys.Sort(); + xs.Sort(); + + var cc = sw.Elapsed.TotalMilliseconds; + + var nodes = spots.ToDictionary(p => p, p => new Node(p)); + + for (var i = 0; i < ys.Count; i++) + { + for (var j = 0; j < xs.Count; j++) + { + var b = new Point(xs[j], ys[i]); + if (!nodes.ContainsKey(b)) + continue; + + if (j > 0) + { + var a = new Point(xs[j - 1], ys[i]); + + if (nodes.ContainsKey(a)) + { + nodes[a].ConnectedTo.Add(nodes[b]); + nodes[b].ConnectedTo.Add(nodes[a]); + } + } - // Create graph - var graph = CreateGraph(spots); + if (i > 0) + { + var a = new Point(xs[j], ys[i - 1]); + + if (nodes.ContainsKey(a)) + { + nodes[a].ConnectedTo.Add(nodes[b]); + nodes[b].ConnectedTo.Add(nodes[a]); + } + } + } + } - // Origin and destination by extruding antennas - var origin = ExtrudeCp(originA, shapeMargin, sideA); - var destination = ExtrudeCp(originB, shapeMargin, sideB); + var nodeA = nodes[GetOriginSpot(originA, sideA, shapeMargin)]; + var nodeB = nodes[GetOriginSpot(originB, sideB, shapeMargin)]; + var path = new AStarPathfinder().GetPath(nodeA, nodeB); - var path = ShortestPath(graph, origin, destination); - if (path.Length > 0) + if (path.Count > 0) { - return SimplifyPath(path); + return path.ToArray(); } else { @@ -100,21 +138,6 @@ public static Point[] Orthogonal(Diagram _, BaseLinkModel link) } } - private static Point GetOriginSpot(Point p, PortAlignment alignment, double shapeMargin) - { - return alignment switch - { - PortAlignment.Top => p.Add(0, -shapeMargin), - PortAlignment.Right => p.Add(shapeMargin, 0), - PortAlignment.Bottom => p.Add(0, shapeMargin), - PortAlignment.Left => p.Add(-shapeMargin, 0), - _ => throw new NotImplementedException() - }; - } - - private static bool IsVerticalSide(PortAlignment alignment) - => alignment == PortAlignment.Top || alignment == PortAlignment.Bottom; // Add others - private static Grid RulersToGrid(List verticals, List horizontals, Rectangle bounds) { var result = new Grid(); @@ -156,20 +179,35 @@ private static Grid RulersToGrid(List verticals, List horizontal return result; } - private static List GridToSpots(Grid grid, Rectangle[] obstacles) + private static HashSet GridToSpots(Grid grid, Rectangle[] obstacles) { - bool obstacleCollision(Point p) => obstacles.Where(o => o.ContainsPoint(p)).Any(); + bool IsInsideObstacles(Point p) + { + foreach (var obstacle in obstacles) + { + if (obstacle.ContainsPoint(p)) + return true; + } - var gridPoints = new List(); - foreach (var (row, data) in grid.Data) + return false; + } + + void AddIfNotInsideObstacles(HashSet list, Point p) { + if (!IsInsideObstacles(p)) + { + list.Add(p); + } + } + var gridPoints = new HashSet(); + foreach (var (row, data) in grid.Data) + { var firstRow = row == 0; var lastRow = row == grid.Rows - 1; foreach (var (col, r) in data) { - var firstCol = col == 0; var lastCol = col == grid.Columns - 1; var nw = firstCol && firstRow; @@ -179,214 +217,68 @@ private static List GridToSpots(Grid grid, Rectangle[] obstacles) if (nw || ne || se || sw) { - gridPoints.Add(r.NorthWest); - gridPoints.Add(r.NorthEast); - gridPoints.Add(r.SouthWest); - gridPoints.Add(r.SouthEast); + AddIfNotInsideObstacles(gridPoints, r.NorthWest); + AddIfNotInsideObstacles(gridPoints, r.NorthEast); + AddIfNotInsideObstacles(gridPoints, r.SouthWest); + AddIfNotInsideObstacles(gridPoints, r.SouthEast); } else if (firstRow) { - gridPoints.Add(r.NorthWest); - gridPoints.Add(r.North); - gridPoints.Add(r.NorthEast); + AddIfNotInsideObstacles(gridPoints, r.NorthWest); + AddIfNotInsideObstacles(gridPoints, r.North); + AddIfNotInsideObstacles(gridPoints, r.NorthEast); } else if (lastRow) { - gridPoints.Add(r.SouthEast); - gridPoints.Add(r.South); - gridPoints.Add(r.SouthWest); + AddIfNotInsideObstacles(gridPoints, r.SouthEast); + AddIfNotInsideObstacles(gridPoints, r.South); + AddIfNotInsideObstacles(gridPoints, r.SouthWest); } else if (firstCol) { - gridPoints.Add(r.NorthWest); - gridPoints.Add(r.West); - gridPoints.Add(r.SouthWest); + AddIfNotInsideObstacles(gridPoints, r.NorthWest); + AddIfNotInsideObstacles(gridPoints, r.West); + AddIfNotInsideObstacles(gridPoints, r.SouthWest); } else if (lastCol) { - gridPoints.Add(r.NorthEast); - gridPoints.Add(r.East); - gridPoints.Add(r.SouthEast); + AddIfNotInsideObstacles(gridPoints, r.NorthEast); + AddIfNotInsideObstacles(gridPoints, r.East); + AddIfNotInsideObstacles(gridPoints, r.SouthEast); } else { - gridPoints.Add(r.NorthWest); - gridPoints.Add(r.North); - gridPoints.Add(r.NorthEast); - gridPoints.Add(r.East); - gridPoints.Add(r.SouthEast); - gridPoints.Add(r.South); - gridPoints.Add(r.SouthWest); - gridPoints.Add(r.West); - gridPoints.Add(r.Center); + AddIfNotInsideObstacles(gridPoints, r.NorthWest); + AddIfNotInsideObstacles(gridPoints, r.North); + AddIfNotInsideObstacles(gridPoints, r.NorthEast); + AddIfNotInsideObstacles(gridPoints, r.East); + AddIfNotInsideObstacles(gridPoints, r.SouthEast); + AddIfNotInsideObstacles(gridPoints, r.South); + AddIfNotInsideObstacles(gridPoints, r.SouthWest); + AddIfNotInsideObstacles(gridPoints, r.West); + AddIfNotInsideObstacles(gridPoints, r.Center); } } } // Reduce repeated points and filter out those who touch shapes - return ReducePoints(gridPoints).Where(p => !obstacleCollision(p)).ToList(); - } - - private static IEnumerable ReducePoints(List points) - { - var map = new Dictionary>(); - foreach (var p in points) - { - (var x, var y) = p; - if (!map.ContainsKey(y)) map.Add(y, new List()); - var arr = map[y]; - - if (!arr.Contains(x)) arr.Add(x); - } - - foreach (var (y, xs) in map) - { - foreach (var x in xs) - { - yield return new Point(x, y); - } - } + return gridPoints; } - private static PointGraph CreateGraph(List spots) - { - var hotXs = new List(); - var hotYs = new List(); - var graph = new PointGraph(); - - spots.ForEach(p => - { - (var x, var y) = p; - if (!hotXs.Contains(x)) hotXs.Add(x); - if (!hotYs.Contains(y)) hotYs.Add(y); - graph.Add(p); - }); - - hotXs.Sort(); - hotYs.Sort(); - - for (var i = 0; i < hotYs.Count; i++) - { - for (var j = 0; j < hotXs.Count; j++) - { - var b = new Point(hotXs[j], hotYs[i]); - if (!graph.Has(b)) continue; - - if (j > 0) - { - var a = new Point(hotXs[j - 1], hotYs[i]); - - if (graph.Has(a)) - { - graph.Connect(a, b); - graph.Connect(b, a); - } - } - - if (i > 0) - { - var a = new Point(hotXs[j], hotYs[i - 1]); - - if (graph.Has(a)) - { - graph.Connect(a, b); - graph.Connect(b, a); - } - } - } - } - - return graph; - } + private static bool IsVerticalSide(PortAlignment alignment) + => alignment == PortAlignment.Top || alignment == PortAlignment.Bottom; - private static Point ExtrudeCp(Point p, double margin, PortAlignment alignment) + private static Point GetOriginSpot(Point p, PortAlignment alignment, double shapeMargin) { return alignment switch { - PortAlignment.Top => p.Add(0, -margin), - PortAlignment.Right => p.Add(margin, 0), - PortAlignment.Bottom => p.Add(0, margin), - PortAlignment.Left => p.Add(-margin, 0), - _ => throw new NotImplementedException(), + PortAlignment.Top => p.Add(0, -shapeMargin), + PortAlignment.Right => p.Add(shapeMargin, 0), + PortAlignment.Bottom => p.Add(0, shapeMargin), + PortAlignment.Left => p.Add(-shapeMargin, 0), + _ => throw new NotImplementedException() }; } - - private static Point[] ShortestPath(PointGraph graph, Point origin, Point destination) - { - var originNode = graph.Get(origin); - var destinationNode = graph.Get(destination); - - if (originNode == null || destinationNode == null) - throw new Exception("Origin node or Destination node not found"); - - graph.CalculateShortestPathFromSource(graph, originNode); - return destinationNode.ShortestPath.Select(n => n.Data).ToArray(); - } - - private static Point[] SimplifyPath(Point[] points) - { - if (points.Length <= 2) - { - return points; - } - - var r = new List() { points[0] }; - for (var i = 1; i < points.Length; i++) - { - var cur = points[i]; - if (i == (points.Length - 1)) - { - r.Add(cur); - break; - } - - var prev = points[i - 1]; - var next = points[i + 1]; - var bend = GetBend(prev, cur, next); - - if (bend != "none") - { - r.Add(cur); - } - } - - return r.ToArray(); - } - - private static string GetBend(Point a, Point b, Point c) - { - var equalX = a.X == b.X && b.X == c.X; - var equalY = a.Y == b.Y && b.Y == c.Y; - var segment1Horizontal = a.Y == b.Y; - var segment1Vertical = a.X == b.X; - var segment2Horizontal = b.Y == c.Y; - var segment2Vertical = b.X == c.X; - - if (equalX || equalY) - { - return "none"; - } - - if ( - !(segment1Vertical || segment1Horizontal) || - !(segment2Vertical || segment2Horizontal) - ) - { - return "unknown"; - } - - if (segment1Horizontal && segment2Vertical) - { - return c.Y > b.Y ? "s" : "n"; - - } - else if (segment1Vertical && segment2Horizontal) - { - return c.X > b.X ? "e" : "w"; - } - - throw new Exception("Nope"); - } } class Grid @@ -412,161 +304,102 @@ public void Set(double row, double column, Rectangle rectangle) Data[row].Add(column, rectangle); } - - public Rectangle? Get(double row, double column) - { - if (!Data.ContainsKey(row)) - return null; - - if (!Data[row].ContainsKey(column)) - return null; - - return Data[row][column]; - } - - public Rectangle[] Rectangles() => Data.SelectMany(r => r.Value.Values).ToArray(); } - class PointGraph + class AStarPathfinder { - public readonly Dictionary> _index = new Dictionary>(); - - public void Add(Point p) - { - (var x, var y) = p; - var xs = x.ToInvariantString(); - var ys = y.ToInvariantString(); - - if (!_index.ContainsKey(xs)) - _index.Add(xs, new Dictionary()); - - if (!_index[xs].ContainsKey(ys)) - _index[xs].Add(ys, new PointNode(p)); - } - - private PointNode GetLowestDistanceNode(HashSet unsettledNodes) + public IReadOnlyList GetPath(Node start, Node goal) { - PointNode? lowestDistanceNode = null; - var lowestDistance = double.MaxValue; - foreach (var node in unsettledNodes) - { - var nodeDistance = node.Distance; - if (nodeDistance < lowestDistance) - { - lowestDistance = nodeDistance; - lowestDistanceNode = node; - } - } + var frontier = new PriorityQueue(); + frontier.Enqueue(start, 0); - return lowestDistanceNode!; - } - - public PointGraph CalculateShortestPathFromSource(PointGraph graph, PointNode source) - { - source.Distance = 0; - var settledNodes = new HashSet(); - var unsettledNodes = new HashSet + while (frontier.Count > 0) { - source - }; + var current = frontier.Dequeue(); - while (unsettledNodes.Count != 0) - { - var currentNode = GetLowestDistanceNode(unsettledNodes); - unsettledNodes.Remove(currentNode); + if (current.Equals(goal)) + break; - foreach ((var adjacentNode, var edgeWeight) in currentNode.AdjacentNodes) + foreach (var next in current.ConnectedTo) { - if (!settledNodes.Contains(adjacentNode)) + var newCost = current.Cost + 1.0; + if (current.Parent != null && IsChangeOfDirection(current.Parent.Position, current.Position, next.Position)) { - CalculateMinimumDistance(adjacentNode, edgeWeight, currentNode); - unsettledNodes.Add(adjacentNode); + newCost *= newCost; + newCost *= newCost; } + if (next.Cost == 0 || newCost < next.Cost) + { + next.Cost = newCost; + var priority = newCost + Heuristic(next.Position, goal.Position); + frontier.Enqueue(next, priority); + next.Parent = current; + } } - settledNodes.Add(currentNode); } - return graph; - } + var result = new List(); + var c = goal.Parent; - private void CalculateMinimumDistance(PointNode evaluationNode, double edgeWeight, PointNode sourceNode) - { - var sourceDistance = sourceNode.Distance; - var comingDirection = InferPathDirection(sourceNode); - var goingDirection = DirectionOfNodes(sourceNode, evaluationNode); - var changingDirection = comingDirection != null && goingDirection != null && comingDirection != goingDirection; - var extraWeigh = changingDirection ? Math.Pow(edgeWeight + 1, 2) : 0; + while (c != null && c != start) + { + result.Insert(0, c.Position); + c = c.Parent; + } - if (sourceDistance + edgeWeight + extraWeigh < evaluationNode.Distance) + if (c == start) + { + return result; + } + else { - evaluationNode.Distance = sourceDistance + edgeWeight + extraWeigh; - var shortestPath = new List(); - shortestPath.AddRange(sourceNode.ShortestPath); - shortestPath.Add(sourceNode); - evaluationNode.ShortestPath = shortestPath; + return Array.Empty(); } } - private char? DirectionOf(Point a, Point b) + private static bool IsChangeOfDirection(Point a, Point b, Point c) { - if (a.X == b.X) return 'h'; - else if (a.Y == b.Y) return 'v'; - return null; - } + if (a.X == b.X && b.X != c.X) + return true; - private char? DirectionOfNodes(PointNode a, PointNode b) => DirectionOf(a.Data, b.Data); - - private char? InferPathDirection(PointNode node) - { - if (node.ShortestPath.Count == 0) - return null; + if (a.Y == b.Y && b.Y != c.Y) + return true; - return DirectionOfNodes(node.ShortestPath[node.ShortestPath.Count - 1], node); + return false; } - public void Connect(Point a, Point b) + private static double Heuristic(Point a, Point b) { - var nodeA = Get(a); - var nodeB = Get(b); - - if (nodeA == null || nodeB == null) - return; - - nodeA.AdjacentNodes.Add(nodeB, a.DistanceTo(b)); + return Math.Abs(a.X - b.X) + Math.Abs(a.Y - b.Y); } + } - public bool Has(Point p) + class Node + { + public Node(Point position) { - (var x, var y) = p; - var xs = x.ToInvariantString(); - var ys = y.ToInvariantString(); - return _index.ContainsKey(xs) && _index[xs].ContainsKey(ys); + Position = position; + ConnectedTo = new List(); } - public PointNode? Get(Point p) - { - (var x, var y) = p; - var xs = x.ToInvariantString(); - var ys = y.ToInvariantString(); + public Point Position { get; } + public List ConnectedTo { get; } - if (_index.ContainsKey(xs) && _index[xs].ContainsKey(ys)) - return _index[xs][ys]; + public double Cost { get; internal set; } + public Node? Parent { get; internal set; } - return null; + public override bool Equals(object? obj) + { + if (obj is not Node item) + return false; + + return Position.Equals(item.Position); } - } - class PointNode - { - public PointNode(Point data) + public override int GetHashCode() { - Data = data; + return Position.GetHashCode(); } - - public Point Data { get; } - public double Distance { get; set; } = double.MaxValue; - public List ShortestPath { get; set; } = new List(); - public Dictionary AdjacentNodes { get; set; } = new Dictionary(); } } From b7f173655a075c24ba79606bb6a4d98391b014b4 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 3 Nov 2022 09:29:32 +0100 Subject: [PATCH 129/193] Update Routers.Orthogonal.cs --- src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs index 9b66518de..1608f726f 100644 --- a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs +++ b/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs @@ -88,8 +88,6 @@ public static Point[] Orthogonal(Diagram _, BaseLinkModel link) ys.Sort(); xs.Sort(); - var cc = sw.Elapsed.TotalMilliseconds; - var nodes = spots.ToDictionary(p => p, p => new Node(p)); for (var i = 0; i < ys.Count; i++) From 973128609e0a64ff9cf4c7a787800efba0314880 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 7 Nov 2022 12:08:15 +0100 Subject: [PATCH 130/193] Convert Routers & PathGenerators to classes instead of functions with delegates --- samples/SharedDemo/Demos/DevTests.razor | 5 +- .../Demos/Links/PathGeneratorsDemo.razor.cs | 11 +- .../Demos/Links/RoutersDemo.razor.cs | 9 +- .../Demos/Links/VerticesDemo.razor.cs | 6 +- samples/SharedDemo/Demos/Simple.razor.cs | 6 +- src/Blazor.Diagrams.Core/Delegates.cs | 5 - .../Models/Base/BaseLinkModel.cs | 191 +++++++++--------- .../Options/DiagramLinkOptions.cs | 6 +- ...thGenerators.Utils.cs => PathGenerator.cs} | 13 +- ...ators.Smooth.cs => SmoothPathGenerator.cs} | 23 ++- ...s.Straight.cs => StraightPathGenerator.cs} | 6 +- .../{Routers.Normal.cs => NormalRouter.cs} | 6 +- ...ters.Orthogonal.cs => OrthogonalRouter.cs} | 30 +-- .../Routers/{Routers.Utils.cs => Router.cs} | 9 +- 14 files changed, 173 insertions(+), 153 deletions(-) rename src/Blazor.Diagrams.Core/PathGenerators/{PathGenerators.Utils.cs => PathGenerator.cs} (65%) rename src/Blazor.Diagrams.Core/PathGenerators/{PathGenerators.Smooth.cs => SmoothPathGenerator.cs} (85%) rename src/Blazor.Diagrams.Core/PathGenerators/{PathGenerators.Straight.cs => StraightPathGenerator.cs} (80%) rename src/Blazor.Diagrams.Core/Routers/{Routers.Normal.cs => NormalRouter.cs} (56%) rename src/Blazor.Diagrams.Core/Routers/{Routers.Orthogonal.cs => OrthogonalRouter.cs} (95%) rename src/Blazor.Diagrams.Core/Routers/{Routers.Utils.cs => Router.cs} (80%) diff --git a/samples/SharedDemo/Demos/DevTests.razor b/samples/SharedDemo/Demos/DevTests.razor index 725004669..1957abd4d 100644 --- a/samples/SharedDemo/Demos/DevTests.razor +++ b/samples/SharedDemo/Demos/DevTests.razor @@ -3,6 +3,7 @@ @using Blazor.Diagrams @using Blazor.Diagrams.Core.Anchors @using Blazor.Diagrams.Core.Controls +@using Blazor.Diagrams.Core.PathGenerators @using Blazor.Diagrams.Core.Positions @using Blazor.Diagrams.Core.Controls.Default @@ -37,7 +38,7 @@ var link1 = new LinkModel(new DynamicAnchor(node1, providers1), new DynamicAnchor(node2, providers2)) { - PathGenerator = PathGenerators.Smooth, + PathGenerator = new SmoothPathGenerator(), SourceMarker = LinkMarker.Arrow, TargetMarker = LinkMarker.Arrow }; @@ -46,7 +47,7 @@ _blazorDiagram.Links.Add(link1); var link2 = _blazorDiagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node3), new LinkAnchor(link1, 0.5)) { - PathGenerator = PathGenerators.Straight, + PathGenerator = new StraightPathGenerator(), SourceMarker = LinkMarker.Arrow, TargetMarker = LinkMarker.Arrow }); diff --git a/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs b/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs index b8892b30c..a9d1eebff 100644 --- a/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs @@ -1,7 +1,8 @@ using Blazor.Diagrams; -using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Blazor.Diagrams.Core.Routers; namespace SharedDemo.Demos.Links { @@ -31,15 +32,15 @@ private void InitializeDiagram() var link1 = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) { - Router = Routers.Normal, - PathGenerator = PathGenerators.Straight + Router = new NormalRouter(), + PathGenerator = new StraightPathGenerator() }; link1.Labels.Add(new LinkLabelModel(link1, "Straight")); var link2 = new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left)) { - Router = Routers.Normal, - PathGenerator = PathGenerators.Smooth + Router = new NormalRouter(), + PathGenerator = new SmoothPathGenerator() }; link2.Labels.Add(new LinkLabelModel(link2, "Smooth")); diff --git a/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs b/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs index 1e5581e0c..fe4d3f4d3 100644 --- a/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs @@ -1,7 +1,8 @@ using Blazor.Diagrams; -using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Blazor.Diagrams.Core.Routers; namespace SharedDemo.Demos.Links { @@ -31,14 +32,14 @@ private void InitializeDiagram() var link1 = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) { - Router = Routers.Normal + Router = new NormalRouter() }; link1.Labels.Add(new LinkLabelModel(link1, "Normal")); var link2 = new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left)) { - Router = Routers.Orthogonal, - PathGenerator = PathGenerators.Straight // Smooth results in weird looking links + Router = new OrthogonalRouter(), + PathGenerator = new StraightPathGenerator() // Smooth results in weird looking links }; link2.Labels.Add(new LinkLabelModel(link2, "Orthogonal")); diff --git a/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs b/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs index d17146860..ab43998da 100644 --- a/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs @@ -1,7 +1,7 @@ using Blazor.Diagrams; -using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; namespace SharedDemo.Demos.Links { @@ -31,7 +31,7 @@ private void InitializeDiagram() var link1 = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) { - PathGenerator = PathGenerators.Straight, + PathGenerator = new StraightPathGenerator(), Segmentable = true }; link1.Labels.Add(new LinkLabelModel(link1, "Content")); @@ -40,7 +40,7 @@ private void InitializeDiagram() var link2 = new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left)) { - PathGenerator = PathGenerators.Smooth, // default + PathGenerator = new SmoothPathGenerator(), // default Segmentable = true }; link2.Labels.Add(new LinkLabelModel(link2, "Content")); diff --git a/samples/SharedDemo/Demos/Simple.razor.cs b/samples/SharedDemo/Demos/Simple.razor.cs index 8c88e3bbb..84dc4651a 100644 --- a/samples/SharedDemo/Demos/Simple.razor.cs +++ b/samples/SharedDemo/Demos/Simple.razor.cs @@ -2,6 +2,8 @@ using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Blazor.Diagrams.Core.Routers; using Microsoft.AspNetCore.Components; namespace SharedDemo @@ -26,8 +28,8 @@ protected override void OnInitialized() }); BlazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Right)) { - Router = Routers.Orthogonal, - PathGenerator = PathGenerators.Straight, + Router = new OrthogonalRouter(), + PathGenerator = new StraightPathGenerator(), SourceMarker = LinkMarker.Arrow, TargetMarker = LinkMarker.Arrow }); diff --git a/src/Blazor.Diagrams.Core/Delegates.cs b/src/Blazor.Diagrams.Core/Delegates.cs index f0cac9bb8..b71be543c 100644 --- a/src/Blazor.Diagrams.Core/Delegates.cs +++ b/src/Blazor.Diagrams.Core/Delegates.cs @@ -1,14 +1,9 @@ using Blazor.Diagrams.Core.Anchors; -using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Core { - public delegate Point[] Router(Diagram diagram, BaseLinkModel link); - - public delegate PathGeneratorResult PathGenerator(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target); - public delegate BaseLinkModel? LinkFactory(Diagram diagram, ILinkable source, Anchor targetAnchor); public delegate Anchor AnchorFactory(Diagram diagram, BaseLinkModel link, ILinkable model); diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index f6273361f..50c6587eb 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -3,124 +3,125 @@ using System; using System.Collections.Generic; using SvgPathProperties; +using Blazor.Diagrams.Core.PathGenerators; +using Blazor.Diagrams.Core.Routers; -namespace Blazor.Diagrams.Core.Models.Base +namespace Blazor.Diagrams.Core.Models.Base; + +public abstract class BaseLinkModel : SelectableModel, IHasBounds, ILinkable { - public abstract class BaseLinkModel : SelectableModel, IHasBounds, ILinkable - { - private readonly List _links = new(); - - public event Action? SourceChanged; - public event Action? TargetChanged; + private readonly List _links = new(); + + public event Action? SourceChanged; + public event Action? TargetChanged; - protected BaseLinkModel(Anchor source, Anchor target) - { - Source = source; - Target = target; - } + protected BaseLinkModel(Anchor source, Anchor target) + { + Source = source; + Target = target; + } - protected BaseLinkModel(string id, Anchor source, Anchor target) : base(id) - { - Source = source; - Target = target; - } + protected BaseLinkModel(string id, Anchor source, Anchor target) : base(id) + { + Source = source; + Target = target; + } - public Anchor Source { get; private set; } - public Anchor Target { get; private set; } - public Diagram? Diagram { get; internal set; } - public PathGeneratorResult GeneratedPathResult { get; private set; } = PathGeneratorResult.Empty; - public SvgPath[] Paths => GeneratedPathResult.Paths; - public bool IsAttached => Source is not PositionAnchor && Target is not PositionAnchor; - public Router? Router { get; set; } - public PathGenerator? PathGenerator { get; set; } - public LinkMarker? SourceMarker { get; set; } - public LinkMarker? TargetMarker { get; set; } - public bool Segmentable { get; set; } = false; - public List Vertices { get; } = new(); - public List Labels { get; } = new(); - public IReadOnlyList Links => _links; - - public override void Refresh() - { - GeneratePath(); - base.Refresh(); - } + public Anchor Source { get; private set; } + public Anchor Target { get; private set; } + public Diagram? Diagram { get; internal set; } + public PathGeneratorResult GeneratedPathResult { get; private set; } = PathGeneratorResult.Empty; + public SvgPath[] Paths => GeneratedPathResult.Paths; + public bool IsAttached => Source is not PositionAnchor && Target is not PositionAnchor; + public Router? Router { get; set; } + public PathGenerator? PathGenerator { get; set; } + public LinkMarker? SourceMarker { get; set; } + public LinkMarker? TargetMarker { get; set; } + public bool Segmentable { get; set; } = false; + public List Vertices { get; } = new(); + public List Labels { get; } = new(); + public IReadOnlyList Links => _links; + + public override void Refresh() + { + GeneratePath(); + base.Refresh(); + } - public void RefreshLinks() + public void RefreshLinks() + { + foreach (var link in Links) { - foreach (var link in Links) - { - link.Refresh(); - } + link.Refresh(); } + } - public void SetSource(Anchor anchor) - { - ArgumentNullException.ThrowIfNull(anchor, nameof(anchor)); - - if (Source == anchor) - return; + public void SetSource(Anchor anchor) + { + ArgumentNullException.ThrowIfNull(anchor, nameof(anchor)); - var old = Source; - Source = anchor; - SourceChanged?.Invoke(this, old, Source); - } + if (Source == anchor) + return; - public void SetTarget(Anchor anchor) - { - if (Target == anchor) - return; + var old = Source; + Source = anchor; + SourceChanged?.Invoke(this, old, Source); + } - var old = Target; - Target = anchor; - TargetChanged?.Invoke(this, old, Target); - } + public void SetTarget(Anchor anchor) + { + if (Target == anchor) + return; - public Rectangle? GetBounds() - { - if (Paths.Length == 0) - return null; + var old = Target; + Target = anchor; + TargetChanged?.Invoke(this, old, Target); + } - var minX = double.PositiveInfinity; - var minY = double.PositiveInfinity; - var maxX = double.NegativeInfinity; - var maxY = double.NegativeInfinity; + public Rectangle? GetBounds() + { + if (Paths.Length == 0) + return null; - foreach (var path in Paths) - { - var bbox = path.GetBBox(); - minX = Math.Min(minX, bbox.Left); - minY = Math.Min(minY, bbox.Top); - maxX = Math.Max(maxX, bbox.Right); - maxY = Math.Max(maxY, bbox.Bottom); - } + var minX = double.PositiveInfinity; + var minY = double.PositiveInfinity; + var maxX = double.NegativeInfinity; + var maxY = double.NegativeInfinity; - return new Rectangle(minX, minY, maxX, maxY); + foreach (var path in Paths) + { + var bbox = path.GetBBox(); + minX = Math.Min(minX, bbox.Left); + minY = Math.Min(minY, bbox.Top); + maxX = Math.Max(maxX, bbox.Right); + maxY = Math.Max(maxY, bbox.Bottom); } - public bool CanAttachTo(ILinkable other) => true; + return new Rectangle(minX, minY, maxX, maxY); + } - private void GeneratePath() + public bool CanAttachTo(ILinkable other) => true; + + private void GeneratePath() + { + if (Diagram != null) { - if (Diagram != null) + var router = Router ?? Diagram.Options.Links.DefaultRouter; + var pathGenerator = PathGenerator ?? Diagram.Options.Links.DefaultPathGenerator; + var route = router.GetRoute(Diagram, this); + var source = Source.GetPosition(this, route); + var target = Target.GetPosition(this, route); + if (source != null && target != null) { - var router = Router ?? Diagram.Options.Links.DefaultRouter; - var pathGenerator = PathGenerator ?? Diagram.Options.Links.DefaultPathGenerator; - var route = router(Diagram, this); - var source = Source.GetPosition(this, route); - var target = Target.GetPosition(this, route); - if (source != null && target != null) - { - GeneratedPathResult = pathGenerator(Diagram, this, route, source, target); - return; - } + GeneratedPathResult = pathGenerator.GetResult(Diagram, this, route, source, target); + return; } - - GeneratedPathResult = PathGeneratorResult.Empty; } - void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); - - void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link); + GeneratedPathResult = PathGeneratorResult.Empty; } + + void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); + + void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link); } diff --git a/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs index 9132348a5..5d2b2834e 100644 --- a/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs +++ b/src/Blazor.Diagrams.Core/Options/DiagramLinkOptions.cs @@ -1,6 +1,8 @@ using System; using Blazor.Diagrams.Core.Anchors; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Blazor.Diagrams.Core.Routers; namespace Blazor.Diagrams.Core.Options; @@ -8,8 +10,8 @@ public class DiagramLinkOptions { private double _snappingRadius = 50; - public Router DefaultRouter { get; set; } = Routers.Normal; - public PathGenerator DefaultPathGenerator { get; set; } = PathGenerators.Smooth; + public Router DefaultRouter { get; set; } = new NormalRouter(); + public PathGenerator DefaultPathGenerator { get; set; } = new SmoothPathGenerator(); public bool EnableSnapping { get; set; } = false; public bool RequireTarget { get; set; } = true; diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Utils.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerator.cs similarity index 65% rename from src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Utils.cs rename to src/Blazor.Diagrams.Core/PathGenerators/PathGenerator.cs index 55933276f..0921f8cde 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Utils.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerator.cs @@ -1,11 +1,14 @@ using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; using System; -namespace Blazor.Diagrams.Core +namespace Blazor.Diagrams.Core.PathGenerators { - public static partial class PathGenerators + public abstract class PathGenerator { - public static double SourceMarkerAdjustement(Point[] route, double markerWidth) + public abstract PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target); + + protected static double SourceMarkerAdjustement(Point[] route, double markerWidth) { var angleInRadians = Math.Atan2(route[1].Y - route[0].Y, route[1].X - route[0].X) + Math.PI; var xChange = markerWidth * Math.Cos(angleInRadians); @@ -14,7 +17,7 @@ public static double SourceMarkerAdjustement(Point[] route, double markerWidth) return angleInRadians * 180 / Math.PI; } - public static double TargetMarkerAdjustement(Point[] route, double markerWidth) + protected static double TargetMarkerAdjustement(Point[] route, double markerWidth) { var angleInRadians = Math.Atan2(route[^1].Y - route[^2].Y, route[^1].X - route[^2].X); var xChange = markerWidth * Math.Cos(angleInRadians); @@ -23,7 +26,7 @@ public static double TargetMarkerAdjustement(Point[] route, double markerWidth) return angleInRadians * 180 / Math.PI; } - public static Point[] ConcatRouteAndSourceAndTarget(Point[] route, Point source, Point target) + protected static Point[] ConcatRouteAndSourceAndTarget(Point[] route, Point source, Point target) { var result = new Point[route.Length + 2]; result[0] = source; diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs b/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs similarity index 85% rename from src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs rename to src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs index 4db5942f6..a3c061dff 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Smooth.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs @@ -2,16 +2,21 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -using System; using SvgPathProperties; +using System; -namespace Blazor.Diagrams.Core +namespace Blazor.Diagrams.Core.PathGenerators { - public static partial class PathGenerators + public class SmoothPathGenerator : PathGenerator { - private const double _margin = 125; + private readonly double _margin; + + public SmoothPathGenerator(double margin = 125) + { + _margin = margin; + } - public static PathGeneratorResult Smooth(Diagram _, BaseLinkModel link, Point[] route, Point source, Point target) + public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target) { route = ConcatRouteAndSourceAndTarget(route, source, target); @@ -39,7 +44,7 @@ public static PathGeneratorResult Smooth(Diagram _, BaseLinkModel link, Point[] return new PathGeneratorResult(new[] { path }, sourceAngle, route[0], targetAngle, route[^1]); } - private static PathGeneratorResult CurveThroughPoints(Point[] route, BaseLinkModel link) + private PathGeneratorResult CurveThroughPoints(Point[] route, BaseLinkModel link) { double? sourceAngle = null; double? targetAngle = null; @@ -68,7 +73,7 @@ private static PathGeneratorResult CurveThroughPoints(Point[] route, BaseLinkMod return new PathGeneratorResult(paths, sourceAngle, route[0], targetAngle, route[^1]); } - private static Point[] GetRouteWithCurvePoints(BaseLinkModel link, Point[] route) + private Point[] GetRouteWithCurvePoints(BaseLinkModel link, Point[] route) { var cX = (route[0].X + route[1].X) / 2; var cY = (route[0].Y + route[1].Y) / 2; @@ -77,7 +82,7 @@ private static Point[] GetRouteWithCurvePoints(BaseLinkModel link, Point[] route return new[] { route[0], curvePointA, curvePointB, route[1] }; } - private static Point GetCurvePoint(Point[] route, Anchor anchor, double pX, double pY, double cX, double cY, bool first) + private Point GetCurvePoint(Point[] route, Anchor anchor, double pX, double pY, double cX, double cY, bool first) { if (anchor is PositionAnchor) return new Point(cX, cY); @@ -103,7 +108,7 @@ private static Point GetCurvePoint(Point[] route, Anchor anchor, double pX, doub } } - private static Point GetCurvePoint(double pX, double pY, double cX, double cY, PortAlignment? alignment) + private Point GetCurvePoint(double pX, double pY, double cX, double cY, PortAlignment? alignment) { var margin = Math.Min(_margin, Math.Pow(Math.Pow(pX - cX, 2) + Math.Pow(pY - cY, 2), .5)); return alignment switch diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs similarity index 80% rename from src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs rename to src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs index f22fab8f6..7a44307d6 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerators.Straight.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs @@ -2,11 +2,11 @@ using Blazor.Diagrams.Core.Models.Base; using SvgPathProperties; -namespace Blazor.Diagrams.Core +namespace Blazor.Diagrams.Core.PathGenerators { - public static partial class PathGenerators + public class StraightPathGenerator : PathGenerator { - public static PathGeneratorResult Straight(Diagram _, BaseLinkModel link, Point[] route, Point source, Point target) + public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target) { route = ConcatRouteAndSourceAndTarget(route, source, target); double? sourceAngle = null; diff --git a/src/Blazor.Diagrams.Core/Routers/Routers.Normal.cs b/src/Blazor.Diagrams.Core/Routers/NormalRouter.cs similarity index 56% rename from src/Blazor.Diagrams.Core/Routers/Routers.Normal.cs rename to src/Blazor.Diagrams.Core/Routers/NormalRouter.cs index e27f84312..f20a1ac31 100644 --- a/src/Blazor.Diagrams.Core/Routers/Routers.Normal.cs +++ b/src/Blazor.Diagrams.Core/Routers/NormalRouter.cs @@ -2,11 +2,11 @@ using Blazor.Diagrams.Core.Models.Base; using System.Linq; -namespace Blazor.Diagrams.Core +namespace Blazor.Diagrams.Core.Routers { - public static partial class Routers + public class NormalRouter : Router { - public static Point[] Normal(Diagram _, BaseLinkModel link) + public override Point[] GetRoute(Diagram diagram, BaseLinkModel link) { return link.Vertices.Select(v => v.Position).ToArray(); } diff --git a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs b/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs similarity index 95% rename from src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs rename to src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs index 1608f726f..3cf4f653b 100644 --- a/src/Blazor.Diagrams.Core/Routers/Routers.Orthogonal.cs +++ b/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs @@ -1,20 +1,26 @@ -using Blazor.Diagrams.Core.Anchors; -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; +using System.Collections.Generic; using System; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Anchors; using System.Linq; -using System.Collections.Generic; -// Originally based from https://gist.github.com/menendezpoo/4a8894c152383b9d7a870c24a04447e4 with some tweaks (especially the use of a*) -namespace Blazor.Diagrams.Core +namespace Blazor.Diagrams.Core.Routers { - public static partial class Routers + public class OrthogonalRouter : Router { - public static Point[] Orthogonal(Diagram _, BaseLinkModel link) + private readonly Router _fallbackRouter; + + public OrthogonalRouter(Router? fallbackRouter = null) + { + _fallbackRouter = fallbackRouter ?? new NormalRouter(); + } + + public override Point[] GetRoute(Diagram diagram, BaseLinkModel link) { if (!link.IsAttached) - return Routers.Normal(_, link); + return _fallbackRouter.GetRoute(diagram, link); if (link.Source is not SinglePortAnchor spa1) throw new DiagramsException("Orthogonal router doesn't work with port-less links yet"); @@ -24,7 +30,7 @@ public static Point[] Orthogonal(Diagram _, BaseLinkModel link) var sourcePort = spa1.Port; if (targetAnchor == null || sourcePort.Parent.Size == null || targetAnchor.Port.Parent.Size == null) - return Normal(_, link); + return _fallbackRouter.GetRoute(diagram, link); var targetPort = targetAnchor.Port; @@ -132,7 +138,7 @@ public static Point[] Orthogonal(Diagram _, BaseLinkModel link) } else { - return Normal(_, link); + return _fallbackRouter.GetRoute(diagram, link); } } @@ -347,7 +353,7 @@ public IReadOnlyList GetPath(Node start, Node goal) } if (c == start) - { + { return result; } else diff --git a/src/Blazor.Diagrams.Core/Routers/Routers.Utils.cs b/src/Blazor.Diagrams.Core/Routers/Router.cs similarity index 80% rename from src/Blazor.Diagrams.Core/Routers/Routers.Utils.cs rename to src/Blazor.Diagrams.Core/Routers/Router.cs index 61fc7b5d8..d5a7d1d53 100644 --- a/src/Blazor.Diagrams.Core/Routers/Routers.Utils.cs +++ b/src/Blazor.Diagrams.Core/Routers/Router.cs @@ -1,11 +1,14 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core +namespace Blazor.Diagrams.Core.Routers { - public static partial class Routers + public abstract class Router { - public static Point GetPortPositionBasedOnAlignment(PortModel port) + public abstract Point[] GetRoute(Diagram diagram, BaseLinkModel link); + + protected static Point GetPortPositionBasedOnAlignment(PortModel port) { var pt = port.Position; switch (port.Alignment) From 230aa6d5e8eabf7f998f71240cd1feccfaf8ebf0 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 13 Nov 2022 20:49:00 +0100 Subject: [PATCH 131/193] Add DistanceTo overload that takes into account the x and y instead of a Point --- src/Blazor.Diagrams.Core/Geometry/Point.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Blazor.Diagrams.Core/Geometry/Point.cs b/src/Blazor.Diagrams.Core/Geometry/Point.cs index 09e899059..0873a1933 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Point.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Point.cs @@ -27,6 +27,7 @@ public Point Lerp(Point other, double t) public Point Substract(double x, double y) => new(X - x, Y - y); public double DistanceTo(Point other) => Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2)); + public double DistanceTo(double x, double y) => Math.Sqrt(Math.Pow(X - x, 2) + Math.Pow(Y - y, 2)); public static Point operator -(Point a, Point b) => new(a.X - b.X, a.Y - b.Y); public static Point operator +(Point a, Point b) => new(a.X + b.X, a.Y + b.Y); From b2d233cea3ddbd624ee0c5aea973dfcdb9a9f830 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 13 Nov 2022 20:53:00 +0100 Subject: [PATCH 132/193] Add FullPath to PathGeneratorResult and Fix issue where only parts of a link would have a hover effect instead of all --- .../Models/Base/BaseLinkModel.cs | 12 +++--- .../PathGenerators/PathGeneratorResult.cs | 7 ++-- .../PathGenerators/SmoothPathGenerator.cs | 6 ++- .../PathGenerators/StraightPathGenerator.cs | 26 +++++++++++-- .../Positions/LinkPathPositionProvider.cs | 21 ++--------- .../Components/LinkWidget.razor | 35 ++++++++++-------- .../Components/LinkWidget.razor.cs | 34 ++++++++++++++++- .../Components/Renderers/LinkLabelRenderer.cs | 21 +++-------- src/Blazor.Diagrams/wwwroot/style.css | 4 -- src/Blazor.Diagrams/wwwroot/style.min.css | 2 +- src/Blazor.Diagrams/wwwroot/style.min.css.gz | Bin 436 -> 404 bytes 11 files changed, 96 insertions(+), 72 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index 50c6587eb..75f420cfe 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -2,7 +2,6 @@ using Blazor.Diagrams.Core.Geometry; using System; using System.Collections.Generic; -using SvgPathProperties; using Blazor.Diagrams.Core.PathGenerators; using Blazor.Diagrams.Core.Routers; @@ -30,8 +29,7 @@ protected BaseLinkModel(string id, Anchor source, Anchor target) : base(id) public Anchor Source { get; private set; } public Anchor Target { get; private set; } public Diagram? Diagram { get; internal set; } - public PathGeneratorResult GeneratedPathResult { get; private set; } = PathGeneratorResult.Empty; - public SvgPath[] Paths => GeneratedPathResult.Paths; + public PathGeneratorResult? PathGeneratorResult { get; private set; } public bool IsAttached => Source is not PositionAnchor && Target is not PositionAnchor; public Router? Router { get; set; } public PathGenerator? PathGenerator { get; set; } @@ -80,7 +78,7 @@ public void SetTarget(Anchor anchor) public Rectangle? GetBounds() { - if (Paths.Length == 0) + if (PathGeneratorResult == null) return null; var minX = double.PositiveInfinity; @@ -88,7 +86,7 @@ public void SetTarget(Anchor anchor) var maxX = double.NegativeInfinity; var maxY = double.NegativeInfinity; - foreach (var path in Paths) + foreach (var path in PathGeneratorResult.Paths) { var bbox = path.GetBBox(); minX = Math.Min(minX, bbox.Left); @@ -113,12 +111,12 @@ private void GeneratePath() var target = Target.GetPosition(this, route); if (source != null && target != null) { - GeneratedPathResult = pathGenerator.GetResult(Diagram, this, route, source, target); + PathGeneratorResult = pathGenerator.GetResult(Diagram, this, route, source, target); return; } } - GeneratedPathResult = PathGeneratorResult.Empty; + PathGeneratorResult = null; } void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGeneratorResult.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGeneratorResult.cs index 43a7f6787..b6eb69aa0 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGeneratorResult.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGeneratorResult.cs @@ -1,16 +1,14 @@ using Blazor.Diagrams.Core.Geometry; using SvgPathProperties; -using System; namespace Blazor.Diagrams.Core { public class PathGeneratorResult { - public static PathGeneratorResult Empty { get; } = new(Array.Empty()); - - public PathGeneratorResult(SvgPath[] paths, double? sourceMarkerAngle = null, Point? sourceMarkerPosition = null, + public PathGeneratorResult(SvgPath fullPath, SvgPath[] paths, double? sourceMarkerAngle = null, Point? sourceMarkerPosition = null, double? targetMarkerAngle = null, Point? targetMarkerPosition = null) { + FullPath = fullPath; Paths = paths; SourceMarkerAngle = sourceMarkerAngle; SourceMarkerPosition = sourceMarkerPosition; @@ -18,6 +16,7 @@ public PathGeneratorResult(SvgPath[] paths, double? sourceMarkerAngle = null, Po TargetMarkerPosition = targetMarkerPosition; } + public SvgPath FullPath { get; } public SvgPath[] Paths { get; } public double? SourceMarkerAngle { get; } public Point? SourceMarkerPosition { get; } diff --git a/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs b/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs index a3c061dff..c8d0cc348 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs @@ -41,7 +41,7 @@ public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel lin .AddMoveTo(route[0].X, route[0].Y) .AddCubicBezierCurve(route[1].X, route[1].Y, route[2].X, route[2].Y, route[3].X, route[3].Y); - return new PathGeneratorResult(new[] { path }, sourceAngle, route[0], targetAngle, route[^1]); + return new PathGeneratorResult(path, Array.Empty(), sourceAngle, route[0], targetAngle, route[^1]); } private PathGeneratorResult CurveThroughPoints(Point[] route, BaseLinkModel link) @@ -61,16 +61,18 @@ private PathGeneratorResult CurveThroughPoints(Point[] route, BaseLinkModel link BezierSpline.GetCurveControlPoints(route, out var firstControlPoints, out var secondControlPoints); var paths = new SvgPath[firstControlPoints.Length]; + var fullPath = new SvgPath().AddMoveTo(route[0].X, route[0].Y); for (var i = 0; i < firstControlPoints.Length; i++) { var cp1 = firstControlPoints[i]; var cp2 = secondControlPoints[i]; + fullPath.AddCubicBezierCurve(cp1.X, cp1.Y, cp2.X, cp2.Y, route[i + 1].X, route[i + 1].Y); paths[i] = new SvgPath().AddMoveTo(route[i].X, route[i].Y).AddCubicBezierCurve(cp1.X, cp1.Y, cp2.X, cp2.Y, route[i + 1].X, route[i + 1].Y); } // Todo: adjust marker positions based on closest control points - return new PathGeneratorResult(paths, sourceAngle, route[0], targetAngle, route[^1]); + return new PathGeneratorResult(fullPath, paths, sourceAngle, route[0], targetAngle, route[^1]); } private Point[] GetRouteWithCurvePoints(BaseLinkModel link, Point[] route) diff --git a/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs index 7a44307d6..87994153c 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs @@ -1,6 +1,7 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; using SvgPathProperties; +using System; namespace Blazor.Diagrams.Core.PathGenerators { @@ -22,13 +23,30 @@ public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel lin targetAngle = TargetMarkerAdjustement(route, link.TargetMarker.Width); } - var paths = new SvgPath[route.Length - 1]; - for (var i = 0; i < route.Length - 1; i++) + if (link.Vertices.Count == 0) { - paths[i] = new SvgPath().AddMoveTo(route[i].X, route[i].Y).AddLineTo(route[i + 1].X, route[i + 1].Y); + var fullPath = new SvgPath().AddMoveTo(route[0].X, route[0].Y); + + for (var i = 0; i < route.Length - 1; i++) + { + fullPath.AddLineTo(route[i + 1].X, route[i + 1].Y); + } + + return new PathGeneratorResult(fullPath, Array.Empty(), sourceAngle, route[0], targetAngle, route[^1]); } + else + { + var paths = new SvgPath[route.Length - 1]; + var fullPath = new SvgPath().AddMoveTo(route[0].X, route[0].Y); - return new PathGeneratorResult(paths, sourceAngle, route[0], targetAngle, route[^1]); + for (var i = 0; i < route.Length - 1; i++) + { + fullPath.AddLineTo(route[i + 1].X, route[i + 1].Y); + paths[i] = new SvgPath().AddMoveTo(route[i].X, route[i].Y).AddLineTo(route[i + 1].X, route[i + 1].Y); + } + + return new PathGeneratorResult(fullPath, paths, sourceAngle, route[0], targetAngle, route[^1]); + } } } } diff --git a/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs b/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs index 606cb40b1..1890e3ca2 100644 --- a/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs @@ -1,4 +1,3 @@ -using System.Linq; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; @@ -22,11 +21,10 @@ public LinkPathPositionProvider(double distance, double offsetX = 0, double offs if (model is not BaseLinkModel link) throw new DiagramsException("LinkPathPositionProvider requires a link model"); - if (link.Paths.Length <= 0) + if (link.PathGeneratorResult == null) return null; - var totalLength = link.Paths.Sum(p => p.Length); - + var totalLength = link.PathGeneratorResult.FullPath.Length; var length = Distance switch { >= 0 and <= 1 => Distance * totalLength, @@ -34,19 +32,8 @@ public LinkPathPositionProvider(double distance, double offsetX = 0, double offs < 0 => totalLength + Distance }; - foreach (var path in link.Paths) - { - var pathLength = path.Length; - if (length <= pathLength) - { - var pt = path.GetPointAtLength(length); - return new Point(pt.X + OffsetX, pt.Y + OffsetY); - } - - length -= pathLength; - } - - return null; + var pt = link.PathGeneratorResult.FullPath.GetPointAtLength(length); + return new Point(pt.X + OffsetX, pt.Y + OffsetY); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor b/src/Blazor.Diagrams/Components/LinkWidget.razor index 6656294e9..04db67c86 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor @@ -1,26 +1,29 @@ @{ var color = Link.Selected ? Link.SelectedColor ?? BlazorDiagram.Options.Links.DefaultSelectedColor : Link.Color ?? BlazorDiagram.Options.Links.DefaultColor; - var result = Link.GeneratedPathResult; + var result = Link.PathGeneratorResult; + if (result == null) + return; + + var d = result.FullPath.ToString(); } -@for (var i = 0; i < result.Paths.Length; i++) -{ - var index = i; - var d = result.Paths[i].ToString(); - - +@if (Link.Vertices.Count == 0) +{ + @GetSelectionHelperPath(color, d, 0) +} +else +{ + @for (var i = 0; i < result.Paths.Length; i++) + { + d = result.Paths[i].ToString(); + var index = i; + @GetSelectionHelperPath(color, d, index) + } } @if (Link.SourceMarker != null && result.SourceMarkerAngle != null && result.SourceMarkerPosition != null) @@ -54,5 +57,5 @@ @foreach (var label in Link.Labels) { - + } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs index f03979dbe..6eb3a51a1 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs @@ -2,15 +2,37 @@ using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; +using System; namespace Blazor.Diagrams.Components; public partial class LinkWidget { - [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; + private bool _hovered; + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; [Parameter] public LinkModel Link { get; set; } = null!; + private RenderFragment GetSelectionHelperPath(string color, string d, int index) + { + return builder => + { + builder.OpenElement(0, "path"); + builder.AddAttribute(1, "class", "selection-helper"); + builder.AddAttribute(2, "stroke", color); + builder.AddAttribute(3, "stroke-width", 12); + builder.AddAttribute(4, "d", d); + builder.AddAttribute(5, "stroke-linecap", "butt"); + builder.AddAttribute(6, "stroke-opacity", _hovered ? 0.05 : 0); + builder.AddAttribute(7, "fill", "none"); + builder.AddAttribute(8, "onmouseenter", EventCallback.Factory.Create(this, OnMouseEnter)); + builder.AddAttribute(9, "onmouseleave", EventCallback.Factory.Create(this, OnMouseLeave)); + builder.AddAttribute(10, "onpointerdown", EventCallback.Factory.Create(this, e => OnPointerDown(e, index))); + builder.AddEventStopPropagationAttribute(11, "onpointerdown", Link.Segmentable); + builder.CloseElement(); + }; + } + private void OnPointerDown(PointerEventArgs e, int index) { if (!Link.Segmentable) @@ -20,6 +42,16 @@ private void OnPointerDown(PointerEventArgs e, int index) BlazorDiagram.TriggerPointerDown(vertex, e.ToCore()); } + private void OnMouseEnter(MouseEventArgs e) + { + _hovered = true; + } + + private void OnMouseLeave(MouseEventArgs e) + { + _hovered = false; + } + private LinkVertexModel CreateVertex(double clientX, double clientY, int index) { var rPt = BlazorDiagram.GetRelativeMousePoint(clientX, clientY); diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs index eb7b3e1fb..0ecefcefb 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs @@ -1,4 +1,5 @@ using System; +using System.IO; using System.Linq; using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; @@ -14,7 +15,7 @@ public class LinkLabelRenderer : ComponentBase, IDisposable { [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; [Parameter] public LinkLabelModel Label { get; set; } = null!; - [Parameter] public SvgPath[] Paths { get; set; } = null!; + [Parameter] public SvgPath Path { get; set; } = null!; public void Dispose() { @@ -58,8 +59,7 @@ private void OnLabelChanged(Model _) private Point? FindPosition() { - var totalLength = Paths.Sum(p => p.Length); - + var totalLength = Path.Length; var length = Label.Distance switch { <= 1 and >= 0 => Label.Distance.Value * totalLength, @@ -68,18 +68,7 @@ private void OnLabelChanged(Model _) _ => totalLength * (Label.Parent.Labels.IndexOf(Label) + 1) / (Label.Parent.Labels.Count + 1) }; - foreach (var path in Paths) - { - var pathLength = path.Length; - if (length < pathLength) - { - var pt = path.GetPointAtLength(length); - return new Point(pt.X, pt.Y); - } - - length -= pathLength; - } - - return null; + var pt = Path.GetPointAtLength(length); + return new Point(pt.X, pt.Y); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/style.css b/src/Blazor.Diagrams/wwwroot/style.css index fe3de9f2f..a3b81d994 100644 --- a/src/Blazor.Diagrams/wwwroot/style.css +++ b/src/Blazor.Diagrams/wwwroot/style.css @@ -54,10 +54,6 @@ cursor: pointer; } -.link path.selection-helper:hover { - stroke-opacity: 0.05; -} - .diagram-navigator { z-index: 10; } diff --git a/src/Blazor.Diagrams/wwwroot/style.min.css b/src/Blazor.Diagrams/wwwroot/style.min.css index d499cbbf2..87e09655e 100644 --- a/src/Blazor.Diagrams/wwwroot/style.min.css +++ b/src/Blazor.Diagrams/wwwroot/style.min.css @@ -1 +1 @@ -.diagram-canvas{width:100%;height:100%;position:relative;outline:none;overflow:hidden;cursor:-webkit-grab;cursor:grab;touch-action:none;}.diagram-svg-layer,.diagram-html-layer{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.html-layer,.svg-layer{position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.node{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.node.locked{cursor:pointer;}.link{pointer-events:visiblePainted;cursor:pointer;}.link path.selection-helper:hover{stroke-opacity:.05;}.diagram-navigator{z-index:10;}.diagram-navigator .current-view{position:absolute;border:2px solid #000;}.group{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.group .children{position:absolute;overflow:visible;pointer-events:none;}.link foreignObject.link-label{overflow:visible;pointer-events:none;}div.control{position:absolute;}.executable.control{pointer-events:all;cursor:pointer;} \ No newline at end of file +.diagram-canvas{width:100%;height:100%;position:relative;outline:none;overflow:hidden;cursor:-webkit-grab;cursor:grab;touch-action:none;}.diagram-svg-layer,.diagram-html-layer{top:0;left:0;right:0;bottom:0;position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.html-layer,.svg-layer{position:absolute;pointer-events:none;-webkit-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0;width:100%;height:100%;overflow:visible;}.node{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.node.locked{cursor:pointer;}.link{pointer-events:visiblePainted;cursor:pointer;}.diagram-navigator{z-index:10;}.diagram-navigator .current-view{position:absolute;border:2px solid #000;}.group{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.group .children{position:absolute;overflow:visible;pointer-events:none;}.link foreignObject.link-label{overflow:visible;pointer-events:none;}div.control{position:absolute;}.executable.control{pointer-events:all;cursor:pointer;} \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/style.min.css.gz b/src/Blazor.Diagrams/wwwroot/style.min.css.gz index 6c287dfd319c50ebc22d1cad44fc0ce875455963..39e00131529644c46f4d76339b60d85945b66808 100644 GIT binary patch literal 404 zcmV;F0c-vriwFP!000003eA#1Zi6roMX!QXbyp+WB^;u?g25A*#&{xoaMGfjtcxD0 zSLlQgS_N0?GAqot{ds@*Umu^jLEU=Yt46!1!?;JoUHMoP_f-ejc6?s%C?F%b(u38E z5h@zkqJz?rla~m7V`*P@XbiZj8GImLsy)Kr^t_Ps4ondlI%@)i!AD++2ru1+-UR|4Wyy%l^s@0uKwVaOFY9>FvD|7}9GLO`D zmWzcKYZVdpiMiC|4fyhDcgRFRlid}?$*J`;>~3a`?TJRRbZCvFNpP*2kSgSP=9!dB yI^>?~S0R`rsdWu@{GW-zm^b9ulTCmeb2vaVu$KG3j;%sn|JrY3o4DxJ1ONaHLBmu4 literal 436 zcmV;l0ZaZLiwFP!000003eA&IZk#X>hOdGY^({suQeWT@?G+53#gjE2WDnj=P)?SI z9;sL8z_K*jb)`P$3G>^Y`Tn2b_s6f&pzebnRHuE?aXz78TR&CRx5fhYmM`m(A~KS% z131l?prMH!dZ;~lc}Wlsj!v~jW574v6e5LMouEA;D{0VPwRO!jbyn$aExA^B-$7#P zmD8^f9(T#|;4a$CG}cw);K1?-D`3^M#7u)Mc1+zyaub6n=ox~71fJui*AW=cU{3}zd{>q!3S Date: Mon, 14 Nov 2022 18:35:51 +0100 Subject: [PATCH 133/193] Add path simplification in orthogonal router --- .../Routers/OrthogonalRouter.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs b/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs index 3cf4f653b..973e30469 100644 --- a/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs +++ b/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs @@ -130,10 +130,11 @@ public override Point[] GetRoute(Diagram diagram, BaseLinkModel link) var nodeA = nodes[GetOriginSpot(originA, sideA, shapeMargin)]; var nodeB = nodes[GetOriginSpot(originB, sideB, shapeMargin)]; - var path = new AStarPathfinder().GetPath(nodeA, nodeB); + var path = AStarPathfinder.GetPath(nodeA, nodeB); if (path.Count > 0) { + Console.WriteLine(string.Join(", ", path)); return path.ToArray(); } else @@ -310,9 +311,9 @@ public void Set(double row, double column, Rectangle rectangle) } } - class AStarPathfinder + static class AStarPathfinder { - public IReadOnlyList GetPath(Node start, Node goal) + public static IReadOnlyList GetPath(Node start, Node goal) { var frontier = new PriorityQueue(); frontier.Enqueue(start, 0); @@ -354,7 +355,7 @@ public IReadOnlyList GetPath(Node start, Node goal) if (c == start) { - return result; + return SimplifyPath(result); } else { @@ -362,6 +363,23 @@ public IReadOnlyList GetPath(Node start, Node goal) } } + private static List SimplifyPath(List path) + { + for (var i = path.Count - 2; i > 0; i--) + { + var prev = path[i + 1]; + var curr = path[i]; + var next = path[i - 1]; + + if ((prev.X == curr.X && curr.X == next.X) || (prev.Y == curr.Y && curr.Y == next.Y)) + { + path.RemoveAt(i); + } + } + + return path; + } + private static bool IsChangeOfDirection(Point a, Point b, Point c) { if (a.X == b.X && b.X != c.X) From 8b3c77eaa3c8f1f6db81b0049a40909a8a430bc9 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 14 Nov 2022 18:36:48 +0100 Subject: [PATCH 134/193] Use fallback router instead of throwing exception in OrthogonalRouter --- src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs b/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs index 973e30469..2b20c2f28 100644 --- a/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs +++ b/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs @@ -23,10 +23,10 @@ public override Point[] GetRoute(Diagram diagram, BaseLinkModel link) return _fallbackRouter.GetRoute(diagram, link); if (link.Source is not SinglePortAnchor spa1) - throw new DiagramsException("Orthogonal router doesn't work with port-less links yet"); + return _fallbackRouter.GetRoute(diagram, link); if (link.Target is not SinglePortAnchor targetAnchor) - throw new DiagramsException("Orthogonal router doesn't work with port-less links yet"); + return _fallbackRouter.GetRoute(diagram, link); var sourcePort = spa1.Port; if (targetAnchor == null || sourcePort.Parent.Size == null || targetAnchor.Port.Parent.Size == null) From 084f22ad875a1757ddee0cc1ebea88bc466490c1 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 14 Nov 2022 18:41:21 +0100 Subject: [PATCH 135/193] Add margin options to OrthogonalRouter --- src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs b/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs index 2b20c2f28..d2ded7d26 100644 --- a/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs +++ b/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs @@ -11,9 +11,13 @@ namespace Blazor.Diagrams.Core.Routers public class OrthogonalRouter : Router { private readonly Router _fallbackRouter; + private double _shapeMargin; + private double _globalMargin; - public OrthogonalRouter(Router? fallbackRouter = null) + public OrthogonalRouter(double shapeMargin = 10d, double globalMargin = 50d, Router? fallbackRouter = null) { + _shapeMargin = shapeMargin; + _globalMargin = globalMargin; _fallbackRouter = fallbackRouter ?? new NormalRouter(); } @@ -34,8 +38,8 @@ public override Point[] GetRoute(Diagram diagram, BaseLinkModel link) var targetPort = targetAnchor.Port; - var shapeMargin = 10; - var globalBoundsMargin = 50; + var shapeMargin = _shapeMargin; + var globalBoundsMargin = _globalMargin; var spots = new HashSet(); var verticals = new List(); var horizontals = new List(); @@ -134,7 +138,6 @@ public override Point[] GetRoute(Diagram diagram, BaseLinkModel link) if (path.Count > 0) { - Console.WriteLine(string.Join(", ", path)); return path.ToArray(); } else From d3af252f99eaddb57257036407984ea67f05fc5d Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 20 Nov 2022 23:24:28 +0100 Subject: [PATCH 136/193] Only keep bends in the output of the Orthogonal router --- .../Routers/OrthogonalRouter.cs | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs b/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs index d2ded7d26..50448e490 100644 --- a/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs +++ b/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs @@ -358,7 +358,23 @@ public static IReadOnlyList GetPath(Node start, Node goal) if (c == start) { - return SimplifyPath(result); + result = SimplifyPath(result); + + // In case of paths with one bend + if (result.Count > 2) + { + if (AreOnSameLine(result[^2], result[^1], goal.Position)) + { + result.RemoveAt(result.Count - 1); + } + + if (AreOnSameLine(start.Position, result[0], result[1])) + { + result.RemoveAt(0); + } + } + + return result; } else { @@ -366,6 +382,11 @@ public static IReadOnlyList GetPath(Node start, Node goal) } } + private static bool AreOnSameLine(Point prev, Point curr, Point next) + { + return (prev.X == curr.X && curr.X == next.X) || (prev.Y == curr.Y && curr.Y == next.Y); + } + private static List SimplifyPath(List path) { for (var i = path.Count - 2; i > 0; i--) @@ -374,7 +395,7 @@ private static List SimplifyPath(List path) var curr = path[i]; var next = path[i - 1]; - if ((prev.X == curr.X && curr.X == next.X) || (prev.Y == curr.Y && curr.Y == next.Y)) + if (AreOnSameLine(prev, curr, next)) { path.RemoveAt(i); } From 8d5df782d4b7303707e6494de1b7a47912d6e8a7 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 20 Nov 2022 23:25:42 +0100 Subject: [PATCH 137/193] Add radius option to StraightPathGenerator in order to generate rounded paths --- .../PathGenerators/PathGenerator.cs | 6 +- .../PathGenerators/SmoothPathGenerator.cs | 8 +-- .../PathGenerators/StraightPathGenerator.cs | 70 ++++++++++++++----- 3 files changed, 59 insertions(+), 25 deletions(-) diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerator.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerator.cs index 0921f8cde..afe745c30 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerator.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerator.cs @@ -8,8 +8,8 @@ public abstract class PathGenerator { public abstract PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target); - protected static double SourceMarkerAdjustement(Point[] route, double markerWidth) - { + protected static double AdjustRouteForSourceMarker(Point[] route, double markerWidth) + { var angleInRadians = Math.Atan2(route[1].Y - route[0].Y, route[1].X - route[0].X) + Math.PI; var xChange = markerWidth * Math.Cos(angleInRadians); var yChange = markerWidth * Math.Sin(angleInRadians); @@ -17,7 +17,7 @@ protected static double SourceMarkerAdjustement(Point[] route, double markerWidt return angleInRadians * 180 / Math.PI; } - protected static double TargetMarkerAdjustement(Point[] route, double markerWidth) + protected static double AdjustRouteForTargetMarker(Point[] route, double markerWidth) { var angleInRadians = Math.Atan2(route[^1].Y - route[^2].Y, route[^1].X - route[^2].X); var xChange = markerWidth * Math.Cos(angleInRadians); diff --git a/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs b/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs index c8d0cc348..9b3162c7a 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs @@ -29,12 +29,12 @@ public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel lin if (link.SourceMarker != null) { - sourceAngle = SourceMarkerAdjustement(route, link.SourceMarker.Width); + sourceAngle = AdjustRouteForSourceMarker(route, link.SourceMarker.Width); } if (link.TargetMarker != null) { - targetAngle = TargetMarkerAdjustement(route, link.TargetMarker.Width); + targetAngle = AdjustRouteForTargetMarker(route, link.TargetMarker.Width); } var path = new SvgPath() @@ -51,12 +51,12 @@ private PathGeneratorResult CurveThroughPoints(Point[] route, BaseLinkModel link if (link.SourceMarker != null) { - sourceAngle = SourceMarkerAdjustement(route, link.SourceMarker.Width); + sourceAngle = AdjustRouteForSourceMarker(route, link.SourceMarker.Width); } if (link.TargetMarker != null) { - targetAngle = TargetMarkerAdjustement(route, link.TargetMarker.Width); + targetAngle = AdjustRouteForTargetMarker(route, link.TargetMarker.Width); } BezierSpline.GetCurveControlPoints(route, out var firstControlPoints, out var secondControlPoints); diff --git a/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs index 87994153c..6d2c11be1 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs @@ -7,46 +7,80 @@ namespace Blazor.Diagrams.Core.PathGenerators { public class StraightPathGenerator : PathGenerator { + private readonly double _radius; + + public StraightPathGenerator(double radius = 20) + { + _radius = radius; + } + public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target) { route = ConcatRouteAndSourceAndTarget(route, source, target); + double? sourceAngle = null; double? targetAngle = null; if (link.SourceMarker != null) { - sourceAngle = SourceMarkerAdjustement(route, link.SourceMarker.Width); + sourceAngle = AdjustRouteForSourceMarker(route, link.SourceMarker.Width); } if (link.TargetMarker != null) { - targetAngle = TargetMarkerAdjustement(route, link.TargetMarker.Width); + targetAngle = AdjustRouteForTargetMarker(route, link.TargetMarker.Width); } - if (link.Vertices.Count == 0) - { - var fullPath = new SvgPath().AddMoveTo(route[0].X, route[0].Y); + var paths = link.Vertices.Count > 0 ? new SvgPath[route.Length - 1] : null; + var fullPath = new SvgPath().AddMoveTo(route[0].X, route[0].Y); - for (var i = 0; i < route.Length - 1; i++) - { - fullPath.AddLineTo(route[i + 1].X, route[i + 1].Y); - } + double? firstDist = null; + double? secondDist = null; - return new PathGeneratorResult(fullPath, Array.Empty(), sourceAngle, route[0], targetAngle, route[^1]); - } - else + for (var i = 0; i < route.Length - 1; i++) { - var paths = new SvgPath[route.Length - 1]; - var fullPath = new SvgPath().AddMoveTo(route[0].X, route[0].Y); + if (_radius > 0 && i > 0) + { + var previous = route[i - 1]; + var current = route[i]; + var next = route[i + 1]; - for (var i = 0; i < route.Length - 1; i++) + firstDist = secondDist ?? (current.DistanceTo(previous) / 2); + secondDist = current.DistanceTo(next) / 2; + + var p1 = -Math.Min(_radius, firstDist.Value); + var p2 = -Math.Min(_radius, secondDist.Value); + + var fp = current.MoveAlongLine(previous, p1); + var sp = current.MoveAlongLine(next, p2); + + var sweepFlag = GetAngle(previous, current, next) == 270; + fullPath.AddLineTo(fp.X, fp.Y).AddArc(_radius, _radius, 0, false, sweepFlag, sp.X, sp.Y); + + if (i == route.Length - 2) + { + fullPath.AddLineTo(route[^1].X, route[^1].Y); + } + } + else if (_radius == 0 || route.Length == 2) { fullPath.AddLineTo(route[i + 1].X, route[i + 1].Y); - paths[i] = new SvgPath().AddMoveTo(route[i].X, route[i].Y).AddLineTo(route[i + 1].X, route[i + 1].Y); - } - return new PathGeneratorResult(fullPath, paths, sourceAngle, route[0], targetAngle, route[^1]); + if (paths != null) + { + paths[i] = new SvgPath().AddMoveTo(route[i].X, route[i].Y).AddLineTo(route[i + 1].X, route[i + 1].Y); + } + } } + + return new PathGeneratorResult(fullPath, paths ?? Array.Empty(), sourceAngle, route[0], targetAngle, route[^1]); + } + + private static double GetAngle(Point prev, Point curr, Point next) + { + var r = Math.Atan2(next.Y - curr.Y, next.X - curr.X) - Math.Atan2(prev.Y - curr.Y, prev.X - curr.X); + var ang = r * (180 / Math.PI); + return ang < 0 ? ang + 360 : ang; } } } From 2cf4a77ef27332545ee793fdea64208ba0bd2afc Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 20 Nov 2022 23:26:19 +0100 Subject: [PATCH 138/193] Add MoveAlongLine method to Point (by a distance) --- src/Blazor.Diagrams.Core/Geometry/Point.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Blazor.Diagrams.Core/Geometry/Point.cs b/src/Blazor.Diagrams.Core/Geometry/Point.cs index 0873a1933..088048531 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Point.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Point.cs @@ -1,4 +1,5 @@ using System; +using System.Reflection.Metadata; namespace Blazor.Diagrams.Core.Geometry { @@ -29,6 +30,16 @@ public Point Lerp(Point other, double t) public double DistanceTo(Point other) => Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2)); public double DistanceTo(double x, double y) => Math.Sqrt(Math.Pow(X - x, 2) + Math.Pow(Y - y, 2)); + public Point MoveAlongLine(Point from, double dist) + { + var x = X - from.X; + var y = Y - from.Y; + var angle = Math.Atan2(y, x); + var xOffset = Math.Cos(angle) * dist; + var yOffset = Math.Sin(angle) * dist; + return new Point(X + xOffset, Y + yOffset); + } + public static Point operator -(Point a, Point b) => new(a.X - b.X, a.Y - b.Y); public static Point operator +(Point a, Point b) => new(a.X + b.X, a.Y + b.Y); From 20b0ccfccef8fa5c86bd16e0850f10e07717d2c4 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 20 Nov 2022 23:27:21 +0100 Subject: [PATCH 139/193] Update StraightPathGenerator.cs --- .../PathGenerators/StraightPathGenerator.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs index 6d2c11be1..320cebb7e 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs @@ -33,8 +33,6 @@ public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel lin var paths = link.Vertices.Count > 0 ? new SvgPath[route.Length - 1] : null; var fullPath = new SvgPath().AddMoveTo(route[0].X, route[0].Y); - - double? firstDist = null; double? secondDist = null; for (var i = 0; i < route.Length - 1; i++) @@ -45,7 +43,7 @@ public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel lin var current = route[i]; var next = route[i + 1]; - firstDist = secondDist ?? (current.DistanceTo(previous) / 2); + double? firstDist = secondDist ?? (current.DistanceTo(previous) / 2); secondDist = current.DistanceTo(next) / 2; var p1 = -Math.Min(_radius, firstDist.Value); From cbee9b87b56ca14d0a387abdcb950b7fba501177 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 21 Nov 2022 21:03:40 +0100 Subject: [PATCH 140/193] Use Quadratic curve for rounded corners --- .../PathGenerators/StraightPathGenerator.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs index 320cebb7e..eeb12ad41 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs @@ -9,7 +9,7 @@ public class StraightPathGenerator : PathGenerator { private readonly double _radius; - public StraightPathGenerator(double radius = 20) + public StraightPathGenerator(double radius = 0) { _radius = radius; } @@ -34,6 +34,7 @@ public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel lin var paths = link.Vertices.Count > 0 ? new SvgPath[route.Length - 1] : null; var fullPath = new SvgPath().AddMoveTo(route[0].X, route[0].Y); double? secondDist = null; + var lastPt = route[0]; for (var i = 0; i < route.Length - 1; i++) { @@ -52,12 +53,23 @@ public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel lin var fp = current.MoveAlongLine(previous, p1); var sp = current.MoveAlongLine(next, p2); - var sweepFlag = GetAngle(previous, current, next) == 270; - fullPath.AddLineTo(fp.X, fp.Y).AddArc(_radius, _radius, 0, false, sweepFlag, sp.X, sp.Y); + fullPath.AddLineTo(fp.X, fp.Y).AddQuadraticBezierCurve(current.X, current.Y, sp.X, sp.Y); + + if (paths != null) + { + paths[i - 1] = new SvgPath().AddMoveTo(lastPt.X, lastPt.Y).AddLineTo(fp.X, fp.Y).AddQuadraticBezierCurve(current.X, current.Y, sp.X, sp.Y); + } + + lastPt = sp; if (i == route.Length - 2) { fullPath.AddLineTo(route[^1].X, route[^1].Y); + + if (paths != null) + { + paths[i] = new SvgPath().AddMoveTo(lastPt.X, lastPt.Y).AddLineTo(route[^1].X, route[^1].Y); + } } } else if (_radius == 0 || route.Length == 2) From fe4af0e732843851f82657c4ebcdd22f01ecf621 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 21 Nov 2022 21:44:43 +0100 Subject: [PATCH 141/193] Remove unused method --- .../PathGenerators/StraightPathGenerator.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs index eeb12ad41..460e44604 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs @@ -85,12 +85,5 @@ public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel lin return new PathGeneratorResult(fullPath, paths ?? Array.Empty(), sourceAngle, route[0], targetAngle, route[^1]); } - - private static double GetAngle(Point prev, Point curr, Point next) - { - var r = Math.Atan2(next.Y - curr.Y, next.X - curr.X) - Math.Atan2(prev.Y - curr.Y, prev.X - curr.X); - var ang = r * (180 / Math.PI); - return ang < 0 ? ang + 360 : ang; - } } } From 8803be9de603a8fad62ad51b1da069164eecdc2e Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 22 Nov 2022 19:37:27 +0100 Subject: [PATCH 142/193] Add LinkVertexRenderer in order to be able to use custom component for vertices --- .../Components/LinkVertexWidget.razor | 10 -- .../Components/LinkVertexWidget.razor.cs | 60 ------------ .../Components/LinkWidget.razor | 16 ++-- .../Components/Renderers/LinkLabelRenderer.cs | 2 - .../Renderers/LinkVertexRenderer.cs | 96 +++++++++++++++++++ 5 files changed, 103 insertions(+), 81 deletions(-) delete mode 100644 src/Blazor.Diagrams/Components/LinkVertexWidget.razor delete mode 100644 src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs create mode 100644 src/Blazor.Diagrams/Components/Renderers/LinkVertexRenderer.cs diff --git a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor deleted file mode 100644 index 3ed0177ee..000000000 --- a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor +++ /dev/null @@ -1,10 +0,0 @@ - \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs deleted file mode 100644 index 60e0beec8..000000000 --- a/src/Blazor.Diagrams/Components/LinkVertexWidget.razor.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; -using Blazor.Diagrams.Extensions; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Web; - -namespace Blazor.Diagrams.Components; - -public partial class LinkVertexWidget : IDisposable -{ - private bool _shouldRender = true; - - [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; - [Parameter] public LinkVertexModel Vertex { get; set; } = null!; - [Parameter] public string? Color { get; set; } - [Parameter] public string? SelectedColor { get; set; } - - private string? ColorToUse => Vertex.Selected ? SelectedColor : Color; - - public void Dispose() - { - Vertex.Changed -= OnVertexChanged; - } - - protected override void OnInitialized() - { - Vertex.Changed += OnVertexChanged; - } - - protected override bool ShouldRender() - { - if (!_shouldRender) return false; - - _shouldRender = false; - return true; - } - - private void OnVertexChanged(Model _) - { - _shouldRender = true; - InvokeAsync(StateHasChanged); - } - - private void OnPointerDown(PointerEventArgs e) - { - BlazorDiagram.TriggerPointerDown(Vertex, e.ToCore()); - } - - private void OnPointerUp(PointerEventArgs e) - { - BlazorDiagram.TriggerPointerUp(Vertex, e.ToCore()); - } - - private void OnDoubleClick(MouseEventArgs e) - { - Vertex.Parent.Vertices.Remove(Vertex); - Vertex.Parent.Refresh(); - } -} \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor b/src/Blazor.Diagrams/Components/LinkWidget.razor index 04db67c86..bf4a88205 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor @@ -44,15 +44,13 @@ else { var selectedColor = Link.SelectedColor ?? BlazorDiagram.Options.Links.DefaultSelectedColor; var normalColor = Link.Color ?? BlazorDiagram.Options.Links.DefaultColor; - - @foreach (var vertex in Link.Vertices) - { - - } - + @foreach (var vertex in Link.Vertices) + { + + } } @foreach (var label in Link.Labels) diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs index 0ecefcefb..cee45c432 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkLabelRenderer.cs @@ -1,6 +1,4 @@ using System; -using System.IO; -using System.Linq; using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkVertexRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkVertexRenderer.cs new file mode 100644 index 000000000..8976e5e7e --- /dev/null +++ b/src/Blazor.Diagrams/Components/Renderers/LinkVertexRenderer.cs @@ -0,0 +1,96 @@ +using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Models; +using Microsoft.AspNetCore.Components; +using System; +using Microsoft.AspNetCore.Components.Web; +using Blazor.Diagrams.Extensions; +using Blazor.Diagrams.Core.Extensions; +using Microsoft.AspNetCore.Components.Rendering; + +namespace Blazor.Diagrams.Components.Renderers +{ + public class LinkVertexRenderer : ComponentBase, IDisposable + { + private bool _shouldRender = true; + + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; + [Parameter] public LinkVertexModel Vertex { get; set; } = null!; + [Parameter] public string? Color { get; set; } + [Parameter] public string? SelectedColor { get; set; } + + private string? ColorToUse => Vertex.Selected ? SelectedColor : Color; + + public void Dispose() + { + Vertex.Changed -= OnVertexChanged; + } + + protected override void OnInitialized() + { + Vertex.Changed += OnVertexChanged; + } + + protected override bool ShouldRender() + { + if (!_shouldRender) return false; + + _shouldRender = false; + return true; + } + + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + var componentType = BlazorDiagram.GetComponent(Vertex); + + builder.OpenElement(0, "g"); + builder.AddAttribute(1, "class", "link-vertex"); + builder.AddAttribute(4, "cursor", "move"); + builder.AddAttribute(5, "ondblclick", value: EventCallback.Factory.Create(this, OnDoubleClick)); + builder.AddAttribute(6, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); + builder.AddAttribute(7, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); + builder.AddEventStopPropagationAttribute(8, "onpointerdown", true); + builder.AddEventStopPropagationAttribute(9, "onpointerup", true); + + if (componentType == null) + { + builder.OpenElement(10, "circle"); + builder.AddAttribute(11, "cx", Vertex.Position.X.ToInvariantString()); + builder.AddAttribute(12, "cy", Vertex.Position.Y.ToInvariantString()); + builder.AddAttribute(13, "r", "5"); + builder.AddAttribute(14, "fill", ColorToUse); + builder.CloseElement(); + } + else + { + builder.OpenComponent(15, componentType); + builder.AddAttribute(16, "Vertex", Vertex); + builder.AddAttribute(17, "Color", ColorToUse); + builder.CloseComponent(); + } + + builder.CloseElement(); + } + + private void OnVertexChanged(Model _) + { + _shouldRender = true; + InvokeAsync(StateHasChanged); + } + + private void OnPointerDown(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerDown(Vertex, e.ToCore()); + } + + private void OnPointerUp(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerUp(Vertex, e.ToCore()); + } + + private void OnDoubleClick(MouseEventArgs e) + { + Vertex.Parent.Vertices.Remove(Vertex); + Vertex.Parent.Refresh(); + } + } +} From b3caf1a3cc66a32ec7a923a28c8bcc1b661fe364 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 22 Nov 2022 21:02:44 +0100 Subject: [PATCH 143/193] Add unit test for custom vertex component case --- .../Blazor.Diagrams.Tests.csproj | 3 +- .../Components/LinkVertexWidgetTests.cs | 57 +++++++++++++------ .../TestComponents/CustomVertexWidget.razor | 8 +++ tests/Blazor.Diagrams.Tests/_Imports.razor | 10 ++++ 4 files changed, 60 insertions(+), 18 deletions(-) create mode 100644 tests/Blazor.Diagrams.Tests/TestComponents/CustomVertexWidget.razor create mode 100644 tests/Blazor.Diagrams.Tests/_Imports.razor diff --git a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj index b6f8cf0ab..bed2d3311 100644 --- a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj +++ b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj @@ -1,9 +1,10 @@ - + net6.0 enable false + true diff --git a/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs index ff06c1846..990762f55 100644 --- a/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs +++ b/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs @@ -1,16 +1,11 @@ - -using Blazor.Diagrams.Components; +using Blazor.Diagrams.Components.Renderers; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; - +using Blazor.Diagrams.Tests.TestComponents; using Bunit; - using FluentAssertions; - using Microsoft.AspNetCore.Components.Web; - using System.Threading.Tasks; - using Xunit; namespace Blazor.Diagrams.Tests.Components @@ -29,13 +24,14 @@ public void ShouldRenderCircle() link.Vertices.Add(vertex); // Act - var cut = ctx.RenderComponent(parameters => parameters + var cut = ctx.RenderComponent(parameters => parameters .Add(n => n.Vertex, vertex) .Add(n => n.Color, "red") - .Add(n => n.SelectedColor, "blue")); + .Add(n => n.SelectedColor, "blue") + .Add(n => n.BlazorDiagram, new BlazorDiagram())); // Assert - cut.MarkupMatches(""); + cut.MarkupMatches(""); } [Fact] @@ -51,13 +47,14 @@ public void ShouldRenderCircleWithSelectedColor_WhenVertexIsSelected() vertex.Selected = true; // Act - var cut = ctx.RenderComponent(parameters => parameters + var cut = ctx.RenderComponent(parameters => parameters .Add(n => n.Vertex, vertex) .Add(n => n.Color, "red") - .Add(n => n.SelectedColor, "blue")); + .Add(n => n.SelectedColor, "blue") + .Add(n => n.BlazorDiagram, new BlazorDiagram())); // Assert - cut.MarkupMatches(""); + cut.MarkupMatches(""); } [Fact] @@ -72,10 +69,11 @@ public void ShouldRerender_WhenVertexIsRefreshed() link.Vertices.Add(vertex); // Act - var cut = ctx.RenderComponent(parameters => parameters + var cut = ctx.RenderComponent(parameters => parameters .Add(n => n.Vertex, vertex) .Add(n => n.Color, "red") - .Add(n => n.SelectedColor, "blue")); + .Add(n => n.SelectedColor, "blue") + .Add(n => n.BlazorDiagram, new BlazorDiagram())); // Assert cut.RenderCount.Should().Be(1); @@ -97,10 +95,11 @@ public async Task ShouldDeleteItselfAndRefreshParent_WhenDoubleClicked() link.Changed += _ => linkRefreshes++; // Act - var cut = ctx.RenderComponent(parameters => parameters + var cut = ctx.RenderComponent(parameters => parameters .Add(n => n.Vertex, vertex) .Add(n => n.Color, "red") - .Add(n => n.SelectedColor, "blue")); + .Add(n => n.SelectedColor, "blue") + .Add(n => n.BlazorDiagram, new BlazorDiagram())); await cut.Find("circle").DoubleClickAsync(new MouseEventArgs()); @@ -108,5 +107,29 @@ public async Task ShouldDeleteItselfAndRefreshParent_WhenDoubleClicked() link.Vertices.Should().BeEmpty(); linkRefreshes.Should().Be(1); } + + [Fact] + public void ShouldUseCustomComponent_WhenProvided() + { + // Arrange + using var ctx = new TestContext(); + var diagram = new BlazorDiagram(); + diagram.RegisterComponent(); + var node1 = new NodeModel(); + var node2 = new NodeModel(); + var link = new LinkModel(node1, node2); + var vertex = new LinkVertexModel(link, new Point(10.5, 20)); + link.Vertices.Add(vertex); + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.Vertex, vertex) + .Add(n => n.Color, "red") + .Add(n => n.SelectedColor, "blue") + .Add(n => n.BlazorDiagram, diagram)); + + // Assert + cut.MarkupMatches(""); + } } } diff --git a/tests/Blazor.Diagrams.Tests/TestComponents/CustomVertexWidget.razor b/tests/Blazor.Diagrams.Tests/TestComponents/CustomVertexWidget.razor new file mode 100644 index 000000000..21bc519c4 --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/TestComponents/CustomVertexWidget.razor @@ -0,0 +1,8 @@ +@using Blazor.Diagrams.Core.Models; + + + +@code { + [Parameter] public LinkVertexModel Vertex { get; set; } = null!; + [Parameter] public string Color { get; set; } = null!; +} \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Tests/_Imports.razor b/tests/Blazor.Diagrams.Tests/_Imports.razor new file mode 100644 index 000000000..f1075da7a --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/_Imports.razor @@ -0,0 +1,10 @@ +@using Microsoft.AspNetCore.Components.Web +@using Blazor.Diagrams.Core; +@using Blazor.Diagrams.Core.Models; +@using Blazor.Diagrams.Components; +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Blazor.Diagrams.Core.Extensions; +@using Blazor.Diagrams.Core.Geometry; +@using Blazor.Diagrams.Components.Renderers; +@using Blazor.Diagrams.Components.Widgets; From 9198933916a6c307a2ec31fe2f9a6719adde9c7c Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 23 Nov 2022 21:12:35 +0100 Subject: [PATCH 144/193] Add AutoSize option to groups to control whether moving children resizes the group --- .../Behaviors/DragMovablesBehavior.cs | 15 +++++-- src/Blazor.Diagrams.Core/Models/GroupModel.cs | 4 +- .../Behaviors/DragMovablesBehaviorTests.cs | 41 ++++++++++++++++++- 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index c8158beee..7d4240ba1 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -33,12 +33,17 @@ private void OnPointerDown(Model? model, PointerEventArgs e) 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 node) + if (Diagram.Options.GridSnapToCenter && movable is NodeModel n) { - position = new Point(movable.Position.X + (node.Size?.Width ?? 0) / 2, - movable.Position.Y + (node.Size?.Height ?? 0) / 2); + position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2, + movable.Position.Y + (n.Size?.Height ?? 0) / 2); } + _initialPositions.Add(movable, position); } @@ -61,9 +66,13 @@ private void OnPointerMove(Model? model, PointerEventArgs e) 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); + } } } diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index e657f230c..4153fbb17 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -10,17 +10,19 @@ public class GroupModel : NodeModel { private readonly List _children; - public GroupModel(IEnumerable children, byte padding = 30) + public GroupModel(IEnumerable children, byte padding = 30, bool autoSize = true) { _children = new List(); Size = Size.Zero; Padding = padding; + AutoSize = autoSize; Initialize(children); } public IReadOnlyList Children => _children; public byte Padding { get; } + public bool AutoSize { get; } public void AddChild(NodeModel child) { diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs index a896f1738..ba73788b3 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -1,4 +1,3 @@ -using System.Linq; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; @@ -101,4 +100,44 @@ public void Behavior_ShouldNotTriggerMoved_WhenMovableDidntMove() // 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); + } } \ No newline at end of file From 2422aa802dde68ce942da7396e20a78f50b63342 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 24 Nov 2022 10:53:51 +0100 Subject: [PATCH 145/193] Update Versions & CHANGELOG --- CHANGELOG.md | 26 +++++++++++++++++++ .../Blazor.Diagrams.Algorithms.csproj | 2 +- .../Blazor.Diagrams.Core.csproj | 2 +- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 2 +- 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e31467d2..fd2c8f1c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Diagrams (3.0.0-beta.5) - 2022-11-23 + +### Added + +- `AdditionalSvg` option to `DiagramCanvas` in order to render any exatra SVG content you want +- `AdditionalHtml` option to `DiagramCanvas` in order to render any exatra SVG content you want +- `DistanceTo` overload method to `Point` that takes x and y +- `MoveAlongLine` method to `Point` +- `FullPath` to `PathGeneratorResult` to represent the full path without cuts +- Fallback router to Orthogonal router +- Margin options to `OrthogonalRouter` +- `radius` option to `StraightPathGenerator` in order to generate rounded bends +- Support for custom vertices +- `AutoSize` option to groups to control whether moving children resizes the group + +### Changed + +- All routers are now classes instead of functions, they inherit from the new abstract class `Router` +- All path generators are now classes instead of functions, they inherit from the new abstract class `PathGenerator` +- Optimize Orthogonal router by using custom A* (x5 improvement) + +### Removed + +- `Router` delegate +- `PathGenerator` delegate + ## Diagrams (3.0.0-beta.4) - 2022-10-16 ### Added diff --git a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj index 0b6719914..8ea2c3f29 100644 --- a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj +++ b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj @@ -10,7 +10,7 @@ 3.0.0 3.0.0 https://github.com/zHaytam/Blazor.Diagrams - 3.0.0-beta.4 + 3.0.0-beta.5 Z.Blazor.Diagrams.Algorithms blazor diagrams diagramming svg drag algorithms layouts Z.Blazor.Diagrams.Algorithms diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index c7449a49c..ca57e422b 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -10,7 +10,7 @@ 3.0.0 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams - 3.0.0-beta.4 + 3.0.0-beta.5 Z.Blazor.Diagrams.Core blazor diagrams diagramming svg drag Z.Blazor.Diagrams.Core diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index a9c9e0d8d..aff38c09b 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -9,7 +9,7 @@ 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams A fully customizable and extensible all-purpose diagrams library for Blazor - 3.0.0-beta.4 + 3.0.0-beta.5 true blazor diagrams diagramming svg drag Z.Blazor.Diagrams From 814932cf611b25e88851713e515b94f4261b8c78 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 4 Dec 2022 17:26:03 +0100 Subject: [PATCH 146/193] Add statistics line --- Blazor.Diagrams.sln | 2 +- .../Landing/LandingShowcaseDiagram.razor | 6 +- .../Landing/LandingShowcaseDiagram.razor.cs | 5 +- .../Components/Landing/StatisticsLine.razor | 27 + .../Landing/StatisticsLine.razor.cs | 57 ++ site/Site/Pages/Index.razor | 514 ++++++++---------- site/Site/wwwroot/css/app.css | 17 + .../Components/Renderers/PortRenderer.cs | 17 +- 8 files changed, 351 insertions(+), 294 deletions(-) create mode 100644 site/Site/Components/Landing/StatisticsLine.razor create mode 100644 site/Site/Components/Landing/StatisticsLine.razor.cs diff --git a/Blazor.Diagrams.sln b/Blazor.Diagrams.sln index 6ae5eb041..5f73d1027 100644 --- a/Blazor.Diagrams.sln +++ b/Blazor.Diagrams.sln @@ -43,7 +43,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Diagrams.Tests", "te EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "site", "site", "{F1E6F4C0-3EC7-4CFF-834A-0CF207CCFF3E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Site", "site\Site\Site.csproj", "{F26307EC-C188-44BD-B3E5-960318F43C0C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Site", "site\Site\Site.csproj", "{F26307EC-C188-44BD-B3E5-960318F43C0C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/site/Site/Components/Landing/LandingShowcaseDiagram.razor b/site/Site/Components/Landing/LandingShowcaseDiagram.razor index f794cb908..89cf0bab4 100644 --- a/site/Site/Components/Landing/LandingShowcaseDiagram.razor +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor @@ -1,4 +1,4 @@ -
    +
    @@ -10,9 +10,9 @@
    -

    +

    Blazor Diagrams -

    +

    A fully customizable, extensible and all-purpose
    open-source diagrams library for Blazor

    diff --git a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs index 9078a28c8..95e6bb959 100644 --- a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs @@ -3,7 +3,6 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -using Microsoft.AspNetCore.Components; using Site.Models.Landing; namespace Site.Components.Landing; @@ -16,8 +15,8 @@ protected override void OnInitialized() { _diagram.Options.Zoom.Enabled = false; _diagram.Options.GridSize = 20; - _diagram.RegisterModelComponent(); - _diagram.RegisterModelComponent(); + _diagram.RegisterComponent(); + _diagram.RegisterComponent(); _diagram.Nodes.Added += OnNodeAdded; _diagram.Nodes.Removed += OnNodeRemoved; diff --git a/site/Site/Components/Landing/StatisticsLine.razor b/site/Site/Components/Landing/StatisticsLine.razor new file mode 100644 index 000000000..25fedc4a9 --- /dev/null +++ b/site/Site/Components/Landing/StatisticsLine.razor @@ -0,0 +1,27 @@ +
    +
    +
    + +
    +

    @_stars

    +
    Stars
    +
    + +
    +

    MIT

    +
    License
    +
    + +
    +

    @_downloads

    +
    Downloads
    +
    + +
    +

    @_version

    +
    Version
    +
    + +
    +
    +
    \ No newline at end of file diff --git a/site/Site/Components/Landing/StatisticsLine.razor.cs b/site/Site/Components/Landing/StatisticsLine.razor.cs new file mode 100644 index 000000000..6d19fbbd2 --- /dev/null +++ b/site/Site/Components/Landing/StatisticsLine.razor.cs @@ -0,0 +1,57 @@ +using Microsoft.AspNetCore.Components; +using System.Net.Http.Json; +using System.Text.Json; + +namespace Site.Components.Landing +{ + public partial class StatisticsLine + { + private int _stars; + private int _downloads; + private string _version = "1.0.0"; + + [Inject] private HttpClient HttpClient { get; set; } = null!; + + protected override async Task OnInitializedAsync() + { + (_version, _downloads) = await GetVersionAndDownloads(); + _stars = await GetStars(); + } + + private async Task GetStars() + { + var content = await HttpClient.GetFromJsonAsync("https://api.github.com/repos/Blazor-Diagrams/Blazor.Diagrams"); + if (content == null) + return 0; + + return content.RootElement.GetProperty("stargazers_count").GetInt32(); + } + + private async Task<(string, int)> GetVersionAndDownloads() + { + var content = await HttpClient.GetFromJsonAsync("https://api.nuget.org/v3/index.json"); + if (content != null) + { + foreach (var resource in content.RootElement.GetProperty("resources").EnumerateArray()) + { + if (resource.GetProperty("@type").GetString() == "SearchQueryService") + { + var url = resource.GetProperty("@id").GetString(); + var packageContent = await HttpClient.GetFromJsonAsync(url + "?prerelease=true&q=packageid:Z.Blazor.Diagrams"); + if (packageContent != null) + { + foreach (var data in packageContent.RootElement.GetProperty("data").EnumerateArray()) + { + var version = data.GetProperty("version").GetString()!; + var downloads = data.GetProperty("totalDownloads").GetInt32(); + return (version, downloads); + } + } + } + } + } + + return ("1.0.0", 0); + } + } +} diff --git a/site/Site/Pages/Index.razor b/site/Site/Pages/Index.razor index a90241bba..23f0907a2 100644 --- a/site/Site/Pages/Index.razor +++ b/site/Site/Pages/Index.razor @@ -10,16 +10,14 @@ @* configurable *@ - + - + @@ -27,281 +25,241 @@ + + +
    -
    -

    - Title -

    -
    -
    -
    -
    -
    -

    - Lorem ipsum dolor sit amet -

    -

    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at ipsum eu nunc commodo posuere et sit amet ligula. -
    -
    +

    +

    + Title +

    +
    +
    +
    +
    +
    +

    + Lorem ipsum dolor sit amet +

    +

    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at ipsum eu nunc commodo posuere et sit amet ligula. +
    +
    - Images from: + Images from: - undraw.co -

    -
    -
    - - travel booking - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    -
    - - connected world - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    -

    - Lorem ipsum dolor sit amet -

    -

    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at ipsum eu nunc commodo posuere et sit amet ligula. -
    -
    - Images from: + undraw.co +

    +
    +
    + + travel booking + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +
    + + connected world + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +
    +
    +

    + Lorem ipsum dolor sit amet +

    +

    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at ipsum eu nunc commodo posuere et sit amet ligula. +
    +
    + Images from: - undraw.co -

    + undraw.co +

    +
    +
    -
    -
    @@ -465,16 +423,14 @@ - + - + diff --git a/site/Site/wwwroot/css/app.css b/site/Site/wwwroot/css/app.css index bd1eacf9a..04267f5d3 100644 --- a/site/Site/wwwroot/css/app.css +++ b/site/Site/wwwroot/css/app.css @@ -715,6 +715,10 @@ video { display: flex; } +.grid { + display: grid; +} + .hidden { display: none; } @@ -775,6 +779,10 @@ video { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } +.grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + .flex-col { flex-direction: column; } @@ -811,6 +819,10 @@ video { justify-content: space-between; } +.gap-4 { + gap: 1rem; +} + .overflow-hidden { overflow: hidden; } @@ -942,6 +954,11 @@ video { padding-right: 1rem; } +.py-3 { + padding-top: 0.75rem; + padding-bottom: 0.75rem; +} + .pt-4 { padding-top: 1rem; } diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs index e7bda37dc..6a240750f 100644 --- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs @@ -64,16 +64,17 @@ protected override void BuildRenderTree(RenderTreeBuilder builder) return; builder.OpenElement(0, _isParentSvg ? "g" : "div"); - builder.AddAttribute(1, "class", + builder.AddAttribute(1, "style", Style); + builder.AddAttribute(2, "class", "port" + " " + Port.Alignment.ToString().ToLower() + " " + (Port.Links.Count > 0 ? "has-links" : "") + " " + Class); - builder.AddAttribute(2, "data-port-id", Port.Id); - builder.AddAttribute(3, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); - builder.AddEventStopPropagationAttribute(4, "onpointerdown", true); - builder.AddAttribute(5, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); - builder.AddEventStopPropagationAttribute(6, "onpointerup", true); - builder.AddElementReferenceCapture(7, __value => { _element = __value; }); - builder.AddContent(8, ChildContent); + builder.AddAttribute(3, "data-port-id", Port.Id); + builder.AddAttribute(4, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); + builder.AddEventStopPropagationAttribute(5, "onpointerdown", true); + builder.AddAttribute(6, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); + builder.AddEventStopPropagationAttribute(7, "onpointerup", true); + builder.AddElementReferenceCapture(8, __value => { _element = __value; }); + builder.AddContent(9, ChildContent); builder.CloseElement(); } From eed64839fc54b36dc82b3c07b19d2d1b47d85d98 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 7 Dec 2022 11:31:19 +0100 Subject: [PATCH 147/193] Fix portless links in children not refreshing when moving the parent group --- src/Blazor.Diagrams.Core/Models/GroupModel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index 4153fbb17..88a9636fc 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -61,7 +61,10 @@ public override void SetPosition(double x, double y) base.SetPosition(x, y); foreach (var node in Children) + { node.UpdatePositionSilently(deltaX, deltaY); + node.RefreshLinks(); + } Refresh(); RefreshLinks(); From 22753279494596cb1b7c5e56074cc0e5c4ed9abc Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Thu, 8 Dec 2022 16:02:20 +0100 Subject: [PATCH 148/193] Add Widgets, Groups and Svg/Html features --- .../Landing/ColoredNodeWidget.razor | 17 + .../Landing/Groups/GroupWidget.razor | 15 + .../Landing/Groups/GroupsExample.razor | 37 ++ .../Landing/Groups/GroupsExample.razor.cs | 50 ++ .../SvgAndHtml/BatteryChargerNodeWidget.razor | 18 + .../SvgAndHtml/BatteryNodeWidget.razor | 19 + .../SvgAndHtml/SvgAndHtmlExample.razor | 29 ++ .../SvgAndHtml/SvgAndHtmlExample.razor.cs | 35 ++ .../Components/Landing/WidgetsExample.razor | 41 ++ .../Landing/WidgetsExample.razor.cs | 49 ++ site/Site/Models/Landing/ColoredNodeModel.cs | 23 + .../Landing/Groups/ColoredGroupModel.cs | 14 + .../SvgAndHtml/BatteryChargerNodeModel.cs | 18 + .../Landing/SvgAndHtml/BatteryNodeModel.cs | 37 ++ site/Site/Pages/Index.razor | 454 ------------------ site/Site/Pages/Landing/Index.razor | 71 +++ site/Site/Site.csproj | 4 - site/Site/_Imports.razor | 4 + site/Site/tailwind.config.js | 2 +- site/Site/wwwroot/css/app.css | 401 ++++++---------- site/Site/wwwroot/css/input.css | 36 ++ 21 files changed, 655 insertions(+), 719 deletions(-) create mode 100644 site/Site/Components/Landing/ColoredNodeWidget.razor create mode 100644 site/Site/Components/Landing/Groups/GroupWidget.razor create mode 100644 site/Site/Components/Landing/Groups/GroupsExample.razor create mode 100644 site/Site/Components/Landing/Groups/GroupsExample.razor.cs create mode 100644 site/Site/Components/Landing/SvgAndHtml/BatteryChargerNodeWidget.razor create mode 100644 site/Site/Components/Landing/SvgAndHtml/BatteryNodeWidget.razor create mode 100644 site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor create mode 100644 site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor.cs create mode 100644 site/Site/Components/Landing/WidgetsExample.razor create mode 100644 site/Site/Components/Landing/WidgetsExample.razor.cs create mode 100644 site/Site/Models/Landing/ColoredNodeModel.cs create mode 100644 site/Site/Models/Landing/Groups/ColoredGroupModel.cs create mode 100644 site/Site/Models/Landing/SvgAndHtml/BatteryChargerNodeModel.cs create mode 100644 site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs delete mode 100644 site/Site/Pages/Index.razor create mode 100644 site/Site/Pages/Landing/Index.razor diff --git a/site/Site/Components/Landing/ColoredNodeWidget.razor b/site/Site/Components/Landing/ColoredNodeWidget.razor new file mode 100644 index 000000000..228466bf5 --- /dev/null +++ b/site/Site/Components/Landing/ColoredNodeWidget.razor @@ -0,0 +1,17 @@ +@using System.Text; +@using Blazor.Diagrams.Extensions; +@{ + var borderColor = Node.Selected ? $"outline-{Node.Color}-darker" : $"outline-{Node.Color}"; + var extraClasses = new StringBuilder().Append($"bg-{Node.Color}").AppendIf(" rounded-full", Node.Round); +} + +
    + @Node.Title +
    + +@code { + + [Parameter] + public ColoredNodeModel Node { get; set; } = null!; + +} \ No newline at end of file diff --git a/site/Site/Components/Landing/Groups/GroupWidget.razor b/site/Site/Components/Landing/Groups/GroupWidget.razor new file mode 100644 index 000000000..912079b6a --- /dev/null +++ b/site/Site/Components/Landing/Groups/GroupWidget.razor @@ -0,0 +1,15 @@ +
    + + + @foreach (var port in Group.Ports) + { + + } +
    + +@code { + + [Parameter] + public ColoredGroupModel Group { get; set; } = null!; + +} \ No newline at end of file diff --git a/site/Site/Components/Landing/Groups/GroupsExample.razor b/site/Site/Components/Landing/Groups/GroupsExample.razor new file mode 100644 index 000000000..2c0199ed6 --- /dev/null +++ b/site/Site/Components/Landing/Groups/GroupsExample.razor @@ -0,0 +1,37 @@ +
    +
    +
    +
    +
    + + + + + + + + + +
    +
    +
    +

    + Groups +

    +

    + Blazor Diagrams supports groups and nested hierarchies. You can have: +

    +
      +
    • Nodes inside of groups
    • +
    • Groups inside of other groups
    • +
    • Group auto size to its children's bounds
    • +
    • Links, of course
    • +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/site/Site/Components/Landing/Groups/GroupsExample.razor.cs b/site/Site/Components/Landing/Groups/GroupsExample.razor.cs new file mode 100644 index 000000000..8e41f31ee --- /dev/null +++ b/site/Site/Components/Landing/Groups/GroupsExample.razor.cs @@ -0,0 +1,50 @@ +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Site.Models.Landing; +using Site.Models.Landing.Groups; + +namespace Site.Components.Landing.Groups +{ + public partial class GroupsExample + { + private readonly BlazorDiagram _diagram = new(); + + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.GridSize = 30; + _diagram.Options.LinksLayerOrder = 2; + _diagram.Options.NodesLayerOrder = 1; + _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); + _diagram.RegisterComponent(); + _diagram.RegisterComponent(); + + var node1 = _diagram.Nodes.Add(new ColoredNodeModel("Node 1", false, "color3", new Point(90, 60))); + var node2 = _diagram.Nodes.Add(new ColoredNodeModel("Node 2", false, "color3", new Point(390, 60))); + var node3 = _diagram.Nodes.Add(new ColoredNodeModel("Node 3", false, "color1", new Point(180, 240))); + var node4 = _diagram.Nodes.Add(new ColoredNodeModel("Node 4", false, "color1", new Point(330, 240))); + + var group1 = _diagram.Groups.Add(new ColoredGroupModel(new[] { node3, node4 }, "color2")); + var group2 = _diagram.Groups.Add(new ColoredGroupModel(new[] { group1, node1, node2 }, "color2")); + + _diagram.Links.Add(new LinkModel(node1, node2) + { + TargetMarker = LinkMarker.Arrow, + }); + _diagram.Links.Add(new LinkModel(node2, node3) + { + TargetMarker = LinkMarker.Arrow, + }); + _diagram.Links.Add(new LinkModel(node3, node1) + { + TargetMarker = LinkMarker.Arrow, + }); + _diagram.Links.Add(new LinkModel(node2, group1) + { + TargetMarker = LinkMarker.Arrow, + }); + } + } +} diff --git a/site/Site/Components/Landing/SvgAndHtml/BatteryChargerNodeWidget.razor b/site/Site/Components/Landing/SvgAndHtml/BatteryChargerNodeWidget.razor new file mode 100644 index 000000000..fb4519062 --- /dev/null +++ b/site/Site/Components/Landing/SvgAndHtml/BatteryChargerNodeWidget.razor @@ -0,0 +1,18 @@ +
    +

    + HTML Charger +

    + +
    + +@code { + [Parameter] public BatteryChargerNodeModel Node { get; set; } = null!; + + private void OnValueChanged(ChangeEventArgs e) + { + Node.Setter(int.Parse(e.Value!.ToString()!)); + } +} diff --git a/site/Site/Components/Landing/SvgAndHtml/BatteryNodeWidget.razor b/site/Site/Components/Landing/SvgAndHtml/BatteryNodeWidget.razor new file mode 100644 index 000000000..abfedbe29 --- /dev/null +++ b/site/Site/Components/Landing/SvgAndHtml/BatteryNodeWidget.razor @@ -0,0 +1,19 @@ +@{ + var color = Node.Percentage > 70 ? "green" : (Node.Percentage > 30 ? "orange" : "red"); +} + + + + + + + + + + + +SVG Battery + +@code { + [Parameter] public BatteryNodeModel Node { get; set; } = null!; +} \ No newline at end of file diff --git a/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor b/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor new file mode 100644 index 000000000..4fd73bcfb --- /dev/null +++ b/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor @@ -0,0 +1,29 @@ +
    + +
    +
    +
    +
    + + + + + + + + +
    +
    +
    +

    + SVG and HTML +

    +

    + Blazor Diagrams contains two layers, SVG and HTML.
    + The HTML layer is responsible for rendering nodes only, for maximum customization and interactivity. + While the SVG layer is responsible for rendering the links, but can also render nodes. +

    +
    +
    +
    +
    \ No newline at end of file diff --git a/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor.cs b/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor.cs new file mode 100644 index 000000000..68e253383 --- /dev/null +++ b/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor.cs @@ -0,0 +1,35 @@ +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Site.Models.Landing.SvgAndHtml; + +namespace Site.Components.Landing.SvgAndHtml +{ + public partial class SvgAndHtmlExample + { + private readonly BlazorDiagram _diagram = new(); + + protected override void OnInitialized() + { + _diagram.Options.GridSize = 30; + _diagram.Options.Constraints.ShouldDeleteLink = _ => ValueTask.FromResult(false); + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); + _diagram.RegisterComponent(); + _diagram.RegisterComponent(); + + var battery = new BatteryNodeModel(new Point(90, 150)); + var port1 = battery.AddPort(PortAlignment.Right); + var port2 = battery.AddPort(PortAlignment.Right); + + _diagram.Nodes.Add(battery); + var charger1 = _diagram.Nodes.Add(new BatteryChargerNodeModel(() => battery.FirstCharge, i => battery.FirstCharge = i, new Point(300, 60))); + var charger2 = _diagram.Nodes.Add(new BatteryChargerNodeModel(() => battery.SecondCharge, i => battery.SecondCharge = i, new Point(300, 180))); + + _diagram.Links.Add(new LinkModel(new SinglePortAnchor(port1), new ShapeIntersectionAnchor(charger1))); + _diagram.Links.Add(new LinkModel(new SinglePortAnchor(port2), new ShapeIntersectionAnchor(charger2))); + } + } +} diff --git a/site/Site/Components/Landing/WidgetsExample.razor b/site/Site/Components/Landing/WidgetsExample.razor new file mode 100644 index 000000000..428275e23 --- /dev/null +++ b/site/Site/Components/Landing/WidgetsExample.razor @@ -0,0 +1,41 @@ +
    + +
    +
    +
    +

    + Widgets +

    +

    + Blazor Diagrams includes a couple of widgets to speed up your development. +

    +
      +
    • GridWidget (dots or lines)
    • +
    • SelectionBoxWidget (shift and drag)
    • +
    • NavigatorWidget
    • +
    +
    +
    +
    + + + + + + + + + +
    + + +
    +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/site/Site/Components/Landing/WidgetsExample.razor.cs b/site/Site/Components/Landing/WidgetsExample.razor.cs new file mode 100644 index 000000000..212231ed7 --- /dev/null +++ b/site/Site/Components/Landing/WidgetsExample.razor.cs @@ -0,0 +1,49 @@ +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Site.Models.Landing; + +namespace Site.Components.Landing +{ + public partial class WidgetsExample + { + private readonly BlazorDiagram _diagram = new(); + private bool _gridPoints; + + public bool GridPoints + { + get => _gridPoints; + set + { + _gridPoints = value; + _diagram.Refresh(); + } + } + + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.GridSize = 30; + _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); + _diagram.RegisterComponent(); + + var node1 = _diagram.Nodes.Add(new ColoredNodeModel("Node 1", false, "color1", new Point(90, 60))); + var node2 = _diagram.Nodes.Add(new ColoredNodeModel("Node 2", true, "color2", new Point(450, 60))); + var node3 = _diagram.Nodes.Add(new ColoredNodeModel("Node 3", false, "color3", new Point(270, 240))); + + _diagram.Links.Add(new LinkModel(node1, node2) + { + TargetMarker = LinkMarker.Arrow + }); + _diagram.Links.Add(new LinkModel(node2, node3) + { + TargetMarker = LinkMarker.Arrow + }); + _diagram.Links.Add(new LinkModel(node3, node1) + { + TargetMarker = LinkMarker.Arrow + }); + } + } +} diff --git a/site/Site/Models/Landing/ColoredNodeModel.cs b/site/Site/Models/Landing/ColoredNodeModel.cs new file mode 100644 index 000000000..3571552d2 --- /dev/null +++ b/site/Site/Models/Landing/ColoredNodeModel.cs @@ -0,0 +1,23 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; + +namespace Site.Models.Landing +{ + public class ColoredNodeModel : NodeModel + { + public ColoredNodeModel(string title, bool round, string color, Point position) : base(position) + { + Title = title; + Round = round; + Color = color; + } + + public bool Round { get; } + public string Color { get; } + + public override IShape GetShape() + { + return Round ? Shapes.Circle(this) : Shapes.Rectangle(this); + } + } +} diff --git a/site/Site/Models/Landing/Groups/ColoredGroupModel.cs b/site/Site/Models/Landing/Groups/ColoredGroupModel.cs new file mode 100644 index 000000000..eac06140a --- /dev/null +++ b/site/Site/Models/Landing/Groups/ColoredGroupModel.cs @@ -0,0 +1,14 @@ +using Blazor.Diagrams.Core.Models; + +namespace Site.Models.Landing.Groups +{ + public class ColoredGroupModel : GroupModel + { + public ColoredGroupModel(IEnumerable children, string color, byte padding = 30, bool autoSize = true) : base(children, padding, autoSize) + { + Color = color; + } + + public string Color { get; } + } +} diff --git a/site/Site/Models/Landing/SvgAndHtml/BatteryChargerNodeModel.cs b/site/Site/Models/Landing/SvgAndHtml/BatteryChargerNodeModel.cs new file mode 100644 index 000000000..98407d4a6 --- /dev/null +++ b/site/Site/Models/Landing/SvgAndHtml/BatteryChargerNodeModel.cs @@ -0,0 +1,18 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; + +namespace Site.Models.Landing.SvgAndHtml +{ + public class BatteryChargerNodeModel : NodeModel + { + public BatteryChargerNodeModel(Func getter, Action setter, Point position) : base(position) + { + Getter = getter; + Setter = setter; + } + + public BatteryNodeModel? Battery { get; private set; } + public Func Getter { get; } + public Action Setter { get; } + } +} diff --git a/site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs b/site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs new file mode 100644 index 000000000..b1aa29bf3 --- /dev/null +++ b/site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs @@ -0,0 +1,37 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Models; + +namespace Site.Models.Landing.SvgAndHtml +{ + public class BatteryNodeModel : SvgNodeModel + { + private int _firstCharge = 5; + private int _secondCharge = 15; + + public BatteryNodeModel(Point position) : base(position) + { + } + + public int FirstCharge + { + get => _firstCharge; + set + { + _firstCharge = value; + Refresh(); + } + } + + public int SecondCharge + { + get => _secondCharge; + set + { + _secondCharge = value; + Refresh(); + } + } + + public int Percentage => FirstCharge + SecondCharge; + } +} diff --git a/site/Site/Pages/Index.razor b/site/Site/Pages/Index.razor deleted file mode 100644 index 23f0907a2..000000000 --- a/site/Site/Pages/Index.razor +++ /dev/null @@ -1,454 +0,0 @@ -@page "/" - -Index - - - - - - - - - @* configurable *@ - - - - - - - - - - - - - - - - - -
    -
    -

    - Title -

    -
    -
    -
    -
    -
    -

    - Lorem ipsum dolor sit amet -

    -

    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at ipsum eu nunc commodo posuere et sit amet ligula. -
    -
    - - Images from: - - undraw.co -

    -
    -
    - - travel booking - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    -
    - - connected world - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    -
    -

    - Lorem ipsum dolor sit amet -

    -

    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam at ipsum eu nunc commodo posuere et sit amet ligula. -
    -
    - Images from: - - undraw.co -

    -
    -
    -
    -
    -
    -
    - -
    -
    -
    -

    - Pricing -

    -
    -
    -
    -
    -
    -
    -
    - Free -
    -
      -
    • Thing
    • -
    • Thing
    • -
    • Thing
    • -
    -
    -
    -
    - £0 - for one user -
    -
    - -
    -
    -
    -
    -
    -
    Basic
    -
    -
      -
    • Thing
    • -
    • Thing
    • -
    • Thing
    • -
    • Thing
    • -
    -
    -
    -
    - £x.99 - / per user -
    -
    - -
    -
    -
    -
    -
    -
    - Pro -
    -
      -
    • Thing
    • -
    • Thing
    • -
    • Thing
    • -
    -
    -
    -
    - £x.99 - / per user -
    -
    - -
    -
    -
    -
    -
    -
    - - - - - - - - - - - - - - - - - - - -
    -

    - Call to Action -

    -
    -
    -
    -

    - Main Hero Message to sell yourself! -

    - -
    \ No newline at end of file diff --git a/site/Site/Pages/Landing/Index.razor b/site/Site/Pages/Landing/Index.razor new file mode 100644 index 000000000..c61596f7a --- /dev/null +++ b/site/Site/Pages/Landing/Index.razor @@ -0,0 +1,71 @@ +@page "/" + +Blazor Diagrams + + + + + + + + + @* configurable *@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    +

    + Call to Action +

    +
    +
    +
    +

    + Main Hero Message to sell yourself! +

    + +
    \ No newline at end of file diff --git a/site/Site/Site.csproj b/site/Site/Site.csproj index 5d16d2168..a85adb61e 100644 --- a/site/Site/Site.csproj +++ b/site/Site/Site.csproj @@ -23,8 +23,4 @@ - - - - diff --git a/site/Site/_Imports.razor b/site/Site/_Imports.razor index fbc1f3d8a..a407837f2 100644 --- a/site/Site/_Imports.razor +++ b/site/Site/_Imports.razor @@ -10,8 +10,12 @@ @using Site.Shared @using Site.Components @using Site.Components.Landing +@using Site.Components.Landing.SvgAndHtml +@using Site.Components.Landing.Groups @using Site.Models @using Site.Models.Landing +@using Site.Models.Landing.Groups +@using Site.Models.Landing.SvgAndHtml @using Blazor.Diagrams.Core; @using Blazor.Diagrams.Core.Models; @using Blazor.Diagrams.Components; diff --git a/site/Site/tailwind.config.js b/site/Site/tailwind.config.js index 88376e1e0..25abd77a8 100644 --- a/site/Site/tailwind.config.js +++ b/site/Site/tailwind.config.js @@ -1,6 +1,6 @@ /** @type {import('tailwindcss').Config} */ module.exports = { - content: ["./**/*.{razor,html,cshtml}"], + content: ["./**/*.{razor,html,cshtml,cs}"], theme: { extend: {}, }, diff --git a/site/Site/wwwroot/css/app.css b/site/Site/wwwroot/css/app.css index 04267f5d3..158d960b1 100644 --- a/site/Site/wwwroot/css/app.css +++ b/site/Site/wwwroot/css/app.css @@ -585,6 +585,10 @@ video { } } +.static { + position: static; +} + .fixed { position: fixed; } @@ -601,10 +605,6 @@ video { top: 0px; } -.z-10 { - z-index: 10; -} - .z-30 { z-index: 30; } @@ -622,6 +622,16 @@ video { margin-right: auto; } +.my-4 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.my-6 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} + .my-2 { margin-top: 0.5rem; margin-bottom: 0.5rem; @@ -632,73 +642,50 @@ video { margin-bottom: 0px; } -.my-6 { - margin-top: 1.5rem; - margin-bottom: 1.5rem; +.mt-0 { + margin-top: 0px; } -.my-12 { - margin-top: 3rem; - margin-bottom: 3rem; +.mt-2 { + margin-top: 0.5rem; } -.my-4 { - margin-top: 1rem; - margin-bottom: 1rem; +.mr-3 { + margin-right: 0.75rem; } -.mb-4 { - margin-bottom: 1rem; +.mt-4 { + margin-top: 1rem; } -.mb-3 { - margin-bottom: 0.75rem; +.mb-6 { + margin-bottom: 1.5rem; } -.mb-8 { - margin-bottom: 2rem; +.mr-2 { + margin-right: 0.5rem; } -.mt-6 { - margin-top: 1.5rem; +.mb-2 { + margin-bottom: 0.5rem; } -.mb-5 { - margin-bottom: 1.25rem; +.mb-3 { + margin-bottom: 0.75rem; } -.mt-auto { - margin-top: auto; +.mb-4 { + margin-bottom: 1rem; } -.mt-4 { - margin-top: 1rem; +.mb-8 { + margin-bottom: 2rem; } .mb-12 { margin-bottom: 3rem; } -.mt-0 { - margin-top: 0px; -} - -.mt-2 { - margin-top: 0.5rem; -} - -.mr-3 { - margin-right: 0.75rem; -} - -.mb-6 { - margin-bottom: 1.5rem; -} - -.mr-2 { - margin-right: 0.5rem; -} - .block { display: block; } @@ -723,10 +710,6 @@ video { display: none; } -.h-1 { - height: 0.25rem; -} - .h-8 { height: 2rem; } @@ -735,26 +718,26 @@ video { height: 1.5rem; } -.w-full { - width: 100%; +.h-full { + height: 100%; } -.w-64 { - width: 16rem; -} - -.w-5\/6 { - width: 83.333333%; +.h-1 { + height: 0.25rem; } -.w-1\/6 { - width: 16.666667%; +.w-full { + width: 100%; } .w-6 { width: 1.5rem; } +.w-1\/6 { + width: 16.666667%; +} + .max-w-5xl { max-width: 64rem; } @@ -763,14 +746,6 @@ video { flex: 1 1 0%; } -.flex-none { - flex: none; -} - -.flex-shrink { - flex-shrink: 1; -} - .flex-grow { flex-grow: 1; } @@ -779,6 +754,14 @@ video { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } +.list-inside { + list-style-position: inside; +} + +.list-disc { + list-style-type: disc; +} + .grid-cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } @@ -787,10 +770,6 @@ video { flex-direction: column; } -.flex-col-reverse { - flex-direction: column-reverse; -} - .flex-wrap { flex-wrap: wrap; } @@ -803,10 +782,6 @@ video { align-items: center; } -.justify-start { - justify-content: flex-start; -} - .justify-end { justify-content: flex-end; } @@ -823,22 +798,10 @@ video { gap: 1rem; } -.overflow-hidden { - overflow: hidden; -} - .rounded-full { border-radius: 9999px; } -.rounded-none { - border-radius: 0px; -} - -.rounded-lg { - border-radius: 0.5rem; -} - .rounded { border-radius: 0.25rem; } @@ -848,21 +811,6 @@ video { border-top-right-radius: 0.25rem; } -.rounded-b-none { - border-bottom-right-radius: 0px; - border-bottom-left-radius: 0px; -} - -.rounded-b { - border-bottom-right-radius: 0.25rem; - border-bottom-left-radius: 0.25rem; -} - -.rounded-t-none { - border-top-left-radius: 0px; - border-top-right-radius: 0px; -} - .border { border-width: 1px; } @@ -871,8 +819,9 @@ video { border-bottom-width: 1px; } -.border-b-4 { - border-bottom-width: 4px; +.border-black { + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity)); } .bg-white { @@ -880,23 +829,10 @@ video { background-color: rgb(255 255 255 / var(--tw-bg-opacity)); } -.bg-gray-100 { - --tw-bg-opacity: 1; - background-color: rgb(243 244 246 / var(--tw-bg-opacity)); -} - .fill-current { fill: currentColor; } -.p-6 { - padding: 1.5rem; -} - -.p-8 { - padding: 2rem; -} - .p-1 { padding: 0.25rem; } @@ -909,19 +845,18 @@ video { padding: 0.5rem; } -.py-8 { - padding-top: 2rem; - padding-bottom: 2rem; +.p-6 { + padding: 1.5rem; } -.py-0 { - padding-top: 0px; - padding-bottom: 0px; +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; } -.px-6 { - padding-left: 1.5rem; - padding-right: 1.5rem; +.px-4 { + padding-left: 1rem; + padding-right: 1rem; } .py-4 { @@ -934,45 +869,19 @@ video { padding-right: 2rem; } -.px-2 { - padding-left: 0.5rem; - padding-right: 0.5rem; -} - .py-6 { padding-top: 1.5rem; padding-bottom: 1.5rem; } -.py-2 { - padding-top: 0.5rem; - padding-bottom: 0.5rem; -} - -.px-4 { - padding-left: 1rem; - padding-right: 1rem; -} - -.py-3 { - padding-top: 0.75rem; - padding-bottom: 0.75rem; -} - -.pt-4 { - padding-top: 1rem; -} - -.pb-12 { - padding-bottom: 3rem; -} - -.pt-12 { - padding-top: 3rem; +.py-8 { + padding-top: 2rem; + padding-bottom: 2rem; } -.pt-6 { - padding-top: 1.5rem; +.py-0 { + padding-top: 0px; + padding-bottom: 0px; } .pl-4 { @@ -995,13 +904,9 @@ video { text-align: center; } -.align-middle { - vertical-align: middle; -} - -.text-5xl { - font-size: 3rem; - line-height: 1; +.text-2xl { + font-size: 1.5rem; + line-height: 2rem; } .text-3xl { @@ -1009,34 +914,9 @@ video { line-height: 2.25rem; } -.text-xs { - font-size: 0.75rem; - line-height: 1rem; -} - -.text-xl { - font-size: 1.25rem; - line-height: 1.75rem; -} - -.text-base { - font-size: 1rem; - line-height: 1.5rem; -} - -.text-sm { - font-size: 0.875rem; - line-height: 1.25rem; -} - -.text-4xl { - font-size: 2.25rem; - line-height: 2.5rem; -} - -.text-2xl { - font-size: 1.5rem; - line-height: 2rem; +.text-5xl { + font-size: 3rem; + line-height: 1; } .font-bold { @@ -1051,40 +931,30 @@ video { text-transform: uppercase; } -.leading-tight { - line-height: 1.25; +.leading-normal { + line-height: 1.5; } .leading-none { line-height: 1; } -.leading-normal { - line-height: 1.5; +.leading-tight { + line-height: 1.25; } .tracking-normal { letter-spacing: 0em; } -.text-gray-800 { - --tw-text-opacity: 1; - color: rgb(31 41 55 / var(--tw-text-opacity)); -} - -.text-gray-600 { - --tw-text-opacity: 1; - color: rgb(75 85 99 / var(--tw-text-opacity)); -} - -.text-pink-500 { +.text-white { --tw-text-opacity: 1; - color: rgb(236 72 153 / var(--tw-text-opacity)); + color: rgb(255 255 255 / var(--tw-text-opacity)); } -.text-white { +.text-gray-800 { --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); + color: rgb(31 41 55 / var(--tw-text-opacity)); } .text-pink-800 { @@ -1107,9 +977,9 @@ video { color: rgb(107 114 128 / var(--tw-text-opacity)); } -.underline { - -webkit-text-decoration-line: underline; - text-decoration-line: underline; +.text-gray-600 { + --tw-text-opacity: 1; + color: rgb(75 85 99 / var(--tw-text-opacity)); } .no-underline { @@ -1117,14 +987,14 @@ video { text-decoration-line: none; } -.opacity-25 { - opacity: 0.25; -} - .opacity-75 { opacity: 0.75; } +.opacity-25 { + opacity: 0.25; +} + .shadow { --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); @@ -1185,6 +1055,42 @@ video { color: #40BABD; } +.bg-color1 { + background-color: #A0B15B; +} + +.bg-color2 { + background-color: #DC9A7A; +} + +.bg-color3 { + background-color: #9EA5E3; +} + +.outline-color1 { + box-shadow: 0px 0px 0px 2px #A0B15B inset; +} + +.outline-color2 { + box-shadow: 0px 0px 0px 2px #DC9A7A inset; +} + +.outline-color3 { + box-shadow: 0px 0px 0px 2px #9EA5E3 inset; +} + +.outline-color1-darker { + box-shadow: 0px 0px 0px 2px #515a2b inset; +} + +.outline-color2-darker { + box-shadow: 0px 0px 0px 2px #874423 inset; +} + +.outline-color3-darker { + box-shadow: 0px 0px 0px 2px #2b3595 inset; +} + .hover\:scale-105:hover { --tw-scale-x: 1.05; --tw-scale-y: 1.05; @@ -1221,30 +1127,15 @@ video { outline-offset: 2px; } -@media (min-width: 640px) { - .sm\:my-4 { - margin-top: 1rem; - margin-bottom: 1rem; - } - - .sm\:-mt-6 { - margin-top: -1.5rem; - } - - .sm\:h-64 { - height: 16rem; - } - - .sm\:w-1\/2 { - width: 50%; +@media (min-width: 768px) { + .md\:col-span-3 { + grid-column: span 3 / span 3; } - .sm\:flex-row { - flex-direction: row; + .md\:col-span-2 { + grid-column: span 2 / span 2; } -} -@media (min-width: 768px) { .md\:mb-6 { margin-bottom: 1.5rem; } @@ -1257,25 +1148,28 @@ video { display: block; } - .md\:w-1\/3 { - width: 33.333333%; + .md\:grid { + display: grid; } .md\:w-2\/5 { width: 40%; } + .md\:grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + .md\:flex-row { flex-direction: row; } - .md\:text-left { - text-align: left; + .md\:gap-4 { + gap: 1rem; } - .md\:text-sm { - font-size: 0.875rem; - line-height: 1.25rem; + .md\:text-left { + text-align: left; } } @@ -1297,14 +1191,6 @@ video { display: none; } - .lg\:w-1\/4 { - width: 25%; - } - - .lg\:w-1\/3 { - width: 33.333333%; - } - .lg\:w-auto { width: auto; } @@ -1313,11 +1199,6 @@ video { align-items: center; } - .lg\:rounded-l-lg { - border-top-left-radius: 0.5rem; - border-bottom-left-radius: 0.5rem; - } - .lg\:bg-transparent { background-color: transparent; } diff --git a/site/Site/wwwroot/css/input.css b/site/Site/wwwroot/css/input.css index b30690de3..cb7883d2a 100644 --- a/site/Site/wwwroot/css/input.css +++ b/site/Site/wwwroot/css/input.css @@ -27,4 +27,40 @@ .text-main { color: #40BABD; +} + +.bg-color1 { + background-color: #A0B15B; +} + +.bg-color2 { + background-color: #DC9A7A; +} + +.bg-color3 { + background-color: #9EA5E3; +} + +.outline-color1 { + box-shadow: 0px 0px 0px 2px #A0B15B inset; +} + +.outline-color2 { + box-shadow: 0px 0px 0px 2px #DC9A7A inset; +} + +.outline-color3 { + box-shadow: 0px 0px 0px 2px #9EA5E3 inset; +} + +.outline-color1-darker { + box-shadow: 0px 0px 0px 2px #515a2b inset; +} + +.outline-color2-darker { + box-shadow: 0px 0px 0px 2px #874423 inset; +} + +.outline-color3-darker { + box-shadow: 0px 0px 0px 2px #2b3595 inset; } \ No newline at end of file From 92e43f0d63bbc05cce2e1f602484b31e439eace5 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 12 Dec 2022 10:16:51 +0100 Subject: [PATCH 149/193] Fix link GetBounds --- .../Models/Base/BaseLinkModel.cs | 16 ++++++-------- .../Models/Base/BaseLinkModelTests.cs | 22 +++++++++++++++++++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index 75f420cfe..e0a7cc1bd 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -10,7 +10,7 @@ namespace Blazor.Diagrams.Core.Models.Base; public abstract class BaseLinkModel : SelectableModel, IHasBounds, ILinkable { private readonly List _links = new(); - + public event Action? SourceChanged; public event Action? TargetChanged; @@ -86,14 +86,12 @@ public void SetTarget(Anchor anchor) var maxX = double.NegativeInfinity; var maxY = double.NegativeInfinity; - foreach (var path in PathGeneratorResult.Paths) - { - var bbox = path.GetBBox(); - minX = Math.Min(minX, bbox.Left); - minY = Math.Min(minY, bbox.Top); - maxX = Math.Max(maxX, bbox.Right); - maxY = Math.Max(maxY, bbox.Bottom); - } + var path = PathGeneratorResult.FullPath; + var bbox = path.GetBBox(); + minX = Math.Min(minX, bbox.Left); + minY = Math.Min(minY, bbox.Top); + maxX = Math.Max(maxX, bbox.Right); + maxY = Math.Max(maxY, bbox.Bottom); return new Rectangle(minX, minY, maxX, maxY); } diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs index d9596fc8e..577843a1d 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs @@ -2,6 +2,8 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.PathGenerators; +using Blazor.Diagrams.Core.Routers; using FluentAssertions; using Xunit; @@ -74,5 +76,25 @@ public void SetTarget_ShouldChangePropertiesAndTriggerEvent() linkInstance.Should().BeSameAs(link); link.Target!.Model.Should().BeSameAs(port); } + + [Fact] + public void GetBounds_ShouldReturnPathBBox() + { + // Arrange + var link = new LinkModel(new PositionAnchor(new Point(10, 5)), new PositionAnchor(new Point(100, 80))); + link.Diagram = new TestDiagram(); + link.PathGenerator = new StraightPathGenerator(); + link.Router = new NormalRouter(); + + // Act + link.Refresh(); + var bounds = link.GetBounds()!; + + // Assert + bounds.Left.Should().Be(10); + bounds.Top.Should().Be(5); + bounds.Width.Should().Be(90); + bounds.Height.Should().Be(75); + } } } From caaac64f3d0a05bd1c641f4c42fdb608af122b1f Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 24 Dec 2022 13:40:06 +0100 Subject: [PATCH 150/193] Finish landing page --- site/Site/Components/Footer.razor | 71 +++++++++ .../Landing/ColoredNodeWidget.razor | 12 +- .../Landing/Features/FeaturesExample.razor | 34 ++++ .../Landing/Features/FeaturesExample.razor.cs | 113 +++++++++++++ site/Site/Pages/Landing/Index.razor | 12 +- site/Site/Properties/launchSettings.json | 2 +- site/Site/Shared/LandingLayout.razor | 89 ++--------- site/Site/_Imports.razor | 1 + site/Site/wwwroot/css/app.css | 150 +++++++++++++++--- site/Site/wwwroot/css/input.css | 15 ++ 10 files changed, 391 insertions(+), 108 deletions(-) create mode 100644 site/Site/Components/Footer.razor create mode 100644 site/Site/Components/Landing/Features/FeaturesExample.razor create mode 100644 site/Site/Components/Landing/Features/FeaturesExample.razor.cs diff --git a/site/Site/Components/Footer.razor b/site/Site/Components/Footer.razor new file mode 100644 index 000000000..20712847d --- /dev/null +++ b/site/Site/Components/Footer.razor @@ -0,0 +1,71 @@ + \ No newline at end of file diff --git a/site/Site/Components/Landing/ColoredNodeWidget.razor b/site/Site/Components/Landing/ColoredNodeWidget.razor index 228466bf5..590cba0df 100644 --- a/site/Site/Components/Landing/ColoredNodeWidget.razor +++ b/site/Site/Components/Landing/ColoredNodeWidget.razor @@ -7,6 +7,16 @@
    @Node.Title + + @foreach (var port in Node.Ports) + { + var classes = new StringBuilder("absolute -translate-y-1/2 ") + .Append(extraClasses) + .AppendIf(" top-1/2 -right-3", port.Alignment == PortAlignment.Right) + .AppendIf(" top-1/2 -left-3", port.Alignment == PortAlignment.Left); + + + }
    @code { @@ -14,4 +24,4 @@ [Parameter] public ColoredNodeModel Node { get; set; } = null!; -} \ No newline at end of file +} \ No newline at end of file diff --git a/site/Site/Components/Landing/Features/FeaturesExample.razor b/site/Site/Components/Landing/Features/FeaturesExample.razor new file mode 100644 index 000000000..15af784b0 --- /dev/null +++ b/site/Site/Components/Landing/Features/FeaturesExample.razor @@ -0,0 +1,34 @@ +
    + +
    +
    +
    +

    + Lots of features +

    +

    + Blazor Diagrams offers many features, including: +

    +
      +
    • Touch support
    • +
    • Fully customization and extensibility
    • +
    • Link routers, path generators, markers and labels
    • +
    • Controls on top of nodes/links
    • +
    • Node Ports
    • +
    • And much more
    • +
    +
    +
    +
    + + + + + + + +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/site/Site/Components/Landing/Features/FeaturesExample.razor.cs b/site/Site/Components/Landing/Features/FeaturesExample.razor.cs new file mode 100644 index 000000000..e24399efe --- /dev/null +++ b/site/Site/Components/Landing/Features/FeaturesExample.razor.cs @@ -0,0 +1,113 @@ +using Blazor.Diagrams; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.PathGenerators; +using Blazor.Diagrams.Core.Positions; +using Blazor.Diagrams.Core.Routers; +using Site.Models.Landing; + +namespace Site.Components.Landing.Features +{ + public partial class FeaturesExample + { + private readonly BlazorDiagram _diagram = new(); + + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.GridSize = 10; + _diagram.Options.Links.RequireTarget = false; + _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); + _diagram.RegisterComponent(); + + var smoothPathGenerator = new SmoothPathGenerator(); + + // ArrowHeadControl example + var node1 = _diagram.Nodes.Add(new ColoredNodeModel("Locked", true, "color2", new Point(20, 20))); + node1.Locked = true; + var link1 = _diagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node1), new PositionAnchor(new Point(340, 60))) + { + Color = "#DC9A7A", + SelectedColor = "#874423", + TargetMarker = LinkMarker.Arrow + }); + _diagram.Controls.AddFor(link1).Add(new ArrowHeadControl(false)); + link1.Labels.Add(new LinkLabelModel(link1, "I am a free link", 0.5, new Point(0, -15))); + + // Labels example + var node2 = _diagram.Nodes.Add(new ColoredNodeModel("Movable", true, "color1", new Point(20, 350))); + var link2 = _diagram.Links.Add(new LinkModel(node1, node2) + { + Color = "#DC9A7A", + SelectedColor = "#874423", + TargetMarker = LinkMarker.Arrow + }); + link2.Labels.Add(new LinkLabelModel(link1, "Start", 0.1, new Point(0, 0))); + link2.Labels.Add(new LinkLabelModel(link1, "Middle", 0.5, new Point(0, 0))); + link2.Labels.Add(new LinkLabelModel(link1, "End", 0.9, new Point(0, 0))); + + // Controls example + var node3 = _diagram.Nodes.Add(new ColoredNodeModel("Select me", false, "color3", new Point(320, 370))); + var link3 = _diagram.Links.Add(new LinkModel(node1, node3) + { + Color = "#9EA5E3", + SelectedColor = "#2b3595", + PathGenerator = smoothPathGenerator, + TargetMarker = LinkMarker.Arrow + }); + link3.Labels.Add(new LinkLabelModel(link3, "Select me to show controls", 0.5, new Point(0, -15))); + + _diagram.Controls.AddFor(link3) + .Add(new ArrowHeadControl(true)) + .Add(new ArrowHeadControl(false)) + .Add(new BoundaryControl()) + .Add(new RemoveControl(new LinkPathPositionProvider(0.8, 0, -10))); + + _diagram.Controls.AddFor(node3) + .Add(new BoundaryControl()) + .Add(new RemoveControl(new BoundsBasedPositionProvider(1, 0, 10, -10))); + + // Ports and Routes example + var node4 = _diagram.Nodes.Add(new ColoredNodeModel("With ports", false, "color1", new Point(560, 20))); + var node5 = _diagram.Nodes.Add(new ColoredNodeModel("Locked", true, "color2", new Point(720, 350))); + node5.Locked = true; + + var port1 = node3.AddPort(PortAlignment.Right); + var port2 = node4.AddPort(PortAlignment.Left); + var port3 = node4.AddPort(PortAlignment.Right); + var port4 = node5.AddPort(PortAlignment.Left); + var port5 = node5.AddPort(PortAlignment.Right); + + var link4 = _diagram.Links.Add(new LinkModel(port1, port2) + { + Color = "#9EA5E3", + SelectedColor = "#2b3595", + PathGenerator = smoothPathGenerator, + TargetMarker = LinkMarker.Arrow + }); + link4.Labels.Add(new LinkLabelModel(link4, "Smooth PathGenerator", 0.5)); + + var link5 = _diagram.Links.Add(new LinkModel(port3, port4) + { + Color = "#A0B15B", + SelectedColor = "#515a2b", + Router = new OrthogonalRouter(), + PathGenerator = new StraightPathGenerator(20), + TargetMarker = new LinkMarker("M 0 -8 L 8 -8 4 0 8 8 0 8 4 0 z", 8) + }); + link5.Labels.Add(new LinkLabelModel(link5, "Orthogonal Router", 0.3)); + link5.Labels.Add(new LinkLabelModel(link5, "Custom marker", 0.8)); + + _diagram.Links.Add(new LinkModel(port3, port5) + { + Color = "#A0B15B", + SelectedColor = "#515a2b", + Router = new OrthogonalRouter(), + PathGenerator = new StraightPathGenerator(), + TargetMarker = LinkMarker.Arrow + }); + } + } +} diff --git a/site/Site/Pages/Landing/Index.razor b/site/Site/Pages/Landing/Index.razor index c61596f7a..5e9936a48 100644 --- a/site/Site/Pages/Landing/Index.razor +++ b/site/Site/Pages/Landing/Index.razor @@ -28,6 +28,8 @@ + + @@ -57,15 +59,15 @@

    - Call to Action + Get in touch

    - Main Hero Message to sell yourself! + Don't hesitate to ask about anything either on GitHub or even by email!

    - + @* *@
    \ No newline at end of file diff --git a/site/Site/Properties/launchSettings.json b/site/Site/Properties/launchSettings.json index 6e91e69c3..4219fb5f4 100644 --- a/site/Site/Properties/launchSettings.json +++ b/site/Site/Properties/launchSettings.json @@ -13,7 +13,7 @@ "dotnetRunMessages": true, "launchBrowser": true, "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "https://localhost:7025;http://localhost:5037", + "applicationUrl": "https://localhost:8032;http://localhost:8031", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/site/Site/Shared/LandingLayout.razor b/site/Site/Shared/LandingLayout.razor index 91c3e6f96..93a277656 100644 --- a/site/Site/Shared/LandingLayout.razor +++ b/site/Site/Shared/LandingLayout.razor @@ -20,93 +20,26 @@
    -@Body - - \ No newline at end of file +@Body \ No newline at end of file diff --git a/site/Site/_Imports.razor b/site/Site/_Imports.razor index a407837f2..b98c25f0d 100644 --- a/site/Site/_Imports.razor +++ b/site/Site/_Imports.razor @@ -12,6 +12,7 @@ @using Site.Components.Landing @using Site.Components.Landing.SvgAndHtml @using Site.Components.Landing.Groups +@using Site.Components.Landing.Features @using Site.Models @using Site.Models.Landing @using Site.Models.Landing.Groups diff --git a/site/Site/wwwroot/css/app.css b/site/Site/wwwroot/css/app.css index 158d960b1..733256aa3 100644 --- a/site/Site/wwwroot/css/app.css +++ b/site/Site/wwwroot/css/app.css @@ -605,6 +605,78 @@ video { top: 0px; } +.top-1\/2 { + top: 50%; +} + +.right-0\.5 { + right: 0.125rem; +} + +.right-0 { + right: 0px; +} + +.-right-0\.5 { + right: -0.125rem; +} + +.-right-0 { + right: -0px; +} + +.-right-1 { + right: -0.25rem; +} + +.-right-1\.5 { + right: -0.375rem; +} + +.-left-1\.5 { + left: -0.375rem; +} + +.-left-1 { + left: -0.25rem; +} + +.left-1\.5 { + left: 0.375rem; +} + +.left-1 { + left: 0.25rem; +} + +.-left-2 { + left: -0.5rem; +} + +.right-2 { + right: 0.5rem; +} + +.-right-2 { + right: -0.5rem; +} + +.-right-2\.5 { + right: -0.625rem; +} + +.-left-2\.5 { + left: -0.625rem; +} + +.-right-3 { + right: -0.75rem; +} + +.-left-3 { + left: -0.75rem; +} + .z-30 { z-index: 30; } @@ -666,8 +738,8 @@ video { margin-right: 0.5rem; } -.mb-2 { - margin-bottom: 0.5rem; +.mb-8 { + margin-bottom: 2rem; } .mb-3 { @@ -678,14 +750,14 @@ video { margin-bottom: 1rem; } -.mb-8 { - margin-bottom: 2rem; -} - .mb-12 { margin-bottom: 3rem; } +.mb-2 { + margin-bottom: 0.5rem; +} + .block { display: block; } @@ -718,14 +790,14 @@ video { height: 1.5rem; } -.h-full { - height: 100%; -} - .h-1 { height: 0.25rem; } +.h-full { + height: 100%; +} + .w-full { width: 100%; } @@ -750,6 +822,26 @@ video { flex-grow: 1; } +.translate-x-1\/2 { + --tw-translate-x: 50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.translate-y-1\/2 { + --tw-translate-y: 50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.-translate-y-1\/2 { + --tw-translate-y: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + +.-translate-x-1\/2 { + --tw-translate-x: -50%; + transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); +} + .transform { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } @@ -909,16 +1001,16 @@ video { line-height: 2rem; } -.text-3xl { - font-size: 1.875rem; - line-height: 2.25rem; -} - .text-5xl { font-size: 3rem; line-height: 1; } +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + .font-bold { font-weight: 700; } @@ -935,14 +1027,14 @@ video { line-height: 1.5; } -.leading-none { - line-height: 1; -} - .leading-tight { line-height: 1.25; } +.leading-none { + line-height: 1; +} + .tracking-normal { letter-spacing: 0em; } @@ -1091,6 +1183,18 @@ video { box-shadow: 0px 0px 0px 2px #2b3595 inset; } +.link-label > div { + display: inline-block; + color: #000000; + font-size: 0.875rem; + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + transform: translate(-50%, -50%); + white-space: nowrap; + background-color: white; +} + .hover\:scale-105:hover { --tw-scale-x: 1.05; --tw-scale-y: 1.05; @@ -1128,14 +1232,14 @@ video { } @media (min-width: 768px) { - .md\:col-span-3 { - grid-column: span 3 / span 3; - } - .md\:col-span-2 { grid-column: span 2 / span 2; } + .md\:col-span-3 { + grid-column: span 3 / span 3; + } + .md\:mb-6 { margin-bottom: 1.5rem; } diff --git a/site/Site/wwwroot/css/input.css b/site/Site/wwwroot/css/input.css index cb7883d2a..47f23ae36 100644 --- a/site/Site/wwwroot/css/input.css +++ b/site/Site/wwwroot/css/input.css @@ -63,4 +63,19 @@ .outline-color3-darker { box-shadow: 0px 0px 0px 2px #2b3595 inset; +} + +.link-label > div { + display: inline-block; + color: #000000; + font-size: 0.875rem; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-transform: translate(-50%, -50%); + -ms-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + white-space: nowrap; + background-color: white; } \ No newline at end of file From d29705914188c4f94dda5f7edc5781b280b12968 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 24 Dec 2022 16:02:47 +0100 Subject: [PATCH 151/193] Add GitHub link in landing page --- site/Site/Pages/Landing/Index.razor | 3 --- site/Site/Shared/LandingLayout.razor | 8 +++++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/site/Site/Pages/Landing/Index.razor b/site/Site/Pages/Landing/Index.razor index 5e9936a48..5e3a2b116 100644 --- a/site/Site/Pages/Landing/Index.razor +++ b/site/Site/Pages/Landing/Index.razor @@ -67,7 +67,4 @@

    Don't hesitate to ask about anything either on GitHub or even by email!

    - @* *@
    \ No newline at end of file diff --git a/site/Site/Shared/LandingLayout.razor b/site/Site/Shared/LandingLayout.razor index 93a277656..f1c53b855 100644 --- a/site/Site/Shared/LandingLayout.razor +++ b/site/Site/Shared/LandingLayout.razor @@ -20,17 +20,19 @@
    \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Diagram/Api.razor b/site/Site/Pages/Documentation/Diagram/Api.razor new file mode 100644 index 000000000..d13cfcc83 --- /dev/null +++ b/site/Site/Pages/Documentation/Diagram/Api.razor @@ -0,0 +1,361 @@ +@page "/documentation/diagram-api" +@layout DocumentationLayout +@inherits DocumentationPage + +@{ + RenderFragment readonlySubtitle = @Read only; +} + +Diagram API - Documentation - Blazor Diagrams + +

    Diagram API

    + +

    Properties

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDefaultDescription
    Options @readonlySubtitleBlazorDiagramOptions-
    Nodes @readonlySubtitleNodeLayer-Acts as a list for the nodes
    Links @readonlySubtitleLinkLayer-Acts as a list for the links
    Groups @readonlySubtitleGroupLayer-Acts as a list for the groups
    Controls @readonlySubtitleControlsLayer-Only way to Get/Add/Remove controls for a model
    Container @readonlySubtitleRectangle?nullBoundaries representing the canvas, retrieved with JS
    Pan @readonlySubtitlePoint?Zero
    Zoom @readonlySubtitleDouble1
    SuspendRefreshBooleanfalseSuspends the refreshes related to the diagram (canvas)
    OrderedSelectables @readonlySubtitleIReadOnlyList<SelectableModel>-Ordered list of models using the Order property
    + +

    Events

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDescription
    PointerDownAction<Model?, PointerEventArgs>?
    PointerMoveAction<Model?, PointerEventArgs>?
    PointerUpAction<Model?, PointerEventArgs>?
    PointerEnterAction<Model?, PointerEventArgs>?
    PointerLeaveAction<Model?, PointerEventArgs>?
    KeyDownAction<KeyboardEventArgs>?
    WheelAction<WheelEventArgs>?
    PointerClickAction<Model?, PointerEventArgs>?
    PointerDoubleClickAction<Model?, PointerEventArgs>?
    SelectionChangedAction<SelectableModel>?
    PanChangedAction?
    ZoomChangedAction?
    ContainerChangedAction?
    ChangedAction?
    + +

    Methods

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameReturn TypeDescription
    RegisterComponent<TModel, TComponent>(bool replace = false)voidRegister a Blazor component to render for every instance of TModel
    RegisterComponent(Type modelType, Type componentType, bool replace = false)voidRegister a Blazor component to render for every instance of modelType
    GetComponent(Type modelType, bool checkSubclasses = true)Type?Returns the component Type registered for the modelType
    GetComponent<TModel>(bool checkSubclasses = true)Type?Returns the component Type registered for TModel
    GetComponent(Model model, bool checkSubclasses = true)Type?Returns the component Type registered for typeof(Model)
    Refresh()void
    Batch(Action action)void
    GetSelectedModels()IEnumerable<SelectableModel>
    SelectModel(SelectableModel model, bool unselectOthers)void
    UnselectModel(SelectableModel model)void
    UnselectAll()void
    RegisterBehavior(Behavior behavior)void
    GetBehavior<T>() where T : BehaviorT?
    UnregisterBehavior<T>() where T : Behaviorvoid
    ZoomToFit(double margin void
    SetPan(double x, double y)void
    UpdatePan(double deltaX, double deltaY)void
    SetZoom(double newZoom)void
    SetContainer(Rectangle newRect)void
    GetRelativeMousePoint(double clientX, double clientY)Point
    GetRelativePoint(double clientX, double clientY)Point
    GetScreenPoint(double clientX, double clientY)Point
    SendToBack(SelectableModel model)void
    SendToFront(SelectableModel model)void
    GetMinOrder()int
    GetMaxOrder()int
    TriggerPointerDown(Model? model, PointerEventArgs e) void
    TriggerPointerMove(Model? model, PointerEventArgs e) void
    TriggerPointerUp(Model? model, PointerEventArgs e) void
    TriggerPointerEnter(Model? model, PointerEventArgs e) void
    TriggerPointerLeave(Model? model, PointerEventArgs e) void
    TriggerKeyDown(KeyboardEventArgs e) void
    TriggerWheel(WheelEventArgs e) void
    TriggerPointerClick(Model? model, PointerEventArgs e) void
    TriggerPointerDoubleClick(Model? model, PointerEventArgs e) void
    + + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Diagram/Api.razor.css b/site/Site/Pages/Documentation/Diagram/Api.razor.css new file mode 100644 index 000000000..50dbcec91 --- /dev/null +++ b/site/Site/Pages/Documentation/Diagram/Api.razor.css @@ -0,0 +1,3 @@ +tr td:first-child { + font-weight: 600; +} diff --git a/site/Site/Pages/Documentation/Diagram/Behaviors.razor b/site/Site/Pages/Documentation/Diagram/Behaviors.razor new file mode 100644 index 000000000..4076ebd07 --- /dev/null +++ b/site/Site/Pages/Documentation/Diagram/Behaviors.razor @@ -0,0 +1,120 @@ +@page "/documentation/diagram-behaviors" +@layout DocumentationLayout +@inherits DocumentationPage + +Diagram Behaviors - Documentation - Blazor Diagrams + +

    Diagram Behaviors

    + +

    + Behaviors are a way to encapsulate a functionality. + They are separated into classes mainly for readability, separation of concerns, and for the library users to be able to remove/replace them. + The behaviors inherit from the base class Behavior and use the available events to do what they need. +

    + +

    Example

    + +
    
    +using Blazor.Diagrams.Core.Models.Base;
    +using Blazor.Diagrams.Core.Events;
    +
    +namespace Blazor.Diagrams.Core.Behaviors
    +{
    +    public class SelectionBehavior : Behavior
    +    {
    +        public SelectionBehavior(Diagram diagram) : base(diagram)
    +        {
    +            Diagram.PointerDown += OnPointerDown;
    +        }
    +
    +        private void OnPointerDown(Model? model, PointerEventArgs e)
    +        {
    +            var ctrlKey = e.CtrlKey;
    +
    +            switch (model)
    +            {
    +                case null:
    +                    Diagram.UnselectAll();
    +                    break;
    +                case SelectableModel sm when ctrlKey && sm.Selected:
    +                    Diagram.UnselectModel(sm);
    +                    break;
    +                case SelectableModel sm:
    +                {
    +                    if (!sm.Selected)
    +                    {
    +                        Diagram.SelectModel(sm, !ctrlKey || !Diagram.Options.AllowMultiSelection);
    +                    }
    +
    +                    break;
    +                }
    +            }
    +        }
    +
    +        public override void Dispose()
    +        {
    +            Diagram.PointerDown -= OnPointerDown;
    +        }
    +    }
    +}
    +
    + +

    + As you can see, the behavior simply uses the PointerDown event in order to (un)select stuff. +

    + +

    Replacing a behavior

    + +Let's say you didn't like how the SelectionBehavior works, let's replace it by a simpler one.
    +We just want to keep selecting models without CTRL and only unselect everything once the canvas is clicked. + +
    
    +using Blazor.Diagrams.Core.Models.Base;
    +using Blazor.Diagrams.Core.Events;
    +
    +namespace Blazor.Diagrams.Core.Behaviors
    +{
    +    public class MySelectionBehavior : Behavior
    +    {
    +        public SelectionBehavior(Diagram diagram) : base(diagram)
    +        {
    +            Diagram.PointerDown += OnPointerDown;
    +        }
    +
    +        private void OnPointerDown(Model? model, PointerEventArgs e)
    +        {
    +            if (model == null) // Canvas
    +            {
    +                Diagram.UnselectAll();
    +            }
    +            else if (model is SelectableModel sm)
    +            {
    +                Diagram.SelectModel(sm);
    +            }
    +        }
    +
    +        public override void Dispose()
    +        {
    +            Diagram.PointerDown -= OnPointerDown;
    +        }
    +    }
    +}
    +
    + +

    + A very simple class indeed. Inherting from Behavior automatically forces you to have the constructor, in order to have the Diagram instance, + but also the Dispose method in order to remove any event handlers in case the behavior is unregistered.

    + Let's now replace the old behavior with ours: +

    + +
    
    +// In case you want to keep the old behavior intance
    +var oldSelectionBehavior = Diagram.GetBehavior<SelectionBehavior>()!;
    +Diagram.UnregisterBehavior<SelectionBehavior>();
    +Diagram.RegisterBehavior(new MySelectionBehavior(Diagram));
    +
    + + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Diagram/Layers.razor b/site/Site/Pages/Documentation/Diagram/Layers.razor new file mode 100644 index 000000000..d4522acb0 --- /dev/null +++ b/site/Site/Pages/Documentation/Diagram/Layers.razor @@ -0,0 +1,37 @@ +@page "/documentation/diagram-layers" +@layout DocumentationLayout +@inherits DocumentationPage + +Diagram Layers - Documentation - Blazor Diagrams + +

    Diagram Layers

    + +

    + It is very important to know that in Blazor Diagrams, everything is rendered in 2 layers: +

    + +

    SVG

    + +
    
    +<svg class="diagram-svg-layer" style="transform: translate(0px, 0px) scale(1); z-index: 0;">
    +
    + +

    + This layer is basically a svg. It is mainly used to render links, since they require the <path> element.
    + However, it is also possible to render nodes in it by inherting from SvgNodeModel or SvgGroupModel. + This lets you use any svg elements and features with ease. +

    + +

    HTML

    + +
    
    +<div class="diagram-html-layer" style="transform: translate(0px, 0px) scale(1);">
    +
    + +

    + This layer is a simple html div. It is mainly used to render nodes. + It is very useful since you can re-use any html you might already have in your nodes, as well as include interactivity through inputs for example. +

    + + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Diagram/Options.razor b/site/Site/Pages/Documentation/Diagram/Options.razor new file mode 100644 index 000000000..7c0fc5ce1 --- /dev/null +++ b/site/Site/Pages/Documentation/Diagram/Options.razor @@ -0,0 +1,284 @@ +@page "/documentation/diagram-options" +@layout DocumentationLayout +@inherits DocumentationPage + +Diagram Options - Documentation - Blazor Diagrams + +

    Diagram Options

    + +

    + Here are all the available options in BlazorDiagramOptions: +

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDefaultDescription
    LinksLayerOrderInteger0The order (z-index) of the SVG layer
    NodesLayerOrderInteger0The order (z-index) of the HTML layer
    ZoomBlazorDiagramZoomOptions
    LinksBlazorDiagramLinkOptions
    GroupsBlazorDiagramGroupOptions
    ConstraintsBlazorDiagramConstraintsOptions
    VirtualizationBlazorDiagramVirtualizationOptions
    + +

    Zoom

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDefaultDescription
    EnabledBooleantrue
    InverseBooleanfalse
    MinimumDouble0.1
    MaximumDouble2
    ScaleFactorDouble1.05
    + +

    Links

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDefaultDescription
    DefaultColorStringblack
    DefaultSelectedColorStringrgb(110, 159, 212)
    DefaultRouterRouterNormalRouter
    DefaultPathGeneratorPathGeneratorSmoothPathGenerator
    EnableSnappingBooleanfalseIf true, dragging new links will snap them to the closest ports
    SnappingRadiusDouble50Used to calculate the distance and decide whether to snap the link
    RequireTargetBooleantrueIf false, links can be free (without target)
    FactoryLinkFactory-Delegate to control how a link is instanciated
    TargetAnchorFactoryAnchorFactory-Delegate to control how the target anchor is instanciated
    + +

    Groups

    + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDefaultDescription
    EnabledBooleanfalse
    FactoryGroupFactory-Delegate to control how a group is instanciated
    + +

    Constraints

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDefaultDescription
    ShouldDeleteNodeFunc<NodeModel, ValueTask<bool>>true
    ShouldDeleteLinkFunc<BaseLinkModel, ValueTask<bool>>true
    ShouldDeleteGroupFunc<GroupModel, ValueTask<bool>>true
    + +

    Virtualization

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    NameTypeDefaultDescription
    EnabledBooleanfalse
    OnNodesBooleantrue
    OnGroupsBooleanfalse
    OnLinksBooleanfalse
    + + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/GettingStarted/Display.razor b/site/Site/Pages/Documentation/GettingStarted/Display.razor index d5da732a7..b249297f7 100644 --- a/site/Site/Pages/Documentation/GettingStarted/Display.razor +++ b/site/Site/Pages/Documentation/GettingStarted/Display.razor @@ -29,7 +29,7 @@ Now create a scoped CSS file with the following content: } -Now you have a visible container, let's display the diagram: +Now that we have a visible container, let's display the diagram:
    MyDiagram.razor
    
    diff --git a/site/Site/Shared/LandingLayout.razor b/site/Site/Shared/LandingLayout.razor
    index 5f4264a0c..0eb50b998 100644
    --- a/site/Site/Shared/LandingLayout.razor
    +++ b/site/Site/Shared/LandingLayout.razor
    @@ -43,9 +43,9 @@
                         
                     
                 
    -            
    +            
             
    diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs index 2c9e96a4d..d81920552 100644 --- a/site/Site/Static/Documentation.cs +++ b/site/Site/Static/Documentation.cs @@ -15,6 +15,20 @@ public static class Documentation new MenuItem("Installation", "/documentation/installation"), new MenuItem("Diagram Creation", "/documentation/diagram-creation"), new MenuItem("Display", "/documentation/display"), + }), + new MenuGroup("Diagram", new List + { + new MenuItem("Layers", "/documentation/diagram-layers"), + new MenuItem("Behaviors", "/documentation/diagram-behaviors"), + new MenuItem("Options", "/documentation/diagram-options"), + new MenuItem("API", "/documentation/diagram-api"), + }), + new MenuGroup("Customization", new List + { + new MenuItem("Custom Nodes", "/documentation/custom-nodes"), + new MenuItem("Custom Ports", "/documentation/custom-ports"), + new MenuItem("Custom Links", "/documentation/custom-links"), + new MenuItem("Custom Groups", "/documentation/custom-groups"), }) }); } diff --git a/site/Site/wwwroot/css/app.css b/site/Site/wwwroot/css/app.css index 4bb2a0981..804be6c5f 100644 --- a/site/Site/wwwroot/css/app.css +++ b/site/Site/wwwroot/css/app.css @@ -609,6 +609,26 @@ h3 { font-weight: 700; } +table { + margin-top: 1rem; + margin-bottom: 1rem; + width: 100%; + table-layout: auto; + border-collapse: collapse; +} + +tr { + border-bottom-width: 1px; +} + +td, th { + padding-left: 1.5rem; + padding-right: 1.5rem; + padding-top: 1rem; + padding-bottom: 1rem; + text-align: left; +} + .sr-only { position: absolute; width: 1px; @@ -776,14 +796,14 @@ h3 { margin-bottom: 1rem; } -.mb-2 { - margin-bottom: 0.5rem; -} - .mb-12 { margin-bottom: 3rem; } +.mb-2 { + margin-bottom: 0.5rem; +} + .block { display: block; } @@ -800,6 +820,10 @@ h3 { display: flex; } +.table { + display: table; +} + .grid { display: grid; } @@ -1195,6 +1219,11 @@ h3 { line-height: 2.25rem; } +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + .font-bold { font-weight: 700; } @@ -1207,6 +1236,10 @@ h3 { font-weight: 500; } +.font-normal { + font-weight: 400; +} + .uppercase { text-transform: uppercase; } diff --git a/site/Site/wwwroot/css/input.css b/site/Site/wwwroot/css/input.css index f5996e5e8..a670b948a 100644 --- a/site/Site/wwwroot/css/input.css +++ b/site/Site/wwwroot/css/input.css @@ -14,6 +14,18 @@ h3 { @apply text-xl font-bold my-4; } + + table { + @apply border-collapse table-auto w-full my-4; + } + + tr { + @apply border-b; + } + + td, th { + @apply px-6 py-4 text-left; + } } #blazor-error-ui { From 30de559783e344c1d2c0974fa048f551de75cae4 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 1 Jan 2023 10:46:40 +0100 Subject: [PATCH 157/193] Update Api.razor --- site/Site/Pages/Documentation/Diagram/Api.razor | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/site/Site/Pages/Documentation/Diagram/Api.razor b/site/Site/Pages/Documentation/Diagram/Api.razor index d13cfcc83..af548a11f 100644 --- a/site/Site/Pages/Documentation/Diagram/Api.razor +++ b/site/Site/Pages/Documentation/Diagram/Api.razor @@ -358,4 +358,5 @@ - \ No newline at end of file + \ No newline at end of file From 421ae5e15cd685a1d8c5f82f45e20310080357e5 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 7 May 2023 22:27:28 +0100 Subject: [PATCH 158/193] Fix port snapping not choosing the closest one --- .../Behaviors/DragNewLinkBehavior.cs | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 4171853ef..340a4406b 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -114,17 +114,29 @@ private void OnPointerUp(Model? model, MouseEventArgs e) private PortModel? FindNearPortToAttachTo() { - var ongoingPosition = _targetPositionAnchor!.GetPosition(_ongoingLink!)!; - foreach (var port in Diagram.Nodes.SelectMany(n => n.Ports)) + 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)) { - if (ongoingPosition.DistanceTo(port.MiddlePosition) < Diagram.Options.Links.SnappingRadius - && (_ongoingLink!.Source.Model == null || _ongoingLink.Source.Model.CanAttachTo(port))) + var distance = position.DistanceTo(port.Position); + + if (distance <= Diagram.Options.Links.SnappingRadius && (_ongoingLink.Source.Model?.CanAttachTo(port) != false)) { - return port; + if (distance < nearestSnapPortDistance) + { + nearestSnapPortDistance = distance; + nearestSnapPort = port; + } } } - return null; + return nearestSnapPort; } public override void Dispose() From 628f2f27aafae2d416af661628f5ee1e3abd1dfe Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 7 May 2023 23:05:19 +0100 Subject: [PATCH 159/193] Add Groups Documentation and Keyboards Shortcuts --- .../Diagram/KeyboardShortcuts.razor | 59 ++++++++++++++++ .../Pages/Documentation/Diagram/Options.razor | 4 +- .../Pages/Documentation/Groups/Overview.razor | 68 +++++++++++++++++++ .../Site/Pages/Documentation/Groups/SVG.razor | 58 ++++++++++++++++ site/Site/Static/Documentation.cs | 6 ++ .../Behaviors/KeyboardShortcutsBehavior.cs | 1 - .../Behaviors/SelectionBehavior.cs | 5 +- 7 files changed, 195 insertions(+), 6 deletions(-) create mode 100644 site/Site/Pages/Documentation/Diagram/KeyboardShortcuts.razor create mode 100644 site/Site/Pages/Documentation/Groups/Overview.razor create mode 100644 site/Site/Pages/Documentation/Groups/SVG.razor diff --git a/site/Site/Pages/Documentation/Diagram/KeyboardShortcuts.razor b/site/Site/Pages/Documentation/Diagram/KeyboardShortcuts.razor new file mode 100644 index 000000000..89d1b39f2 --- /dev/null +++ b/site/Site/Pages/Documentation/Diagram/KeyboardShortcuts.razor @@ -0,0 +1,59 @@ +@page "/documentation/keyboard-shortcuts" +@layout DocumentationLayout +@inherits DocumentationPage + +Keyboard Shortcuts - Documentation - Blazor Diagrams + +

    Keyboard Shortcuts

    + +

    + Blazor Diagrams provides a utility behavior to handle keyboard shortcuts: KeyboardShortcutsBehavior. +

    + +

    Default shortcuts

    + + + + + + + + + + + + + + + + + + + + + +
    KeysImplementationDescription
    DeleteKeyboardShortcutsDefaults.DeleteSelectionDeletes the selected models (nodes, groups and links)
    Ctrl+Alt+gKeyboardShortcutsDefaults.GroupingGroups the selected models
    + +

    Adding a shortcut

    + +
    
    +var ksb = _blazorDiagram.GetBehavior<KeyboardShortcutsBehavior>();
    +ksb.SetShortcut("s", ctrl: true, shift: true, alt: true, SaveToMyServer);
    +
    +private async ValueTask SaveToMyServer()
    +{
    +    await FakeHttpCall();
    +}
    +
    + +

    Removing a shortcut

    + +
    
    +var ksb = _blazorDiagram.GetBehavior<KeyboardShortcutsBehavior>();
    +ksb.RemoveShortcut("s", ctrl: true, shift: true, alt: true);
    +
    + + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Diagram/Options.razor b/site/Site/Pages/Documentation/Diagram/Options.razor index 7c0fc5ce1..f74f01c67 100644 --- a/site/Site/Pages/Documentation/Diagram/Options.razor +++ b/site/Site/Pages/Documentation/Diagram/Options.razor @@ -280,5 +280,5 @@ \ No newline at end of file + NextTitle="Keyboard Shortcuts" + NextLink="/documentation/keyboard-shortcuts" /> \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Groups/Overview.razor b/site/Site/Pages/Documentation/Groups/Overview.razor new file mode 100644 index 000000000..4e7b56ada --- /dev/null +++ b/site/Site/Pages/Documentation/Groups/Overview.razor @@ -0,0 +1,68 @@ +@page "/documentation/groups-overview" +@layout DocumentationLayout +@inherits DocumentationPage + +Groups - Documentation - Blazor Diagrams + +

    Groups

    + +

    + In Blazor Diagrams, Groups are a way to group nodes together.
    + Groups can also contain other groups, so you can create hierarchies. +

    + +

    Structure

    + +

    + The component GroupRenderer generates the follolwing structure: +

    + +
    
    +<div class="diagram-group ..."
    +     data-group-id="28e9606d-08dd-47d5-a4c7-b95e541bcf1e"
    +     style="top: 10px; left: 10px; width: 100px; height: 100px">
    +    <!-- YOUR CONTENT WILL BE HERE -->
    +</div>
    +
    + +

    + The classes that the div can have (beside diagram-group) are: locked, selected and default. +

    + +

    Creating a group

    + +

    Interactively

    + +

    + By default, users can group selected nodes/groups using the keys Ctrl+g (if Options.Groups.Enabled is set to true). + Check out Keyboard Shortcuts in order to change the required keys. +

    + +

    Programmatically

    + +Assuming we have two nodes, we can simply: + +
    
    +var node1 = new NodeModel(new Point(10, 10));
    +var node2 = new NodeModel(new Point(50, 50));
    +var group = BlazorDiagram.Groups.Group(node1, node2);
    +
    + +

    ! Important Note

    + +Most of the time, groups will have a background of some sort (e.g. a color), which will hide all the links beneath it since +if you remember, the nodes layer is above the links layer. There are 2 solutions for this: + +

    SVG Group

    + +If you use svg nodes, then make sure you add your groups first, then your links. +
    +If you're grouping nodes dynamically, make sure to bring links associated to those groups and their nodes to the front (or the created groups to the back) using the ordering methods available. + +

    Layers order

    + +The BlazorDiagramOptions class contains two properties in order to control the order (z-index) of the layers: LinksLayerOrder and NodesLayerOrder. +All you have to do for groups to work properly is to set LinksLayerOrder to a bigger value than the other. + + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Groups/SVG.razor b/site/Site/Pages/Documentation/Groups/SVG.razor new file mode 100644 index 000000000..17e1fc756 --- /dev/null +++ b/site/Site/Pages/Documentation/Groups/SVG.razor @@ -0,0 +1,58 @@ +@page "/documentation/groups-svg" +@layout DocumentationLayout +@inherits DocumentationPage + +SVG Groups - Documentation - Blazor Diagrams + +

    SVG Groups

    + +

    + SVG groups are groups that will be rendered in the SVG layer.
    + In this page, we will talk about the differences between them and normal groups. +

    + +

    Structure

    + +

    + The component GroupRenderer generates the follolwing structure: +

    + +
    
    +<g class="diagram-group ..."
    +   data-group-id="28e9606d-08dd-47d5-a4c7-b95e541bcf1e"
    +   transform="translate(10 10)">
    +
    +   <rect width="100" height="100" fill="none"></rect>
    +
    +    <!-- YOUR CONTENT WILL BE HERE -->
    +
    +</g>
    +
    + +

    Creating a SVG group

    + +

    Interactively

    + +First, we will need how groups are created: + +
    
    +yourDiagram.Options.Groups.Factory = (diagram, children) => new SvgGroupModel(children);
    +
    + +

    + By default, users can group selected nodes/groups using the keys Ctrl+g (if Options.Groups.Enabled is set to true). + Check out Keyboard Shortcuts in order to change the required keys. +

    + +

    Programmatically

    + +Assuming we have two nodes, we can simply: + +
    
    +var node1 = new SvgNodeModel(new Point(10, 10));
    +var node2 = new SvgNodeModel(new Point(50, 50));
    +var group = new SvgGroupModel(new[] { node1, node2 });
    +
    + + \ No newline at end of file diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs index d81920552..2415380ee 100644 --- a/site/Site/Static/Documentation.cs +++ b/site/Site/Static/Documentation.cs @@ -21,8 +21,14 @@ public static class Documentation new MenuItem("Layers", "/documentation/diagram-layers"), new MenuItem("Behaviors", "/documentation/diagram-behaviors"), new MenuItem("Options", "/documentation/diagram-options"), + new MenuItem("Keyboard Shortcuts", "/documentation/keyboard-shortcuts"), new MenuItem("API", "/documentation/diagram-api"), }), + new MenuGroup("Groups", new List + { + new MenuItem("Overview", "/documentation/groups-overview"), + new MenuItem("SVG", "/documentation/groups-svg"), + }), new MenuGroup("Customization", new List { new MenuItem("Custom Nodes", "/documentation/custom-nodes"), diff --git a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs index 86f6b6a53..c48293617 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs @@ -34,7 +34,6 @@ public bool RemoveShortcut(string key, bool ctrl, bool shift, bool alt) private async void OnDiagramKeyDown(KeyboardEventArgs e) { var k = KeysUtils.GetStringRepresentation(e.CtrlKey, e.ShiftKey, e.AltKey, e.Key); - Console.WriteLine(k); if (_shortcuts.TryGetValue(k, out var action)) { await action(Diagram); diff --git a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs index 94c730eee..016427eb8 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs @@ -10,10 +10,9 @@ public SelectionBehavior(Diagram diagram) : base(diagram) Diagram.PointerDown += OnPointerDown; } - private void OnPointerDown(Model? model, PointerEventArgs e) => Process(model, e.CtrlKey); - - private void Process(Model? model, bool ctrlKey) + private void OnPointerDown(Model? model, PointerEventArgs e) { + var ctrlKey = e.CtrlKey; switch (model) { case null: From e059949972ec4a05f035f11e04c51dc5331c62b1 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 8 May 2023 11:43:06 +0100 Subject: [PATCH 160/193] Replace Layers documentation with Overview --- .../Diagram/{Layers.razor => Overview.razor} | 41 ++++++++++++++++--- .../Site/Pages/Documentation/Groups/SVG.razor | 2 + site/Site/Static/Documentation.cs | 9 +--- 3 files changed, 38 insertions(+), 14 deletions(-) rename site/Site/Pages/Documentation/Diagram/{Layers.razor => Overview.razor} (54%) diff --git a/site/Site/Pages/Documentation/Diagram/Layers.razor b/site/Site/Pages/Documentation/Diagram/Overview.razor similarity index 54% rename from site/Site/Pages/Documentation/Diagram/Layers.razor rename to site/Site/Pages/Documentation/Diagram/Overview.razor index d4522acb0..0ce5b26f1 100644 --- a/site/Site/Pages/Documentation/Diagram/Layers.razor +++ b/site/Site/Pages/Documentation/Diagram/Overview.razor @@ -1,16 +1,30 @@ -@page "/documentation/diagram-layers" +@page "/documentation/diagram" @layout DocumentationLayout @inherits DocumentationPage -Diagram Layers - Documentation - Blazor Diagrams +Diagram - Documentation - Blazor Diagrams -

    Diagram Layers

    +

    Diagram

    + +

    Structure

    + +The component DiagramCanvas generates the follolwing structure: + +
    
    +<div class="diagram-canvas" tabindex="-1">
    +    <!-- LAYERS -->
    +</div>
    +
    + +You can add your own CSS classes using the Class parameter. + +

    Layers

    It is very important to know that in Blazor Diagrams, everything is rendered in 2 layers:

    -

    SVG

    +

    SVG

    
     <svg class="diagram-svg-layer" style="transform: translate(0px, 0px) scale(1); z-index: 0;">
    @@ -22,10 +36,10 @@
         This lets you use any svg elements and features with ease.
     

    -

    HTML

    +

    HTML

    
    -<div class="diagram-html-layer" style="transform: translate(0px, 0px) scale(1);">
    +<div class="diagram-html-layer" style="transform: translate(0px, 0px) scale(1); z-index: 0;">
     

    @@ -33,5 +47,20 @@ It is very useful since you can re-use any html you might already have in your nodes, as well as include interactivity through inputs for example.

    +

    Additional Content

    + +You can add additional content to each layer using the available render fragments: + +
    
    +<DiagramCanvas>
    +    <AdditionalHtml>
    +        <!-- EXTRA HTML -->
    +    </AdditionalHtml>
    +    <AdditionalSvg>
    +        <!-- EXTRA SVG -->
    +    </AdditionalSvg>
    +</DiagramCanvas>
    +
    + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Groups/SVG.razor b/site/Site/Pages/Documentation/Groups/SVG.razor index 17e1fc756..449bac86d 100644 --- a/site/Site/Pages/Documentation/Groups/SVG.razor +++ b/site/Site/Pages/Documentation/Groups/SVG.razor @@ -29,6 +29,8 @@ </g>
    +

    The rect is there for you to be able to style the background of the group, since we can't style g elements.

    +

    Creating a SVG group

    Interactively

    diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs index 2415380ee..4ebb32f1f 100644 --- a/site/Site/Static/Documentation.cs +++ b/site/Site/Static/Documentation.cs @@ -18,7 +18,7 @@ public static class Documentation }), new MenuGroup("Diagram", new List { - new MenuItem("Layers", "/documentation/diagram-layers"), + new MenuItem("Overview", "/documentation/diagram"), new MenuItem("Behaviors", "/documentation/diagram-behaviors"), new MenuItem("Options", "/documentation/diagram-options"), new MenuItem("Keyboard Shortcuts", "/documentation/keyboard-shortcuts"), @@ -28,13 +28,6 @@ public static class Documentation { new MenuItem("Overview", "/documentation/groups-overview"), new MenuItem("SVG", "/documentation/groups-svg"), - }), - new MenuGroup("Customization", new List - { - new MenuItem("Custom Nodes", "/documentation/custom-nodes"), - new MenuItem("Custom Ports", "/documentation/custom-ports"), - new MenuItem("Custom Links", "/documentation/custom-links"), - new MenuItem("Custom Groups", "/documentation/custom-groups"), }) }); } From 30275c24a16f099d58ad5d4285b3e68fb3fb364b Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 8 May 2023 11:43:53 +0100 Subject: [PATCH 161/193] Update Behaviors.razor --- site/Site/Pages/Documentation/Diagram/Behaviors.razor | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/Site/Pages/Documentation/Diagram/Behaviors.razor b/site/Site/Pages/Documentation/Diagram/Behaviors.razor index 4076ebd07..7a0c20e93 100644 --- a/site/Site/Pages/Documentation/Diagram/Behaviors.razor +++ b/site/Site/Pages/Documentation/Diagram/Behaviors.razor @@ -114,7 +114,7 @@ Diagram.UnregisterBehavior<SelectionBehavior>(); Diagram.RegisterBehavior(new MySelectionBehavior(Diagram));
    - \ No newline at end of file From 2ce9e82f89899485820dff7b63d2cad747fa2580 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 9 May 2023 10:57:15 +0100 Subject: [PATCH 162/193] Add TargetAttached event --- .../Behaviors/DragNewLinkBehavior.cs | 2 + .../Models/Base/BaseLinkModel.cs | 6 ++ .../Behaviors/DragNewLinkBehaviorTests.cs | 79 ++++++++++++++++++- 3 files changed, 86 insertions(+), 1 deletion(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 340a4406b..2ac47210e 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -93,6 +93,7 @@ private void OnPointerUp(Model? model, MouseEventArgs e) if (_ongoingLink.IsAttached) // Snapped already { + _ongoingLink.TriggerTargetAttached(); _ongoingLink = null; return; } @@ -101,6 +102,7 @@ private void OnPointerUp(Model? model, MouseEventArgs e) { var targetAnchor = Diagram.Options.Links.TargetAnchorFactory(Diagram, _ongoingLink, linkable); _ongoingLink.SetTarget(targetAnchor); + _ongoingLink.TriggerTargetAttached(); _ongoingLink.Refresh(); _ongoingLink.RefreshLinks(); } diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index e0a7cc1bd..da51dd783 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -13,6 +13,7 @@ public abstract class BaseLinkModel : SelectableModel, IHasBounds, ILinkable public event Action? SourceChanged; public event Action? TargetChanged; + public event Action? TargetAttached; protected BaseLinkModel(Anchor source, Anchor target) { @@ -98,6 +99,11 @@ public void SetTarget(Anchor anchor) public bool CanAttachTo(ILinkable other) => true; + /// + /// Triggers the TargetAttached event + /// + public void TriggerTargetAttached() => TargetAttached?.Invoke(this); + private void GeneratePath() { if (Diagram != null) diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index 01134f6ef..bf31df407 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -255,7 +255,7 @@ public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNo } [Fact] - public void Behavior_ShouldRemoveLink_WhenMouseUpOnCanvas() + public void Behavior_ShouldRemoveLink_WhenMouseUpOnCanvasAndRequireTargetIsTrue() { // Arrange var diagram = new TestDiagram(); @@ -364,5 +364,82 @@ public void Behavior_ShouldNotCreateOngoingLink_WhenFactoryReturnsNull() // 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); + } } } \ No newline at end of file From 286f092912b616a6cd03e2ccf91b95ae802c68cf Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 9 May 2023 18:48:55 +0100 Subject: [PATCH 163/193] Fix issue where diagram overwrites model order AND add SuspendSorting option and RefreshOrders method --- src/Blazor.Diagrams.Core/Diagram.cs | 37 ++++++++++++++----- .../DiagramOrderingTests.cs | 33 +++++++++++++++++ 2 files changed, 60 insertions(+), 10 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 502295be7..4467a62f8 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -21,7 +21,6 @@ public abstract class Diagram { private readonly Dictionary _behaviors; private readonly List _orderedSelectables; - private bool _suspendSorting; public event Action? PointerDown; public event Action? PointerMove; @@ -77,6 +76,7 @@ protected Diagram() 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() @@ -304,12 +304,12 @@ public void SendToBack(SelectableModel model) // Todo: can optimize this by only updating the order of items before model Batch(() => { - _suspendSorting = true; + SuspendSorting = true; for (var i = 0; i < _orderedSelectables.Count; i++) { _orderedSelectables[i].Order = i + 1; } - _suspendSorting = false; + SuspendSorting = false; }); } @@ -324,9 +324,9 @@ public void SendToFront(SelectableModel model) _orderedSelectables.Add(model); - _suspendSorting = true; + SuspendSorting = true; model.Order = maxOrder + 1; - _suspendSorting = false; + SuspendSorting = false; Refresh(); } @@ -340,11 +340,29 @@ 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); - model.Order = maxOrder + 1; + + if (model.Order == 0) + { + var maxOrder = GetMaxOrder(); + model.Order = maxOrder + 1; + } + model.OrderChanged += OnModelOrderChanged; } @@ -356,11 +374,10 @@ private void OnSelectableRemoved(SelectableModel model) private void OnModelOrderChanged(Model model) { - if (_suspendSorting) + if (SuspendSorting) return; - _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); - Refresh(); + RefreshOrders(); } #endregion diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs index 8add0604e..492f139b8 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs @@ -161,5 +161,38 @@ public void Diagram_ShouldRefreshOnceWhenMultipleModelsWereRemoved() // Assert refreshes.Should().Be(1); } + + [Fact] + public void Diagram_ShouldNotUpdateOrders_WhenSuspendSortingIsTrue() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SuspendSorting = true; + var node1 = diagram.Nodes.Add(new NodeModel()); // 1 + var node2 = diagram.Nodes.Add(new NodeModel()); // 2 + + // Act + node1.Order = 10; + + // Assert + diagram.OrderedSelectables[0].Should().Be(node1); + diagram.OrderedSelectables[1].Should().Be(node2); + } + + [Fact] + public void RefreshOrders_ShouldSortModels() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel() { Order = 10 }); + var node2 = diagram.Nodes.Add(new NodeModel() { Order = 5 }); + + // Act + diagram.RefreshOrders(); + + // Assert + diagram.OrderedSelectables[0].Should().Be(node2); + diagram.OrderedSelectables[1].Should().Be(node1); + } } } From 02634bfd223abb6ebc62b0df66afa520fc06f74b Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 10 May 2023 14:15:48 +0100 Subject: [PATCH 164/193] Update Versions & CHANGELOG --- CHANGELOG.md | 25 +++++++++++++++++++ .../Blazor.Diagrams.Algorithms.csproj | 2 +- .../Blazor.Diagrams.Core.csproj | 2 +- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 2 +- 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd2c8f1c9..0a89280b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Diagrams (3.0.0-beta.6) - 2023-05-09 + +### Added + +- `Style` parameter to `PortRenderer` +- `TargetAttached` to links, which triggers when a dragged link attaches to a target + - If port snapping is enabled, it will trigger only once when you let go of the mouse +- `SuspendSorting` to `Diagram` in order to suspend sorting models in each `OrderChanged` + - If you know what you're doing, you could save some processing and avoid sorting everytime +- `RefreshOrders` to be called after unsuspending sorting in order to sort the models again and refresh the diagram + +### Changed + +- `BaseLayer.Add` now returns the specific type given to it in argument +- **[BREAKING]** CSS classes are now prefixed with `diagram-` to avoid clashes with other libraries + - `diagram-group`, `diagram-node`, `diagram-link`, `diagram-port`, `diagram-link-label`, `diagram-link-vertex`, `diagram-control` + +### Fixed + +- Portless links in children not refreshing when moving the parent group +- Link's `GetBounds` not returning a valid box +- Port snapping choosing the first port in radius rather than the closest one +- Remove `Console.WriteLine` from `KeyboardShortcutsBehavior` +- Diagram overwriting `Order` when it's not zero (zero being the default int value, which we now consider as not set) + ## Diagrams (3.0.0-beta.5) - 2022-11-23 ### Added diff --git a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj index 8ea2c3f29..78a50f4e0 100644 --- a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj +++ b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj @@ -10,7 +10,7 @@ 3.0.0 3.0.0 https://github.com/zHaytam/Blazor.Diagrams - 3.0.0-beta.5 + 3.0.0-beta.6 Z.Blazor.Diagrams.Algorithms blazor diagrams diagramming svg drag algorithms layouts Z.Blazor.Diagrams.Algorithms diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index ca57e422b..0c6375669 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -10,7 +10,7 @@ 3.0.0 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams - 3.0.0-beta.5 + 3.0.0-beta.6 Z.Blazor.Diagrams.Core blazor diagrams diagramming svg drag Z.Blazor.Diagrams.Core diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index aff38c09b..6113ca473 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -9,7 +9,7 @@ 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams A fully customizable and extensible all-purpose diagrams library for Blazor - 3.0.0-beta.5 + 3.0.0-beta.6 true blazor diagrams diagramming svg drag Z.Blazor.Diagrams From e42d07c5448baa2b435f7aa73f662d1241ddb6dd Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 10 May 2023 14:30:01 +0100 Subject: [PATCH 165/193] Update Diagram.cs --- src/Blazor.Diagrams.Core/Diagram.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 4467a62f8..3284429ba 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -355,11 +355,11 @@ public void RefreshOrders(bool refresh = true) private void OnSelectableAdded(SelectableModel model) { + var maxOrder = GetMaxOrder(); _orderedSelectables.Add(model); if (model.Order == 0) { - var maxOrder = GetMaxOrder(); model.Order = maxOrder + 1; } From 0f7cc884898dac2de25495640eb69696abf5026c Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Wed, 10 May 2023 14:31:44 +0100 Subject: [PATCH 166/193] Update Versions & CHANGELOG --- .../Blazor.Diagrams.Algorithms.csproj | 4 ++-- src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj | 2 +- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj index 78a50f4e0..ee22699ff 100644 --- a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj +++ b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -10,7 +10,7 @@ 3.0.0 3.0.0 https://github.com/zHaytam/Blazor.Diagrams - 3.0.0-beta.6 + 3.0.0-beta.7 Z.Blazor.Diagrams.Algorithms blazor diagrams diagramming svg drag algorithms layouts Z.Blazor.Diagrams.Algorithms diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index 0c6375669..513db9495 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -10,7 +10,7 @@ 3.0.0 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams - 3.0.0-beta.6 + 3.0.0-beta.7 Z.Blazor.Diagrams.Core blazor diagrams diagramming svg drag Z.Blazor.Diagrams.Core diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index 6113ca473..f62649b17 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -9,7 +9,7 @@ 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams A fully customizable and extensible all-purpose diagrams library for Blazor - 3.0.0-beta.6 + 3.0.0-beta.7 true blazor diagrams diagramming svg drag Z.Blazor.Diagrams From 60f8ee19f8ced4a8f79467c09c3713725600d54f Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 20 Jun 2023 00:58:42 +0100 Subject: [PATCH 167/193] Ensure that element exists in (un)observe (js) --- src/Blazor.Diagrams/wwwroot/script.js | 2 ++ src/Blazor.Diagrams/wwwroot/script.min.js | 2 +- src/Blazor.Diagrams/wwwroot/script.min.js.gz | Bin 517 -> 520 bytes 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Blazor.Diagrams/wwwroot/script.js b/src/Blazor.Diagrams/wwwroot/script.js index 840a619b5..293b8768d 100644 --- a/src/Blazor.Diagrams/wwwroot/script.js +++ b/src/Blazor.Diagrams/wwwroot/script.js @@ -26,6 +26,7 @@ var s = { } }), observe: (element, ref, id) => { + if (!element) return; s.ro.observe(element); s.tracked[id] = { ref: ref @@ -39,6 +40,7 @@ var s = { } }, unobserve: (element, id) => { + if (!element) return; s.ro.unobserve(element); delete s.tracked[id]; delete s.canvases[id]; diff --git a/src/Blazor.Diagrams/wwwroot/script.min.js b/src/Blazor.Diagrams/wwwroot/script.min.js index c3027f1b9..f295e538c 100644 --- a/src/Blazor.Diagrams/wwwroot/script.min.js +++ b/src/Blazor.Diagrams/wwwroot/script.min.js @@ -1 +1 @@ -var s={canvases:{},tracked:{},getBoundingClientRect:n=>n.getBoundingClientRect(),mo:new MutationObserver(()=>{for(id in s.canvases){const t=s.canvases[id],i=t.lastBounds,n=t.elem.getBoundingClientRect();(i.left!==n.left||i.top!==n.top||i.width!==n.width||i.height!==n.height)&&(t.lastBounds=n,t.ref.invokeMethodAsync("OnResize",n))}}),ro:new ResizeObserver(n=>{for(const t of n){let i=Array.from(t.target.attributes).find(n=>n.name.startsWith("_bl")).name.substring(4),n=s.tracked[i];n&&n.ref.invokeMethodAsync("OnResize",t.target.getBoundingClientRect())}}),observe:(n,t,i)=>{s.ro.observe(n),s.tracked[i]={ref:t},n.classList.contains("diagram-canvas")&&(s.canvases[i]={elem:n,ref:t,lastBounds:n.getBoundingClientRect()})},unobserve:(n,t)=>{s.ro.unobserve(n),delete s.tracked[t],delete s.canvases[t]}};window.ZBlazorDiagrams=s;window.addEventListener("scroll",()=>{for(id in s.canvases){const n=s.canvases[id];n.lastBounds=n.elem.getBoundingClientRect();n.ref.invokeMethodAsync("OnResize",n.lastBounds)}});s.mo.observe(document.body,{childList:!0,subtree:!0}); \ No newline at end of file +var s={canvases:{},tracked:{},getBoundingClientRect:n=>n.getBoundingClientRect(),mo:new MutationObserver(()=>{for(id in s.canvases){const t=s.canvases[id],i=t.lastBounds,n=t.elem.getBoundingClientRect();(i.left!==n.left||i.top!==n.top||i.width!==n.width||i.height!==n.height)&&(t.lastBounds=n,t.ref.invokeMethodAsync("OnResize",n))}}),ro:new ResizeObserver(n=>{for(const t of n){let i=Array.from(t.target.attributes).find(n=>n.name.startsWith("_bl")).name.substring(4),n=s.tracked[i];n&&n.ref.invokeMethodAsync("OnResize",t.target.getBoundingClientRect())}}),observe:(n,t,i)=>{n&&(s.ro.observe(n),s.tracked[i]={ref:t},n.classList.contains("diagram-canvas")&&(s.canvases[i]={elem:n,ref:t,lastBounds:n.getBoundingClientRect()}))},unobserve:(n,t)=>{n&&(s.ro.unobserve(n),delete s.tracked[t],delete s.canvases[t])}};window.ZBlazorDiagrams=s;window.addEventListener("scroll",()=>{for(id in s.canvases){const n=s.canvases[id];n.lastBounds=n.elem.getBoundingClientRect();n.ref.invokeMethodAsync("OnResize",n.lastBounds)}});s.mo.observe(document.body,{childList:!0,subtree:!0}); \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/script.min.js.gz b/src/Blazor.Diagrams/wwwroot/script.min.js.gz index 1c87080a9512a58bcc56ed90ab5de2335543177d..e444f12bdf743d6e9412c5556505e665bd16d3f3 100644 GIT binary patch literal 520 zcmV+j0{8tNiwFP!000003XM|1Zrd;n{T0`TKms&jhn*a_K)da>qChVLRs@4$(XmjQ z8YDVF>*mXL*eC55M%iiN0?WFSDbnQQ<9ok<{@R+5#d4?&ZcT((40}bvR1aWNxB^*g_COmX|~AgK#!;m__{pDTm5q zq>Rc_?%vszawX~3#0e%U#bqCTj2?^a7(EbwgxNAxrI9r^i*QX%*#$|Xs8xQIG2KGqdGg`Yw0SJ!OTk$CdCO}gih!(p9;@KnHdk+& zKPfgh|B@{{bKt*?{~zMc$0Z?`QFkZFtgrfx=jpY#k7}qI*V;s)xOlJFSQG#_{q%Lr*dkAZdGR5FnQV4UB>o*VZD^ zvLIJB97Qkdp-<`;N=lBcL*4YG!I zM;2-Ou|XhyfQkz$uSx&)N=VhZf?%KBb!-?N-LC`q9e5#RdEIZE7icr2Ea+EC*;kGN zXI!4KN3<)2CF{n7i6$tDM`)nE#Jm)!8`$twS<+a%yr5?HV?4)iI_}YOJpyBupc-&n zPmiWjE*4@=T#{n#VWW|D?g{RIYiDo5fhv*T(*p#2hFp=9!yuKP)KBP1I~}DTg=TJ( zk?b4b49gqu%|UOxYa=TgAJu8Y>~Y<3T#nu#+0=kEnHF@2zi{}9T#Nksy2+(H;@vvL zN3<0mWL#CyM;9M)wWP&@-d1p8zp&p?pPM8VA}&Q?vNUM#^br;$)ttHPBWuBfB3;FW zhA$Xc$0e{q3L>{?w%)YwCfD<{_<024^W=9yYV4xUmJ}D`H^@PCG@tS`;|Wev+Sr*P zI}gjNGj>YPd?2f zzl_11T~_`{}i_2h~?KHa3YUuHLH{C-wlbjIaIxHUl^o HCj Date: Tue, 20 Jun 2023 01:18:30 +0100 Subject: [PATCH 168/193] Add Nodes customization --- .../Nodes/AddTwoNumbersWidget.razor | 20 +++ .../Nodes/AddTwoNumbersWidget.razor.css | 40 +++++ site/Site/Models/Nodes/AddTwoNumbersNode.cs | 14 ++ .../Documentation/Diagram/Overview.razor | 2 + .../Documentation/Groups/Customization.razor | 12 ++ .../Pages/Documentation/Groups/Overview.razor | 2 +- .../Site/Pages/Documentation/Groups/SVG.razor | 6 +- .../Documentation/Nodes/Customization.razor | 158 ++++++++++++++++++ .../Nodes/Customization.razor.css | 5 + .../Pages/Documentation/Nodes/Overview.razor | 39 +++++ site/Site/Pages/Documentation/Nodes/SVG.razor | 38 +++++ site/Site/Static/Documentation.cs | 8 +- site/Site/wwwroot/css/app.css | 4 + 13 files changed, 343 insertions(+), 5 deletions(-) create mode 100644 site/Site/Components/Documentation/Nodes/AddTwoNumbersWidget.razor create mode 100644 site/Site/Components/Documentation/Nodes/AddTwoNumbersWidget.razor.css create mode 100644 site/Site/Models/Nodes/AddTwoNumbersNode.cs create mode 100644 site/Site/Pages/Documentation/Groups/Customization.razor create mode 100644 site/Site/Pages/Documentation/Nodes/Customization.razor create mode 100644 site/Site/Pages/Documentation/Nodes/Customization.razor.css create mode 100644 site/Site/Pages/Documentation/Nodes/Overview.razor create mode 100644 site/Site/Pages/Documentation/Nodes/SVG.razor diff --git a/site/Site/Components/Documentation/Nodes/AddTwoNumbersWidget.razor b/site/Site/Components/Documentation/Nodes/AddTwoNumbersWidget.razor new file mode 100644 index 000000000..12749799f --- /dev/null +++ b/site/Site/Components/Documentation/Nodes/AddTwoNumbersWidget.razor @@ -0,0 +1,20 @@ +@using Blazor.Diagrams.Components.Renderers; +@using Site.Models.Nodes; + +
    +
    Add
    + + + + @foreach (var port in Node.Ports) + { + // In case you have any ports to show + // IMPORTANT: You are always in charge of rendering ports + + } +
    + +@code { + // This gets filled by the library + [Parameter] public AddTwoNumbersNode Node { get; set; } = null!; +} \ No newline at end of file diff --git a/site/Site/Components/Documentation/Nodes/AddTwoNumbersWidget.razor.css b/site/Site/Components/Documentation/Nodes/AddTwoNumbersWidget.razor.css new file mode 100644 index 000000000..1e7266352 --- /dev/null +++ b/site/Site/Components/Documentation/Nodes/AddTwoNumbersWidget.razor.css @@ -0,0 +1,40 @@ +div { + width: 230px; + outline: 1px solid black; + padding: 20px; + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19); +} + + div > h5 { + font-weight: 600; + text-transform: uppercase; + margin-bottom: 10px; + } + + div > input[type=number] { + padding: 3px; + border-radius: 3px; + border: 1px solid black; + margin-bottom: 8px; + } + +::deep .diagram-port { + position: absolute; + width: 30px; + height: 20px; + background-color: black; + left: 50%; + transform: translate(-50%, -50%); +} + + ::deep .diagram-port.top { + border-top-left-radius: 50%; + border-top-right-radius: 50%; + top: -10px; + } + + ::deep .diagram-port.bottom { + border-bottom-left-radius: 50%; + border-bottom-right-radius: 50%; + bottom: -30px; + } diff --git a/site/Site/Models/Nodes/AddTwoNumbersNode.cs b/site/Site/Models/Nodes/AddTwoNumbersNode.cs new file mode 100644 index 000000000..4e78bdaa1 --- /dev/null +++ b/site/Site/Models/Nodes/AddTwoNumbersNode.cs @@ -0,0 +1,14 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; + +namespace Site.Models.Nodes; + +public class AddTwoNumbersNode : NodeModel +{ + public AddTwoNumbersNode(Point? position = null) : base(position) { } + + public double FirstNumber { get; set; } + public double SecondNumber { get; set; } + + // Here, you can put whatever you want, such as a method that does the addition +} diff --git a/site/Site/Pages/Documentation/Diagram/Overview.razor b/site/Site/Pages/Documentation/Diagram/Overview.razor index 0ce5b26f1..4eeb9ac7f 100644 --- a/site/Site/Pages/Documentation/Diagram/Overview.razor +++ b/site/Site/Pages/Documentation/Diagram/Overview.razor @@ -28,6 +28,8 @@ You can add your own CSS classes using the Class parameter.
    
     <svg class="diagram-svg-layer" style="transform: translate(0px, 0px) scale(1); z-index: 0;">
    +    <!-- Links, Nodes, Controls, ... -->
    +</svg>
     

    diff --git a/site/Site/Pages/Documentation/Groups/Customization.razor b/site/Site/Pages/Documentation/Groups/Customization.razor new file mode 100644 index 000000000..aaba04be2 --- /dev/null +++ b/site/Site/Pages/Documentation/Groups/Customization.razor @@ -0,0 +1,12 @@ +@page "/documentation/groups-customization" +@layout DocumentationLayout +@inherits DocumentationPage + +Groups Customization - Documentation - Blazor Diagrams + +

    Groups Customization

    + +

    + In Blazor Diagrams, Groups are a way to group nodes together.
    + Groups can also contain other groups, so you can create hierarchies. +

    \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Groups/Overview.razor b/site/Site/Pages/Documentation/Groups/Overview.razor index 4e7b56ada..32a67a2ee 100644 --- a/site/Site/Pages/Documentation/Groups/Overview.razor +++ b/site/Site/Pages/Documentation/Groups/Overview.razor @@ -1,4 +1,4 @@ -@page "/documentation/groups-overview" +@page "/documentation/groups" @layout DocumentationLayout @inherits DocumentationPage diff --git a/site/Site/Pages/Documentation/Groups/SVG.razor b/site/Site/Pages/Documentation/Groups/SVG.razor index 449bac86d..9de6e054f 100644 --- a/site/Site/Pages/Documentation/Groups/SVG.razor +++ b/site/Site/Pages/Documentation/Groups/SVG.razor @@ -35,7 +35,7 @@

    Interactively

    -First, we will need how groups are created: +First, we will need to change how groups are created:
    
     yourDiagram.Options.Groups.Factory = (diagram, children) => new SvgGroupModel(children);
    @@ -56,5 +56,5 @@ var node2 = new SvgNodeModel(new Point(50, 50));
     var group = new SvgGroupModel(new[] { node1, node2 });
     
    - \ No newline at end of file + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Nodes/Customization.razor b/site/Site/Pages/Documentation/Nodes/Customization.razor new file mode 100644 index 000000000..afa65bf21 --- /dev/null +++ b/site/Site/Pages/Documentation/Nodes/Customization.razor @@ -0,0 +1,158 @@ +@page "/documentation/nodes-customization" +@using Site.Components.Documentation.Nodes; +@using Site.Models.Nodes; +@layout DocumentationLayout +@inherits DocumentationPage + +Nodes Customization - Documentation - Blazor Diagrams + +

    Nodes Customization

    + +

    + Customizing nodes in Blazor Diagrams is very easy! +

    + +

    Creating a model

    + +

    + Most of the time, your nodes will hold more information than just a title, which is why we create a new model to encapsulate all the data and behavior we want. + In a perfect world, you would have one node model for each behavior and or UI representation.

    + + Let's assume that we want to create a new node that represents addition: +

    + +
    AddTwoNumbersNode.cs
    +
    
    +using Blazor.Diagrams.Core.Geometry;
    +using Blazor.Diagrams.Core.Models;
    +
    +namespace YourNamespace;
    +
    +public class AddTwoNumbersNode : NodeModel
    +{
    +    public AddTwoNumbersNode(Point? position = null) : base(position) { }
    +
    +    public double FirstNumber { get; set; }
    +    public double SecondNumber { get; set; }
    +
    +    // Here, you can put whatever you want, such as a method that does the addition
    +}
    +
    + +

    Creating a component

    + +

    + Let's create a UI component to control how the node looks like: +

    + +
    AddTwoNumbersWidget.razor
    +
    
    +@@using Blazor.Diagrams.Components.Renderers;
    +@@using Site.Models.Nodes;
    +
    +<div>
    +    <h5 class="card-title">Add</h5>
    +    <input type="number" class="form-control" @@bind-value="Node.FirstNumber" placeholder="Number 1" />
    +    <input type="number" class="form-control" @@bind-value="Node.SecondNumber" placeholder="Number 2" />
    +
    +    @@foreach (var port in Node.Ports)
    +    {
    +        // In case you have any ports to show
    +        // IMPORTANT: You are always in charge of rendering ports
    +        <PortRenderer @@key="port" Port="port" />
    +    }
    +</div>
    +
    +@@code {
    +    // This gets filled by the library
    +    [Parameter] public AddTwoNumbersNode Node { get; set; } = null!;
    +}
    +
    + +Let's also style our component! + +
    AddTwoNumbersWidget.razor.css
    +
    
    +div {
    +    width: 230px;
    +    outline: 1px solid black;
    +    padding: 20px;
    +    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
    +}
    +
    +    div > h5 {
    +        font-weight: 600;
    +        text-transform: uppercase;
    +        margin-bottom: 10px;
    +    }
    +
    +    div > input[type=number] {
    +        padding: 3px;
    +        border-radius: 3px;
    +        border: 1px solid black;
    +        margin-bottom: 8px;
    +    }
    +
    +.diagram-port {
    +    position: absolute;
    +    width: 30px;
    +    height: 20px;
    +    background-color: black;
    +    left: 50%;
    +    transform: translate(-50%, -50%);
    +}
    +
    +    .diagram-port.top {
    +        border-top-left-radius: 50%;
    +        border-top-right-radius: 50%;
    +        top: -10px;
    +    }
    +
    +    .diagram-port.bottom {
    +        border-bottom-left-radius: 50%;
    +        border-bottom-right-radius: 50%;
    +        bottom: 10px;
    +    }
    +
    + +

    Displaying

    + +

    + All we have to do now is register our new creation! +

    + +
    
    +private BlazorDiagram Diagram { get; set; } = new();
    +
    +protected override void OnInitialized()
    +{
    +    base.OnInitialized();
    +
    +    Diagram.RegisterComponent<AddTwoNumbersNode, AddTwoNumbersWidget>();
    +
    +    var node = Diagram.Nodes.Add(new AddTwoNumbersNode(new Point(80, 80)));
    +    node.AddPort(PortAlignment.Top);
    +    node.AddPort(PortAlignment.Bottom);
    +}
    +
    + +
    + + + +
    + +@code { + private BlazorDiagram Diagram { get; set; } = new(); + + protected override void OnInitialized() + { + base.OnInitialized(); + + Diagram.RegisterComponent(); + + var node = Diagram.Nodes.Add(new AddTwoNumbersNode(new Point(80, 80))); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Bottom); + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Nodes/Customization.razor.css b/site/Site/Pages/Documentation/Nodes/Customization.razor.css new file mode 100644 index 000000000..f9eb822e8 --- /dev/null +++ b/site/Site/Pages/Documentation/Nodes/Customization.razor.css @@ -0,0 +1,5 @@ +.diagram-container { + width: 100%; + height: 400px; + border: 1px solid black; +} diff --git a/site/Site/Pages/Documentation/Nodes/Overview.razor b/site/Site/Pages/Documentation/Nodes/Overview.razor new file mode 100644 index 000000000..cb9fd0e00 --- /dev/null +++ b/site/Site/Pages/Documentation/Nodes/Overview.razor @@ -0,0 +1,39 @@ +@page "/documentation/nodes" +@layout DocumentationLayout +@inherits DocumentationPage + +Nodes - Documentation - Blazor Diagrams + +

    Nodes

    + +

    + Nodes are the most important concept in Blazor Diagrams. +

    + +

    Structure

    + +

    + The component NodeRenderer generates the follolwing structure: +

    + +
    
    +<div class="diagram-node ..."
    +     data-node-id="28e9606d-08dd-47d5-a4c7-b25e541bcf1e"
    +     style="top: 10px; left: 10px;">
    +    <!-- YOUR CONTENT WILL BE HERE -->
    +</div>
    +
    + +

    + The classes that the div can have (beside diagram-node) are: locked, selected and grouped. +

    + +

    Creating a node

    + +
    
    +var node1 = new NodeModel(new Point(10, 10));
    +BlazorDiagram.Nodes.Add(node1);
    +
    + + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Nodes/SVG.razor b/site/Site/Pages/Documentation/Nodes/SVG.razor new file mode 100644 index 000000000..0fa507a30 --- /dev/null +++ b/site/Site/Pages/Documentation/Nodes/SVG.razor @@ -0,0 +1,38 @@ +@page "/documentation/nodes-svg" +@layout DocumentationLayout +@inherits DocumentationPage + +SVG Nodes - Documentation - Blazor Diagrams + +

    SVG Nodes

    + +

    + SVG nodes are nodes that will be rendered in the SVG layer.
    + In this page, we will talk about the differences between them and normal nodes. +

    + +

    Structure

    + +

    + The component NodeRenderer generates the follolwing structure: +

    + +
    
    +<g class="diagram-node ..."
    +   data-node-id="28e9606d-08dd-47d5-a4c7-b25e541bcf1e"
    +   transform="translate(10 10)">
    +    <!-- YOUR CONTENT WILL BE HERE -->
    +</g>
    +
    + +

    Creating a SVG node

    + +
    
    +var node1 = new SvgNodeModel(new Point(10, 10));
    +BlazorDiagram.Nodes.Add(node1);
    +
    + + \ No newline at end of file diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs index 4ebb32f1f..0fd8a9c4f 100644 --- a/site/Site/Static/Documentation.cs +++ b/site/Site/Static/Documentation.cs @@ -24,9 +24,15 @@ public static class Documentation new MenuItem("Keyboard Shortcuts", "/documentation/keyboard-shortcuts"), new MenuItem("API", "/documentation/diagram-api"), }), + new MenuGroup("Nodes", new List + { + new MenuItem("Overview", "/documentation/nodes"), + new MenuItem("SVG", "/documentation/nodes-svg"), + new MenuItem("Customization", "/documentation/nodes-customization") + }), new MenuGroup("Groups", new List { - new MenuItem("Overview", "/documentation/groups-overview"), + new MenuItem("Overview", "/documentation/groups"), new MenuItem("SVG", "/documentation/groups-svg"), }) }); diff --git a/site/Site/wwwroot/css/app.css b/site/Site/wwwroot/css/app.css index 804be6c5f..8e3536eb1 100644 --- a/site/Site/wwwroot/css/app.css +++ b/site/Site/wwwroot/css/app.css @@ -1345,6 +1345,10 @@ td, th { box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); } +.outline { + outline-style: solid; +} + .drop-shadow-lg { --tw-drop-shadow: drop-shadow(0 10px 8px rgb(0 0 0 / 0.04)) drop-shadow(0 4px 3px rgb(0 0 0 / 0.1)); filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow); From 82d6ba795cd4519e8f7d797076548ca627a9629d Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 3 Jul 2023 11:18:50 +0100 Subject: [PATCH 169/193] File-scoped namespaces for less indent --- docs/CustomNodesLinks/Models/DiagramLink.cs | 21 +- .../Models/DiagramLinkLabel.cs | 25 +- docs/CustomNodesLinks/Models/DiagramNode.cs | 19 +- docs/CustomNodesLinks/Pages/Error.cshtml.cs | 33 +- docs/CustomNodesLinks/Program.cs | 21 +- docs/CustomNodesLinks/Startup.cs | 83 +- docs/Diagram-Demo/Pages/Error.cshtml.cs | 31 +- docs/Diagram-Demo/Program.cs | 21 +- docs/Diagram-Demo/Startup.cs | 83 +- docs/Layouts/Pages/Error.cshtml.cs | 33 +- docs/Layouts/Program.cs | 21 +- docs/Layouts/Startup.cs | 89 +- samples/ServerSide/Program.cs | 25 +- samples/ServerSide/Startup.cs | 65 +- .../ReconnectLinksToClosestPorts.razor.cs | 47 +- samples/SharedDemo/Demos/BotAnswerNode.cs | 11 +- .../Demos/CustomGroup/CustomGroupModel.cs | 17 +- .../Demos/CustomGroup/Demo.razor.cs | 71 +- .../SharedDemo/Demos/CustomLink/Demo.razor.cs | 65 +- .../SharedDemo/Demos/CustomLink/ThickLink.cs | 9 +- samples/SharedDemo/Demos/CustomNode.razor.cs | 43 +- .../Demos/CustomPort/ColoredPort.cs | 39 +- .../SharedDemo/Demos/CustomPort/Demo.razor.cs | 47 +- .../CustomSvgGroup/CustomSvgGroupModel.cs | 11 +- .../Demos/CustomSvgGroup/Demo.razor.cs | 69 +- samples/SharedDemo/Demos/DragAndDrop.razor.cs | 67 +- .../Demos/DynamicInsertions.razor.cs | 139 ++- samples/SharedDemo/Demos/Events.razor.cs | 119 ++- .../Demos/Groups/CustomShortcut.razor.cs | 71 +- .../SharedDemo/Demos/Groups/Dynamic.razor.cs | 105 ++- .../SharedDemo/Demos/Groups/Factory.razor.cs | 81 +- .../SharedDemo/Demos/Groups/Grouping.razor.cs | 75 +- .../Demos/Links/LabelsDemo.razor.cs | 103 ++- .../Demos/Links/MarkersDemo.razor.cs | 163 ++-- .../Demos/Links/PathGeneratorsDemo.razor.cs | 87 +- .../Demos/Links/RoutersDemo.razor.cs | 81 +- .../Demos/Links/SnappingDemo.razor.cs | 51 +- .../Demos/Links/VerticesDemo.razor.cs | 95 +- samples/SharedDemo/Demos/Locked.razor.cs | 51 +- .../Demos/Nodes/PortlessLinks.razor.cs | 111 ++- .../SharedDemo/Demos/Nodes/SvgDemo.razor.cs | 101 ++- samples/SharedDemo/Demos/Performance.razor.cs | 31 +- samples/SharedDemo/Demos/Simple.razor.cs | 79 +- samples/SharedDemo/Demos/SnapToGrid.razor.cs | 45 +- samples/SharedDemo/Demos/ZoomToFit.razor.cs | 31 +- samples/SharedDemo/DocPage.cs | 19 +- samples/SharedDemo/LayoutData.cs | 19 +- samples/SharedDemo/ReflectionUtils.cs | 99 +-- samples/Wasm/Program.cs | 19 +- .../Nodes/GingerbreadWidget.razor | 22 + .../Nodes/GingerbreadWidget.razor.css | 19 + .../Landing/Features/FeaturesExample.razor.cs | 177 ++-- .../Landing/Groups/GroupsExample.razor.cs | 71 +- .../Landing/StatisticsLine.razor.cs | 69 +- .../SvgAndHtml/SvgAndHtmlExample.razor.cs | 41 +- .../Landing/WidgetsExample.razor.cs | 67 +- site/Site/Models/Documentation/Menu.cs | 11 +- site/Site/Models/Landing/ColoredNodeModel.cs | 27 +- .../Landing/Groups/ColoredGroupModel.cs | 15 +- .../SvgAndHtml/BatteryChargerNodeModel.cs | 21 +- .../Landing/SvgAndHtml/BatteryNodeModel.cs | 47 +- site/Site/Models/Nodes/GingerbreadNode.cs | 11 + .../Pages/Documentation/DocumentationPage.cs | 15 +- .../Documentation/Nodes/Customization.razor | 13 +- .../Pages/Documentation/Nodes/Overview.razor | 13 + .../Nodes/SvgCustomization.razor | 158 ++++ .../Nodes/SvgCustomization.razor.css | 6 + site/Site/Shared/DocumentationLayout.razor.cs | 41 +- site/Site/Static/Documentation.cs | 66 +- site/Site/Static/Icons.cs | 13 +- .../LinksReconnectionAlgorithms.cs | 85 +- src/Blazor.Diagrams.Core/Anchors/Anchor.cs | 61 +- .../Anchors/DynamicAnchor.cs | 43 +- .../Anchors/PositionAnchor.cs | 23 +- .../Anchors/ShapeIntersectionAnchor.cs | 57 +- .../Anchors/SinglePortAnchor.cs | 85 +- src/Blazor.Diagrams.Core/Behavior.cs | 17 +- .../Behaviors/DebugEventsBehavior.cs | 147 ++- .../Behaviors/DragMovablesBehavior.cs | 169 ++-- .../Behaviors/DragNewLinkBehavior.cs | 207 +++-- .../Behaviors/EventsBehavior.cs | 99 +-- .../Behaviors/KeyboardShortcutsBehavior.cs | 61 +- .../Behaviors/KeyboardShortcutsDefaults.cs | 97 +- .../Behaviors/PanBehavior.cs | 95 +- .../Behaviors/SelectionBehavior.cs | 51 +- .../Behaviors/ZoomBehavior.cs | 91 +- .../Controls/Default/ArrowHeadControl.cs | 71 +- src/Blazor.Diagrams.Core/Delegates.cs | 11 +- src/Blazor.Diagrams.Core/Diagram.cs | 605 +++++++------ src/Blazor.Diagrams.Core/DiagramsException.cs | 9 +- .../Events/KeyboardEventArgs.cs | 7 +- .../Events/MouseEventArgs.cs | 7 +- .../Events/TouchEventArgs.cs | 9 +- .../Events/WheelEventArgs.cs | 29 +- .../Extensions/DiagramExtensions.cs | 67 +- .../Extensions/DoubleExtensions.cs | 11 +- .../Extensions/NumberExtensions.cs | 9 +- .../Geometry/BezierSpline.cs | 377 ++++---- src/Blazor.Diagrams.Core/Geometry/Ellipse.cs | 99 +-- src/Blazor.Diagrams.Core/Geometry/IShape.cs | 11 +- src/Blazor.Diagrams.Core/Geometry/Line.cs | 63 +- src/Blazor.Diagrams.Core/Geometry/Point.cs | 89 +- .../Geometry/Rectangle.cs | 215 +++-- src/Blazor.Diagrams.Core/Geometry/Shapes.cs | 51 +- src/Blazor.Diagrams.Core/Geometry/Size.cs | 23 +- src/Blazor.Diagrams.Core/Layers/BaseLayer.cs | 147 ++- src/Blazor.Diagrams.Core/Layers/GroupLayer.cs | 69 +- src/Blazor.Diagrams.Core/Layers/LinkLayer.cs | 89 +- src/Blazor.Diagrams.Core/Layers/NodeLayer.cs | 21 +- src/Blazor.Diagrams.Core/Models/Base/Model.cs | 49 +- .../Models/Base/MovableModel.cs | 41 +- .../Models/Base/SelectableModel.cs | 33 +- src/Blazor.Diagrams.Core/Models/GroupModel.cs | 193 ++-- .../Models/LinkLabelModel.cs | 53 +- src/Blazor.Diagrams.Core/Models/LinkMarker.cs | 41 +- src/Blazor.Diagrams.Core/Models/LinkModel.cs | 33 +- .../Models/LinkVertexModel.cs | 25 +- src/Blazor.Diagrams.Core/Models/NodeModel.cs | 236 ++--- .../Models/PortAlignment.cs | 23 +- src/Blazor.Diagrams.Core/Models/PortModel.cs | 103 ++- src/Blazor.Diagrams.Core/MouseEventButton.cs | 17 +- .../PathGenerators/PathGenerator.cs | 55 +- .../PathGenerators/PathGeneratorResult.cs | 37 +- .../PathGenerators/SmoothPathGenerator.cs | 179 ++-- .../PathGenerators/StraightPathGenerator.cs | 111 ++- .../Routers/NormalRouter.cs | 11 +- .../Routers/OrthogonalRouter.cs | 681 +++++++------- src/Blazor.Diagrams.Core/Routers/Router.cs | 49 +- src/Blazor.Diagrams.Core/Utils/KeysUtils.cs | 21 +- .../Renderers/LinkVertexRenderer.cs | 137 ++- .../Components/Renderers/NodeRenderer.cs | 12 +- .../Anchors/DynamicAnchorTests.cs | 371 ++++---- .../Behaviors/DragNewLinkBehaviorTests.cs | 839 +++++++++--------- .../Behaviors/EventsBehaviorTests.cs | 197 ++-- .../KeyboardShortcutsBehaviorTests.cs | 131 ++- .../KeyboardShortcutsDefaultsTests.cs | 235 +++-- .../DiagramOrderingTests.cs | 377 ++++---- .../DiagramTests.cs | 225 +++-- .../Extensions/DiagramExtensionsTests.cs | 65 +- .../Extensions/DoubleExtensionsTests.cs | 27 +- .../Geometry/PointTests.cs | 25 +- .../Layers/GroupLayerTests.cs | 279 +++--- .../Layers/NodeLayerTests.cs | 197 ++-- .../Models/Base/BaseLinkModelTests.cs | 151 ++-- .../Components/LinkVertexWidgetTests.cs | 241 +++-- .../Components/NodeWidgetTests.cs | 43 +- .../Components/SvgNodeWidgetTests.cs | 23 +- tests/Blazor.Diagrams.Tests/DiagramTests.cs | 147 ++- 148 files changed, 6529 insertions(+), 6425 deletions(-) create mode 100644 site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor create mode 100644 site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor.css create mode 100644 site/Site/Models/Nodes/GingerbreadNode.cs create mode 100644 site/Site/Pages/Documentation/Nodes/SvgCustomization.razor create mode 100644 site/Site/Pages/Documentation/Nodes/SvgCustomization.razor.css diff --git a/docs/CustomNodesLinks/Models/DiagramLink.cs b/docs/CustomNodesLinks/Models/DiagramLink.cs index a64954402..325e96839 100644 --- a/docs/CustomNodesLinks/Models/DiagramLink.cs +++ b/docs/CustomNodesLinks/Models/DiagramLink.cs @@ -1,16 +1,15 @@ using Blazor.Diagrams.Core.Models; -namespace CustomNodesLinks.Models +namespace CustomNodesLinks.Models; + +public sealed class DiagramLink : LinkModel +{ +public DiagramLink(string name, NodeModel sourceNode, NodeModel? targetNode) : + base(name, sourceNode, targetNode) { - public sealed class DiagramLink : LinkModel - { - public DiagramLink(string name, NodeModel sourceNode, NodeModel? targetNode) : - base(name, sourceNode, targetNode) - { - Name = name; - Labels.Add(new DiagramLinkLabel(this, Name)); - } + Name = name; + Labels.Add(new DiagramLinkLabel(this, Name)); +} - public string Name { get; set; } - } +public string Name { get; set; } } diff --git a/docs/CustomNodesLinks/Models/DiagramLinkLabel.cs b/docs/CustomNodesLinks/Models/DiagramLinkLabel.cs index 2b799cee7..99a0cbbaa 100644 --- a/docs/CustomNodesLinks/Models/DiagramLinkLabel.cs +++ b/docs/CustomNodesLinks/Models/DiagramLinkLabel.cs @@ -2,20 +2,19 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -namespace CustomNodesLinks.Models +namespace CustomNodesLinks.Models; + +public sealed class DiagramLinkLabel : LinkLabelModel +{ +public DiagramLinkLabel(BaseLinkModel parent, string id, string content, double? distance = null, Point? offset = null) : + base(parent, id, content, distance, offset) { - public sealed class DiagramLinkLabel : LinkLabelModel - { - public DiagramLinkLabel(BaseLinkModel parent, string id, string content, double? distance = null, Point? offset = null) : - base(parent, id, content, distance, offset) - { - } +} - public DiagramLinkLabel(BaseLinkModel parent, string content, double? distance = null, Point? offset = null) : - base(parent, content, distance, offset) - { - } +public DiagramLinkLabel(BaseLinkModel parent, string content, double? distance = null, Point? offset = null) : + base(parent, content, distance, offset) +{ +} - public bool ShowLabel { get; set; } = true; - } +public bool ShowLabel { get; set; } = true; } diff --git a/docs/CustomNodesLinks/Models/DiagramNode.cs b/docs/CustomNodesLinks/Models/DiagramNode.cs index 66e5e97b0..20eebd0c8 100644 --- a/docs/CustomNodesLinks/Models/DiagramNode.cs +++ b/docs/CustomNodesLinks/Models/DiagramNode.cs @@ -1,16 +1,15 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -namespace CustomNodesLinks.Models +namespace CustomNodesLinks.Models; + +public sealed class DiagramNode : NodeModel +{ +public DiagramNode(string name, Point pos) : + base(name, pos) { - public sealed class DiagramNode : NodeModel - { - public DiagramNode(string name, Point pos) : - base(name, pos) - { - Name = name; - } + Name = name; +} - public string Name { get; set; } - } +public string Name { get; set; } } diff --git a/docs/CustomNodesLinks/Pages/Error.cshtml.cs b/docs/CustomNodesLinks/Pages/Error.cshtml.cs index b83dc4a17..889588be6 100644 --- a/docs/CustomNodesLinks/Pages/Error.cshtml.cs +++ b/docs/CustomNodesLinks/Pages/Error.cshtml.cs @@ -7,26 +7,25 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace CustomNodesLinks.Pages +namespace CustomNodesLinks.Pages; + +[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] +[IgnoreAntiforgeryToken] +public class ErrorModel : PageModel { - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - [IgnoreAntiforgeryToken] - public class ErrorModel : PageModel - { - public string RequestId { get; set; } +public string RequestId { get; set; } - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); +public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - private readonly ILogger _logger; +private readonly ILogger _logger; - public ErrorModel(ILogger logger) - { - _logger = logger; - } +public ErrorModel(ILogger logger) +{ + _logger = logger; +} - public void OnGet() - { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } - } +public void OnGet() +{ + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; +} } diff --git a/docs/CustomNodesLinks/Program.cs b/docs/CustomNodesLinks/Program.cs index 7cd4ae325..9c6c3966b 100644 --- a/docs/CustomNodesLinks/Program.cs +++ b/docs/CustomNodesLinks/Program.cs @@ -1,17 +1,16 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -namespace CustomNodesLinks +namespace CustomNodesLinks; + +public class Program +{ +public static void Main(string[] args) { - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } + CreateHostBuilder(args).Build().Run(); +} - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); - } +public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } diff --git a/docs/CustomNodesLinks/Startup.cs b/docs/CustomNodesLinks/Startup.cs index 5ccdae6c6..c233b2e33 100644 --- a/docs/CustomNodesLinks/Startup.cs +++ b/docs/CustomNodesLinks/Startup.cs @@ -4,49 +4,48 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -namespace CustomNodesLinks +namespace CustomNodesLinks; + +public class Startup +{ +public Startup(IConfiguration configuration) +{ + Configuration = configuration; +} + +public IConfiguration Configuration { get; } + +// This method gets called by the runtime. Use this method to add services to the container. +// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 +public void ConfigureServices(IServiceCollection services) +{ + services.AddRazorPages(); + services.AddServerSideBlazor(); +} + +// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. +public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - public class Startup + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - services.AddRazorPages(); - services.AddServerSideBlazor(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapBlazorHub(); - endpoints.MapFallbackToPage("/_Host"); - }); - } + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapBlazorHub(); + endpoints.MapFallbackToPage("/_Host"); + }); +} } diff --git a/docs/Diagram-Demo/Pages/Error.cshtml.cs b/docs/Diagram-Demo/Pages/Error.cshtml.cs index 0d45fc81b..a924eac35 100644 --- a/docs/Diagram-Demo/Pages/Error.cshtml.cs +++ b/docs/Diagram-Demo/Pages/Error.cshtml.cs @@ -7,26 +7,25 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Diagram_Demo.Pages +namespace Diagram_Demo.Pages; + +[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] +[IgnoreAntiforgeryToken] +public class ErrorModel : PageModel { - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - [IgnoreAntiforgeryToken] - public class ErrorModel : PageModel - { - public string RequestId { get; set; } + public string RequestId { get; set; } - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - private readonly ILogger _logger; + private readonly ILogger _logger; - public ErrorModel(ILogger logger) - { - _logger = logger; - } + public ErrorModel(ILogger logger) + { + _logger = logger; + } - public void OnGet() - { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } + public void OnGet() + { + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; } } diff --git a/docs/Diagram-Demo/Program.cs b/docs/Diagram-Demo/Program.cs index 455a21dc1..f421aa96f 100644 --- a/docs/Diagram-Demo/Program.cs +++ b/docs/Diagram-Demo/Program.cs @@ -1,17 +1,16 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -namespace Diagram_Demo +namespace Diagram_Demo; + +public class Program +{ +public static void Main(string[] args) { - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } + CreateHostBuilder(args).Build().Run(); +} - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); - } +public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } diff --git a/docs/Diagram-Demo/Startup.cs b/docs/Diagram-Demo/Startup.cs index a1e1d8926..a398e3df6 100644 --- a/docs/Diagram-Demo/Startup.cs +++ b/docs/Diagram-Demo/Startup.cs @@ -4,49 +4,48 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -namespace Diagram_Demo +namespace Diagram_Demo; + +public class Startup +{ +public Startup(IConfiguration configuration) +{ + Configuration = configuration; +} + +public IConfiguration Configuration { get; } + +// This method gets called by the runtime. Use this method to add services to the container. +// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 +public void ConfigureServices(IServiceCollection services) +{ + services.AddRazorPages(); + services.AddServerSideBlazor(); +} + +// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. +public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - public class Startup + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - services.AddRazorPages(); - services.AddServerSideBlazor(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapBlazorHub(); - endpoints.MapFallbackToPage("/_Host"); - }); - } + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapBlazorHub(); + endpoints.MapFallbackToPage("/_Host"); + }); +} } diff --git a/docs/Layouts/Pages/Error.cshtml.cs b/docs/Layouts/Pages/Error.cshtml.cs index 274e1ca6a..ab885857f 100644 --- a/docs/Layouts/Pages/Error.cshtml.cs +++ b/docs/Layouts/Pages/Error.cshtml.cs @@ -7,26 +7,25 @@ using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.Logging; -namespace Layouts.Pages +namespace Layouts.Pages; + +[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] +[IgnoreAntiforgeryToken] +public class ErrorModel : PageModel { - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - [IgnoreAntiforgeryToken] - public class ErrorModel : PageModel - { - public string RequestId { get; set; } +public string RequestId { get; set; } - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); +public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - private readonly ILogger _logger; +private readonly ILogger _logger; - public ErrorModel(ILogger logger) - { - _logger = logger; - } +public ErrorModel(ILogger logger) +{ + _logger = logger; +} - public void OnGet() - { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } - } +public void OnGet() +{ + RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; +} } diff --git a/docs/Layouts/Program.cs b/docs/Layouts/Program.cs index 71e2194a2..57bc41d32 100644 --- a/docs/Layouts/Program.cs +++ b/docs/Layouts/Program.cs @@ -1,17 +1,16 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; -namespace Layouts +namespace Layouts; + +public class Program +{ +public static void Main(string[] args) { - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } + CreateHostBuilder(args).Build().Run(); +} - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); - } +public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); } diff --git a/docs/Layouts/Startup.cs b/docs/Layouts/Startup.cs index 66cf271b8..cea43ff5f 100644 --- a/docs/Layouts/Startup.cs +++ b/docs/Layouts/Startup.cs @@ -5,52 +5,51 @@ using Microsoft.Extensions.Hosting; using MatBlazor; -namespace Layouts +namespace Layouts; + +public class Startup +{ +public Startup(IConfiguration configuration) +{ + Configuration = configuration; +} + +public IConfiguration Configuration { get; } + +// This method gets called by the runtime. Use this method to add services to the container. +// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 +public void ConfigureServices(IServiceCollection services) +{ + services.AddRazorPages(); + services.AddServerSideBlazor(); + services + //.AddFontAwesomeIcons() + .AddMatBlazor(); +} + +// This method gets called by the runtime. Use this method to configure the HTTP request pipeline. +public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { - public class Startup + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + else { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 - public void ConfigureServices(IServiceCollection services) - { - services.AddRazorPages(); - services.AddServerSideBlazor(); - services - //.AddFontAwesomeIcons() - .AddMatBlazor(); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapBlazorHub(); - endpoints.MapFallbackToPage("/_Host"); - }); - } + app.UseExceptionHandler("/Error"); + // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. + app.UseHsts(); } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapBlazorHub(); + endpoints.MapFallbackToPage("/_Host"); + }); +} } diff --git a/samples/ServerSide/Program.cs b/samples/ServerSide/Program.cs index 26ad3b4c7..86fdb3e83 100644 --- a/samples/ServerSide/Program.cs +++ b/samples/ServerSide/Program.cs @@ -9,20 +9,19 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace ServerSide +namespace ServerSide; + +public class Program { - public class Program + public static void Main(string[] args) { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); + CreateHostBuilder(args).Build().Run(); } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); } diff --git a/samples/ServerSide/Startup.cs b/samples/ServerSide/Startup.cs index d46344395..579163f80 100644 --- a/samples/ServerSide/Startup.cs +++ b/samples/ServerSide/Startup.cs @@ -5,46 +5,45 @@ using Microsoft.Extensions.Hosting; using SharedDemo; -namespace ServerSide +namespace ServerSide; + +public class Startup { - public class Startup + public Startup(IConfiguration configuration) { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } + Configuration = configuration; + } - public IConfiguration Configuration { get; } + public IConfiguration Configuration { get; } + + public void ConfigureServices(IServiceCollection services) + { + services.AddRazorPages(); + services.AddServerSideBlazor(); + services.AddSingleton(); + } - public void ConfigureServices(IServiceCollection services) + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) { - services.AddRazorPages(); - services.AddServerSideBlazor(); - services.AddSingleton(); + app.UseDeveloperExceptionPage(); } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + else { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseEndpoints(endpoints => - { - endpoints.MapBlazorHub(); - endpoints.MapFallbackToPage("/_Host"); - }); + app.UseExceptionHandler("/Error"); + app.UseHsts(); } + + app.UseHttpsRedirection(); + app.UseStaticFiles(); + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapBlazorHub(); + endpoints.MapFallbackToPage("/_Host"); + }); } } diff --git a/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor.cs b/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor.cs index 27604b658..bb3303a37 100644 --- a/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor.cs +++ b/samples/SharedDemo/Demos/Algorithms/ReconnectLinksToClosestPorts.razor.cs @@ -4,36 +4,35 @@ using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; -namespace SharedDemo.Demos.Algorithms +namespace SharedDemo.Demos.Algorithms; + +public class ReconnectLinksToClosestPortsComponent : ComponentBase { - public class ReconnectLinksToClosestPortsComponent : ComponentBase - { - protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); - protected override void OnInitialized() - { - base.OnInitialized(); + protected override void OnInitialized() + { + base.OnInitialized(); - var node1 = NewNode(50, 50); - var node2 = NewNode(300, 300); - var node3 = NewNode(300, 50); - BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Top), node2.GetPort(PortAlignment.Right))); - BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Bottom), node3.GetPort(PortAlignment.Top))); - BlazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - } + var node1 = NewNode(50, 50); + var node2 = NewNode(300, 300); + var node3 = NewNode(300, 50); + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Top), node2.GetPort(PortAlignment.Right))); + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Bottom), node3.GetPort(PortAlignment.Top))); + BlazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + } - protected void ReconnectLinks() => BlazorDiagram.ReconnectLinksToClosestPorts(); + protected void ReconnectLinks() => BlazorDiagram.ReconnectLinksToClosestPorts(); - private NodeModel NewNode(double x, double y) - { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + private NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/BotAnswerNode.cs b/samples/SharedDemo/Demos/BotAnswerNode.cs index b345facbe..3a34c9b59 100644 --- a/samples/SharedDemo/Demos/BotAnswerNode.cs +++ b/samples/SharedDemo/Demos/BotAnswerNode.cs @@ -1,12 +1,11 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -namespace SharedDemo.Demos +namespace SharedDemo.Demos; + +public class BotAnswerNode : NodeModel { - public class BotAnswerNode : NodeModel - { - public BotAnswerNode(Point position = null) : base(position) { } + public BotAnswerNode(Point position = null) : base(position) { } - public string Answer { get; set; } - } + public string Answer { get; set; } } diff --git a/samples/SharedDemo/Demos/CustomGroup/CustomGroupModel.cs b/samples/SharedDemo/Demos/CustomGroup/CustomGroupModel.cs index 021431e8b..65d949d68 100644 --- a/samples/SharedDemo/Demos/CustomGroup/CustomGroupModel.cs +++ b/samples/SharedDemo/Demos/CustomGroup/CustomGroupModel.cs @@ -1,15 +1,14 @@ using Blazor.Diagrams.Core.Models; -namespace SharedDemo.Demos.CustomGroup +namespace SharedDemo.Demos.CustomGroup; + +public class CustomGroupModel : GroupModel { - public class CustomGroupModel : GroupModel + public CustomGroupModel(NodeModel[] children, string title, byte padding = 30) + : base(children, padding) { - public CustomGroupModel(NodeModel[] children, string title, byte padding = 30) - : base(children, padding) - { - Title = title; - } - - public string Title { get; } + Title = title; } + + public string Title { get; } } diff --git a/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs b/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs index efb426ae3..edfcbc11d 100644 --- a/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomGroup/Demo.razor.cs @@ -2,43 +2,42 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -namespace SharedDemo.Demos.CustomGroup +namespace SharedDemo.Demos.CustomGroup; + +partial class Demo { - partial class Demo + private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() + { + base.OnInitialized(); + + LayoutData.Title = "Custom group"; + LayoutData.Info = "Creating your own custom groups is very easy!"; + LayoutData.DataChanged(); + + _blazorDiagram.Options.LinksLayerOrder = 2; + _blazorDiagram.Options.NodesLayerOrder = 1; + _blazorDiagram.RegisterComponent(); + + var node1 = NewNode(50, 50); + var node2 = NewNode(300, 300); + var node3 = NewNode(500, 100); + + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + _blazorDiagram.Groups.Add(new CustomGroupModel(new[] { node2, node3 }, "Group 1")); + + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + } + + private NodeModel NewNode(double x, double y) { - private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); - - protected override void OnInitialized() - { - base.OnInitialized(); - - LayoutData.Title = "Custom group"; - LayoutData.Info = "Creating your own custom groups is very easy!"; - LayoutData.DataChanged(); - - _blazorDiagram.Options.LinksLayerOrder = 2; - _blazorDiagram.Options.NodesLayerOrder = 1; - _blazorDiagram.RegisterComponent(); - - var node1 = NewNode(50, 50); - var node2 = NewNode(300, 300); - var node3 = NewNode(500, 100); - - _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - _blazorDiagram.Groups.Add(new CustomGroupModel(new[] { node2, node3 }, "Group 1")); - - _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); - } - - private NodeModel NewNode(double x, double y) - { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs b/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs index 5f3691e90..06e0a69d5 100644 --- a/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomLink/Demo.razor.cs @@ -2,40 +2,39 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -namespace SharedDemo.Demos.CustomLink +namespace SharedDemo.Demos.CustomLink; + +partial class Demo { - partial class Demo + private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() + { + base.OnInitialized(); + + LayoutData.Title = "Custom link"; + LayoutData.Info = "Creating your own custom links is very easy!"; + LayoutData.DataChanged(); + + _blazorDiagram.RegisterComponent(); + // Also usable: _diagram.Options.Links.DefaultLinkComponent = typeof(ThickLink); + + var node1 = NewNode(50, 50); + var node2 = NewNode(300, 300); + var node3 = NewNode(500, 50); + + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + _blazorDiagram.Links.Add(new ThickLink(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new ThickLink(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + } + + private NodeModel NewNode(double x, double y) { - private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); - - protected override void OnInitialized() - { - base.OnInitialized(); - - LayoutData.Title = "Custom link"; - LayoutData.Info = "Creating your own custom links is very easy!"; - LayoutData.DataChanged(); - - _blazorDiagram.RegisterComponent(); - // Also usable: _diagram.Options.Links.DefaultLinkComponent = typeof(ThickLink); - - var node1 = NewNode(50, 50); - var node2 = NewNode(300, 300); - var node3 = NewNode(500, 50); - - _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - _blazorDiagram.Links.Add(new ThickLink(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - _blazorDiagram.Links.Add(new ThickLink(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); - } - - private NodeModel NewNode(double x, double y) - { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/CustomLink/ThickLink.cs b/samples/SharedDemo/Demos/CustomLink/ThickLink.cs index 4156c18cf..1fb8e285e 100644 --- a/samples/SharedDemo/Demos/CustomLink/ThickLink.cs +++ b/samples/SharedDemo/Demos/CustomLink/ThickLink.cs @@ -1,9 +1,8 @@ using Blazor.Diagrams.Core.Models; -namespace SharedDemo.Demos.CustomLink +namespace SharedDemo.Demos.CustomLink; + +public class ThickLink : LinkModel { - public class ThickLink : LinkModel - { - public ThickLink(PortModel sourcePort, PortModel targetPort = null) : base(sourcePort, targetPort) { } - } + public ThickLink(PortModel sourcePort, PortModel targetPort = null) : base(sourcePort, targetPort) { } } diff --git a/samples/SharedDemo/Demos/CustomNode.razor.cs b/samples/SharedDemo/Demos/CustomNode.razor.cs index 9e41e433a..6d4ff99e2 100644 --- a/samples/SharedDemo/Demos/CustomNode.razor.cs +++ b/samples/SharedDemo/Demos/CustomNode.razor.cs @@ -3,33 +3,32 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams; -namespace SharedDemo.Demos +namespace SharedDemo.Demos; + +public class CustomNodeComponent : ComponentBase { - public class CustomNodeComponent : ComponentBase - { - protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); - protected override void OnInitialized() - { - base.OnInitialized(); + protected override void OnInitialized() + { + base.OnInitialized(); - BlazorDiagram.RegisterComponent(); + BlazorDiagram.RegisterComponent(); - var node = new NodeModel(new Point(20, 20)); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Right); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Left); + var node = new NodeModel(new Point(20, 20)); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Right); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Left); - BlazorDiagram.Nodes.Add(new[] { node, NewNode(100, 100), NewNode(300, 300) }); - } + BlazorDiagram.Nodes.Add(new[] { node, NewNode(100, 100), NewNode(300, 300) }); + } - private BotAnswerNode NewNode(double x, double y) - { - var node = new BotAnswerNode(new Point(x, y)); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Bottom); - return node; - } + private BotAnswerNode NewNode(double x, double y) + { + var node = new BotAnswerNode(new Point(x, y)); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Bottom); + return node; } } diff --git a/samples/SharedDemo/Demos/CustomPort/ColoredPort.cs b/samples/SharedDemo/Demos/CustomPort/ColoredPort.cs index 9d4696707..f83a54f40 100644 --- a/samples/SharedDemo/Demos/CustomPort/ColoredPort.cs +++ b/samples/SharedDemo/Demos/CustomPort/ColoredPort.cs @@ -1,31 +1,30 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -namespace SharedDemo.Demos.CustomPort +namespace SharedDemo.Demos.CustomPort; + +public class ColoredPort : PortModel { - public class ColoredPort : PortModel + public ColoredPort(NodeModel parent, PortAlignment alignment, bool isRed) : base(parent, alignment, null, null) { - public ColoredPort(NodeModel parent, PortAlignment alignment, bool isRed) : base(parent, alignment, null, null) - { - IsRed = isRed; - } + IsRed = isRed; + } - public bool IsRed { get; set; } + public bool IsRed { get; set; } - public override bool CanAttachTo(ILinkable other) - { - if (other is not PortModel port) - return false; - - // Checks for same-node/port attachments - if (!base.CanAttachTo(port)) - return false; + public override bool CanAttachTo(ILinkable other) + { + if (other is not PortModel port) + return false; + + // Checks for same-node/port attachments + if (!base.CanAttachTo(port)) + return false; - // Only able to attach to the same port type - if (port is not ColoredPort cp) - return false; + // Only able to attach to the same port type + if (port is not ColoredPort cp) + return false; - return IsRed == cp.IsRed; - } + return IsRed == cp.IsRed; } } diff --git a/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs b/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs index dca336305..12708c81f 100644 --- a/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomPort/Demo.razor.cs @@ -2,35 +2,34 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -namespace SharedDemo.Demos.CustomPort +namespace SharedDemo.Demos.CustomPort; + +partial class Demo { - partial class Demo - { - private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); + private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); - protected override void OnInitialized() - { - base.OnInitialized(); + protected override void OnInitialized() + { + base.OnInitialized(); - LayoutData.Title = "Custom port"; - LayoutData.Info = "Creating your own custom ports is very easy!
    " + - "In this example, you can only attach links from/to ports with the same color."; - LayoutData.DataChanged(); + LayoutData.Title = "Custom port"; + LayoutData.Info = "Creating your own custom ports is very easy!
    " + + "In this example, you can only attach links from/to ports with the same color."; + LayoutData.DataChanged(); - _blazorDiagram.RegisterComponent(replace: true); + _blazorDiagram.RegisterComponent(replace: true); - var node1 = NewNode(50, 50); - var node2 = NewNode(300, 300); - _blazorDiagram.Nodes.Add(new[] { node1, node2, NewNode(500, 50) }); - _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Top), node2.GetPort(PortAlignment.Top))); - } + var node1 = NewNode(50, 50); + var node2 = NewNode(300, 300); + _blazorDiagram.Nodes.Add(new[] { node1, node2, NewNode(500, 50) }); + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Top), node2.GetPort(PortAlignment.Top))); + } - private NodeModel NewNode(double x, double y) - { - var node = new NodeModel(new Point(x, y)); - node.AddPort(new ColoredPort(node, PortAlignment.Top, true)); - node.AddPort(new ColoredPort(node, PortAlignment.Left, false)); - return node; - } + private NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(new ColoredPort(node, PortAlignment.Top, true)); + node.AddPort(new ColoredPort(node, PortAlignment.Left, false)); + return node; } } diff --git a/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupModel.cs b/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupModel.cs index 7a88a16cc..002945171 100644 --- a/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupModel.cs +++ b/samples/SharedDemo/Demos/CustomSvgGroup/CustomSvgGroupModel.cs @@ -1,13 +1,12 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Models; -namespace SharedDemo.Demos.CustomSvgGroup +namespace SharedDemo.Demos.CustomSvgGroup; + +public class CustomSvgGroupModel : SvgGroupModel { - public class CustomSvgGroupModel : SvgGroupModel + public CustomSvgGroupModel(NodeModel[] children, string title, byte padding = 30) : base(children, padding) { - public CustomSvgGroupModel(NodeModel[] children, string title, byte padding = 30) : base(children, padding) - { - Title = title; - } + Title = title; } } diff --git a/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs index 5aed0ac85..50a419aed 100644 --- a/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs +++ b/samples/SharedDemo/Demos/CustomSvgGroup/Demo.razor.cs @@ -4,42 +4,41 @@ using Blazor.Diagrams.Models; using SharedDemo.Demos.Nodes; -namespace SharedDemo.Demos.CustomSvgGroup +namespace SharedDemo.Demos.CustomSvgGroup; + +partial class Demo { - partial class Demo + private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() + { + base.OnInitialized(); + + LayoutData.Title = "Custom SVG group"; + LayoutData.Info = "Creating your own custom svg groups is very easy!"; + LayoutData.DataChanged(); + + _blazorDiagram.RegisterComponent(); + _blazorDiagram.RegisterComponent(); + + var node1 = NewNode(50, 50); + var node2 = NewNode(300, 300); + var node3 = NewNode(500, 100); + + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + _blazorDiagram.Groups.Add(new CustomSvgGroupModel(new[] { node2, node3 }, "Group 1")); + + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + } + + private NodeModel NewNode(double x, double y) { - private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); - - protected override void OnInitialized() - { - base.OnInitialized(); - - LayoutData.Title = "Custom SVG group"; - LayoutData.Info = "Creating your own custom svg groups is very easy!"; - LayoutData.DataChanged(); - - _blazorDiagram.RegisterComponent(); - _blazorDiagram.RegisterComponent(); - - var node1 = NewNode(50, 50); - var node2 = NewNode(300, 300); - var node3 = NewNode(500, 100); - - _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - _blazorDiagram.Groups.Add(new CustomSvgGroupModel(new[] { node2, node3 }, "Group 1")); - - _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); - } - - private NodeModel NewNode(double x, double y) - { - var node = new SvgNodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + var node = new SvgNodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/DragAndDrop.razor.cs b/samples/SharedDemo/Demos/DragAndDrop.razor.cs index ff054494b..0e049b058 100644 --- a/samples/SharedDemo/Demos/DragAndDrop.razor.cs +++ b/samples/SharedDemo/Demos/DragAndDrop.razor.cs @@ -2,41 +2,40 @@ using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components.Web; -namespace SharedDemo.Demos +namespace SharedDemo.Demos; + +public partial class DragAndDrop { - public partial class DragAndDrop + private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); + private int? _draggedType; + + protected override void OnInitialized() { - private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); - private int? _draggedType; - - protected override void OnInitialized() - { - base.OnInitialized(); - - LayoutData.Title = "Drag & Drop"; - LayoutData.Info = "A very simple drag & drop implementation using the HTML5 events."; - LayoutData.DataChanged(); - - _blazorDiagram.RegisterComponent(); - } - - private void OnDragStart(int key) - { - // Can also use transferData, but this is probably "faster" - _draggedType = key; - } - - private void OnDrop(DragEventArgs e) - { - if (_draggedType == null) // Unkown item - return; - - var position = _blazorDiagram.GetRelativeMousePoint(e.ClientX, e.ClientY); - var node = _draggedType == 0 ? new NodeModel(position) : new BotAnswerNode(position); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Bottom); - _blazorDiagram.Nodes.Add(node); - _draggedType = null; - } + base.OnInitialized(); + + LayoutData.Title = "Drag & Drop"; + LayoutData.Info = "A very simple drag & drop implementation using the HTML5 events."; + LayoutData.DataChanged(); + + _blazorDiagram.RegisterComponent(); + } + + private void OnDragStart(int key) + { + // Can also use transferData, but this is probably "faster" + _draggedType = key; + } + + private void OnDrop(DragEventArgs e) + { + if (_draggedType == null) // Unkown item + return; + + var position = _blazorDiagram.GetRelativeMousePoint(e.ClientX, e.ClientY); + var node = _draggedType == 0 ? new NodeModel(position) : new BotAnswerNode(position); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Bottom); + _blazorDiagram.Nodes.Add(node); + _draggedType = null; } } diff --git a/samples/SharedDemo/Demos/DynamicInsertions.razor.cs b/samples/SharedDemo/Demos/DynamicInsertions.razor.cs index 8c9e5d39e..7b4cae3f3 100644 --- a/samples/SharedDemo/Demos/DynamicInsertions.razor.cs +++ b/samples/SharedDemo/Demos/DynamicInsertions.razor.cs @@ -6,96 +6,95 @@ using System; using System.Linq; -namespace SharedDemo.Demos +namespace SharedDemo.Demos; + +public class DynamicInsertionsComponent : ComponentBase { - public class DynamicInsertionsComponent : ComponentBase - { - private static readonly Random _random = new Random(); - protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); + private static readonly Random _random = new Random(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); - protected override void OnInitialized() - { - base.OnInitialized(); + protected override void OnInitialized() + { + base.OnInitialized(); - BlazorDiagram.Options.Groups.Enabled = true; - BlazorDiagram.Nodes.Add(new NodeModel(new Point(300, 50))); - BlazorDiagram.Nodes.Add(new NodeModel(new Point(300, 400))); + BlazorDiagram.Options.Groups.Enabled = true; + BlazorDiagram.Nodes.Add(new NodeModel(new Point(300, 50))); + BlazorDiagram.Nodes.Add(new NodeModel(new Point(300, 400))); - BlazorDiagram.Options.Links.Factory = (d, s, ta) => + BlazorDiagram.Options.Links.Factory = (d, s, ta) => + { + var link = new LinkModel(new SinglePortAnchor(s as PortModel) + { + UseShapeAndAlignment = false + }, ta) { - var link = new LinkModel(new SinglePortAnchor(s as PortModel) - { - UseShapeAndAlignment = false - }, ta) - { - SourceMarker = LinkMarker.Arrow - }; - return link; + SourceMarker = LinkMarker.Arrow }; - } + return link; + }; + } - protected void AddNode() - { - var x = _random.Next(0, (int)BlazorDiagram.Container.Width - 120); - var y = _random.Next(0, (int)BlazorDiagram.Container.Height - 100); - BlazorDiagram.Nodes.Add(new NodeModel(new Point(x, y))); - } + protected void AddNode() + { + var x = _random.Next(0, (int)BlazorDiagram.Container.Width - 120); + var y = _random.Next(0, (int)BlazorDiagram.Container.Height - 100); + BlazorDiagram.Nodes.Add(new NodeModel(new Point(x, y))); + } - protected void RemoveNode() - { - var i = _random.Next(0, BlazorDiagram.Nodes.Count); - BlazorDiagram.Nodes.Remove(BlazorDiagram.Nodes[i]); - } + protected void RemoveNode() + { + var i = _random.Next(0, BlazorDiagram.Nodes.Count); + BlazorDiagram.Nodes.Remove(BlazorDiagram.Nodes[i]); + } - protected void AddPort() - { - var node = BlazorDiagram.Nodes.FirstOrDefault(n => n.Selected); - if (node == null) - return; + protected void AddPort() + { + var node = BlazorDiagram.Nodes.FirstOrDefault(n => n.Selected); + if (node == null) + return; - foreach (PortAlignment portAlignment in Enum.GetValues(typeof(PortAlignment))) + foreach (PortAlignment portAlignment in Enum.GetValues(typeof(PortAlignment))) + { + if (node.GetPort(portAlignment) == null) { - if (node.GetPort(portAlignment) == null) - { - node.AddPort(portAlignment); - node.Refresh(); - break; - } + node.AddPort(portAlignment); + node.Refresh(); + break; } } + } - protected void RemovePort() - { - var node = BlazorDiagram.Nodes.FirstOrDefault(n => n.Selected); - if (node == null) - return; + protected void RemovePort() + { + var node = BlazorDiagram.Nodes.FirstOrDefault(n => n.Selected); + if (node == null) + return; - if (node.Ports.Count == 0) - return; + if (node.Ports.Count == 0) + return; - var i = _random.Next(0, node.Ports.Count); - var port = node.Ports[i]; + var i = _random.Next(0, node.Ports.Count); + var port = node.Ports[i]; - BlazorDiagram.Links.Remove(port.Links.ToArray()); - node.RemovePort(port); - node.Refresh(); - } + BlazorDiagram.Links.Remove(port.Links.ToArray()); + node.RemovePort(port); + node.Refresh(); + } - protected void AddLink() - { - var selectedNodes = BlazorDiagram.Nodes.Where(n => n.Selected).ToArray(); - if (selectedNodes.Length != 2) - return; + protected void AddLink() + { + var selectedNodes = BlazorDiagram.Nodes.Where(n => n.Selected).ToArray(); + if (selectedNodes.Length != 2) + return; - var node1 = selectedNodes[0]; - var node2 = selectedNodes[1]; + var node1 = selectedNodes[0]; + var node2 = selectedNodes[1]; - if (node1 == null || node1.Ports.Count == 0 || node2 == null || node2.Ports.Count == 0) - return; + if (node1 == null || node1.Ports.Count == 0 || node2 == null || node2.Ports.Count == 0) + return; - var sourcePort = node1.Ports[_random.Next(0, node1.Ports.Count)]; - var targetPort = node2.Ports[_random.Next(0, node2.Ports.Count)]; - BlazorDiagram.Links.Add(new LinkModel(sourcePort, targetPort)); - } + var sourcePort = node1.Ports[_random.Next(0, node1.Ports.Count)]; + var targetPort = node2.Ports[_random.Next(0, node2.Ports.Count)]; + BlazorDiagram.Links.Add(new LinkModel(sourcePort, targetPort)); } } diff --git a/samples/SharedDemo/Demos/Events.razor.cs b/samples/SharedDemo/Demos/Events.razor.cs index 9bd170b95..f44ed5310 100644 --- a/samples/SharedDemo/Demos/Events.razor.cs +++ b/samples/SharedDemo/Demos/Events.razor.cs @@ -4,80 +4,79 @@ using Microsoft.AspNetCore.Components; using System.Collections.Generic; -namespace SharedDemo.Demos +namespace SharedDemo.Demos; + +public class EventsComponent : ComponentBase { - public class EventsComponent : ComponentBase - { - protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); - protected readonly List events = new List(); + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); + protected readonly List events = new List(); - protected override void OnInitialized() - { - base.OnInitialized(); + protected override void OnInitialized() + { + base.OnInitialized(); - RegisterEvents(); + RegisterEvents(); - var node1 = NewNode(50, 50); - var node2 = NewNode(300, 300); - BlazorDiagram.Nodes.Add(new[] { node1, node2, NewNode(300, 50) }); - BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - } + var node1 = NewNode(50, 50); + var node2 = NewNode(300, 300); + BlazorDiagram.Nodes.Add(new[] { node1, node2, NewNode(300, 50) }); + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + } - private void RegisterEvents() + private void RegisterEvents() + { + BlazorDiagram.Changed += () => { - BlazorDiagram.Changed += () => - { - events.Add("Changed"); - StateHasChanged(); - }; - - BlazorDiagram.Nodes.Added += (n) => events.Add($"NodesAdded, NodeId={n.Id}"); - BlazorDiagram.Nodes.Removed += (n) => events.Add($"NodesRemoved, NodeId={n.Id}"); + events.Add("Changed"); + StateHasChanged(); + }; - BlazorDiagram.SelectionChanged += (m) => - { - events.Add($"SelectionChanged, Id={m.Id}, Type={m.GetType().Name}, Selected={m.Selected}"); - StateHasChanged(); - }; + BlazorDiagram.Nodes.Added += (n) => events.Add($"NodesAdded, NodeId={n.Id}"); + BlazorDiagram.Nodes.Removed += (n) => events.Add($"NodesRemoved, NodeId={n.Id}"); - BlazorDiagram.Links.Added += (l) => events.Add($"Links.Added, LinkId={l.Id}"); + BlazorDiagram.SelectionChanged += (m) => + { + events.Add($"SelectionChanged, Id={m.Id}, Type={m.GetType().Name}, Selected={m.Selected}"); + StateHasChanged(); + }; - BlazorDiagram.Links.Removed += (l) => events.Add($"Links.Removed, LinkId={l.Id}"); + BlazorDiagram.Links.Added += (l) => events.Add($"Links.Added, LinkId={l.Id}"); - BlazorDiagram.PointerDown += (m, e) => - { - events.Add($"MouseDown, Type={m?.GetType().Name}, ModelId={m?.Id}"); - StateHasChanged(); - }; + BlazorDiagram.Links.Removed += (l) => events.Add($"Links.Removed, LinkId={l.Id}"); - BlazorDiagram.PointerUp += (m, e) => - { - events.Add($"MouseUp, Type={m?.GetType().Name}, ModelId={m?.Id}"); - StateHasChanged(); - }; + BlazorDiagram.PointerDown += (m, e) => + { + events.Add($"MouseDown, Type={m?.GetType().Name}, ModelId={m?.Id}"); + StateHasChanged(); + }; - BlazorDiagram.PointerClick += (m, e) => - { - events.Add($"MouseClick, Type={m?.GetType().Name}, ModelId={m?.Id}"); - StateHasChanged(); - }; + BlazorDiagram.PointerUp += (m, e) => + { + events.Add($"MouseUp, Type={m?.GetType().Name}, ModelId={m?.Id}"); + StateHasChanged(); + }; - BlazorDiagram.PointerDoubleClick += (m, e) => - { - events.Add($"MouseDoubleClick, Type={m?.GetType().Name}, ModelId={m?.Id}"); - StateHasChanged(); - }; - } + BlazorDiagram.PointerClick += (m, e) => + { + events.Add($"MouseClick, Type={m?.GetType().Name}, ModelId={m?.Id}"); + StateHasChanged(); + }; - private NodeModel NewNode(double x, double y) + BlazorDiagram.PointerDoubleClick += (m, e) => { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - node.Moved += (m) => events.Add($"Node.Moved, NodeId={node.Id}"); - return node; - } + events.Add($"MouseDoubleClick, Type={m?.GetType().Name}, ModelId={m?.Id}"); + StateHasChanged(); + }; + } + + private NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + node.Moved += (m) => events.Add($"Node.Moved, NodeId={node.Id}"); + return node; } } diff --git a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs index acb071f2b..cfb0dbfeb 100644 --- a/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs +++ b/samples/SharedDemo/Demos/Groups/CustomShortcut.razor.cs @@ -3,43 +3,42 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -namespace SharedDemo.Demos.Groups +namespace SharedDemo.Demos.Groups; + +public partial class CustomShortcut { - public partial class CustomShortcut + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() + { + base.OnInitialized(); + + LayoutData.Title = "Custom Shortcut"; + LayoutData.Info = "You can customize what needs to be pressed to group selected nodes. Ctrl+Shift+k in this example."; + LayoutData.DataChanged(); + + _blazorDiagram.Options.Groups.Enabled = true; + _blazorDiagram.Options.LinksLayerOrder = 2; + _blazorDiagram.Options.NodesLayerOrder = 1; + var ksb = _blazorDiagram.GetBehavior(); + ksb.RemoveShortcut("g", true, false, true); + ksb.SetShortcut("k", true, true, false, KeyboardShortcutsDefaults.Grouping); + + var node1 = NewNode(50, 50); + var node2 = NewNode(250, 250); + var node3 = NewNode(500, 100); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + } + + private NodeModel NewNode(double x, double y) { - private BlazorDiagram _blazorDiagram = new BlazorDiagram(); - - protected override void OnInitialized() - { - base.OnInitialized(); - - LayoutData.Title = "Custom Shortcut"; - LayoutData.Info = "You can customize what needs to be pressed to group selected nodes. Ctrl+Shift+k in this example."; - LayoutData.DataChanged(); - - _blazorDiagram.Options.Groups.Enabled = true; - _blazorDiagram.Options.LinksLayerOrder = 2; - _blazorDiagram.Options.NodesLayerOrder = 1; - var ksb = _blazorDiagram.GetBehavior(); - ksb.RemoveShortcut("g", true, false, true); - ksb.SetShortcut("k", true, true, false, KeyboardShortcutsDefaults.Grouping); - - var node1 = NewNode(50, 50); - var node2 = NewNode(250, 250); - var node3 = NewNode(500, 100); - _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - - _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - } - - private NodeModel NewNode(double x, double y) - { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs b/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs index 2c0f88b42..fa64b8e10 100644 --- a/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Dynamic.razor.cs @@ -3,75 +3,74 @@ using Blazor.Diagrams.Core.Models; using System; -namespace SharedDemo.Demos.Groups +namespace SharedDemo.Demos.Groups; + +public partial class Dynamic { - public partial class Dynamic - { - private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); + private readonly BlazorDiagram _blazorDiagram = new BlazorDiagram(); - protected override void OnInitialized() - { - base.OnInitialized(); + protected override void OnInitialized() + { + base.OnInitialized(); - LayoutData.Title = "Dynamic Groups"; - LayoutData.Info = "You can create and modify groups dynamically!"; - LayoutData.DataChanged(); - - _blazorDiagram.Options.LinksLayerOrder = 2; - _blazorDiagram.Options.NodesLayerOrder = 1; + LayoutData.Title = "Dynamic Groups"; + LayoutData.Info = "You can create and modify groups dynamically!"; + LayoutData.DataChanged(); + + _blazorDiagram.Options.LinksLayerOrder = 2; + _blazorDiagram.Options.NodesLayerOrder = 1; - var node1 = NewNode(50, 150); - var node2 = NewNode(250, 350); - var node3 = NewNode(500, 200); - _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - } + var node1 = NewNode(50, 150); + var node2 = NewNode(250, 350); + var node3 = NewNode(500, 200); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + } - private void AddEmptyGroup() + private void AddEmptyGroup() + { + _blazorDiagram.Groups.Add(new GroupModel(Array.Empty()) { - _blazorDiagram.Groups.Add(new GroupModel(Array.Empty()) - { - Position = new Point(100, 100) - }); - } + Position = new Point(100, 100) + }); + } - private void AddChildToGroup() - { - if (_blazorDiagram.Groups.Count == 0) - return; + private void AddChildToGroup() + { + if (_blazorDiagram.Groups.Count == 0) + return; - foreach (var node in _blazorDiagram.Nodes) + foreach (var node in _blazorDiagram.Nodes) + { + if (node.Group == null) { - if (node.Group == null) - { - _blazorDiagram.Groups[0].AddChild(node); - _blazorDiagram.Refresh(); - return; - } + _blazorDiagram.Groups[0].AddChild(node); + _blazorDiagram.Refresh(); + return; } } + } - private void RemoveChildFromGroup() + private void RemoveChildFromGroup() + { + foreach (var node in _blazorDiagram.Nodes) { - foreach (var node in _blazorDiagram.Nodes) + if (node.Group != null) { - if (node.Group != null) - { - node.Group.RemoveChild(node); - _blazorDiagram.Refresh(); - return; - } + node.Group.RemoveChild(node); + _blazorDiagram.Refresh(); + return; } } + } - private NodeModel NewNode(double x, double y) - { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + private NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/Groups/Factory.razor.cs b/samples/SharedDemo/Demos/Groups/Factory.razor.cs index 2b5ef9a34..ac008eadc 100644 --- a/samples/SharedDemo/Demos/Groups/Factory.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Factory.razor.cs @@ -2,50 +2,49 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -namespace SharedDemo.Demos.Groups +namespace SharedDemo.Demos.Groups; + +public partial class Factory { - public partial class Factory + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() { - protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); + base.OnInitialized(); - protected override void OnInitialized() - { - base.OnInitialized(); - - LayoutData.Title = "Groups Factory"; - LayoutData.Info = "Factory setting is a way to customize how groups (models) are created when the user uses the shortcut. " + - "Try to group nodes using CTRL+ALT+G now."; - LayoutData.DataChanged(); - - BlazorDiagram.Options.Groups.Enabled = true; - BlazorDiagram.Options.LinksLayerOrder = 2; - BlazorDiagram.Options.NodesLayerOrder = 1; - BlazorDiagram.Options.Groups.Factory = (diagram, children) => - { - var group = new GroupModel(children, 25); - group.AddPort(PortAlignment.Top); - group.AddPort(PortAlignment.Bottom); - group.AddPort(PortAlignment.Right); - group.AddPort(PortAlignment.Left); - return group; - }; - - var node1 = NewNode(50, 50); - var node2 = NewNode(250, 250); - var node3 = NewNode(500, 100); - BlazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - - BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - } - - private NodeModel NewNode(double x, double y) + LayoutData.Title = "Groups Factory"; + LayoutData.Info = "Factory setting is a way to customize how groups (models) are created when the user uses the shortcut. " + + "Try to group nodes using CTRL+ALT+G now."; + LayoutData.DataChanged(); + + BlazorDiagram.Options.Groups.Enabled = true; + BlazorDiagram.Options.LinksLayerOrder = 2; + BlazorDiagram.Options.NodesLayerOrder = 1; + BlazorDiagram.Options.Groups.Factory = (diagram, children) => { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + var group = new GroupModel(children, 25); + group.AddPort(PortAlignment.Top); + group.AddPort(PortAlignment.Bottom); + group.AddPort(PortAlignment.Right); + group.AddPort(PortAlignment.Left); + return group; + }; + + var node1 = NewNode(50, 50); + var node2 = NewNode(250, 250); + var node3 = NewNode(500, 100); + BlazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + } + + private NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs index 1a2a3d2a4..dd87f311e 100644 --- a/samples/SharedDemo/Demos/Groups/Grouping.razor.cs +++ b/samples/SharedDemo/Demos/Groups/Grouping.razor.cs @@ -3,45 +3,44 @@ using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; -namespace SharedDemo.Demos +namespace SharedDemo.Demos; + +public class GroupingComponent : ComponentBase { - public class GroupingComponent : ComponentBase + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() + { + base.OnInitialized(); + + BlazorDiagram.Options.Groups.Enabled = true; + BlazorDiagram.Options.LinksLayerOrder = 2; + BlazorDiagram.Options.NodesLayerOrder = 1; + var node1 = NewNode(50, 50); + var node2 = NewNode(250, 250); + var node3 = NewNode(500, 100); + var node4 = NewNode(700, 350); + BlazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + BlazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + + var group1 = BlazorDiagram.Groups.Group(node1, node2); + var group2 = BlazorDiagram.Groups.Group(group1, node3); + + BlazorDiagram.Nodes.Add(node4); + + BlazorDiagram.Links.Add(new LinkModel(group2, node4)); + } + + private NodeModel NewNode(double x, double y) { - protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); - - protected override void OnInitialized() - { - base.OnInitialized(); - - BlazorDiagram.Options.Groups.Enabled = true; - BlazorDiagram.Options.LinksLayerOrder = 2; - BlazorDiagram.Options.NodesLayerOrder = 1; - var node1 = NewNode(50, 50); - var node2 = NewNode(250, 250); - var node3 = NewNode(500, 100); - var node4 = NewNode(700, 350); - BlazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - - BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - BlazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); - BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); - - var group1 = BlazorDiagram.Groups.Group(node1, node2); - var group2 = BlazorDiagram.Groups.Group(group1, node3); - - BlazorDiagram.Nodes.Add(node4); - - BlazorDiagram.Links.Add(new LinkModel(group2, node4)); - } - - private NodeModel NewNode(double x, double y) - { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/Links/LabelsDemo.razor.cs b/samples/SharedDemo/Demos/Links/LabelsDemo.razor.cs index a5104fdff..a882016a2 100644 --- a/samples/SharedDemo/Demos/Links/LabelsDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/LabelsDemo.razor.cs @@ -2,74 +2,73 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -namespace SharedDemo.Demos.Links +namespace SharedDemo.Demos.Links; + +public partial class LabelsDemo { - public partial class LabelsDemo - { - private BlazorDiagram _blazorDiagram = new BlazorDiagram(); + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); - protected override void OnInitialized() - { - base.OnInitialized(); + protected override void OnInitialized() + { + base.OnInitialized(); - LayoutData.Title = "Link Labels"; - LayoutData.Info = "Labels help you show more information through out a link. You can specify a distance or an offset.
    " + - "The content of the labels is still limited because of Blazor's poor SVG support."; - LayoutData.DataChanged(); + LayoutData.Title = "Link Labels"; + LayoutData.Info = "Labels help you show more information through out a link. You can specify a distance or an offset.
    " + + "The content of the labels is still limited because of Blazor's poor SVG support."; + LayoutData.DataChanged(); - InitializeDiagram(); - } + InitializeDiagram(); + } - private void InitializeDiagram() - { - var node1 = NewNode(50, 50); - var node2 = NewNode(400, 50); + private void InitializeDiagram() + { + var node1 = NewNode(50, 50); + var node2 = NewNode(400, 50); - _blazorDiagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); - var link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); - link.Labels.Add(new LinkLabelModel(link, "Content")); - _blazorDiagram.Links.Add(link); + var link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); + link.Labels.Add(new LinkLabelModel(link, "Content")); + _blazorDiagram.Links.Add(link); - node1 = NewNode(50, 160); - node2 = NewNode(400, 160); + node1 = NewNode(50, 160); + node2 = NewNode(400, 160); - _blazorDiagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); - link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); - link.Labels.Add(new LinkLabelModel(link, "0.25", 0.3)); - link.Labels.Add(new LinkLabelModel(link, "0.75", 0.7)); - _blazorDiagram.Links.Add(link); + link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); + link.Labels.Add(new LinkLabelModel(link, "0.25", 0.3)); + link.Labels.Add(new LinkLabelModel(link, "0.75", 0.7)); + _blazorDiagram.Links.Add(link); - node1 = NewNode(50, 270); - node2 = NewNode(400, 270); + node1 = NewNode(50, 270); + node2 = NewNode(400, 270); - _blazorDiagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); - link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); - link.Labels.Add(new LinkLabelModel(link, "50", 50)); - link.Labels.Add(new LinkLabelModel(link, "-50", -50)); - _blazorDiagram.Links.Add(link); + link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); + link.Labels.Add(new LinkLabelModel(link, "50", 50)); + link.Labels.Add(new LinkLabelModel(link, "-50", -50)); + _blazorDiagram.Links.Add(link); - node1 = NewNode(50, 380); - node2 = NewNode(400, 380); + node1 = NewNode(50, 380); + node2 = NewNode(400, 380); - _blazorDiagram.Nodes.Add(new[] { node1, node2 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); - link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); - link.Labels.Add(new LinkLabelModel(link, "(0,-20)", 50, new Point(0, -20))); - link.Labels.Add(new LinkLabelModel(link, "(0,20)", -50, new Point(0, 20))); - _blazorDiagram.Links.Add(link); - } + link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); + link.Labels.Add(new LinkLabelModel(link, "(0,-20)", 50, new Point(0, -20))); + link.Labels.Add(new LinkLabelModel(link, "(0,20)", -50, new Point(0, 20))); + _blazorDiagram.Links.Add(link); + } - private NodeModel NewNode(double x, double y) - { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + private NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/Links/MarkersDemo.razor.cs b/samples/SharedDemo/Demos/Links/MarkersDemo.razor.cs index 8f876d1af..d0036050c 100644 --- a/samples/SharedDemo/Demos/Links/MarkersDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/MarkersDemo.razor.cs @@ -2,89 +2,88 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -namespace SharedDemo.Demos.Links +namespace SharedDemo.Demos.Links; + +public partial class MarkersDemo { - public partial class MarkersDemo + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() + { + base.OnInitialized(); + + LayoutData.Title = "Link Markers"; + LayoutData.Info = "Markers are SVG Paths that you can put at the beginning or at the end of your links."; + LayoutData.DataChanged(); + + InitializeDiagram(); + } + + private void InitializeDiagram() + { + var node1 = NewNode(50, 50); + var node2 = NewNode(400, 50); + + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); + + var link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); + link.SourceMarker = LinkMarker.Arrow; + link.TargetMarker = LinkMarker.Arrow; + link.Labels.Add(new LinkLabelModel(link, "Arrow")); + _blazorDiagram.Links.Add(link); + + node1 = NewNode(50, 160); + node2 = NewNode(400, 160); + + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); + + link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); + link.SourceMarker = LinkMarker.Circle; + link.TargetMarker = LinkMarker.Circle; + link.Labels.Add(new LinkLabelModel(link, "Circle")); + _blazorDiagram.Links.Add(link); + + node1 = NewNode(50, 270); + node2 = NewNode(400, 270); + + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); + + link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); + link.SourceMarker = LinkMarker.Square; + link.TargetMarker = LinkMarker.Square; + link.Labels.Add(new LinkLabelModel(link, "Square")); + _blazorDiagram.Links.Add(link); + + node1 = NewNode(50, 380); + node2 = NewNode(400, 380); + + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); + + link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); + link.SourceMarker = LinkMarker.NewRectangle(10, 20); + link.TargetMarker = LinkMarker.NewArrow(20, 10); + link.Labels.Add(new LinkLabelModel(link, "Factory")); + _blazorDiagram.Links.Add(link); + + node1 = NewNode(50, 490); + node2 = NewNode(400, 490); + + _blazorDiagram.Nodes.Add(new[] { node1, node2 }); + + link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); + link.SourceMarker = new LinkMarker("M 0 -8 L 3 -8 3 8 0 8 z M 4 -8 7 -8 7 8 4 8 z M 8 -8 16 0 8 8 z", 16); + link.TargetMarker = new LinkMarker("M 0 -8 L 8 -8 4 0 8 8 0 8 4 0 z", 8); + link.Labels.Add(new LinkLabelModel(link, "Custom")); + _blazorDiagram.Links.Add(link); + } + + private NodeModel NewNode(double x, double y) { - private BlazorDiagram _blazorDiagram = new BlazorDiagram(); - - protected override void OnInitialized() - { - base.OnInitialized(); - - LayoutData.Title = "Link Markers"; - LayoutData.Info = "Markers are SVG Paths that you can put at the beginning or at the end of your links."; - LayoutData.DataChanged(); - - InitializeDiagram(); - } - - private void InitializeDiagram() - { - var node1 = NewNode(50, 50); - var node2 = NewNode(400, 50); - - _blazorDiagram.Nodes.Add(new[] { node1, node2 }); - - var link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); - link.SourceMarker = LinkMarker.Arrow; - link.TargetMarker = LinkMarker.Arrow; - link.Labels.Add(new LinkLabelModel(link, "Arrow")); - _blazorDiagram.Links.Add(link); - - node1 = NewNode(50, 160); - node2 = NewNode(400, 160); - - _blazorDiagram.Nodes.Add(new[] { node1, node2 }); - - link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); - link.SourceMarker = LinkMarker.Circle; - link.TargetMarker = LinkMarker.Circle; - link.Labels.Add(new LinkLabelModel(link, "Circle")); - _blazorDiagram.Links.Add(link); - - node1 = NewNode(50, 270); - node2 = NewNode(400, 270); - - _blazorDiagram.Nodes.Add(new[] { node1, node2 }); - - link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); - link.SourceMarker = LinkMarker.Square; - link.TargetMarker = LinkMarker.Square; - link.Labels.Add(new LinkLabelModel(link, "Square")); - _blazorDiagram.Links.Add(link); - - node1 = NewNode(50, 380); - node2 = NewNode(400, 380); - - _blazorDiagram.Nodes.Add(new[] { node1, node2 }); - - link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); - link.SourceMarker = LinkMarker.NewRectangle(10, 20); - link.TargetMarker = LinkMarker.NewArrow(20, 10); - link.Labels.Add(new LinkLabelModel(link, "Factory")); - _blazorDiagram.Links.Add(link); - - node1 = NewNode(50, 490); - node2 = NewNode(400, 490); - - _blazorDiagram.Nodes.Add(new[] { node1, node2 }); - - link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)); - link.SourceMarker = new LinkMarker("M 0 -8 L 3 -8 3 8 0 8 z M 4 -8 7 -8 7 8 4 8 z M 8 -8 16 0 8 8 z", 16); - link.TargetMarker = new LinkMarker("M 0 -8 L 8 -8 4 0 8 8 0 8 4 0 z", 8); - link.Labels.Add(new LinkLabelModel(link, "Custom")); - _blazorDiagram.Links.Add(link); - } - - private NodeModel NewNode(double x, double y) - { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs b/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs index a9d1eebff..55df9b899 100644 --- a/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/PathGeneratorsDemo.razor.cs @@ -4,57 +4,56 @@ using Blazor.Diagrams.Core.PathGenerators; using Blazor.Diagrams.Core.Routers; -namespace SharedDemo.Demos.Links +namespace SharedDemo.Demos.Links; + +public partial class PathGeneratorsDemo { - public partial class PathGeneratorsDemo + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() { - private BlazorDiagram _blazorDiagram = new BlazorDiagram(); + base.OnInitialized(); - protected override void OnInitialized() - { - base.OnInitialized(); + LayoutData.Title = "Link Path Generators"; + LayoutData.Info = "Path generators are functions that take as input the calculated route and output SVG paths, " + + "alongside the markers positions and their angles. There are currently two generators: Straight and Smooth."; + LayoutData.DataChanged(); - LayoutData.Title = "Link Path Generators"; - LayoutData.Info = "Path generators are functions that take as input the calculated route and output SVG paths, " + - "alongside the markers positions and their angles. There are currently two generators: Straight and Smooth."; - LayoutData.DataChanged(); + InitializeDiagram(); + } + + private void InitializeDiagram() + { + var node1 = NewNode(50, 80); + var node2 = NewNode(300, 350); + var node3 = NewNode(400, 100); - InitializeDiagram(); - } + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - private void InitializeDiagram() + var link1 = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) { - var node1 = NewNode(50, 80); - var node2 = NewNode(300, 350); - var node3 = NewNode(400, 100); - - _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - - var link1 = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) - { - Router = new NormalRouter(), - PathGenerator = new StraightPathGenerator() - }; - link1.Labels.Add(new LinkLabelModel(link1, "Straight")); - - var link2 = new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left)) - { - Router = new NormalRouter(), - PathGenerator = new SmoothPathGenerator() - }; - link2.Labels.Add(new LinkLabelModel(link2, "Smooth")); - - _blazorDiagram.Links.Add(new[] { link1, link2 }); - } - - private NodeModel NewNode(double x, double y) + Router = new NormalRouter(), + PathGenerator = new StraightPathGenerator() + }; + link1.Labels.Add(new LinkLabelModel(link1, "Straight")); + + var link2 = new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left)) { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + Router = new NormalRouter(), + PathGenerator = new SmoothPathGenerator() + }; + link2.Labels.Add(new LinkLabelModel(link2, "Smooth")); + + _blazorDiagram.Links.Add(new[] { link1, link2 }); + } + + private NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs b/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs index fe4d3f4d3..c96651a06 100644 --- a/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/RoutersDemo.razor.cs @@ -4,56 +4,55 @@ using Blazor.Diagrams.Core.PathGenerators; using Blazor.Diagrams.Core.Routers; -namespace SharedDemo.Demos.Links +namespace SharedDemo.Demos.Links; + +public partial class RoutersDemo { - public partial class RoutersDemo - { - private BlazorDiagram _blazorDiagram = new BlazorDiagram(); + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); - protected override void OnInitialized() - { - base.OnInitialized(); + protected override void OnInitialized() + { + base.OnInitialized(); - LayoutData.Title = "Link Routers"; - LayoutData.Info = "Routers are functions that take as input the link's vertices and can add points in between. " + - "There are currently two routers: Normal and Orthogonal."; - LayoutData.DataChanged(); + LayoutData.Title = "Link Routers"; + LayoutData.Info = "Routers are functions that take as input the link's vertices and can add points in between. " + + "There are currently two routers: Normal and Orthogonal."; + LayoutData.DataChanged(); - InitializeDiagram(); - } + InitializeDiagram(); + } - private void InitializeDiagram() - { - var node1 = NewNode(50, 80); - var node2 = NewNode(300, 350); - var node3 = NewNode(400, 100); + private void InitializeDiagram() + { + var node1 = NewNode(50, 80); + var node2 = NewNode(300, 350); + var node3 = NewNode(400, 100); - _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - var link1 = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) - { - Router = new NormalRouter() - }; - link1.Labels.Add(new LinkLabelModel(link1, "Normal")); + var link1 = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) + { + Router = new NormalRouter() + }; + link1.Labels.Add(new LinkLabelModel(link1, "Normal")); - var link2 = new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left)) - { - Router = new OrthogonalRouter(), - PathGenerator = new StraightPathGenerator() // Smooth results in weird looking links - }; - link2.Labels.Add(new LinkLabelModel(link2, "Orthogonal")); + var link2 = new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left)) + { + Router = new OrthogonalRouter(), + PathGenerator = new StraightPathGenerator() // Smooth results in weird looking links + }; + link2.Labels.Add(new LinkLabelModel(link2, "Orthogonal")); - _blazorDiagram.Links.Add(new[] { link1, link2 }); - } + _blazorDiagram.Links.Add(new[] { link1, link2 }); + } - private NodeModel NewNode(double x, double y) - { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + private NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/Links/SnappingDemo.razor.cs b/samples/SharedDemo/Demos/Links/SnappingDemo.razor.cs index 3d11e0ff9..33edf7ac8 100644 --- a/samples/SharedDemo/Demos/Links/SnappingDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/SnappingDemo.razor.cs @@ -2,37 +2,36 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -namespace SharedDemo.Demos.Links +namespace SharedDemo.Demos.Links; + +public partial class SnappingDemo { - public partial class SnappingDemo - { - private BlazorDiagram _blazorDiagram = new BlazorDiagram(); + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); - protected override void OnInitialized() - { - base.OnInitialized(); + protected override void OnInitialized() + { + base.OnInitialized(); - LayoutData.Title = "Link Snapping"; - LayoutData.Info = "While dragging a new link, it will try to find (and link) to the closest target within a radius."; - LayoutData.DataChanged(); + LayoutData.Title = "Link Snapping"; + LayoutData.Info = "While dragging a new link, it will try to find (and link) to the closest target within a radius."; + LayoutData.DataChanged(); - InitializeDiagram(); - } + InitializeDiagram(); + } - private void InitializeDiagram() - { - _blazorDiagram.Options.Links.EnableSnapping = true; - _blazorDiagram.Nodes.Add(new[] { NewNode(50, 80), NewNode(200, 350), NewNode(400, 100) }); - } + private void InitializeDiagram() + { + _blazorDiagram.Options.Links.EnableSnapping = true; + _blazorDiagram.Nodes.Add(new[] { NewNode(50, 80), NewNode(200, 350), NewNode(400, 100) }); + } - private NodeModel NewNode(double x, double y) - { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + private NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs b/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs index ab43998da..39dfcd39f 100644 --- a/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs +++ b/samples/SharedDemo/Demos/Links/VerticesDemo.razor.cs @@ -3,61 +3,60 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.PathGenerators; -namespace SharedDemo.Demos.Links +namespace SharedDemo.Demos.Links; + +public partial class VerticesDemo { - public partial class VerticesDemo + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() { - private BlazorDiagram _blazorDiagram = new BlazorDiagram(); + base.OnInitialized(); - protected override void OnInitialized() - { - base.OnInitialized(); + LayoutData.Title = "Link Vertices"; + LayoutData.Info = "Click on a link to create a vertex. Double click on a vertex to delete it. " + + "You can drag the vertices around."; + LayoutData.DataChanged(); - LayoutData.Title = "Link Vertices"; - LayoutData.Info = "Click on a link to create a vertex. Double click on a vertex to delete it. " + - "You can drag the vertices around."; - LayoutData.DataChanged(); + InitializeDiagram(); + } - InitializeDiagram(); - } + private void InitializeDiagram() + { + var node1 = NewNode(50, 80); + var node2 = NewNode(200, 350); + var node3 = NewNode(400, 100); - private void InitializeDiagram() + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); + + var link1 = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) { - var node1 = NewNode(50, 80); - var node2 = NewNode(200, 350); - var node3 = NewNode(400, 100); - - _blazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - - var link1 = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) - { - PathGenerator = new StraightPathGenerator(), - Segmentable = true - }; - link1.Labels.Add(new LinkLabelModel(link1, "Content")); - link1.Vertices.Add(new LinkVertexModel(link1, new Point(221, 117))); - link1.Vertices.Add(new LinkVertexModel(link1, new Point(111, 291))); - - var link2 = new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left)) - { - PathGenerator = new SmoothPathGenerator(), // default - Segmentable = true - }; - link2.Labels.Add(new LinkLabelModel(link2, "Content")); - link2.Vertices.Add(new LinkVertexModel(link2, new Point(400, 324))); - link2.Vertices.Add(new LinkVertexModel(link2, new Point(326, 180))); - - _blazorDiagram.Links.Add(new[] { link1, link2 }); - } - - private NodeModel NewNode(double x, double y) + PathGenerator = new StraightPathGenerator(), + Segmentable = true + }; + link1.Labels.Add(new LinkLabelModel(link1, "Content")); + link1.Vertices.Add(new LinkVertexModel(link1, new Point(221, 117))); + link1.Vertices.Add(new LinkVertexModel(link1, new Point(111, 291))); + + var link2 = new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left)) { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + PathGenerator = new SmoothPathGenerator(), // default + Segmentable = true + }; + link2.Labels.Add(new LinkLabelModel(link2, "Content")); + link2.Vertices.Add(new LinkVertexModel(link2, new Point(400, 324))); + link2.Vertices.Add(new LinkVertexModel(link2, new Point(326, 180))); + + _blazorDiagram.Links.Add(new[] { link1, link2 }); + } + + private NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/Locked.razor.cs b/samples/SharedDemo/Demos/Locked.razor.cs index a0124d22e..31fb668ae 100644 --- a/samples/SharedDemo/Demos/Locked.razor.cs +++ b/samples/SharedDemo/Demos/Locked.razor.cs @@ -3,36 +3,35 @@ using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; -namespace SharedDemo -{ - public class LockedComponent : ComponentBase - { - protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); +namespace SharedDemo; - protected override void OnInitialized() - { - base.OnInitialized(); +public class LockedComponent : ComponentBase +{ + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); - var node1 = NewNode(50, 50); - var node2 = NewNode(300, 300); - BlazorDiagram.Nodes.Add(new[] { node1, node2, NewNode(300, 50) }); + protected override void OnInitialized() + { + base.OnInitialized(); - var link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) - { - Locked = true - }; - BlazorDiagram.Links.Add(link); - } + var node1 = NewNode(50, 50); + var node2 = NewNode(300, 300); + BlazorDiagram.Nodes.Add(new[] { node1, node2, NewNode(300, 50) }); - private NodeModel NewNode(double x, double y) + var link = new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top).Locked = true; - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - node.Locked = true; - return node; - } + Locked = true + }; + BlazorDiagram.Links.Add(link); + } + + private NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top).Locked = true; + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + node.Locked = true; + return node; } } diff --git a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs index f70d7401a..a9d807073 100644 --- a/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/PortlessLinks.razor.cs @@ -4,75 +4,74 @@ using Blazor.Diagrams.Core.Anchors; using Blazor.Diagrams.Core.Controls.Default; -namespace SharedDemo.Demos.Nodes +namespace SharedDemo.Demos.Nodes; + +public partial class PortlessLinks { - public partial class PortlessLinks + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() { - private BlazorDiagram _blazorDiagram = new BlazorDiagram(); + base.OnInitialized(); - protected override void OnInitialized() - { - base.OnInitialized(); + LayoutData.Title = "Portless Links"; + LayoutData.Info = "Starting from 2.0, you can create links between nodes directly! " + + "All you need to specify is the shape of your nodes in order to calculate the connection points."; + LayoutData.DataChanged(); - LayoutData.Title = "Portless Links"; - LayoutData.Info = "Starting from 2.0, you can create links between nodes directly! " + - "All you need to specify is the shape of your nodes in order to calculate the connection points."; - LayoutData.DataChanged(); + InitializeDiagram(); + } - InitializeDiagram(); - } + private void InitializeDiagram() + { + _blazorDiagram.RegisterComponent(); - private void InitializeDiagram() + var node1 = new NodeModel(new Point(80, 80)); + var node2 = new RoundedNode(new Point(280, 150)); + var node3 = new NodeModel(new Point(400, 300)); + node3.AddPort(PortAlignment.Left); + _blazorDiagram.Nodes.Add(node1); + _blazorDiagram.Nodes.Add(node2); + _blazorDiagram.Nodes.Add(node3); + _blazorDiagram.Links.Add(new LinkModel(node1, node2) { - _blazorDiagram.RegisterComponent(); - - var node1 = new NodeModel(new Point(80, 80)); - var node2 = new RoundedNode(new Point(280, 150)); - var node3 = new NodeModel(new Point(400, 300)); - node3.AddPort(PortAlignment.Left); - _blazorDiagram.Nodes.Add(node1); - _blazorDiagram.Nodes.Add(node2); - _blazorDiagram.Nodes.Add(node3); - _blazorDiagram.Links.Add(new LinkModel(node1, node2) - { - SourceMarker = LinkMarker.Arrow, - TargetMarker = LinkMarker.Arrow, - Segmentable = true - }); - _blazorDiagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node2), - new SinglePortAnchor(node3.GetPort(PortAlignment.Left))) - { - SourceMarker = LinkMarker.Arrow, - TargetMarker = LinkMarker.Arrow, - Segmentable = true - }); + SourceMarker = LinkMarker.Arrow, + TargetMarker = LinkMarker.Arrow, + Segmentable = true + }); + _blazorDiagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node2), + new SinglePortAnchor(node3.GetPort(PortAlignment.Left))) + { + SourceMarker = LinkMarker.Arrow, + TargetMarker = LinkMarker.Arrow, + Segmentable = true + }); - _blazorDiagram.Controls.AddFor(node1) - .Add(new RemoveControl(1, 0)) - .Add(new DragNewLinkControl(1, 0.5, 20)) - .Add(new BoundaryControl()); + _blazorDiagram.Controls.AddFor(node1) + .Add(new RemoveControl(1, 0)) + .Add(new DragNewLinkControl(1, 0.5, 20)) + .Add(new BoundaryControl()); - _blazorDiagram.Controls.AddFor(node2) - .Add(new RemoveControl(1, 0)) - .Add(new DragNewLinkControl(1, 0.5, 20)) - .Add(new BoundaryControl()); + _blazorDiagram.Controls.AddFor(node2) + .Add(new RemoveControl(1, 0)) + .Add(new DragNewLinkControl(1, 0.5, 20)) + .Add(new BoundaryControl()); - _blazorDiagram.Controls.AddFor(node3) - .Add(new RemoveControl(1, 0)) - .Add(new DragNewLinkControl(1, 0.5, 20)) - .Add(new BoundaryControl()); - } + _blazorDiagram.Controls.AddFor(node3) + .Add(new RemoveControl(1, 0)) + .Add(new DragNewLinkControl(1, 0.5, 20)) + .Add(new BoundaryControl()); } +} - class RoundedNode : NodeModel +class RoundedNode : NodeModel +{ + public RoundedNode(Point position = null) : base(position) { - public RoundedNode(Point position = null) : base(position) - { - } + } - public override IShape GetShape() - { - return Shapes.Circle(this); - } + public override IShape GetShape() + { + return Shapes.Circle(this); } } \ No newline at end of file diff --git a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs index ab9d80078..85cc04c64 100644 --- a/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs +++ b/samples/SharedDemo/Demos/Nodes/SvgDemo.razor.cs @@ -5,58 +5,57 @@ using Blazor.Diagrams.Core.Controls.Default; using Blazor.Diagrams.Models; -namespace SharedDemo.Demos.Nodes +namespace SharedDemo.Demos.Nodes; + +public partial class SvgDemo { - public partial class SvgDemo + private BlazorDiagram _blazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() + { + base.OnInitialized(); + + LayoutData.Title = "SVG Nodes"; + LayoutData.Info = "You can also have SVG nodes! All you need to do is to set the Layer to RenderLayer.SVG."; + LayoutData.DataChanged(); + + InitializeDiagram(); + } + + private void InitializeDiagram() + { + _blazorDiagram.RegisterComponent(); + _blazorDiagram.RegisterComponent(); + _blazorDiagram.RegisterComponent(); + + var node1 = NewNode(50, 50); + var node2 = NewNode(250, 250); + var node3 = NewNode(500, 100); + var node4 = NewNode(700, 350); + _blazorDiagram.Nodes.Add(new[] { node1, node2, node3, node4 }); + + var group1 = _blazorDiagram.Groups.Add(new SvgGroupModel(new[] { node1, node2 })); + var group2 = _blazorDiagram.Groups.Add(new SvgGroupModel(new[] { group1, node3 })); + + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); + var link = _blazorDiagram.Links.Add(new LinkModel(group2, node4)); + + var controls1 = _blazorDiagram.Controls.AddFor(node4); + controls1.Add(new RemoveControl(1, 0)); + controls1.Add(new BoundaryControl()); + + var controls2 = _blazorDiagram.Controls.AddFor(link); + controls2.Add(new RemoveControl(1, 0)); + controls2.Add(new BoundaryControl()); + } + + private NodeModel NewNode(double x, double y, bool svg = true) { - private BlazorDiagram _blazorDiagram = new BlazorDiagram(); - - protected override void OnInitialized() - { - base.OnInitialized(); - - LayoutData.Title = "SVG Nodes"; - LayoutData.Info = "You can also have SVG nodes! All you need to do is to set the Layer to RenderLayer.SVG."; - LayoutData.DataChanged(); - - InitializeDiagram(); - } - - private void InitializeDiagram() - { - _blazorDiagram.RegisterComponent(); - _blazorDiagram.RegisterComponent(); - _blazorDiagram.RegisterComponent(); - - var node1 = NewNode(50, 50); - var node2 = NewNode(250, 250); - var node3 = NewNode(500, 100); - var node4 = NewNode(700, 350); - _blazorDiagram.Nodes.Add(new[] { node1, node2, node3, node4 }); - - var group1 = _blazorDiagram.Groups.Add(new SvgGroupModel(new[] { node1, node2 })); - var group2 = _blazorDiagram.Groups.Add(new SvgGroupModel(new[] { group1, node3 })); - - _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - _blazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); - _blazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Left))); - var link = _blazorDiagram.Links.Add(new LinkModel(group2, node4)); - - var controls1 = _blazorDiagram.Controls.AddFor(node4); - controls1.Add(new RemoveControl(1, 0)); - controls1.Add(new BoundaryControl()); - - var controls2 = _blazorDiagram.Controls.AddFor(link); - controls2.Add(new RemoveControl(1, 0)); - controls2.Add(new BoundaryControl()); - } - - private NodeModel NewNode(double x, double y, bool svg = true) - { - var node = svg ? new SvgNodeModel(new Point(x, y)) : new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + var node = svg ? new SvgNodeModel(new Point(x, y)) : new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/Performance.razor.cs b/samples/SharedDemo/Demos/Performance.razor.cs index 37937fb64..76d9c4859 100644 --- a/samples/SharedDemo/Demos/Performance.razor.cs +++ b/samples/SharedDemo/Demos/Performance.razor.cs @@ -3,29 +3,28 @@ using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; -namespace SharedDemo.Demos +namespace SharedDemo.Demos; + +public class PerformanceCompoent : ComponentBase { - public class PerformanceCompoent : ComponentBase + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() { - protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); + base.OnInitialized(); - protected override void OnInitialized() + for (int r = 0; r < 10; r++) { - base.OnInitialized(); - - for (int r = 0; r < 10; r++) + for (int c = 0; c < 10; c += 2) { - for (int c = 0; c < 10; c += 2) - { - var node1 = new NodeModel(new Point(10 + c * 10 + c * 120, 10 + r * 100)); - var node2 = new NodeModel(new Point(10 + (c + 1) * 130, 10 + r * 100)); + var node1 = new NodeModel(new Point(10 + c * 10 + c * 120, 10 + r * 100)); + var node2 = new NodeModel(new Point(10 + (c + 1) * 130, 10 + r * 100)); - var sourcePort = node1.AddPort(PortAlignment.Right); - var targetPort = node2.AddPort(PortAlignment.Left); + var sourcePort = node1.AddPort(PortAlignment.Right); + var targetPort = node2.AddPort(PortAlignment.Left); - BlazorDiagram.Nodes.Add(new[] { node1, node2 }); - BlazorDiagram.Links.Add(new LinkModel(sourcePort, targetPort)); - } + BlazorDiagram.Nodes.Add(new[] { node1, node2 }); + BlazorDiagram.Links.Add(new LinkModel(sourcePort, targetPort)); } } } diff --git a/samples/SharedDemo/Demos/Simple.razor.cs b/samples/SharedDemo/Demos/Simple.razor.cs index 84dc4651a..b1a995b98 100644 --- a/samples/SharedDemo/Demos/Simple.razor.cs +++ b/samples/SharedDemo/Demos/Simple.razor.cs @@ -6,50 +6,49 @@ using Blazor.Diagrams.Core.Routers; using Microsoft.AspNetCore.Components; -namespace SharedDemo +namespace SharedDemo; + +public class SimpleComponent : ComponentBase { - public class SimpleComponent : ComponentBase + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() { - protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); + base.OnInitialized(); + + var node1 = NewNode(50, 50); + var node2 = NewNode(300, 300); + var node3 = NewNode(300, 50); + BlazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - protected override void OnInitialized() + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) { - base.OnInitialized(); - - var node1 = NewNode(50, 50); - var node2 = NewNode(300, 300); - var node3 = NewNode(300, 50); - BlazorDiagram.Nodes.Add(new[] { node1, node2, node3 }); - - BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left)) - { - SourceMarker = LinkMarker.Arrow, - TargetMarker = LinkMarker.Arrow - }); - BlazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Right)) - { - Router = new OrthogonalRouter(), - PathGenerator = new StraightPathGenerator(), - SourceMarker = LinkMarker.Arrow, - TargetMarker = LinkMarker.Arrow - }); - } - - protected void ToggleZoom() => BlazorDiagram.Options.Zoom.Enabled = !BlazorDiagram.Options.Zoom.Enabled; - - protected void TogglePanning() => BlazorDiagram.Options.AllowPanning = !BlazorDiagram.Options.AllowPanning; - - protected void ToggleVirtualization() - => BlazorDiagram.Options.Virtualization.Enabled = !BlazorDiagram.Options.Virtualization.Enabled; - - private NodeModel NewNode(double x, double y) + SourceMarker = LinkMarker.Arrow, + TargetMarker = LinkMarker.Arrow + }); + BlazorDiagram.Links.Add(new LinkModel(node2.GetPort(PortAlignment.Right), node3.GetPort(PortAlignment.Right)) { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + Router = new OrthogonalRouter(), + PathGenerator = new StraightPathGenerator(), + SourceMarker = LinkMarker.Arrow, + TargetMarker = LinkMarker.Arrow + }); + } + + protected void ToggleZoom() => BlazorDiagram.Options.Zoom.Enabled = !BlazorDiagram.Options.Zoom.Enabled; + + protected void TogglePanning() => BlazorDiagram.Options.AllowPanning = !BlazorDiagram.Options.AllowPanning; + + protected void ToggleVirtualization() + => BlazorDiagram.Options.Virtualization.Enabled = !BlazorDiagram.Options.Virtualization.Enabled; + + private NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/SnapToGrid.razor.cs b/samples/SharedDemo/Demos/SnapToGrid.razor.cs index aacbffb2e..bba495dd2 100644 --- a/samples/SharedDemo/Demos/SnapToGrid.razor.cs +++ b/samples/SharedDemo/Demos/SnapToGrid.razor.cs @@ -4,33 +4,32 @@ using Blazor.Diagrams.Options; using Microsoft.AspNetCore.Components; -namespace SharedDemo +namespace SharedDemo; + +public class SnapToGridComponent : ComponentBase { - public class SnapToGridComponent : ComponentBase + protected readonly BlazorDiagram BlazorDiagram = new(new BlazorDiagramOptions { - protected readonly BlazorDiagram BlazorDiagram = new(new BlazorDiagramOptions - { - GridSize = 75 - }); + GridSize = 75 + }); - protected override void OnInitialized() - { - base.OnInitialized(); + protected override void OnInitialized() + { + base.OnInitialized(); - var node1 = NewNode(50, 50); - var node2 = NewNode(300, 300); - BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); - BlazorDiagram.Nodes.Add(new[] { node1, node2, NewNode(300, 50) }); - } + var node1 = NewNode(50, 50); + var node2 = NewNode(300, 300); + BlazorDiagram.Links.Add(new LinkModel(node1.GetPort(PortAlignment.Right), node2.GetPort(PortAlignment.Left))); + BlazorDiagram.Nodes.Add(new[] { node1, node2, NewNode(300, 50) }); + } - private NodeModel NewNode(double x, double y) - { - var node = new NodeModel(new Point(x, y)); - node.AddPort(PortAlignment.Bottom); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Left); - node.AddPort(PortAlignment.Right); - return node; - } + private NodeModel NewNode(double x, double y) + { + var node = new NodeModel(new Point(x, y)); + node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); + return node; } } diff --git a/samples/SharedDemo/Demos/ZoomToFit.razor.cs b/samples/SharedDemo/Demos/ZoomToFit.razor.cs index db28b5ac2..a859137f1 100644 --- a/samples/SharedDemo/Demos/ZoomToFit.razor.cs +++ b/samples/SharedDemo/Demos/ZoomToFit.razor.cs @@ -3,29 +3,28 @@ using Blazor.Diagrams.Core.Models; using Microsoft.AspNetCore.Components; -namespace SharedDemo.Demos +namespace SharedDemo.Demos; + +public class ZoomToFitComponent : ComponentBase { - public class ZoomToFitComponent : ComponentBase + protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); + + protected override void OnInitialized() { - protected readonly BlazorDiagram BlazorDiagram = new BlazorDiagram(); + base.OnInitialized(); - protected override void OnInitialized() + for (int r = 0; r < 8; r++) { - base.OnInitialized(); - - for (int r = 0; r < 8; r++) + for (int c = 0; c < 8; c += 2) { - for (int c = 0; c < 8; c += 2) - { - var node1 = new NodeModel(new Point(350 + c * 80 + c * 120, 150 + r * 120)); - var node2 = new NodeModel(new Point(350 + (c + 1) * 200, 150 + r * 120)); + var node1 = new NodeModel(new Point(350 + c * 80 + c * 120, 150 + r * 120)); + var node2 = new NodeModel(new Point(350 + (c + 1) * 200, 150 + r * 120)); - var sourcePort = node1.AddPort(PortAlignment.Right); - var targetPort = node2.AddPort(PortAlignment.Left); + var sourcePort = node1.AddPort(PortAlignment.Right); + var targetPort = node2.AddPort(PortAlignment.Left); - BlazorDiagram.Nodes.Add(new[] { node1, node2 }); - BlazorDiagram.Links.Add(new LinkModel(sourcePort, targetPort)); - } + BlazorDiagram.Nodes.Add(new[] { node1, node2 }); + BlazorDiagram.Links.Add(new LinkModel(sourcePort, targetPort)); } } } diff --git a/samples/SharedDemo/DocPage.cs b/samples/SharedDemo/DocPage.cs index 5c83884ab..19c6263cc 100644 --- a/samples/SharedDemo/DocPage.cs +++ b/samples/SharedDemo/DocPage.cs @@ -2,18 +2,17 @@ using Microsoft.JSInterop; using System.Threading.Tasks; -namespace SharedDemo +namespace SharedDemo; + +public class DocPage : ComponentBase { - public class DocPage : ComponentBase - { - [Inject] - private IJSRuntime JsRuntime { get; set; } + [Inject] + private IJSRuntime JsRuntime { get; set; } - protected override async Task OnAfterRenderAsync(bool firstRender) - { - await base.OnAfterRenderAsync(firstRender); + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); - await JsRuntime.InvokeVoidAsync("setup"); - } + await JsRuntime.InvokeVoidAsync("setup"); } } diff --git a/samples/SharedDemo/LayoutData.cs b/samples/SharedDemo/LayoutData.cs index 34cd613b4..20c90ddb3 100644 --- a/samples/SharedDemo/LayoutData.cs +++ b/samples/SharedDemo/LayoutData.cs @@ -1,15 +1,14 @@ using System; -namespace SharedDemo +namespace SharedDemo; + +public class LayoutData { - public class LayoutData - { - public string Title { get; set; } - public string Icon { get; set; } - public string Date { get; set; } - public string Info { get; set; } + public string Title { get; set; } + public string Icon { get; set; } + public string Date { get; set; } + public string Info { get; set; } - public Action OnDataChanged { get; set; } - public void DataChanged() => OnDataChanged?.Invoke(); - } + public Action OnDataChanged { get; set; } + public void DataChanged() => OnDataChanged?.Invoke(); } diff --git a/samples/SharedDemo/ReflectionUtils.cs b/samples/SharedDemo/ReflectionUtils.cs index 4de7d9940..e81548cbf 100644 --- a/samples/SharedDemo/ReflectionUtils.cs +++ b/samples/SharedDemo/ReflectionUtils.cs @@ -3,69 +3,68 @@ using System.ComponentModel; using System.Reflection; -namespace SharedDemo +namespace SharedDemo; + +public static class ReflectionUtils { - public static class ReflectionUtils + public static IEnumerable ExtractPossibleOptions() { - public static IEnumerable ExtractPossibleOptions() - { - var type = typeof(T); - return ExtractPossibleOptions(type, string.Empty, Activator.CreateInstance(type)); - } + var type = typeof(T); + return ExtractPossibleOptions(type, string.Empty, Activator.CreateInstance(type)); + } - private static IEnumerable ExtractPossibleOptions(Type type, string prefix, object instance) + private static IEnumerable ExtractPossibleOptions(Type type, string prefix, object instance) + { + foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) { - foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance)) - { - var name = $"{prefix}{property.Name}"; - var propertyValue = instance == null ? null : property.GetValue(instance); + var name = $"{prefix}{property.Name}"; + var propertyValue = instance == null ? null : property.GetValue(instance); - if (!IsPrimitiveOrNullable(property.PropertyType)) - { - foreach (var entry in ExtractPossibleOptions(property.PropertyType, name + ".", propertyValue)) - yield return entry; - - continue; - } + if (!IsPrimitiveOrNullable(property.PropertyType)) + { + foreach (var entry in ExtractPossibleOptions(property.PropertyType, name + ".", propertyValue)) + yield return entry; - var typeName = FormatPropertyType(property.PropertyType); - var @default = propertyValue?.ToString(); - var description = property.GetCustomAttribute().Description; - yield return new PossibleOption(name, typeName, @default, description); + continue; } - } - private static string FormatPropertyType(Type type) - { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) - return $"{type.GetGenericArguments()[0].Name}?"; - - return type.Name; + var typeName = FormatPropertyType(property.PropertyType); + var @default = propertyValue?.ToString(); + var description = property.GetCustomAttribute().Description; + yield return new PossibleOption(name, typeName, @default, description); } + } - private static bool IsPrimitiveOrNullable(Type type) - { - return type == typeof(object) || - type == typeof(Type) || - Type.GetTypeCode(type) != TypeCode.Object || - Nullable.GetUnderlyingType(type) != null || - typeof(Delegate).IsAssignableFrom(type); - } + private static string FormatPropertyType(Type type) + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>)) + return $"{type.GetGenericArguments()[0].Name}?"; + + return type.Name; } - public class PossibleOption + private static bool IsPrimitiveOrNullable(Type type) { - public string Name { get; } - public string Type { get; } - public string Default { get; } - public string Description { get; } + return type == typeof(object) || + type == typeof(Type) || + Type.GetTypeCode(type) != TypeCode.Object || + Nullable.GetUnderlyingType(type) != null || + typeof(Delegate).IsAssignableFrom(type); + } +} - public PossibleOption(string name, string type, string @default, string description) - { - Name = name; - Type = type; - Default = @default; - Description = description; - } +public class PossibleOption +{ + public string Name { get; } + public string Type { get; } + public string Default { get; } + public string Description { get; } + + public PossibleOption(string name, string type, string @default, string description) + { + Name = name; + Type = type; + Default = @default; + Description = description; } } diff --git a/samples/Wasm/Program.cs b/samples/Wasm/Program.cs index 5d8d147b8..23007d5a1 100644 --- a/samples/Wasm/Program.cs +++ b/samples/Wasm/Program.cs @@ -5,19 +5,18 @@ using System.Net.Http; using System.Threading.Tasks; -namespace Wasm +namespace Wasm; + +public class Program { - public class Program + public static async Task Main(string[] args) { - public static async Task Main(string[] args) - { - var builder = WebAssemblyHostBuilder.CreateDefault(args); - builder.RootComponents.Add("app"); + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add("app"); - builder.Services.AddSingleton(); - builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); + builder.Services.AddSingleton(); + builder.Services.AddTransient(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); - await builder.Build().RunAsync(); - } + await builder.Build().RunAsync(); } } diff --git a/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor b/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor new file mode 100644 index 000000000..8e6ee9c89 --- /dev/null +++ b/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor @@ -0,0 +1,22 @@ +@using Blazor.Diagrams.Components.Renderers; +@using Site.Models.Nodes; + + + + + + + + + + + + + + + + +@code { + // This gets filled by the library + [Parameter] public GingerbreadNode Node { get; set; } = null!; +} \ No newline at end of file diff --git a/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor.css b/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor.css new file mode 100644 index 000000000..385a601e0 --- /dev/null +++ b/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor.css @@ -0,0 +1,19 @@ +.gingerbread .body { + fill: #cd803d; +} + +.gingerbread .eye { + fill: white; +} + +.gingerbread .mouth { + fill: none; + stroke: white; + stroke-width: 2px; +} + +.gingerbread .limb { + stroke: #cd803d; + stroke-width: 35px; + stroke-linecap: round; +} diff --git a/site/Site/Components/Landing/Features/FeaturesExample.razor.cs b/site/Site/Components/Landing/Features/FeaturesExample.razor.cs index e23036ad8..dbf4ddf9b 100644 --- a/site/Site/Components/Landing/Features/FeaturesExample.razor.cs +++ b/site/Site/Components/Landing/Features/FeaturesExample.razor.cs @@ -8,107 +8,106 @@ using Blazor.Diagrams.Core.Routers; using Site.Models.Landing; -namespace Site.Components.Landing.Features +namespace Site.Components.Landing.Features; + +public partial class FeaturesExample { - public partial class FeaturesExample - { - private readonly BlazorDiagram _diagram = new(); + private readonly BlazorDiagram _diagram = new(); - protected override void OnInitialized() - { - _diagram.Options.Zoom.Enabled = false; - _diagram.Options.GridSize = 10; - _diagram.Options.Links.RequireTarget = false; - _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); - _diagram.RegisterComponent(); - _diagram.RegisterComponent(); + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.GridSize = 10; + _diagram.Options.Links.RequireTarget = false; + _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); + _diagram.RegisterComponent(); + _diagram.RegisterComponent(); - var smoothPathGenerator = new SmoothPathGenerator(); + var smoothPathGenerator = new SmoothPathGenerator(); - // ArrowHeadControl example - var node1 = _diagram.Nodes.Add(new ColoredNodeModel("Locked", true, "color2", new Point(20, 20))); - node1.Locked = true; - var link1 = _diagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node1), new PositionAnchor(new Point(340, 60))) - { - Color = "#DC9A7A", - SelectedColor = "#874423", - TargetMarker = LinkMarker.Arrow - }); - _diagram.Controls.AddFor(link1).Add(new ArrowHeadControl(false)); - link1.Labels.Add(new LinkLabelModel(link1, "I am a free link", 0.5, new Point(0, -15))); + // ArrowHeadControl example + var node1 = _diagram.Nodes.Add(new ColoredNodeModel("Locked", true, "color2", new Point(20, 20))); + node1.Locked = true; + var link1 = _diagram.Links.Add(new LinkModel(new ShapeIntersectionAnchor(node1), new PositionAnchor(new Point(340, 60))) + { + Color = "#DC9A7A", + SelectedColor = "#874423", + TargetMarker = LinkMarker.Arrow + }); + _diagram.Controls.AddFor(link1).Add(new ArrowHeadControl(false)); + link1.Labels.Add(new LinkLabelModel(link1, "I am a free link", 0.5, new Point(0, -15))); - // Labels example - var node2 = _diagram.Nodes.Add(new ColoredNodeModel("Movable", true, "color1", new Point(20, 350))); - var link2 = _diagram.Links.Add(new LinkModel(node1, node2) - { - Color = "#DC9A7A", - SelectedColor = "#874423", - TargetMarker = LinkMarker.Arrow - }); - link2.Labels.Add(new LinkLabelModel(link1, "Start", 0.1, new Point(0, 0))); - link2.Labels.Add(new LinkLabelModel(link1, "Middle", 0.5, new Point(0, 0))); - link2.Labels.Add(new LinkLabelModel(link1, "End", 0.9, new Point(0, 0))); + // Labels example + var node2 = _diagram.Nodes.Add(new ColoredNodeModel("Movable", true, "color1", new Point(20, 350))); + var link2 = _diagram.Links.Add(new LinkModel(node1, node2) + { + Color = "#DC9A7A", + SelectedColor = "#874423", + TargetMarker = LinkMarker.Arrow + }); + link2.Labels.Add(new LinkLabelModel(link1, "Start", 0.1, new Point(0, 0))); + link2.Labels.Add(new LinkLabelModel(link1, "Middle", 0.5, new Point(0, 0))); + link2.Labels.Add(new LinkLabelModel(link1, "End", 0.9, new Point(0, 0))); - // Controls example - var node3 = _diagram.Nodes.Add(new ColoredNodeModel("Select me", false, "color3", new Point(320, 370))); - var link3 = _diagram.Links.Add(new LinkModel(node1, node3) - { - Color = "#9EA5E3", - SelectedColor = "#2b3595", - PathGenerator = smoothPathGenerator, - TargetMarker = LinkMarker.Arrow - }); - link3.Labels.Add(new LinkLabelModel(link3, "Select me to show controls", 0.5, new Point(0, -15))); + // Controls example + var node3 = _diagram.Nodes.Add(new ColoredNodeModel("Select me", false, "color3", new Point(320, 370))); + var link3 = _diagram.Links.Add(new LinkModel(node1, node3) + { + Color = "#9EA5E3", + SelectedColor = "#2b3595", + PathGenerator = smoothPathGenerator, + TargetMarker = LinkMarker.Arrow + }); + link3.Labels.Add(new LinkLabelModel(link3, "Select me to show controls", 0.5, new Point(0, -15))); - _diagram.Controls.AddFor(link3) - .Add(new ArrowHeadControl(true)) - .Add(new ArrowHeadControl(false)) - .Add(new BoundaryControl()) - .Add(new RemoveControl(new LinkPathPositionProvider(0.8, 0, -10))); + _diagram.Controls.AddFor(link3) + .Add(new ArrowHeadControl(true)) + .Add(new ArrowHeadControl(false)) + .Add(new BoundaryControl()) + .Add(new RemoveControl(new LinkPathPositionProvider(0.8, 0, -10))); - _diagram.Controls.AddFor(node3) - .Add(new BoundaryControl()) - .Add(new RemoveControl(new BoundsBasedPositionProvider(1, 0, 10, -10))); + _diagram.Controls.AddFor(node3) + .Add(new BoundaryControl()) + .Add(new RemoveControl(new BoundsBasedPositionProvider(1, 0, 10, -10))); - // Ports and Routes example - var node4 = _diagram.Nodes.Add(new ColoredNodeModel("With ports", false, "color1", new Point(560, 20))); - var node5 = _diagram.Nodes.Add(new ColoredNodeModel("Locked", true, "color2", new Point(720, 350))); - node5.Locked = true; + // Ports and Routes example + var node4 = _diagram.Nodes.Add(new ColoredNodeModel("With ports", false, "color1", new Point(560, 20))); + var node5 = _diagram.Nodes.Add(new ColoredNodeModel("Locked", true, "color2", new Point(720, 350))); + node5.Locked = true; - var port1 = node3.AddPort(PortAlignment.Right); - var port2 = node4.AddPort(PortAlignment.Left); - var port3 = node4.AddPort(PortAlignment.Right); - var port4 = node5.AddPort(PortAlignment.Left); - var port5 = node5.AddPort(PortAlignment.Right); + var port1 = node3.AddPort(PortAlignment.Right); + var port2 = node4.AddPort(PortAlignment.Left); + var port3 = node4.AddPort(PortAlignment.Right); + var port4 = node5.AddPort(PortAlignment.Left); + var port5 = node5.AddPort(PortAlignment.Right); - var link4 = _diagram.Links.Add(new LinkModel(port1, port2) - { - Color = "#9EA5E3", - SelectedColor = "#2b3595", - PathGenerator = smoothPathGenerator, - TargetMarker = LinkMarker.Arrow - }); - link4.Labels.Add(new LinkLabelModel(link4, "Smooth PathGenerator", 0.5)); + var link4 = _diagram.Links.Add(new LinkModel(port1, port2) + { + Color = "#9EA5E3", + SelectedColor = "#2b3595", + PathGenerator = smoothPathGenerator, + TargetMarker = LinkMarker.Arrow + }); + link4.Labels.Add(new LinkLabelModel(link4, "Smooth PathGenerator", 0.5)); - var link5 = _diagram.Links.Add(new LinkModel(port3, port4) - { - Color = "#A0B15B", - SelectedColor = "#515a2b", - Router = new OrthogonalRouter(), - PathGenerator = new StraightPathGenerator(20), - TargetMarker = new LinkMarker("M 0 -8 L 8 -8 4 0 8 8 0 8 4 0 z", 8) - }); - link5.Labels.Add(new LinkLabelModel(link5, "Orthogonal Router", 0.3)); - link5.Labels.Add(new LinkLabelModel(link5, "Custom marker", 0.8)); + var link5 = _diagram.Links.Add(new LinkModel(port3, port4) + { + Color = "#A0B15B", + SelectedColor = "#515a2b", + Router = new OrthogonalRouter(), + PathGenerator = new StraightPathGenerator(20), + TargetMarker = new LinkMarker("M 0 -8 L 8 -8 4 0 8 8 0 8 4 0 z", 8) + }); + link5.Labels.Add(new LinkLabelModel(link5, "Orthogonal Router", 0.3)); + link5.Labels.Add(new LinkLabelModel(link5, "Custom marker", 0.8)); - _diagram.Links.Add(new LinkModel(port3, port5) - { - Color = "#A0B15B", - SelectedColor = "#515a2b", - Router = new OrthogonalRouter(), - PathGenerator = new StraightPathGenerator(), - TargetMarker = LinkMarker.Arrow - }); - } + _diagram.Links.Add(new LinkModel(port3, port5) + { + Color = "#A0B15B", + SelectedColor = "#515a2b", + Router = new OrthogonalRouter(), + PathGenerator = new StraightPathGenerator(), + TargetMarker = LinkMarker.Arrow + }); } } diff --git a/site/Site/Components/Landing/Groups/GroupsExample.razor.cs b/site/Site/Components/Landing/Groups/GroupsExample.razor.cs index 1d47bd8f6..dfaa3965a 100644 --- a/site/Site/Components/Landing/Groups/GroupsExample.razor.cs +++ b/site/Site/Components/Landing/Groups/GroupsExample.razor.cs @@ -5,46 +5,45 @@ using Site.Models.Landing; using Site.Models.Landing.Groups; -namespace Site.Components.Landing.Groups +namespace Site.Components.Landing.Groups; + +public partial class GroupsExample { - public partial class GroupsExample - { - private readonly BlazorDiagram _diagram = new(); + private readonly BlazorDiagram _diagram = new(); - protected override void OnInitialized() - { - _diagram.Options.Zoom.Enabled = false; - _diagram.Options.GridSize = 30; - _diagram.Options.LinksLayerOrder = 2; - _diagram.Options.NodesLayerOrder = 1; - _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); - _diagram.RegisterComponent(); - _diagram.RegisterComponent(); + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.GridSize = 30; + _diagram.Options.LinksLayerOrder = 2; + _diagram.Options.NodesLayerOrder = 1; + _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); + _diagram.RegisterComponent(); + _diagram.RegisterComponent(); - var node1 = _diagram.Nodes.Add(new ColoredNodeModel("Node 1", false, "color3", new Point(90, 60))); - var node2 = _diagram.Nodes.Add(new ColoredNodeModel("Node 2", false, "color3", new Point(390, 60))); - var node3 = _diagram.Nodes.Add(new ColoredNodeModel("Node 3", false, "color1", new Point(180, 240))); - var node4 = _diagram.Nodes.Add(new ColoredNodeModel("Node 4", false, "color1", new Point(330, 240))); + var node1 = _diagram.Nodes.Add(new ColoredNodeModel("Node 1", false, "color3", new Point(90, 60))); + var node2 = _diagram.Nodes.Add(new ColoredNodeModel("Node 2", false, "color3", new Point(390, 60))); + var node3 = _diagram.Nodes.Add(new ColoredNodeModel("Node 3", false, "color1", new Point(180, 240))); + var node4 = _diagram.Nodes.Add(new ColoredNodeModel("Node 4", false, "color1", new Point(330, 240))); - var group1 = _diagram.Groups.Add(new ColoredGroupModel(new[] { node3, node4 }, "color2")); - var group2 = _diagram.Groups.Add(new ColoredGroupModel(new[] { (NodeModel)group1, node1, node2 }, "color2")); + var group1 = _diagram.Groups.Add(new ColoredGroupModel(new[] { node3, node4 }, "color2")); + var group2 = _diagram.Groups.Add(new ColoredGroupModel(new[] { (NodeModel)group1, node1, node2 }, "color2")); - _diagram.Links.Add(new LinkModel(node1, node2) - { - TargetMarker = LinkMarker.Arrow, - }); - _diagram.Links.Add(new LinkModel(node2, node3) - { - TargetMarker = LinkMarker.Arrow, - }); - _diagram.Links.Add(new LinkModel(node3, node1) - { - TargetMarker = LinkMarker.Arrow, - }); - _diagram.Links.Add(new LinkModel(node2, group1) - { - TargetMarker = LinkMarker.Arrow, - }); - } + _diagram.Links.Add(new LinkModel(node1, node2) + { + TargetMarker = LinkMarker.Arrow, + }); + _diagram.Links.Add(new LinkModel(node2, node3) + { + TargetMarker = LinkMarker.Arrow, + }); + _diagram.Links.Add(new LinkModel(node3, node1) + { + TargetMarker = LinkMarker.Arrow, + }); + _diagram.Links.Add(new LinkModel(node2, group1) + { + TargetMarker = LinkMarker.Arrow, + }); } } diff --git a/site/Site/Components/Landing/StatisticsLine.razor.cs b/site/Site/Components/Landing/StatisticsLine.razor.cs index 6d19fbbd2..9bcd43087 100644 --- a/site/Site/Components/Landing/StatisticsLine.razor.cs +++ b/site/Site/Components/Landing/StatisticsLine.razor.cs @@ -2,56 +2,55 @@ using System.Net.Http.Json; using System.Text.Json; -namespace Site.Components.Landing +namespace Site.Components.Landing; + +public partial class StatisticsLine { - public partial class StatisticsLine - { - private int _stars; - private int _downloads; - private string _version = "1.0.0"; + private int _stars; + private int _downloads; + private string _version = "1.0.0"; - [Inject] private HttpClient HttpClient { get; set; } = null!; + [Inject] private HttpClient HttpClient { get; set; } = null!; - protected override async Task OnInitializedAsync() - { - (_version, _downloads) = await GetVersionAndDownloads(); - _stars = await GetStars(); - } + protected override async Task OnInitializedAsync() + { + (_version, _downloads) = await GetVersionAndDownloads(); + _stars = await GetStars(); + } - private async Task GetStars() - { - var content = await HttpClient.GetFromJsonAsync("https://api.github.com/repos/Blazor-Diagrams/Blazor.Diagrams"); - if (content == null) - return 0; + private async Task GetStars() + { + var content = await HttpClient.GetFromJsonAsync("https://api.github.com/repos/Blazor-Diagrams/Blazor.Diagrams"); + if (content == null) + return 0; - return content.RootElement.GetProperty("stargazers_count").GetInt32(); - } + return content.RootElement.GetProperty("stargazers_count").GetInt32(); + } - private async Task<(string, int)> GetVersionAndDownloads() + private async Task<(string, int)> GetVersionAndDownloads() + { + var content = await HttpClient.GetFromJsonAsync("https://api.nuget.org/v3/index.json"); + if (content != null) { - var content = await HttpClient.GetFromJsonAsync("https://api.nuget.org/v3/index.json"); - if (content != null) + foreach (var resource in content.RootElement.GetProperty("resources").EnumerateArray()) { - foreach (var resource in content.RootElement.GetProperty("resources").EnumerateArray()) + if (resource.GetProperty("@type").GetString() == "SearchQueryService") { - if (resource.GetProperty("@type").GetString() == "SearchQueryService") + var url = resource.GetProperty("@id").GetString(); + var packageContent = await HttpClient.GetFromJsonAsync(url + "?prerelease=true&q=packageid:Z.Blazor.Diagrams"); + if (packageContent != null) { - var url = resource.GetProperty("@id").GetString(); - var packageContent = await HttpClient.GetFromJsonAsync(url + "?prerelease=true&q=packageid:Z.Blazor.Diagrams"); - if (packageContent != null) + foreach (var data in packageContent.RootElement.GetProperty("data").EnumerateArray()) { - foreach (var data in packageContent.RootElement.GetProperty("data").EnumerateArray()) - { - var version = data.GetProperty("version").GetString()!; - var downloads = data.GetProperty("totalDownloads").GetInt32(); - return (version, downloads); - } + var version = data.GetProperty("version").GetString()!; + var downloads = data.GetProperty("totalDownloads").GetInt32(); + return (version, downloads); } } } } - - return ("1.0.0", 0); } + + return ("1.0.0", 0); } } diff --git a/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor.cs b/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor.cs index 68e253383..31040e3ae 100644 --- a/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor.cs +++ b/site/Site/Components/Landing/SvgAndHtml/SvgAndHtmlExample.razor.cs @@ -5,31 +5,30 @@ using Blazor.Diagrams.Core.PathGenerators; using Site.Models.Landing.SvgAndHtml; -namespace Site.Components.Landing.SvgAndHtml +namespace Site.Components.Landing.SvgAndHtml; + +public partial class SvgAndHtmlExample { - public partial class SvgAndHtmlExample - { - private readonly BlazorDiagram _diagram = new(); + private readonly BlazorDiagram _diagram = new(); - protected override void OnInitialized() - { - _diagram.Options.GridSize = 30; - _diagram.Options.Constraints.ShouldDeleteLink = _ => ValueTask.FromResult(false); - _diagram.Options.Zoom.Enabled = false; - _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); - _diagram.RegisterComponent(); - _diagram.RegisterComponent(); + protected override void OnInitialized() + { + _diagram.Options.GridSize = 30; + _diagram.Options.Constraints.ShouldDeleteLink = _ => ValueTask.FromResult(false); + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); + _diagram.RegisterComponent(); + _diagram.RegisterComponent(); - var battery = new BatteryNodeModel(new Point(90, 150)); - var port1 = battery.AddPort(PortAlignment.Right); - var port2 = battery.AddPort(PortAlignment.Right); + var battery = new BatteryNodeModel(new Point(90, 150)); + var port1 = battery.AddPort(PortAlignment.Right); + var port2 = battery.AddPort(PortAlignment.Right); - _diagram.Nodes.Add(battery); - var charger1 = _diagram.Nodes.Add(new BatteryChargerNodeModel(() => battery.FirstCharge, i => battery.FirstCharge = i, new Point(300, 60))); - var charger2 = _diagram.Nodes.Add(new BatteryChargerNodeModel(() => battery.SecondCharge, i => battery.SecondCharge = i, new Point(300, 180))); + _diagram.Nodes.Add(battery); + var charger1 = _diagram.Nodes.Add(new BatteryChargerNodeModel(() => battery.FirstCharge, i => battery.FirstCharge = i, new Point(300, 60))); + var charger2 = _diagram.Nodes.Add(new BatteryChargerNodeModel(() => battery.SecondCharge, i => battery.SecondCharge = i, new Point(300, 180))); - _diagram.Links.Add(new LinkModel(new SinglePortAnchor(port1), new ShapeIntersectionAnchor(charger1))); - _diagram.Links.Add(new LinkModel(new SinglePortAnchor(port2), new ShapeIntersectionAnchor(charger2))); - } + _diagram.Links.Add(new LinkModel(new SinglePortAnchor(port1), new ShapeIntersectionAnchor(charger1))); + _diagram.Links.Add(new LinkModel(new SinglePortAnchor(port2), new ShapeIntersectionAnchor(charger2))); } } diff --git a/site/Site/Components/Landing/WidgetsExample.razor.cs b/site/Site/Components/Landing/WidgetsExample.razor.cs index 212231ed7..cf0282efb 100644 --- a/site/Site/Components/Landing/WidgetsExample.razor.cs +++ b/site/Site/Components/Landing/WidgetsExample.razor.cs @@ -4,46 +4,45 @@ using Blazor.Diagrams.Core.PathGenerators; using Site.Models.Landing; -namespace Site.Components.Landing +namespace Site.Components.Landing; + +public partial class WidgetsExample { - public partial class WidgetsExample - { - private readonly BlazorDiagram _diagram = new(); - private bool _gridPoints; + private readonly BlazorDiagram _diagram = new(); + private bool _gridPoints; - public bool GridPoints + public bool GridPoints + { + get => _gridPoints; + set { - get => _gridPoints; - set - { - _gridPoints = value; - _diagram.Refresh(); - } + _gridPoints = value; + _diagram.Refresh(); } + } - protected override void OnInitialized() - { - _diagram.Options.Zoom.Enabled = false; - _diagram.Options.GridSize = 30; - _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); - _diagram.RegisterComponent(); + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + _diagram.Options.GridSize = 30; + _diagram.Options.Links.DefaultPathGenerator = new StraightPathGenerator(); + _diagram.RegisterComponent(); - var node1 = _diagram.Nodes.Add(new ColoredNodeModel("Node 1", false, "color1", new Point(90, 60))); - var node2 = _diagram.Nodes.Add(new ColoredNodeModel("Node 2", true, "color2", new Point(450, 60))); - var node3 = _diagram.Nodes.Add(new ColoredNodeModel("Node 3", false, "color3", new Point(270, 240))); + var node1 = _diagram.Nodes.Add(new ColoredNodeModel("Node 1", false, "color1", new Point(90, 60))); + var node2 = _diagram.Nodes.Add(new ColoredNodeModel("Node 2", true, "color2", new Point(450, 60))); + var node3 = _diagram.Nodes.Add(new ColoredNodeModel("Node 3", false, "color3", new Point(270, 240))); - _diagram.Links.Add(new LinkModel(node1, node2) - { - TargetMarker = LinkMarker.Arrow - }); - _diagram.Links.Add(new LinkModel(node2, node3) - { - TargetMarker = LinkMarker.Arrow - }); - _diagram.Links.Add(new LinkModel(node3, node1) - { - TargetMarker = LinkMarker.Arrow - }); - } + _diagram.Links.Add(new LinkModel(node1, node2) + { + TargetMarker = LinkMarker.Arrow + }); + _diagram.Links.Add(new LinkModel(node2, node3) + { + TargetMarker = LinkMarker.Arrow + }); + _diagram.Links.Add(new LinkModel(node3, node1) + { + TargetMarker = LinkMarker.Arrow + }); } } diff --git a/site/Site/Models/Documentation/Menu.cs b/site/Site/Models/Documentation/Menu.cs index d1a3a39d9..7d4d1f779 100644 --- a/site/Site/Models/Documentation/Menu.cs +++ b/site/Site/Models/Documentation/Menu.cs @@ -1,8 +1,7 @@ -namespace Site.Models.Documentation -{ - public record Menu(IEnumerable Items, IEnumerable Groups); +namespace Site.Models.Documentation; - public record MenuGroup(string Title, IEnumerable Children); +public record Menu(IEnumerable Items, IEnumerable Groups); - public record MenuItem(string Title, string Link, string? Icon = null); -} +public record MenuGroup(string Title, IEnumerable Children); + +public record MenuItem(string Title, string Link, string? Icon = null); diff --git a/site/Site/Models/Landing/ColoredNodeModel.cs b/site/Site/Models/Landing/ColoredNodeModel.cs index 3571552d2..3682a4f8a 100644 --- a/site/Site/Models/Landing/ColoredNodeModel.cs +++ b/site/Site/Models/Landing/ColoredNodeModel.cs @@ -1,23 +1,22 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -namespace Site.Models.Landing +namespace Site.Models.Landing; + +public class ColoredNodeModel : NodeModel { - public class ColoredNodeModel : NodeModel + public ColoredNodeModel(string title, bool round, string color, Point position) : base(position) { - public ColoredNodeModel(string title, bool round, string color, Point position) : base(position) - { - Title = title; - Round = round; - Color = color; - } + Title = title; + Round = round; + Color = color; + } - public bool Round { get; } - public string Color { get; } + public bool Round { get; } + public string Color { get; } - public override IShape GetShape() - { - return Round ? Shapes.Circle(this) : Shapes.Rectangle(this); - } + public override IShape GetShape() + { + return Round ? Shapes.Circle(this) : Shapes.Rectangle(this); } } diff --git a/site/Site/Models/Landing/Groups/ColoredGroupModel.cs b/site/Site/Models/Landing/Groups/ColoredGroupModel.cs index eac06140a..e09780a0f 100644 --- a/site/Site/Models/Landing/Groups/ColoredGroupModel.cs +++ b/site/Site/Models/Landing/Groups/ColoredGroupModel.cs @@ -1,14 +1,13 @@ using Blazor.Diagrams.Core.Models; -namespace Site.Models.Landing.Groups +namespace Site.Models.Landing.Groups; + +public class ColoredGroupModel : GroupModel { - public class ColoredGroupModel : GroupModel + public ColoredGroupModel(IEnumerable children, string color, byte padding = 30, bool autoSize = true) : base(children, padding, autoSize) { - public ColoredGroupModel(IEnumerable children, string color, byte padding = 30, bool autoSize = true) : base(children, padding, autoSize) - { - Color = color; - } - - public string Color { get; } + Color = color; } + + public string Color { get; } } diff --git a/site/Site/Models/Landing/SvgAndHtml/BatteryChargerNodeModel.cs b/site/Site/Models/Landing/SvgAndHtml/BatteryChargerNodeModel.cs index 98407d4a6..d81c34152 100644 --- a/site/Site/Models/Landing/SvgAndHtml/BatteryChargerNodeModel.cs +++ b/site/Site/Models/Landing/SvgAndHtml/BatteryChargerNodeModel.cs @@ -1,18 +1,17 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; -namespace Site.Models.Landing.SvgAndHtml +namespace Site.Models.Landing.SvgAndHtml; + +public class BatteryChargerNodeModel : NodeModel { - public class BatteryChargerNodeModel : NodeModel + public BatteryChargerNodeModel(Func getter, Action setter, Point position) : base(position) { - public BatteryChargerNodeModel(Func getter, Action setter, Point position) : base(position) - { - Getter = getter; - Setter = setter; - } - - public BatteryNodeModel? Battery { get; private set; } - public Func Getter { get; } - public Action Setter { get; } + Getter = getter; + Setter = setter; } + + public BatteryNodeModel? Battery { get; private set; } + public Func Getter { get; } + public Action Setter { get; } } diff --git a/site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs b/site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs index b1aa29bf3..1c63cf3f9 100644 --- a/site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs +++ b/site/Site/Models/Landing/SvgAndHtml/BatteryNodeModel.cs @@ -1,37 +1,36 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Models; -namespace Site.Models.Landing.SvgAndHtml +namespace Site.Models.Landing.SvgAndHtml; + +public class BatteryNodeModel : SvgNodeModel { - public class BatteryNodeModel : SvgNodeModel - { - private int _firstCharge = 5; - private int _secondCharge = 15; + private int _firstCharge = 5; + private int _secondCharge = 15; - public BatteryNodeModel(Point position) : base(position) - { - } + public BatteryNodeModel(Point position) : base(position) + { + } - public int FirstCharge + public int FirstCharge + { + get => _firstCharge; + set { - get => _firstCharge; - set - { - _firstCharge = value; - Refresh(); - } + _firstCharge = value; + Refresh(); } + } - public int SecondCharge + public int SecondCharge + { + get => _secondCharge; + set { - get => _secondCharge; - set - { - _secondCharge = value; - Refresh(); - } + _secondCharge = value; + Refresh(); } - - public int Percentage => FirstCharge + SecondCharge; } + + public int Percentage => FirstCharge + SecondCharge; } diff --git a/site/Site/Models/Nodes/GingerbreadNode.cs b/site/Site/Models/Nodes/GingerbreadNode.cs new file mode 100644 index 000000000..01e2b7a1d --- /dev/null +++ b/site/Site/Models/Nodes/GingerbreadNode.cs @@ -0,0 +1,11 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Models; + +namespace Site.Models.Nodes; + +public class GingerbreadNode : SvgNodeModel +{ + public GingerbreadNode(Point? position = null) : base(position) { } + + // Here, you can put whatever you want +} diff --git a/site/Site/Pages/Documentation/DocumentationPage.cs b/site/Site/Pages/Documentation/DocumentationPage.cs index 881f60445..00c8147f2 100644 --- a/site/Site/Pages/Documentation/DocumentationPage.cs +++ b/site/Site/Pages/Documentation/DocumentationPage.cs @@ -1,15 +1,14 @@ using Microsoft.AspNetCore.Components; using Microsoft.JSInterop; -namespace Site.Pages.Documentation +namespace Site.Pages.Documentation; + +public class DocumentationPage : ComponentBase { - public class DocumentationPage : ComponentBase - { - [Inject] protected IJSRuntime JSRuntime { get; set; } = null!; + [Inject] protected IJSRuntime JSRuntime { get; set; } = null!; - protected override async Task OnAfterRenderAsync(bool firstRender) - { - await JSRuntime.InvokeVoidAsync("Prism.highlightAll"); - } + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await JSRuntime.InvokeVoidAsync("Prism.highlightAll"); } } diff --git a/site/Site/Pages/Documentation/Nodes/Customization.razor b/site/Site/Pages/Documentation/Nodes/Customization.razor index afa65bf21..7b685b8c9 100644 --- a/site/Site/Pages/Documentation/Nodes/Customization.razor +++ b/site/Site/Pages/Documentation/Nodes/Customization.razor @@ -93,7 +93,7 @@ div { margin-bottom: 8px; } -.diagram-port { +::deep .diagram-port { position: absolute; width: 30px; height: 20px; @@ -102,16 +102,16 @@ div { transform: translate(-50%, -50%); } - .diagram-port.top { + ::deep .diagram-port.top { border-top-left-radius: 50%; border-top-right-radius: 50%; top: -10px; } - .diagram-port.bottom { + ::deep .diagram-port.bottom { border-bottom-left-radius: 50%; border-bottom-right-radius: 50%; - bottom: 10px; + bottom: -30px; } @@ -142,6 +142,11 @@ protected override void OnInitialized() + + @code { private BlazorDiagram Diagram { get; set; } = new(); diff --git a/site/Site/Pages/Documentation/Nodes/Overview.razor b/site/Site/Pages/Documentation/Nodes/Overview.razor index cb9fd0e00..fbdf5b5be 100644 --- a/site/Site/Pages/Documentation/Nodes/Overview.razor +++ b/site/Site/Pages/Documentation/Nodes/Overview.razor @@ -28,6 +28,19 @@ The classes that the div can have (beside diagram-node) are: locked, selected and grouped.

    +

    Shape

    + +Nodes can have a specific shape, which by default is a rectangle. It is used for two things at the moment: +
      +
    • ShapeAnglePositionProvider: To return a position for the given angle.
    • +
    • NavigatorWidget: To know what to draw.
    • +
    +
    + +

    + You can change the shape of your node by overriding the GetShape method in your custom model. +

    +

    Creating a node

    
    diff --git a/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor b/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor
    new file mode 100644
    index 000000000..57961438d
    --- /dev/null
    +++ b/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor
    @@ -0,0 +1,158 @@
    +@page "/documentation/nodes-customization-svg"
    +@using Site.Components.Documentation.Nodes;
    +@using Site.Models.Nodes;
    +@layout DocumentationLayout
    +@inherits DocumentationPage
    +
    +SVG Nodes Customization - Documentation - Blazor Diagrams
    +
    +

    SVG Nodes Customization

    + +

    + Creating a custom SVG node is as easy as a HTML one, let's go! +

    + +

    Creating a model

    + +

    + Let's assume that we want to create a new node that represents Gingerbread: +

    + +
    GingerbreadNode.cs
    +
    
    +using Blazor.Diagrams.Core.Geometry;
    +using Blazor.Diagrams.Core.Models;
    +
    +namespace YourNamespace;
    +
    +public class AddTwoNumbersNode : NodeModel
    +{
    +    public AddTwoNumbersNode(Point? position = null) : base(position) { }
    +
    +    public double FirstNumber { get; set; }
    +    public double SecondNumber { get; set; }
    +
    +    // Here, you can put whatever you want, such as a method that does the addition
    +}
    +
    + +

    Creating a component

    + +

    + Let's create a UI component to control how the node looks like: +

    + +
    AddTwoNumbersWidget.razor
    +
    
    +@@using Blazor.Diagrams.Components.Renderers;
    +@@using Site.Models.Nodes;
    +
    +<div>
    +    <h5 class="card-title">Add</h5>
    +    <input type="number" class="form-control" @@bind-value="Node.FirstNumber" placeholder="Number 1" />
    +    <input type="number" class="form-control" @@bind-value="Node.SecondNumber" placeholder="Number 2" />
    +
    +    @@foreach (var port in Node.Ports)
    +    {
    +        // In case you have any ports to show
    +        // IMPORTANT: You are always in charge of rendering ports
    +        <PortRenderer @@key="port" Port="port" />
    +    }
    +</div>
    +
    +@@code {
    +    // This gets filled by the library
    +    [Parameter] public AddTwoNumbersNode Node { get; set; } = null!;
    +}
    +
    + +Let's also style our component! + +
    AddTwoNumbersWidget.razor.css
    +
    
    +div {
    +    width: 230px;
    +    outline: 1px solid black;
    +    padding: 20px;
    +    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
    +}
    +
    +    div > h5 {
    +        font-weight: 600;
    +        text-transform: uppercase;
    +        margin-bottom: 10px;
    +    }
    +
    +    div > input[type=number] {
    +        padding: 3px;
    +        border-radius: 3px;
    +        border: 1px solid black;
    +        margin-bottom: 8px;
    +    }
    +
    +::deep .diagram-port {
    +    position: absolute;
    +    width: 30px;
    +    height: 20px;
    +    background-color: black;
    +    left: 50%;
    +    transform: translate(-50%, -50%);
    +}
    +
    +    ::deep .diagram-port.top {
    +        border-top-left-radius: 50%;
    +        border-top-right-radius: 50%;
    +        top: -10px;
    +    }
    +
    +    ::deep .diagram-port.bottom {
    +        border-bottom-left-radius: 50%;
    +        border-bottom-right-radius: 50%;
    +        bottom: -30px;
    +    }
    +
    + +

    Displaying

    + +

    + All we have to do now is register our new creation! +

    + +
    
    +private BlazorDiagram Diagram { get; set; } = new();
    +
    +protected override void OnInitialized()
    +{
    +    base.OnInitialized();
    +
    +    Diagram.RegisterComponent<AddTwoNumbersNode, AddTwoNumbersWidget>();
    +
    +    var node = Diagram.Nodes.Add(new AddTwoNumbersNode(new Point(80, 80)));
    +    node.AddPort(PortAlignment.Top);
    +    node.AddPort(PortAlignment.Bottom);
    +}
    +
    + +
    + + + +
    + + + +@code { + private BlazorDiagram Diagram { get; set; } = new(); + + protected override void OnInitialized() + { + base.OnInitialized(); + + Diagram.RegisterComponent(); + + var node = Diagram.Nodes.Add(new GingerbreadNode(new Point(80, 80))); + node.AddPort(PortAlignment.Top); + node.AddPort(PortAlignment.Bottom); + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor.css b/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor.css new file mode 100644 index 000000000..6f4f39fa9 --- /dev/null +++ b/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor.css @@ -0,0 +1,6 @@ +.diagram-container { + width: 100%; + height: 400px; + border: 1px solid black; + user-select: none; +} diff --git a/site/Site/Shared/DocumentationLayout.razor.cs b/site/Site/Shared/DocumentationLayout.razor.cs index 0f0984fb2..e5d0a0415 100644 --- a/site/Site/Shared/DocumentationLayout.razor.cs +++ b/site/Site/Shared/DocumentationLayout.razor.cs @@ -1,31 +1,30 @@ using Microsoft.AspNetCore.Components; -namespace Site.Shared +namespace Site.Shared; + +public partial class DocumentationLayout { - public partial class DocumentationLayout - { - [Inject] private NavigationManager NavigationManager { get; set; } = null!; + [Inject] private NavigationManager NavigationManager { get; set; } = null!; - private (string, string) GetMenuItemExtraClasses(string link) - { - if (IsActive(link, false)) - return ("font-semibold text-main", "bg-main text-white"); + private (string, string) GetMenuItemExtraClasses(string link) + { + if (IsActive(link, false)) + return ("font-semibold text-main", "bg-main text-white"); - return ("font-medium hover:text-slate-900", "bg-gray-100 text-black"); - } + return ("font-medium hover:text-slate-900", "bg-gray-100 text-black"); + } - private string GetGroupMenuItemExtraClasses(string link) - { - if (IsActive(link, true)) - return "text-palette-main border-palette-main font-semibold"; + private string GetGroupMenuItemExtraClasses(string link) + { + if (IsActive(link, true)) + return "text-palette-main border-palette-main font-semibold"; - return "text-slate-700 hover:border-slate-400 hover:text-slate-900"; - } + return "text-slate-700 hover:border-slate-400 hover:text-slate-900"; + } - private bool IsActive(string link, bool fullMatch) - { - var relativePath = "/" + NavigationManager.ToBaseRelativePath(NavigationManager.Uri).ToLower(); - return (fullMatch && relativePath == link) || (!fullMatch && relativePath.StartsWith(link)); - } + private bool IsActive(string link, bool fullMatch) + { + var relativePath = "/" + NavigationManager.ToBaseRelativePath(NavigationManager.Uri).ToLower(); + return (fullMatch && relativePath == link) || (!fullMatch && relativePath.StartsWith(link)); } } diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs index 0fd8a9c4f..3ad2fb3b1 100644 --- a/site/Site/Static/Documentation.cs +++ b/site/Site/Static/Documentation.cs @@ -1,40 +1,40 @@ using Site.Models.Documentation; -namespace Site.Static +namespace Site.Static; + +public static class Documentation { - public static class Documentation + public static readonly Menu Menu = new(new List + { + new MenuItem("Documentation", "/documentation", Icons.BookOpen), + //new MenuItem("Examples", "/examples", Icons.FolderOpen), + }, new List { - public static readonly Menu Menu = new(new List + new MenuGroup("Getting Started", new List + { + new MenuItem("Installation", "/documentation/installation"), + new MenuItem("Diagram Creation", "/documentation/diagram-creation"), + new MenuItem("Display", "/documentation/display"), + }), + new MenuGroup("Diagram", new List + { + new MenuItem("Overview", "/documentation/diagram"), + new MenuItem("Behaviors", "/documentation/diagram-behaviors"), + new MenuItem("Options", "/documentation/diagram-options"), + new MenuItem("Keyboard Shortcuts", "/documentation/keyboard-shortcuts"), + new MenuItem("API", "/documentation/diagram-api"), + }), + new MenuGroup("Nodes", new List { - new MenuItem("Documentation", "/documentation", Icons.BookOpen), - new MenuItem("Examples", "/examples", Icons.FolderOpen), - }, new List + new MenuItem("Overview", "/documentation/nodes"), + new MenuItem("SVG", "/documentation/nodes-svg"), + new MenuItem("Customization", "/documentation/nodes-customization"), + new MenuItem("Customization (SVG)", "/documentation/nodes-customization-svg") + }), + new MenuGroup("Groups", new List { - new MenuGroup("Getting Started", new List - { - new MenuItem("Installation", "/documentation/installation"), - new MenuItem("Diagram Creation", "/documentation/diagram-creation"), - new MenuItem("Display", "/documentation/display"), - }), - new MenuGroup("Diagram", new List - { - new MenuItem("Overview", "/documentation/diagram"), - new MenuItem("Behaviors", "/documentation/diagram-behaviors"), - new MenuItem("Options", "/documentation/diagram-options"), - new MenuItem("Keyboard Shortcuts", "/documentation/keyboard-shortcuts"), - new MenuItem("API", "/documentation/diagram-api"), - }), - new MenuGroup("Nodes", new List - { - new MenuItem("Overview", "/documentation/nodes"), - new MenuItem("SVG", "/documentation/nodes-svg"), - new MenuItem("Customization", "/documentation/nodes-customization") - }), - new MenuGroup("Groups", new List - { - new MenuItem("Overview", "/documentation/groups"), - new MenuItem("SVG", "/documentation/groups-svg"), - }) - }); - } + new MenuItem("Overview", "/documentation/groups"), + new MenuItem("SVG", "/documentation/groups-svg"), + }) + }); } diff --git a/site/Site/Static/Icons.cs b/site/Site/Static/Icons.cs index 078529be9..2e0df5f81 100644 --- a/site/Site/Static/Icons.cs +++ b/site/Site/Static/Icons.cs @@ -1,9 +1,8 @@ -namespace Site.Static +namespace Site.Static; + +public static class Icons { - public static class Icons - { - public static readonly string Github = "M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z"; - public static readonly string BookOpen = "M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25"; - public static readonly string FolderOpen = "M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776"; - } + public static readonly string Github = "M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z"; + public static readonly string BookOpen = "M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25"; + public static readonly string FolderOpen = "M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776"; } diff --git a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs index 8cda7518d..3c7d6d816 100644 --- a/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs +++ b/src/Blazor.Diagrams.Algorithms/LinksReconnectionAlgorithms.cs @@ -4,63 +4,62 @@ using System.Linq; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Algorithms +namespace Blazor.Diagrams.Algorithms; + +public static class LinksReconnectionAlgorithms { - public static class LinksReconnectionAlgorithms + public static void ReconnectLinksToClosestPorts(this Diagram diagram) { - public static void ReconnectLinksToClosestPorts(this Diagram diagram) - { - // Only refresh ports once - var modelsToRefresh = new HashSet(); + // Only refresh ports once + var modelsToRefresh = new HashSet(); - foreach (var link in diagram.Links.ToArray()) - { - if (link.Source is not SinglePortAnchor spa1 || link.Target is not SinglePortAnchor spa2) - continue; + foreach (var link in diagram.Links.ToArray()) + { + if (link.Source is not SinglePortAnchor spa1 || link.Target is not SinglePortAnchor spa2) + continue; - var sourcePorts = spa1.Port.Parent.Ports; - var targetPorts = spa2.Port.Parent.Ports; + var sourcePorts = spa1.Port.Parent.Ports; + var targetPorts = spa2.Port.Parent.Ports; - // Find the ports with minimal distance - var minDistance = double.MaxValue; - var minSourcePort = spa1.Port; - var minTargetPort = spa2.Port; - foreach (var sourcePort in sourcePorts) + // Find the ports with minimal distance + var minDistance = double.MaxValue; + var minSourcePort = spa1.Port; + var minTargetPort = spa2.Port; + foreach (var sourcePort in sourcePorts) + { + foreach (var targetPort in targetPorts) { - foreach (var targetPort in targetPorts) + var distance = sourcePort.Position.DistanceTo(targetPort.Position); + if (distance < minDistance) { - var distance = sourcePort.Position.DistanceTo(targetPort.Position); - if (distance < minDistance) - { - minDistance = distance; - minSourcePort = sourcePort; - minTargetPort = targetPort; - } + minDistance = distance; + minSourcePort = sourcePort; + minTargetPort = targetPort; } } + } - // Reconnect - if (spa1.Port != minSourcePort) - { - modelsToRefresh.Add(spa1.Port); - modelsToRefresh.Add(minSourcePort); - link.SetSource(new SinglePortAnchor(minSourcePort)); - modelsToRefresh.Add(link); - } - - if (spa2.Port != minTargetPort) - { - modelsToRefresh.Add(spa2.Port); - modelsToRefresh.Add(minTargetPort); - link.SetTarget(new SinglePortAnchor(minTargetPort)); - modelsToRefresh.Add(link); - } + // Reconnect + if (spa1.Port != minSourcePort) + { + modelsToRefresh.Add(spa1.Port); + modelsToRefresh.Add(minSourcePort); + link.SetSource(new SinglePortAnchor(minSourcePort)); + modelsToRefresh.Add(link); } - foreach (var model in modelsToRefresh) + if (spa2.Port != minTargetPort) { - model.Refresh(); + modelsToRefresh.Add(spa2.Port); + modelsToRefresh.Add(minTargetPort); + link.SetTarget(new SinglePortAnchor(minTargetPort)); + modelsToRefresh.Add(link); } } + + foreach (var model in modelsToRefresh) + { + model.Refresh(); + } } } diff --git a/src/Blazor.Diagrams.Core/Anchors/Anchor.cs b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs index 78602d83a..53f100fdf 100644 --- a/src/Blazor.Diagrams.Core/Anchors/Anchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs @@ -3,48 +3,47 @@ using System; using System.Collections.Generic; -namespace Blazor.Diagrams.Core.Anchors +namespace Blazor.Diagrams.Core.Anchors; + +public abstract class Anchor { - public abstract class Anchor + public Anchor(ILinkable? model = null) { - public Anchor(ILinkable? model = null) - { - Model = model; - } + Model = model; + } - public ILinkable? Model { get; } + public ILinkable? Model { get; } - public abstract Point? GetPosition(BaseLinkModel link, Point[] route); + public abstract Point? GetPosition(BaseLinkModel link, Point[] route); - public abstract Point? GetPlainPosition(); + public abstract Point? GetPlainPosition(); - public Point? GetPosition(BaseLinkModel link) => GetPosition(link, Array.Empty()); + public Point? GetPosition(BaseLinkModel link) => GetPosition(link, Array.Empty()); - protected static Point? GetOtherPosition(BaseLinkModel link, bool isTarget) - { - var anchor = isTarget ? link.Source : link.Target!; - return anchor.GetPlainPosition(); - } + protected static Point? GetOtherPosition(BaseLinkModel link, bool isTarget) + { + var anchor = isTarget ? link.Source : link.Target!; + return anchor.GetPlainPosition(); + } - protected static Point? GetClosestPointTo(IEnumerable points, Point point) - { - var minDist = double.MaxValue; - Point? minPoint = null; + protected static Point? GetClosestPointTo(IEnumerable points, Point point) + { + var minDist = double.MaxValue; + Point? minPoint = null; - foreach (var pt in points) + foreach (var pt in points) + { + if (pt == null) + continue; + + var dist = pt.DistanceTo(point); + if (dist < minDist) { - if (pt == null) - continue; - - var dist = pt.DistanceTo(point); - if (dist < minDist) - { - minDist = dist; - minPoint = pt; - } + minDist = dist; + minPoint = pt; } - - return minPoint; } + + return minPoint; } } diff --git a/src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs index ef1f8a54c..7f203c46d 100644 --- a/src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/DynamicAnchor.cs @@ -5,33 +5,32 @@ using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Positions; -namespace Blazor.Diagrams.Core.Anchors +namespace Blazor.Diagrams.Core.Anchors; + +public sealed class DynamicAnchor : Anchor { - public sealed class DynamicAnchor : Anchor + public DynamicAnchor(NodeModel model, IPositionProvider[] providers) : base(model) { - public DynamicAnchor(NodeModel model, IPositionProvider[] providers) : base(model) - { - if (providers.Length == 0) - throw new InvalidOperationException("No providers provided"); - - Node = model; - Providers = providers; - } + if (providers.Length == 0) + throw new InvalidOperationException("No providers provided"); - public NodeModel Node { get; } - public IPositionProvider[] Providers { get; } + Node = model; + Providers = providers; + } - public override Point? GetPosition(BaseLinkModel link, Point[] route) - { - if (Node.Size == null) - return null; + public NodeModel Node { get; } + public IPositionProvider[] Providers { get; } - var isTarget = link.Target == this; - var pt = route.Length > 0 ? route[isTarget ? ^1 : 0] : GetOtherPosition(link, isTarget); - var positions = Providers.Select(e => e.GetPosition(Node)); - return pt is null ? null : GetClosestPointTo(positions, pt); - } + public override Point? GetPosition(BaseLinkModel link, Point[] route) + { + if (Node.Size == null) + return null; - public override Point? GetPlainPosition() => Node.GetBounds()?.Center ?? null; + var isTarget = link.Target == this; + var pt = route.Length > 0 ? route[isTarget ? ^1 : 0] : GetOtherPosition(link, isTarget); + var positions = Providers.Select(e => e.GetPosition(Node)); + return pt is null ? null : GetClosestPointTo(positions, pt); } + + public override Point? GetPlainPosition() => Node.GetBounds()?.Center ?? null; } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Anchors/PositionAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/PositionAnchor.cs index 09113bee4..1b9823f82 100644 --- a/src/Blazor.Diagrams.Core/Anchors/PositionAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/PositionAnchor.cs @@ -1,21 +1,20 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core.Anchors +namespace Blazor.Diagrams.Core.Anchors; + +public sealed class PositionAnchor : Anchor { - public sealed class PositionAnchor : Anchor - { - private Point _position; + private Point _position; - public PositionAnchor(Point position) : base(null) - { - _position = position; - } + public PositionAnchor(Point position) : base(null) + { + _position = position; + } - public void SetPosition(Point position) => _position = position; + public void SetPosition(Point position) => _position = position; - public override Point? GetPlainPosition() => _position; + public override Point? GetPlainPosition() => _position; - public override Point? GetPosition(BaseLinkModel link, Point[] route) => _position; - } + public override Point? GetPosition(BaseLinkModel link, Point[] route) => _position; } diff --git a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs index bdd17bbfd..8790e401f 100644 --- a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs @@ -2,41 +2,40 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core.Anchors +namespace Blazor.Diagrams.Core.Anchors; + +public sealed class ShapeIntersectionAnchor : Anchor { - public sealed class ShapeIntersectionAnchor : Anchor + public ShapeIntersectionAnchor(NodeModel model) : base(model) { - public ShapeIntersectionAnchor(NodeModel model) : base(model) - { - Node = model; - } + Node = model; + } + + public NodeModel Node { get; } - public NodeModel Node { get; } + public override Point? GetPosition(BaseLinkModel link, Point[] route) + { + if (Node.Size == null) + return null; - public override Point? GetPosition(BaseLinkModel link, Point[] route) + var isTarget = link.Target == this; + var nodeCenter = Node.GetBounds()!.Center; + Point? pt; + if (route.Length > 0) + { + pt = route[isTarget ? ^1 : 0]; + } + else { - if (Node.Size == null) - return null; - - var isTarget = link.Target == this; - var nodeCenter = Node.GetBounds()!.Center; - Point? pt; - if (route.Length > 0) - { - pt = route[isTarget ? ^1 : 0]; - } - else - { - pt = GetOtherPosition(link, isTarget); - } - - if (pt is null) return null; - - var line = new Line(pt, nodeCenter); - var intersections = Node.GetShape().GetIntersectionsWithLine(line); - return GetClosestPointTo(intersections, pt); // Todo: use Offset + pt = GetOtherPosition(link, isTarget); } - public override Point? GetPlainPosition() => Node.GetBounds()?.Center ?? null; + if (pt is null) return null; + + var line = new Line(pt, nodeCenter); + var intersections = Node.GetShape().GetIntersectionsWithLine(line); + return GetClosestPointTo(intersections, pt); // Todo: use Offset } + + public override Point? GetPlainPosition() => Node.GetBounds()?.Center ?? null; } diff --git a/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs index ca2cc2eaa..6cd1f8de2 100644 --- a/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/SinglePortAnchor.cs @@ -2,57 +2,56 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core.Anchors +namespace Blazor.Diagrams.Core.Anchors; + +public sealed class SinglePortAnchor : Anchor { - public sealed class SinglePortAnchor : Anchor + public SinglePortAnchor(PortModel port) : base(port) { - public SinglePortAnchor(PortModel port) : base(port) - { - Port = port; - } - - public PortModel Port { get; } - public bool MiddleIfNoMarker { get; set; } = false; - public bool UseShapeAndAlignment { get; set; } = true; - - public override Point? GetPosition(BaseLinkModel link, Point[] route) - { - if (!Port.Initialized) - return null; + Port = port; + } + + public PortModel Port { get; } + public bool MiddleIfNoMarker { get; set; } = false; + public bool UseShapeAndAlignment { get; set; } = true; - if (MiddleIfNoMarker && ((link.Source == this && link.SourceMarker is null) || (link.Target == this && link.TargetMarker is null))) - return Port.MiddlePosition; - - var pt = Port.Position; - if (UseShapeAndAlignment) - { - return Port.Alignment switch - { - PortAlignment.Top => Port.GetShape().GetPointAtAngle(270), - PortAlignment.TopRight => Port.GetShape().GetPointAtAngle(315), - PortAlignment.Right => Port.GetShape().GetPointAtAngle(0), - PortAlignment.BottomRight => Port.GetShape().GetPointAtAngle(45), - PortAlignment.Bottom => Port.GetShape().GetPointAtAngle(90), - PortAlignment.BottomLeft => Port.GetShape().GetPointAtAngle(135), - PortAlignment.Left => Port.GetShape().GetPointAtAngle(180), - PortAlignment.TopLeft => Port.GetShape().GetPointAtAngle(225), - _ => null, - }; - } + public override Point? GetPosition(BaseLinkModel link, Point[] route) + { + if (!Port.Initialized) + return null; + if (MiddleIfNoMarker && ((link.Source == this && link.SourceMarker is null) || (link.Target == this && link.TargetMarker is null))) + return Port.MiddlePosition; + + var pt = Port.Position; + if (UseShapeAndAlignment) + { return Port.Alignment switch { - PortAlignment.Top => new Point(pt.X + Port.Size.Width / 2, pt.Y), - PortAlignment.TopRight => new Point(pt.X + Port.Size.Width, pt.Y), - PortAlignment.Right => new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height / 2), - PortAlignment.BottomRight => new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height), - PortAlignment.Bottom => new Point(pt.X + Port.Size.Width / 2, pt.Y + Port.Size.Height), - PortAlignment.BottomLeft => new Point(pt.X, pt.Y + Port.Size.Height), - PortAlignment.Left => new Point(pt.X, pt.Y + Port.Size.Height / 2), - _ => pt, + PortAlignment.Top => Port.GetShape().GetPointAtAngle(270), + PortAlignment.TopRight => Port.GetShape().GetPointAtAngle(315), + PortAlignment.Right => Port.GetShape().GetPointAtAngle(0), + PortAlignment.BottomRight => Port.GetShape().GetPointAtAngle(45), + PortAlignment.Bottom => Port.GetShape().GetPointAtAngle(90), + PortAlignment.BottomLeft => Port.GetShape().GetPointAtAngle(135), + PortAlignment.Left => Port.GetShape().GetPointAtAngle(180), + PortAlignment.TopLeft => Port.GetShape().GetPointAtAngle(225), + _ => null, }; } - public override Point? GetPlainPosition() => Port.MiddlePosition; + return Port.Alignment switch + { + PortAlignment.Top => new Point(pt.X + Port.Size.Width / 2, pt.Y), + PortAlignment.TopRight => new Point(pt.X + Port.Size.Width, pt.Y), + PortAlignment.Right => new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height / 2), + PortAlignment.BottomRight => new Point(pt.X + Port.Size.Width, pt.Y + Port.Size.Height), + PortAlignment.Bottom => new Point(pt.X + Port.Size.Width / 2, pt.Y + Port.Size.Height), + PortAlignment.BottomLeft => new Point(pt.X, pt.Y + Port.Size.Height), + PortAlignment.Left => new Point(pt.X, pt.Y + Port.Size.Height / 2), + _ => pt, + }; } + + public override Point? GetPlainPosition() => Port.MiddlePosition; } diff --git a/src/Blazor.Diagrams.Core/Behavior.cs b/src/Blazor.Diagrams.Core/Behavior.cs index 2d81088f5..78e020602 100644 --- a/src/Blazor.Diagrams.Core/Behavior.cs +++ b/src/Blazor.Diagrams.Core/Behavior.cs @@ -1,16 +1,15 @@ using System; -namespace Blazor.Diagrams.Core +namespace Blazor.Diagrams.Core; + +public abstract class Behavior : IDisposable { - public abstract class Behavior : IDisposable + public Behavior(Diagram diagram) { - public Behavior(Diagram diagram) - { - Diagram = diagram; - } + Diagram = diagram; + } - protected Diagram Diagram { get; } + protected Diagram Diagram { get; } - public abstract void Dispose(); - } + public abstract void Dispose(); } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs index ff15e342c..f3fe081d1 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs @@ -2,93 +2,92 @@ using Blazor.Diagrams.Core.Models.Base; using System; -namespace Blazor.Diagrams.Core.Behaviors +namespace Blazor.Diagrams.Core.Behaviors; + +public class DebugEventsBehavior : Behavior { - public class DebugEventsBehavior : Behavior + public DebugEventsBehavior(Diagram diagram) : base(diagram) { - public DebugEventsBehavior(Diagram diagram) : base(diagram) - { - Diagram.Changed += Diagram_Changed; - Diagram.ContainerChanged += Diagram_ContainerChanged; - Diagram.PanChanged += Diagram_PanChanged; - Diagram.Nodes.Added += Nodes_Added; - Diagram.Nodes.Removed += Nodes_Removed; - Diagram.Links.Added += Links_Added; - Diagram.Links.Removed += Links_Removed; - Diagram.Groups.Added += Diagram_GroupAdded; - Diagram.Groups.Removed += Diagram_GroupRemoved; - Diagram.SelectionChanged += Diagram_SelectionChanged; - Diagram.ZoomChanged += Diagram_ZoomChanged; - } + Diagram.Changed += Diagram_Changed; + Diagram.ContainerChanged += Diagram_ContainerChanged; + Diagram.PanChanged += Diagram_PanChanged; + Diagram.Nodes.Added += Nodes_Added; + Diagram.Nodes.Removed += Nodes_Removed; + Diagram.Links.Added += Links_Added; + Diagram.Links.Removed += Links_Removed; + Diagram.Groups.Added += Diagram_GroupAdded; + Diagram.Groups.Removed += Diagram_GroupRemoved; + Diagram.SelectionChanged += Diagram_SelectionChanged; + Diagram.ZoomChanged += Diagram_ZoomChanged; + } - private void Diagram_ZoomChanged() - { - Console.WriteLine($"ZoomChanged, Zoom={Diagram.Zoom}"); - } + private void Diagram_ZoomChanged() + { + Console.WriteLine($"ZoomChanged, Zoom={Diagram.Zoom}"); + } - private void Diagram_SelectionChanged(SelectableModel obj) - { - Console.WriteLine($"SelectionChanged, Model={obj.GetType().Name}, Selected={obj.Selected}"); - } + private void Diagram_SelectionChanged(SelectableModel obj) + { + Console.WriteLine($"SelectionChanged, Model={obj.GetType().Name}, Selected={obj.Selected}"); + } - private void Links_Removed(BaseLinkModel obj) - { - Console.WriteLine($"Links.Removed, Links=[{obj}]"); - } + private void Links_Removed(BaseLinkModel obj) + { + Console.WriteLine($"Links.Removed, Links=[{obj}]"); + } - private void Links_Added(BaseLinkModel obj) - { - Console.WriteLine($"Links.Added, Links=[{obj}]"); - } + private void Links_Added(BaseLinkModel obj) + { + Console.WriteLine($"Links.Added, Links=[{obj}]"); + } - private void Nodes_Removed(NodeModel obj) - { - Console.WriteLine($"Nodes.Removed, Nodes=[{obj}]"); - } + private void Nodes_Removed(NodeModel obj) + { + Console.WriteLine($"Nodes.Removed, Nodes=[{obj}]"); + } - private void Nodes_Added(NodeModel obj) - { - Console.WriteLine($"Nodes.Added, Nodes=[{obj}]"); - } + private void Nodes_Added(NodeModel obj) + { + Console.WriteLine($"Nodes.Added, Nodes=[{obj}]"); + } - private void Diagram_PanChanged() - { - Console.WriteLine($"PanChanged, Pan={Diagram.Pan}"); - } + private void Diagram_PanChanged() + { + Console.WriteLine($"PanChanged, Pan={Diagram.Pan}"); + } - private void Diagram_GroupRemoved(GroupModel obj) - { - Console.WriteLine($"GroupRemoved, Id={obj.Id}"); - } + private void Diagram_GroupRemoved(GroupModel obj) + { + Console.WriteLine($"GroupRemoved, Id={obj.Id}"); + } - private void Diagram_GroupAdded(GroupModel obj) - { - Console.WriteLine($"GroupAdded, Id={obj.Id}"); - } + private void Diagram_GroupAdded(GroupModel obj) + { + Console.WriteLine($"GroupAdded, Id={obj.Id}"); + } - private void Diagram_ContainerChanged() - { - Console.WriteLine($"ContainerChanged, Container={Diagram.Container}"); - } + private void Diagram_ContainerChanged() + { + Console.WriteLine($"ContainerChanged, Container={Diagram.Container}"); + } - private void Diagram_Changed() - { - Console.WriteLine("Changed"); - } + private void Diagram_Changed() + { + Console.WriteLine("Changed"); + } - public override void Dispose() - { - Diagram.Changed -= Diagram_Changed; - Diagram.ContainerChanged -= Diagram_ContainerChanged; - Diagram.PanChanged -= Diagram_PanChanged; - Diagram.Nodes.Added -= Nodes_Added; - Diagram.Nodes.Removed -= Nodes_Removed; - Diagram.Links.Added -= Links_Added; - Diagram.Links.Removed -= Links_Removed; - Diagram.Groups.Added -= Diagram_GroupAdded; - Diagram.Groups.Removed -= Diagram_GroupRemoved; - Diagram.SelectionChanged -= Diagram_SelectionChanged; - Diagram.ZoomChanged -= Diagram_ZoomChanged; - } + public override void Dispose() + { + Diagram.Changed -= Diagram_Changed; + Diagram.ContainerChanged -= Diagram_ContainerChanged; + Diagram.PanChanged -= Diagram_PanChanged; + Diagram.Nodes.Added -= Nodes_Added; + Diagram.Nodes.Removed -= Nodes_Removed; + Diagram.Links.Added -= Links_Added; + Diagram.Links.Removed -= Links_Removed; + Diagram.Groups.Added -= Diagram_GroupAdded; + Diagram.Groups.Removed -= Diagram_GroupRemoved; + Diagram.SelectionChanged -= Diagram_SelectionChanged; + Diagram.ZoomChanged -= Diagram_ZoomChanged; } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 7d4240ba1..692a83dd1 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -5,115 +5,114 @@ using System.Collections.Generic; using Blazor.Diagrams.Core.Models; -namespace Blazor.Diagrams.Core.Behaviors +namespace Blazor.Diagrams.Core.Behaviors; + +public class DragMovablesBehavior : Behavior { - public class DragMovablesBehavior : Behavior + private readonly Dictionary _initialPositions; + private double? _lastClientX; + private double? _lastClientY; + private bool _moved; + + public DragMovablesBehavior(Diagram diagram) : base(diagram) { - private readonly Dictionary _initialPositions; - private double? _lastClientX; - private double? _lastClientY; - private bool _moved; + _initialPositions = new Dictionary(); + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + } - 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; - private void OnPointerDown(Model? model, PointerEventArgs e) + _initialPositions.Clear(); + foreach (var sm in Diagram.GetSelectedModels()) { - if (model is not MovableModel) - return; - - _initialPositions.Clear(); - foreach (var sm in Diagram.GetSelectedModels()) - { - if (sm is not MovableModel movable || movable.Locked) - continue; + 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; + // 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); + 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); } - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _moved = false; + _initialPositions.Add(movable, position); } - private 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; - var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; - var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; + 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) + 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) { - 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); - } + 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(); - _lastClientX = null; - _lastClientY = null; } + + _initialPositions.Clear(); + _lastClientX = null; + _lastClientY = null; + } - private double ApplyGridSize(double n) - { - if (Diagram.Options.GridSize == null) - return n; + 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); - } + // 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; - } + public override void Dispose() + { + _initialPositions.Clear(); + + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 2ac47210e..e50b5a9d9 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -4,148 +4,147 @@ using System.Linq; using Blazor.Diagrams.Core.Anchors; -namespace Blazor.Diagrams.Core.Behaviors +namespace Blazor.Diagrams.Core.Behaviors; + +public class DragNewLinkBehavior : Behavior { - public class DragNewLinkBehavior : Behavior + private BaseLinkModel? _ongoingLink; + private PositionAnchor? _targetPositionAnchor; + + public DragNewLinkBehavior(Diagram diagram) : base(diagram) { - private BaseLinkModel? _ongoingLink; - private PositionAnchor? _targetPositionAnchor; + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + } - 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(Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5)); + _ongoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor); + if (_ongoingLink == null) + return; + + Diagram.Links.Add(_ongoingLink); + } - public void StartFrom(ILinkable source, double clientX, double clientY) + public void StartFrom(BaseLinkModel link, double clientX, double clientY) + { + if (_ongoingLink != null) + return; + + _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5)); + _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 (_ongoingLink != null) + if (port.Locked) return; - _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5)); - _ongoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor); + _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5)); + _ongoingLink = Diagram.Options.Links.Factory(Diagram, port, _targetPositionAnchor); if (_ongoingLink == null) return; + _ongoingLink.SetTarget(_targetPositionAnchor); Diagram.Links.Add(_ongoingLink); } + } - public void StartFrom(BaseLinkModel link, double clientX, double clientY) - { - if (_ongoingLink != null) - return; + private void OnPointerMove(Model? model, MouseEventArgs e) + { + if (_ongoingLink == null || model != null) + return; - _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5)); - _ongoingLink = link; - _ongoingLink.SetTarget(_targetPositionAnchor); - _ongoingLink.Refresh(); - _ongoingLink.RefreshLinks(); - } + _targetPositionAnchor!.SetPosition(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5)); - private void OnPointerDown(Model? model, MouseEventArgs e) + if (Diagram.Options.Links.EnableSnapping) { - if (e.Button != (int)MouseEventButton.Left) - return; - - _ongoingLink = null; - _targetPositionAnchor = null; - - if (model is PortModel port) + var nearPort = FindNearPortToAttachTo(); + if (nearPort != null || _ongoingLink.Target is not PositionAnchor) { - if (port.Locked) - return; - - _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5)); - _ongoingLink = Diagram.Options.Links.Factory(Diagram, port, _targetPositionAnchor); - if (_ongoingLink == null) - return; - - _ongoingLink.SetTarget(_targetPositionAnchor); - Diagram.Links.Add(_ongoingLink); + _ongoingLink.SetTarget(nearPort is null ? _targetPositionAnchor : new SinglePortAnchor(nearPort)); } } - private void OnPointerMove(Model? model, MouseEventArgs e) - { - if (_ongoingLink == null || model != null) - return; + _ongoingLink.Refresh(); + _ongoingLink.RefreshLinks(); + } - _targetPositionAnchor!.SetPosition(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5)); + private void OnPointerUp(Model? model, MouseEventArgs e) + { + if (_ongoingLink == null) + return; - 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)); - } - } + 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(); } - - private void OnPointerUp(Model? model, MouseEventArgs e) + else if (Diagram.Options.Links.RequireTarget) { - if (_ongoingLink == null) - return; - - if (_ongoingLink.IsAttached) // Snapped already - { - _ongoingLink.TriggerTargetAttached(); - _ongoingLink = null; - return; - } + Diagram.Links.Remove(_ongoingLink); + } - 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); - } + _ongoingLink = null; + } - _ongoingLink = null; - } + private PortModel? FindNearPortToAttachTo() + { + if (_ongoingLink is null || _targetPositionAnchor is null) + return null; - private PortModel? FindNearPortToAttachTo() - { - if (_ongoingLink is null || _targetPositionAnchor is null) - return null; + PortModel? nearestSnapPort = null; + var nearestSnapPortDistance = double.PositiveInfinity; - PortModel? nearestSnapPort = null; - var nearestSnapPortDistance = double.PositiveInfinity; + var position = _targetPositionAnchor!.GetPosition(_ongoingLink)!; - var position = _targetPositionAnchor!.GetPosition(_ongoingLink)!; + foreach (var port in Diagram.Nodes.SelectMany((NodeModel n) => n.Ports)) + { + var distance = position.DistanceTo(port.Position); - foreach (var port in Diagram.Nodes.SelectMany((NodeModel n) => n.Ports)) + if (distance <= Diagram.Options.Links.SnappingRadius && (_ongoingLink.Source.Model?.CanAttachTo(port) != false)) { - var distance = position.DistanceTo(port.Position); - - if (distance <= Diagram.Options.Links.SnappingRadius && (_ongoingLink.Source.Model?.CanAttachTo(port) != false)) + if (distance < nearestSnapPortDistance) { - if (distance < nearestSnapPortDistance) - { - nearestSnapPortDistance = distance; - nearestSnapPort = port; - } + nearestSnapPortDistance = distance; + nearestSnapPort = port; } } - - return nearestSnapPort; } - public override void Dispose() - { - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - } + return nearestSnapPort; + } + + public override void Dispose() + { + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs index 107659baf..6bb8bc0c7 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs @@ -2,70 +2,69 @@ using Blazor.Diagrams.Core.Events; using System.Diagnostics; -namespace Blazor.Diagrams.Core.Behaviors +namespace Blazor.Diagrams.Core.Behaviors; + +public class EventsBehavior : Behavior { - public class EventsBehavior : Behavior - { - private readonly Stopwatch _mouseClickSw; - private Model? _model; - private bool _captureMouseMove; - private int _mouseMovedCount; + private readonly Stopwatch _mouseClickSw; + private Model? _model; + private bool _captureMouseMove; + private int _mouseMovedCount; - public EventsBehavior(Diagram diagram) : base(diagram) - { - _mouseClickSw = new Stopwatch(); + public EventsBehavior(Diagram diagram) : base(diagram) + { + _mouseClickSw = new Stopwatch(); - Diagram.PointerDown += OnPointerDown; - Diagram.PointerMove += OnPointerMove; - Diagram.PointerUp += OnPointerUp; - Diagram.PointerClick += OnPointerClick; - } + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + Diagram.PointerClick += OnPointerClick; + } - private void OnPointerClick(Model? model, PointerEventArgs e) + private void OnPointerClick(Model? model, PointerEventArgs e) + { + if (_mouseClickSw.IsRunning && _mouseClickSw.ElapsedMilliseconds <= 500) { - if (_mouseClickSw.IsRunning && _mouseClickSw.ElapsedMilliseconds <= 500) - { - Diagram.TriggerPointerDoubleClick(model, e); - } - - _mouseClickSw.Restart(); + Diagram.TriggerPointerDoubleClick(model, e); } - private void OnPointerDown(Model? model, PointerEventArgs e) - { - _captureMouseMove = true; - _mouseMovedCount = 0; - _model = model; - } + _mouseClickSw.Restart(); + } - private void OnPointerMove(Model? model, PointerEventArgs e) - { - if (!_captureMouseMove) - return; + private void OnPointerDown(Model? model, PointerEventArgs e) + { + _captureMouseMove = true; + _mouseMovedCount = 0; + _model = model; + } - _mouseMovedCount++; - } + private void OnPointerMove(Model? model, PointerEventArgs e) + { + if (!_captureMouseMove) + return; - private void OnPointerUp(Model? model, PointerEventArgs e) - { - if (!_captureMouseMove) return; // Only set by OnMouseDown - _captureMouseMove = false; - if (_mouseMovedCount > 0) return; + _mouseMovedCount++; + } - if (_model == model) - { - Diagram.TriggerPointerClick(model, e); - _model = null; - } - } + private void OnPointerUp(Model? model, PointerEventArgs e) + { + if (!_captureMouseMove) return; // Only set by OnMouseDown + _captureMouseMove = false; + if (_mouseMovedCount > 0) return; - public override void Dispose() + if (_model == model) { - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - Diagram.PointerClick -= OnPointerClick; + Diagram.TriggerPointerClick(model, e); _model = null; } } + + public override void Dispose() + { + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + Diagram.PointerClick -= OnPointerClick; + _model = null; + } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs index c48293617..5243bed09 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs @@ -4,45 +4,44 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Blazor.Diagrams.Core.Behaviors +namespace Blazor.Diagrams.Core.Behaviors; + +public class KeyboardShortcutsBehavior : Behavior { - public class KeyboardShortcutsBehavior : Behavior - { - private readonly Dictionary> _shortcuts; + private readonly Dictionary> _shortcuts; - public KeyboardShortcutsBehavior(Diagram diagram) : base(diagram) - { - _shortcuts = new Dictionary>(); - SetShortcut("Delete", false, false, false, KeyboardShortcutsDefaults.DeleteSelection); - SetShortcut("g", true, false, true, KeyboardShortcutsDefaults.Grouping); + public KeyboardShortcutsBehavior(Diagram diagram) : base(diagram) + { + _shortcuts = new Dictionary>(); + SetShortcut("Delete", false, false, false, KeyboardShortcutsDefaults.DeleteSelection); + SetShortcut("g", true, false, true, KeyboardShortcutsDefaults.Grouping); - Diagram.KeyDown += OnDiagramKeyDown; - } + Diagram.KeyDown += OnDiagramKeyDown; + } - public void SetShortcut(string key, bool ctrl, bool shift, bool alt, Func action) - { - var k = KeysUtils.GetStringRepresentation(ctrl, shift, alt, key); - _shortcuts[k] = action; - } + public void SetShortcut(string key, bool ctrl, bool shift, bool alt, Func action) + { + var k = KeysUtils.GetStringRepresentation(ctrl, shift, alt, key); + _shortcuts[k] = action; + } - public bool RemoveShortcut(string key, bool ctrl, bool shift, bool alt) - { - var k = KeysUtils.GetStringRepresentation(ctrl, shift, alt, key); - return _shortcuts.Remove(k); - } + public bool RemoveShortcut(string key, bool ctrl, bool shift, bool alt) + { + var k = KeysUtils.GetStringRepresentation(ctrl, shift, alt, key); + return _shortcuts.Remove(k); + } - private async void OnDiagramKeyDown(KeyboardEventArgs e) + private async void OnDiagramKeyDown(KeyboardEventArgs e) + { + var k = KeysUtils.GetStringRepresentation(e.CtrlKey, e.ShiftKey, e.AltKey, e.Key); + if (_shortcuts.TryGetValue(k, out var action)) { - var k = KeysUtils.GetStringRepresentation(e.CtrlKey, e.ShiftKey, e.AltKey, e.Key); - if (_shortcuts.TryGetValue(k, out var action)) - { - await action(Diagram); - } + await action(Diagram); } + } - public override void Dispose() - { - Diagram.KeyDown -= OnDiagramKeyDown; - } + public override void Dispose() + { + Diagram.KeyDown -= OnDiagramKeyDown; } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs index 4c8fa1786..0c338114a 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsDefaults.cs @@ -3,72 +3,71 @@ using System.Linq; using System.Threading.Tasks; -namespace Blazor.Diagrams.Core.Behaviors +namespace Blazor.Diagrams.Core.Behaviors; + +public static class KeyboardShortcutsDefaults { - public static class KeyboardShortcutsDefaults + public static async ValueTask DeleteSelection(Diagram diagram) { - public static async ValueTask DeleteSelection(Diagram diagram) + var wasSuspended = diagram.SuspendRefresh; + if (!wasSuspended) diagram.SuspendRefresh = true; + + foreach (var sm in diagram.GetSelectedModels().ToArray()) { - var wasSuspended = diagram.SuspendRefresh; - if (!wasSuspended) diagram.SuspendRefresh = true; + if (sm.Locked) + continue; - foreach (var sm in diagram.GetSelectedModels().ToArray()) + if (sm is GroupModel group && (await diagram.Options.Constraints.ShouldDeleteGroup(group))) { - if (sm.Locked) - continue; - - if (sm is GroupModel group && (await diagram.Options.Constraints.ShouldDeleteGroup(group))) - { - diagram.Groups.Delete(group); - } - else if (sm is NodeModel node && (await diagram.Options.Constraints.ShouldDeleteNode(node))) - { - diagram.Nodes.Remove(node); - } - else if (sm is BaseLinkModel link && (await diagram.Options.Constraints.ShouldDeleteLink(link))) - { - diagram.Links.Remove(link); - } + diagram.Groups.Delete(group); } - - if (!wasSuspended) + else if (sm is NodeModel node && (await diagram.Options.Constraints.ShouldDeleteNode(node))) + { + diagram.Nodes.Remove(node); + } + else if (sm is BaseLinkModel link && (await diagram.Options.Constraints.ShouldDeleteLink(link))) { - diagram.SuspendRefresh = false; - diagram.Refresh(); + diagram.Links.Remove(link); } } - public static ValueTask Grouping(Diagram diagram) + if (!wasSuspended) { - if (!diagram.Options.Groups.Enabled) - return ValueTask.CompletedTask; + diagram.SuspendRefresh = false; + diagram.Refresh(); + } + } - if (!diagram.GetSelectedModels().Any()) - return ValueTask.CompletedTask; + public static ValueTask Grouping(Diagram diagram) + { + if (!diagram.Options.Groups.Enabled) + return ValueTask.CompletedTask; - var selectedNodes = diagram.Nodes.Where(n => n.Selected).ToArray(); - var nodesWithGroup = selectedNodes.Where(n => n.Group != null).ToArray(); - if (nodesWithGroup.Length > 0) + if (!diagram.GetSelectedModels().Any()) + return ValueTask.CompletedTask; + + var selectedNodes = diagram.Nodes.Where(n => n.Selected).ToArray(); + var nodesWithGroup = selectedNodes.Where(n => n.Group != null).ToArray(); + if (nodesWithGroup.Length > 0) + { + // Ungroup + foreach (var group in nodesWithGroup.GroupBy(n => n.Group!).Select(g => g.Key)) { - // Ungroup - foreach (var group in nodesWithGroup.GroupBy(n => n.Group!).Select(g => g.Key)) - { - diagram.Groups.Remove(group); - } + diagram.Groups.Remove(group); } - else - { - // Group - if (selectedNodes.Length < 2) - return ValueTask.CompletedTask; - - if (selectedNodes.Any(n => n.Group != null)) - return ValueTask.CompletedTask; + } + else + { + // Group + if (selectedNodes.Length < 2) + return ValueTask.CompletedTask; - diagram.Groups.Group(selectedNodes); - } + if (selectedNodes.Any(n => n.Group != null)) + return ValueTask.CompletedTask; - return ValueTask.CompletedTask; + diagram.Groups.Group(selectedNodes); } + + return ValueTask.CompletedTask; } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs index 3e94b9d58..bb4d974b7 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs @@ -2,66 +2,65 @@ using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Events; -namespace Blazor.Diagrams.Core.Behaviors +namespace Blazor.Diagrams.Core.Behaviors; + +public class PanBehavior : Behavior { - public class PanBehavior : Behavior - { - private Point? _initialPan; - private double _lastClientX; - private double _lastClientY; + private Point? _initialPan; + private double _lastClientX; + private double _lastClientY; - public PanBehavior(Diagram diagram) : base(diagram) - { - Diagram.PointerDown += OnPointerDown; - Diagram.PointerMove += OnPointerMove; - Diagram.PointerUp += OnPointerUp; - } + public PanBehavior(Diagram diagram) : base(diagram) + { + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + } - private void OnPointerDown(Model? model, PointerEventArgs e) - { - if (e.Button != (int)MouseEventButton.Left) - return; + private void OnPointerDown(Model? model, PointerEventArgs e) + { + if (e.Button != (int)MouseEventButton.Left) + return; - Start(model, e.ClientX, e.ClientY, e.ShiftKey); - } + Start(model, e.ClientX, e.ClientY, e.ShiftKey); + } - private void OnPointerMove(Model? model, PointerEventArgs e) => Move(e.ClientX, e.ClientY); + private void OnPointerMove(Model? model, PointerEventArgs e) => Move(e.ClientX, e.ClientY); - private void OnPointerUp(Model? model, PointerEventArgs e) => End(); + private void OnPointerUp(Model? model, PointerEventArgs e) => End(); - private void Start(Model? model, double clientX, double clientY, bool shiftKey) - { - if (!Diagram.Options.AllowPanning || model != null || shiftKey) - return; + private void Start(Model? model, double clientX, double clientY, bool shiftKey) + { + if (!Diagram.Options.AllowPanning || model != null || shiftKey) + return; - _initialPan = Diagram.Pan; - _lastClientX = clientX; - _lastClientY = clientY; - } + _initialPan = Diagram.Pan; + _lastClientX = clientX; + _lastClientY = clientY; + } - private void Move(double clientX, double clientY) - { - if (!Diagram.Options.AllowPanning || _initialPan == null) - return; + private void Move(double clientX, double clientY) + { + if (!Diagram.Options.AllowPanning || _initialPan == null) + return; - var deltaX = clientX - _lastClientX - (Diagram.Pan.X - _initialPan.X); - var deltaY = clientY - _lastClientY - (Diagram.Pan.Y - _initialPan.Y); - Diagram.UpdatePan(deltaX, deltaY); - } + var deltaX = clientX - _lastClientX - (Diagram.Pan.X - _initialPan.X); + var deltaY = clientY - _lastClientY - (Diagram.Pan.Y - _initialPan.Y); + Diagram.UpdatePan(deltaX, deltaY); + } - private void End() - { - if (!Diagram.Options.AllowPanning) - return; + private void End() + { + if (!Diagram.Options.AllowPanning) + return; - _initialPan = null; - } + _initialPan = null; + } - public override void Dispose() - { - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - } + public override void Dispose() + { + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs index 016427eb8..8c3d75ab8 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs @@ -1,41 +1,40 @@ using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Events; -namespace Blazor.Diagrams.Core.Behaviors +namespace Blazor.Diagrams.Core.Behaviors; + +public class SelectionBehavior : Behavior { - public class SelectionBehavior : Behavior + public SelectionBehavior(Diagram diagram) : base(diagram) { - public SelectionBehavior(Diagram diagram) : base(diagram) - { - Diagram.PointerDown += OnPointerDown; - } + Diagram.PointerDown += OnPointerDown; + } - private void OnPointerDown(Model? model, PointerEventArgs e) + private void OnPointerDown(Model? model, PointerEventArgs e) + { + var ctrlKey = e.CtrlKey; + switch (model) { - var ctrlKey = e.CtrlKey; - switch (model) + case null: + Diagram.UnselectAll(); + break; + case SelectableModel sm when ctrlKey && sm.Selected: + Diagram.UnselectModel(sm); + break; + case SelectableModel sm: { - case null: - Diagram.UnselectAll(); - break; - case SelectableModel sm when ctrlKey && sm.Selected: - Diagram.UnselectModel(sm); - break; - case SelectableModel sm: + if (!sm.Selected) { - if (!sm.Selected) - { - Diagram.SelectModel(sm, !ctrlKey || !Diagram.Options.AllowMultiSelection); - } - - break; + Diagram.SelectModel(sm, !ctrlKey || !Diagram.Options.AllowMultiSelection); } + + break; } } + } - public override void Dispose() - { - Diagram.PointerDown -= OnPointerDown; - } + public override void Dispose() + { + Diagram.PointerDown -= OnPointerDown; } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs index c145d35e3..ee6cca73e 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs @@ -4,55 +4,54 @@ using System; -namespace Blazor.Diagrams.Core.Behaviors +namespace Blazor.Diagrams.Core.Behaviors; + +public class ZoomBehavior : Behavior { - public class ZoomBehavior : Behavior + public ZoomBehavior(Diagram diagram) : base(diagram) { - public ZoomBehavior(Diagram diagram) : base(diagram) - { - Diagram.Wheel += Diagram_Wheel; - } + Diagram.Wheel += Diagram_Wheel; + } - private void Diagram_Wheel(WheelEventArgs e) - { - if (Diagram.Container == null || e.DeltaY == 0) - return; - - if (!Diagram.Options.Zoom.Enabled) - 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); - - 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; - - Diagram.Batch(() => - { - Diagram.SetPan(newPanX, newPanY); - Diagram.SetZoom(newZoom); - }); - } - - public override void Dispose() + private void Diagram_Wheel(WheelEventArgs e) + { + if (Diagram.Container == null || e.DeltaY == 0) + return; + + if (!Diagram.Options.Zoom.Enabled) + 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); + + 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; + + Diagram.Batch(() => { - Diagram.Wheel -= Diagram_Wheel; - } + Diagram.SetPan(newPanX, newPanY); + Diagram.SetZoom(newZoom); + }); + } + + public override void Dispose() + { + Diagram.Wheel -= Diagram_Wheel; } } diff --git a/src/Blazor.Diagrams.Core/Controls/Default/ArrowHeadControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ArrowHeadControl.cs index 0fccc3911..802bd6178 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/ArrowHeadControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/ArrowHeadControl.cs @@ -7,53 +7,52 @@ using System; using System.Threading.Tasks; -namespace Blazor.Diagrams.Core.Controls.Default +namespace Blazor.Diagrams.Core.Controls.Default; + +public class ArrowHeadControl : ExecutableControl { - public class ArrowHeadControl : ExecutableControl + public ArrowHeadControl(bool source, LinkMarker? marker = null) { - public ArrowHeadControl(bool source, LinkMarker? marker = null) - { - Source = source; - Marker = marker ?? LinkMarker.NewArrow(20, 20); - } + Source = source; + Marker = marker ?? LinkMarker.NewArrow(20, 20); + } - public bool Source { get; } - public LinkMarker Marker { get; } - public double Angle { get; private set; } + public bool Source { get; } + public LinkMarker Marker { get; } + public double Angle { get; private set; } - public override Point? GetPosition(Model model) - { - if (model is not BaseLinkModel link) - throw new DiagramsException("ArrowHeadControl only works for models of type BaseLinkModel"); + public override Point? GetPosition(Model model) + { + if (model is not BaseLinkModel link) + throw new DiagramsException("ArrowHeadControl only works for models of type BaseLinkModel"); - var dist = Source ? Marker.Width - (link.SourceMarker?.Width ?? 0) : (link.TargetMarker?.Width ?? 0) - Marker.Width; - var pp = new LinkPathPositionProvider(dist); - var p1 = pp.GetPosition(link); - if (p1 is not null) + var dist = Source ? Marker.Width - (link.SourceMarker?.Width ?? 0) : (link.TargetMarker?.Width ?? 0) - Marker.Width; + var pp = new LinkPathPositionProvider(dist); + var p1 = pp.GetPosition(link); + if (p1 is not null) + { + var p2 = Source ? link.Source.GetPosition(link) : link.Target.GetPosition(link); + if (p2 is not null) { - var p2 = Source ? link.Source.GetPosition(link) : link.Target.GetPosition(link); - if (p2 is not null) - { - Angle = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X) * 180 / Math.PI; - } + Angle = Math.Atan2(p2.Y - p1.Y, p2.X - p1.X) * 180 / Math.PI; } - - return p1; } - public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) - { - if (model is not BaseLinkModel link) - throw new DiagramsException("ArrowHeadControl only works for models of type BaseLinkModel"); + return p1; + } - var dnlb = diagram.GetBehavior()!; - if (Source) - { - link.SetSource(link.Target); - } + public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) + { + if (model is not BaseLinkModel link) + throw new DiagramsException("ArrowHeadControl only works for models of type BaseLinkModel"); - dnlb.StartFrom(link, e.ClientX, e.ClientY); - return ValueTask.CompletedTask; + var dnlb = diagram.GetBehavior()!; + if (Source) + { + link.SetSource(link.Target); } + + dnlb.StartFrom(link, e.ClientX, e.ClientY); + return ValueTask.CompletedTask; } } diff --git a/src/Blazor.Diagrams.Core/Delegates.cs b/src/Blazor.Diagrams.Core/Delegates.cs index b71be543c..4a128a2d3 100644 --- a/src/Blazor.Diagrams.Core/Delegates.cs +++ b/src/Blazor.Diagrams.Core/Delegates.cs @@ -2,11 +2,10 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core -{ - public delegate BaseLinkModel? LinkFactory(Diagram diagram, ILinkable source, Anchor targetAnchor); +namespace Blazor.Diagrams.Core; - public delegate Anchor AnchorFactory(Diagram diagram, BaseLinkModel link, ILinkable model); +public delegate BaseLinkModel? LinkFactory(Diagram diagram, ILinkable source, Anchor targetAnchor); - public delegate GroupModel GroupFactory(Diagram diagram, NodeModel[] children); -} +public delegate Anchor AnchorFactory(Diagram diagram, BaseLinkModel link, ILinkable model); + +public delegate GroupModel GroupFactory(Diagram diagram, NodeModel[] children); diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 3284429ba..a2fffeacb 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -15,393 +15,392 @@ [assembly: InternalsVisibleTo("Blazor.Diagrams.Tests")] [assembly: InternalsVisibleTo("Blazor.Diagrams.Core.Tests")] -namespace Blazor.Diagrams.Core +namespace Blazor.Diagrams.Core; + +public abstract class Diagram { - 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() { - 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(); - - Nodes.Added += OnSelectableAdded; - Links.Added += OnSelectableAdded; - Groups.Added += OnSelectableAdded; - - Nodes.Removed += OnSelectableRemoved; - Links.Removed += OnSelectableRemoved; - Groups.Removed += OnSelectableRemoved; - - 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)); - } + _behaviors = new Dictionary(); + _orderedSelectables = new List(); + + Nodes = new NodeLayer(this); + Links = new LinkLayer(this); + Groups = new GroupLayer(this); + Controls = new ControlsLayer(); + + Nodes.Added += OnSelectableAdded; + Links.Added += OnSelectableAdded; + Groups.Added += OnSelectableAdded; + + Nodes.Removed += OnSelectableRemoved; + Links.Removed += OnSelectableRemoved; + Groups.Removed += OnSelectableRemoved; + + 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)); + } - public abstract DiagramOptions Options { 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 abstract DiagramOptions Options { 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(); - } + Changed?.Invoke(); + } - public void Batch(Action action) + public void Batch(Action action) + { + if (SuspendRefresh) { - 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; + // If it's already suspended, just execute the action and leave it suspended + // It's probably handled by an outer batch action(); - SuspendRefresh = false; - Refresh(); + return; } - #region Selection + SuspendRefresh = true; + action(); + SuspendRefresh = false; + Refresh(); + } - public IEnumerable GetSelectedModels() + #region Selection + + public IEnumerable GetSelectedModels() + { + foreach (var node in Nodes) { - foreach (var node in Nodes) - { - if (node.Selected) - yield return node; - } + 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 link in Links) + { + if (link.Selected) + yield return link; - foreach (var group in Groups) + foreach (var vertex in link.Vertices) { - if (group.Selected) - yield return group; + if (vertex.Selected) + yield return vertex; } } - public void SelectModel(SelectableModel model, bool unselectOthers) + foreach (var group in Groups) { - if (model.Selected) - return; + 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); + } + + 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); } + } - 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 + #region Behaviors - public void RegisterBehavior(Behavior behavior) - { - var type = behavior.GetType(); - if (_behaviors.ContainsKey(type)) - throw new Exception($"Behavior '{type.Name}' already registered"); + 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); - } + _behaviors.Add(type, behavior); + } - public T? GetBehavior() where T : Behavior - { - var type = typeof(T); - return (T?)(_behaviors.ContainsKey(type) ? _behaviors[type] : null); - } + 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; + public void UnregisterBehavior() where T : Behavior + { + var type = typeof(T); + if (!_behaviors.ContainsKey(type)) + return; - _behaviors[type].Dispose(); - _behaviors.Remove(type); - } + _behaviors[type].Dispose(); + _behaviors.Remove(type); + } - #endregion + #endregion - public void ZoomToFit(double margin = 10) - { - if (Container == null || Nodes.Count == 0) - return; + 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; + 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; + SuspendRefresh = true; - var xf = Container.Width / width; - var yf = Container.Height / height; - SetZoom(Math.Min(xf, yf)); + 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); + 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(); - } + SuspendRefresh = false; + Refresh(); + } - public void SetPan(double x, double y) - { - Pan = new Point(x, y); - PanChanged?.Invoke(); - 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 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"); + 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; + if (newZoom < Options.Zoom.Minimum) + newZoom = Options.Zoom.Minimum; - Zoom = newZoom; - ZoomChanged?.Invoke(); - Refresh(); - } + Zoom = newZoom; + ZoomChanged?.Invoke(); + Refresh(); + } - public void SetContainer(Rectangle newRect) - { - if (newRect.Equals(Container)) - return; + public void SetContainer(Rectangle newRect) + { + if (newRect.Equals(Container)) + return; - Container = newRect; - ContainerChanged?.Invoke(); - Refresh(); - } + 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"); + 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); - } + 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"); + 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); - } + 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"); + 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); - } + return new Point(Zoom * clientX + Container.Left + Pan.X, Zoom * clientY + Container.Top + Pan.Y); + } - #region Ordering + #region Ordering - public void SendToBack(SelectableModel model) - { - var minOrder = GetMinOrder(); - if (model.Order == minOrder) - return; + public void SendToBack(SelectableModel model) + { + var minOrder = GetMinOrder(); + if (model.Order == minOrder) + return; - if (!_orderedSelectables.Remove(model)) - return; + if (!_orderedSelectables.Remove(model)) + return; - _orderedSelectables.Insert(0, model); + _orderedSelectables.Insert(0, model); - // Todo: can optimize this by only updating the order of items before model - Batch(() => + // Todo: can optimize this by only updating the order of items before model + Batch(() => + { + SuspendSorting = true; + for (var i = 0; i < _orderedSelectables.Count; i++) { - SuspendSorting = true; - for (var i = 0; i < _orderedSelectables.Count; i++) - { - _orderedSelectables[i].Order = i + 1; - } - SuspendSorting = false; - }); - } + _orderedSelectables[i].Order = i + 1; + } + SuspendSorting = false; + }); + } - public void SendToFront(SelectableModel model) - { - var maxOrder = GetMaxOrder(); - if (model.Order == maxOrder) - return; + public void SendToFront(SelectableModel model) + { + var maxOrder = GetMaxOrder(); + if (model.Order == maxOrder) + return; - if (!_orderedSelectables.Remove(model)) - return; + if (!_orderedSelectables.Remove(model)) + return; - _orderedSelectables.Add(model); + _orderedSelectables.Add(model); - SuspendSorting = true; - model.Order = maxOrder + 1; - SuspendSorting = false; - Refresh(); - } + 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) + /// + /// 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) { - _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); - - if (refresh) - { - 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 OnSelectableAdded(SelectableModel model) + { + var maxOrder = GetMaxOrder(); + _orderedSelectables.Add(model); - private void OnSelectableRemoved(SelectableModel model) + if (model.Order == 0) { - model.OrderChanged -= OnModelOrderChanged; - _orderedSelectables.Remove(model); + model.Order = maxOrder + 1; } - private void OnModelOrderChanged(Model model) - { - if (SuspendSorting) - return; + model.OrderChanged += OnModelOrderChanged; + } - RefreshOrders(); - } + private void OnSelectableRemoved(SelectableModel model) + { + model.OrderChanged -= OnModelOrderChanged; + _orderedSelectables.Remove(model); + } + + private void OnModelOrderChanged(Model model) + { + if (SuspendSorting) + return; + + 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/DiagramsException.cs b/src/Blazor.Diagrams.Core/DiagramsException.cs index ac16ec75b..f56cdc0c4 100644 --- a/src/Blazor.Diagrams.Core/DiagramsException.cs +++ b/src/Blazor.Diagrams.Core/DiagramsException.cs @@ -1,11 +1,10 @@ using System; -namespace Blazor.Diagrams.Core +namespace Blazor.Diagrams.Core; + +public class DiagramsException : Exception { - public class DiagramsException : Exception + public DiagramsException(string? message) : base(message) { - public DiagramsException(string? message) : base(message) - { - } } } diff --git a/src/Blazor.Diagrams.Core/Events/KeyboardEventArgs.cs b/src/Blazor.Diagrams.Core/Events/KeyboardEventArgs.cs index b5791ab5d..ee1fc02cd 100644 --- a/src/Blazor.Diagrams.Core/Events/KeyboardEventArgs.cs +++ b/src/Blazor.Diagrams.Core/Events/KeyboardEventArgs.cs @@ -1,4 +1,3 @@ -namespace Blazor.Diagrams.Core.Events -{ - public record KeyboardEventArgs(string Key, string Code, float Location, bool CtrlKey, bool ShiftKey, bool AltKey); -} +namespace Blazor.Diagrams.Core.Events; + +public record KeyboardEventArgs(string Key, string Code, float Location, bool CtrlKey, bool ShiftKey, bool AltKey); diff --git a/src/Blazor.Diagrams.Core/Events/MouseEventArgs.cs b/src/Blazor.Diagrams.Core/Events/MouseEventArgs.cs index e083db90b..94c4cba58 100644 --- a/src/Blazor.Diagrams.Core/Events/MouseEventArgs.cs +++ b/src/Blazor.Diagrams.Core/Events/MouseEventArgs.cs @@ -1,4 +1,3 @@ -namespace Blazor.Diagrams.Core.Events -{ - public record MouseEventArgs(double ClientX, double ClientY, long Button, long Buttons, bool CtrlKey, bool ShiftKey, bool AltKey); -} +namespace Blazor.Diagrams.Core.Events; + +public record MouseEventArgs(double ClientX, double ClientY, long Button, long Buttons, bool CtrlKey, bool ShiftKey, bool AltKey); diff --git a/src/Blazor.Diagrams.Core/Events/TouchEventArgs.cs b/src/Blazor.Diagrams.Core/Events/TouchEventArgs.cs index 9c20defe7..231234390 100644 --- a/src/Blazor.Diagrams.Core/Events/TouchEventArgs.cs +++ b/src/Blazor.Diagrams.Core/Events/TouchEventArgs.cs @@ -1,5 +1,4 @@ -namespace Blazor.Diagrams.Core.Events -{ - public record TouchEventArgs(TouchPoint[] ChangedTouches, bool CtrlKey, bool ShiftKey, bool AltKey); - public record TouchPoint(long Identifier, double ClientX, double ClientY); -} +namespace Blazor.Diagrams.Core.Events; + +public record TouchEventArgs(TouchPoint[] ChangedTouches, bool CtrlKey, bool ShiftKey, bool AltKey); +public record TouchPoint(long Identifier, double ClientX, double ClientY); diff --git a/src/Blazor.Diagrams.Core/Events/WheelEventArgs.cs b/src/Blazor.Diagrams.Core/Events/WheelEventArgs.cs index d51b0687d..826b61e55 100644 --- a/src/Blazor.Diagrams.Core/Events/WheelEventArgs.cs +++ b/src/Blazor.Diagrams.Core/Events/WheelEventArgs.cs @@ -1,15 +1,14 @@ -namespace Blazor.Diagrams.Core.Events -{ - public record WheelEventArgs( - double ClientX, - double ClientY, - long Button, - long Buttons, - bool CtrlKey, - bool ShiftKey, - bool AltKey, - double DeltaX, - double DeltaY, - double DeltaZ, - long DeltaMode) : MouseEventArgs(ClientX, ClientY, Button, Buttons, CtrlKey, ShiftKey, AltKey); -} +namespace Blazor.Diagrams.Core.Events; + +public record WheelEventArgs( + double ClientX, + double ClientY, + long Button, + long Buttons, + bool CtrlKey, + bool ShiftKey, + bool AltKey, + double DeltaX, + double DeltaY, + double DeltaZ, + long DeltaMode) : MouseEventArgs(ClientX, ClientY, Button, Buttons, CtrlKey, ShiftKey, AltKey); diff --git a/src/Blazor.Diagrams.Core/Extensions/DiagramExtensions.cs b/src/Blazor.Diagrams.Core/Extensions/DiagramExtensions.cs index 2d45f2c06..24d8572aa 100644 --- a/src/Blazor.Diagrams.Core/Extensions/DiagramExtensions.cs +++ b/src/Blazor.Diagrams.Core/Extensions/DiagramExtensions.cs @@ -3,47 +3,46 @@ using System.Collections.Generic; using System.Linq; -namespace Blazor.Diagrams.Core.Extensions +namespace Blazor.Diagrams.Core.Extensions; + +public static class DiagramExtensions { - public static class DiagramExtensions + public static Rectangle GetBounds(this IEnumerable nodes) { - public static Rectangle GetBounds(this IEnumerable nodes) - { - if (!nodes.Any()) - return Rectangle.Zero; + if (!nodes.Any()) + return Rectangle.Zero; - var minX = double.MaxValue; - var maxX = double.MinValue; - var minY = double.MaxValue; - var maxY = double.MinValue; + var minX = double.MaxValue; + var maxX = double.MinValue; + var minY = double.MaxValue; + var maxY = double.MinValue; - foreach (var node in nodes) - { - if (node.Size == null) // Ignore nodes that didn't get a size yet - continue; + foreach (var node in nodes) + { + if (node.Size == null) // Ignore nodes that didn't get a size yet + continue; - var trX = node.Position.X + node.Size!.Width; - var bY = node.Position.Y + node.Size.Height; + var trX = node.Position.X + node.Size!.Width; + var bY = node.Position.Y + node.Size.Height; - if (node.Position.X < minX) - { - minX = node.Position.X; - } - if (trX > maxX) - { - maxX = trX; - } - if (node.Position.Y < minY) - { - minY = node.Position.Y; - } - if (bY > maxY) - { - maxY = bY; - } + if (node.Position.X < minX) + { + minX = node.Position.X; + } + if (trX > maxX) + { + maxX = trX; + } + if (node.Position.Y < minY) + { + minY = node.Position.Y; + } + if (bY > maxY) + { + maxY = bY; } - - return new Rectangle(minX, minY, maxX, maxY); } + + return new Rectangle(minX, minY, maxX, maxY); } } diff --git a/src/Blazor.Diagrams.Core/Extensions/DoubleExtensions.cs b/src/Blazor.Diagrams.Core/Extensions/DoubleExtensions.cs index 9f3832bad..1b3d9ed56 100644 --- a/src/Blazor.Diagrams.Core/Extensions/DoubleExtensions.cs +++ b/src/Blazor.Diagrams.Core/Extensions/DoubleExtensions.cs @@ -1,10 +1,9 @@ using System; -namespace Blazor.Diagrams.Core.Extensions +namespace Blazor.Diagrams.Core.Extensions; + +public static class DoubleExtensions { - public static class DoubleExtensions - { - public static bool AlmostEqualTo(this double double1, double double2, double tolerance = 0.0001) - => Math.Abs(double1 - double2) < tolerance; - } + public static bool AlmostEqualTo(this double double1, double double2, double tolerance = 0.0001) + => Math.Abs(double1 - double2) < tolerance; } diff --git a/src/Blazor.Diagrams.Core/Extensions/NumberExtensions.cs b/src/Blazor.Diagrams.Core/Extensions/NumberExtensions.cs index b5aa06f7b..e4c049991 100644 --- a/src/Blazor.Diagrams.Core/Extensions/NumberExtensions.cs +++ b/src/Blazor.Diagrams.Core/Extensions/NumberExtensions.cs @@ -1,9 +1,8 @@ using System.Globalization; -namespace Blazor.Diagrams.Core.Extensions +namespace Blazor.Diagrams.Core.Extensions; + +public static class NumberExtensions { - public static class NumberExtensions - { - public static string ToInvariantString(this double n) => n.ToString(CultureInfo.InvariantCulture); - } + public static string ToInvariantString(this double n) => n.ToString(CultureInfo.InvariantCulture); } diff --git a/src/Blazor.Diagrams.Core/Geometry/BezierSpline.cs b/src/Blazor.Diagrams.Core/Geometry/BezierSpline.cs index 4c3a8faaf..860d89ae9 100644 --- a/src/Blazor.Diagrams.Core/Geometry/BezierSpline.cs +++ b/src/Blazor.Diagrams.Core/Geometry/BezierSpline.cs @@ -1,206 +1,205 @@ using System; -namespace Blazor.Diagrams.Core.Geometry +namespace Blazor.Diagrams.Core.Geometry; + +/// +/// Bezier Spline methods +/// +/// +/// Modified: Peter Lee (peterlee.com.cn < at > gmail.com) +/// Update: 2009-03-16 +/// +/// see also: +/// Draw a smooth curve through a set of 2D points with Bezier primitives +/// http://www.codeproject.com/KB/graphics/BezierSpline.aspx +/// By Oleg V. Polikarpotchkin +/// +/// Algorithm Descripition: +/// +/// To make a sequence of individual Bezier curves to be a spline, we +/// should calculate Bezier control points so that the spline curve +/// has two continuous derivatives at knot points. +/// +/// Note: `[]` denotes subscript +/// `^` denotes supscript +/// `'` denotes first derivative +/// `''` denotes second derivative + /// +/// A Bezier curve on a single interval can be expressed as: +/// +/// B(t) = (1-t)^3 P0 + 3(1-t)^2 t P1 + 3(1-t)t^2 P2 + t^3 P3 (*) +/// +/// where t is in [0,1], and +/// 1. P0 - first knot point +/// 2. P1 - first control point (close to P0) +/// 3. P2 - second control point (close to P3) +/// 4. P3 - second knot point +/// +/// The first derivative of (*) is: +/// +/// B'(t) = -3(1-t)^2 P0 + 3(3t^2–4t+1) P1 + 3(2–3t)t P2 + 3t^2 P3 +/// +/// The second derivative of (*) is: +/// +/// B''(t) = 6(1-t) P0 + 6(3t-2) P1 + 6(1–3t) P2 + 6t P3 +/// +/// Considering a set of piecewise Bezier curves with n+1 points +/// (Q[0..n]) and n subintervals, the (i-1)-th curve should connect +/// to the i-th one: +/// +/// Q[0] = P0[1], +/// Q[1] = P0[2] = P3[1], ... , Q[i-1] = P0[i] = P3[i-1] (i = 1..n) (@) +/// +/// At the i-th subinterval, the Bezier curve is: +/// +/// B[i](t) = (1-t)^3 P0[i] + 3(1-t)^2 t P1[i] + +/// 3(1-t)t^2 P2[i] + t^3 P3[i] (i = 1..n) +/// +/// applying (@): +/// +/// B[i](t) = (1-t)^3 Q[i-1] + 3(1-t)^2 t P1[i] + +/// 3(1-t)t^2 P2[i] + t^3 Q[i] (i = 1..n) (i) +/// +/// From (i), the first derivative at the i-th subinterval is: +/// +/// B'[i](t) = -3(1-t)^2 Q[i-1] + 3(3t^2–4t+1) P1[i] + +/// 3(2–3t)t P2[i] + 3t^2 Q[i] (i = 1..n) +/// +/// Using the first derivative continuity condition: +/// +/// B'[i-1](1) = B'[i](0) +/// +/// we get: +/// +/// P1[i] + P2[i-1] = 2Q[i-1] (i = 2..n) (1) +/// +/// From (i), the second derivative at the i-th subinterval is: +/// +/// B''[i](t) = 6(1-t) Q[i-1] + 6(3t-2) P1[i] + +/// 6(1-3t) P2[i] + 6t Q[i] (i = 1..n) +/// +/// Using the second derivative continuity condition: +/// +/// B''[i-1](1) = B''[i](0) +/// +/// we get: +/// +/// P1[i-1] + 2P1[i] = P2[i] + 2P2[i-1] (i = 2..n) (2) +/// +/// Then, using the so-called "natural conditions": +/// +/// B''[1](0) = 0 +/// +/// B''[n](1) = 0 +/// +/// to the second derivative equations, and we get: +/// +/// 2P1[1] - P2[1] = Q[0] (3) +/// +/// 2P2[n] - P1[n] = Q[n] (4) +/// +/// From (1)(2)(3)(4), we have 2n conditions for n first control points +/// P1[1..n], and n second control points P2[1..n]. +/// +/// Eliminating P2[1..n], we get (be patient to get :-) a set of n +/// equations for solving P1[1..n]: +/// +/// 2P1[1] + P1[2] + = Q[0] + 2Q[1] +/// P1[1] + 4P1[2] + P1[3] = 4Q[1] + 2Q[2] +/// ... +/// P1[i-1] + 4P1[i] + P1[i+1] = 4Q[i-1] + 2Q[i] +/// ... +/// P1[n-2] + 4P1[n-1] + P1[n] = 4Q[n-2] + 2Q[n-1] +/// P1[n-1] + 3.5P1[n] = (8Q[n-1] + Q[n]) / 2 +/// +/// From this set of equations, P1[1..n] are easy but tedious to solve. +/// + public static class BezierSpline { /// - /// Bezier Spline methods + /// Get open-ended Bezier Spline Control Points. /// - /// - /// Modified: Peter Lee (peterlee.com.cn < at > gmail.com) - /// Update: 2009-03-16 - /// - /// see also: - /// Draw a smooth curve through a set of 2D points with Bezier primitives - /// http://www.codeproject.com/KB/graphics/BezierSpline.aspx - /// By Oleg V. Polikarpotchkin - /// - /// Algorithm Descripition: - /// - /// To make a sequence of individual Bezier curves to be a spline, we - /// should calculate Bezier control points so that the spline curve - /// has two continuous derivatives at knot points. - /// - /// Note: `[]` denotes subscript - /// `^` denotes supscript - /// `'` denotes first derivative - /// `''` denotes second derivative - /// - /// A Bezier curve on a single interval can be expressed as: - /// - /// B(t) = (1-t)^3 P0 + 3(1-t)^2 t P1 + 3(1-t)t^2 P2 + t^3 P3 (*) - /// - /// where t is in [0,1], and - /// 1. P0 - first knot point - /// 2. P1 - first control point (close to P0) - /// 3. P2 - second control point (close to P3) - /// 4. P3 - second knot point - /// - /// The first derivative of (*) is: - /// - /// B'(t) = -3(1-t)^2 P0 + 3(3t^2–4t+1) P1 + 3(2–3t)t P2 + 3t^2 P3 - /// - /// The second derivative of (*) is: - /// - /// B''(t) = 6(1-t) P0 + 6(3t-2) P1 + 6(1–3t) P2 + 6t P3 - /// - /// Considering a set of piecewise Bezier curves with n+1 points - /// (Q[0..n]) and n subintervals, the (i-1)-th curve should connect - /// to the i-th one: - /// - /// Q[0] = P0[1], - /// Q[1] = P0[2] = P3[1], ... , Q[i-1] = P0[i] = P3[i-1] (i = 1..n) (@) - /// - /// At the i-th subinterval, the Bezier curve is: - /// - /// B[i](t) = (1-t)^3 P0[i] + 3(1-t)^2 t P1[i] + - /// 3(1-t)t^2 P2[i] + t^3 P3[i] (i = 1..n) - /// - /// applying (@): - /// - /// B[i](t) = (1-t)^3 Q[i-1] + 3(1-t)^2 t P1[i] + - /// 3(1-t)t^2 P2[i] + t^3 Q[i] (i = 1..n) (i) - /// - /// From (i), the first derivative at the i-th subinterval is: - /// - /// B'[i](t) = -3(1-t)^2 Q[i-1] + 3(3t^2–4t+1) P1[i] + - /// 3(2–3t)t P2[i] + 3t^2 Q[i] (i = 1..n) - /// - /// Using the first derivative continuity condition: - /// - /// B'[i-1](1) = B'[i](0) - /// - /// we get: - /// - /// P1[i] + P2[i-1] = 2Q[i-1] (i = 2..n) (1) - /// - /// From (i), the second derivative at the i-th subinterval is: - /// - /// B''[i](t) = 6(1-t) Q[i-1] + 6(3t-2) P1[i] + - /// 6(1-3t) P2[i] + 6t Q[i] (i = 1..n) - /// - /// Using the second derivative continuity condition: - /// - /// B''[i-1](1) = B''[i](0) - /// - /// we get: - /// - /// P1[i-1] + 2P1[i] = P2[i] + 2P2[i-1] (i = 2..n) (2) - /// - /// Then, using the so-called "natural conditions": - /// - /// B''[1](0) = 0 - /// - /// B''[n](1) = 0 - /// - /// to the second derivative equations, and we get: - /// - /// 2P1[1] - P2[1] = Q[0] (3) - /// - /// 2P2[n] - P1[n] = Q[n] (4) - /// - /// From (1)(2)(3)(4), we have 2n conditions for n first control points - /// P1[1..n], and n second control points P2[1..n]. - /// - /// Eliminating P2[1..n], we get (be patient to get :-) a set of n - /// equations for solving P1[1..n]: - /// - /// 2P1[1] + P1[2] + = Q[0] + 2Q[1] - /// P1[1] + 4P1[2] + P1[3] = 4Q[1] + 2Q[2] - /// ... - /// P1[i-1] + 4P1[i] + P1[i+1] = 4Q[i-1] + 2Q[i] - /// ... - /// P1[n-2] + 4P1[n-1] + P1[n] = 4Q[n-2] + 2Q[n-1] - /// P1[n-1] + 3.5P1[n] = (8Q[n-1] + Q[n]) / 2 - /// - /// From this set of equations, P1[1..n] are easy but tedious to solve. - /// - public static class BezierSpline + /// Input Knot Bezier spline points. + /// Output First Control points array of knots.Length - 1 length. + /// Output Second Control points array of knots.Length - 1 length. + /// parameter must be not null. + /// array must containg at least two points. + public static void GetCurveControlPoints(Point[] knots, out Point[] firstControlPoints, out Point[] secondControlPoints) { - /// - /// Get open-ended Bezier Spline Control Points. - /// - /// Input Knot Bezier spline points. - /// Output First Control points array of knots.Length - 1 length. - /// Output Second Control points array of knots.Length - 1 length. - /// parameter must be not null. - /// array must containg at least two points. - public static void GetCurveControlPoints(Point[] knots, out Point[] firstControlPoints, out Point[] secondControlPoints) - { - if (knots == null) - throw new ArgumentNullException("knots"); - int n = knots.Length - 1; - if (n < 1) - throw new ArgumentException("At least two knot points required", "knots"); - if (n == 1) - { // Special case: Bezier curve should be a straight line. - firstControlPoints = new Point[1]; - // 3P1 = 2P0 + P3 - firstControlPoints[0] = new Point((2 * knots[0].X + knots[1].X) / 3, (2 * knots[0].Y + knots[1].Y) / 3); + if (knots == null) + throw new ArgumentNullException("knots"); + int n = knots.Length - 1; + if (n < 1) + throw new ArgumentException("At least two knot points required", "knots"); + if (n == 1) + { // Special case: Bezier curve should be a straight line. + firstControlPoints = new Point[1]; + // 3P1 = 2P0 + P3 + firstControlPoints[0] = new Point((2 * knots[0].X + knots[1].X) / 3, (2 * knots[0].Y + knots[1].Y) / 3); - secondControlPoints = new Point[1]; - // P2 = 2P1 – P0 - secondControlPoints[0] = new Point(2 * firstControlPoints[0].X - knots[0].X, 2 * firstControlPoints[0].Y - knots[0].Y); - return; - } - - // Calculate first Bezier control points - // Right hand side vector - double[] rhs = new double[n]; + secondControlPoints = new Point[1]; + // P2 = 2P1 – P0 + secondControlPoints[0] = new Point(2 * firstControlPoints[0].X - knots[0].X, 2 * firstControlPoints[0].Y - knots[0].Y); + return; + } - // Set right hand side X values - for (int i = 1; i < n - 1; ++i) - rhs[i] = 4 * knots[i].X + 2 * knots[i + 1].X; - rhs[0] = knots[0].X + 2 * knots[1].X; - rhs[n - 1] = (8 * knots[n - 1].X + knots[n].X) / 2.0; - // Get first control points X-values - double[] x = GetFirstControlPoints(rhs); + // Calculate first Bezier control points + // Right hand side vector + double[] rhs = new double[n]; - // Set right hand side Y values - for (int i = 1; i < n - 1; ++i) - rhs[i] = 4 * knots[i].Y + 2 * knots[i + 1].Y; - rhs[0] = knots[0].Y + 2 * knots[1].Y; - rhs[n - 1] = (8 * knots[n - 1].Y + knots[n].Y) / 2.0; - // Get first control points Y-values - double[] y = GetFirstControlPoints(rhs); + // Set right hand side X values + for (int i = 1; i < n - 1; ++i) + rhs[i] = 4 * knots[i].X + 2 * knots[i + 1].X; + rhs[0] = knots[0].X + 2 * knots[1].X; + rhs[n - 1] = (8 * knots[n - 1].X + knots[n].X) / 2.0; + // Get first control points X-values + double[] x = GetFirstControlPoints(rhs); - // Fill output arrays. - firstControlPoints = new Point[n]; - secondControlPoints = new Point[n]; - for (int i = 0; i < n; ++i) - { - // First control point - firstControlPoints[i] = new Point(x[i], y[i]); - // Second control point - if (i < n - 1) - secondControlPoints[i] = new Point(2 * knots[i + 1].X - x[i + 1], 2 * knots[i + 1].Y - y[i + 1]); - else - secondControlPoints[i] = new Point((knots[n].X + x[n - 1]) / 2, (knots[n].Y + y[n - 1]) / 2); - } - } + // Set right hand side Y values + for (int i = 1; i < n - 1; ++i) + rhs[i] = 4 * knots[i].Y + 2 * knots[i + 1].Y; + rhs[0] = knots[0].Y + 2 * knots[1].Y; + rhs[n - 1] = (8 * knots[n - 1].Y + knots[n].Y) / 2.0; + // Get first control points Y-values + double[] y = GetFirstControlPoints(rhs); - /// - /// Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points. - /// - /// Right hand side vector. - /// Solution vector. - private static double[] GetFirstControlPoints(double[] rhs) + // Fill output arrays. + firstControlPoints = new Point[n]; + secondControlPoints = new Point[n]; + for (int i = 0; i < n; ++i) { - int n = rhs.Length; - double[] x = new double[n]; // Solution vector. - double[] tmp = new double[n]; // Temp workspace. + // First control point + firstControlPoints[i] = new Point(x[i], y[i]); + // Second control point + if (i < n - 1) + secondControlPoints[i] = new Point(2 * knots[i + 1].X - x[i + 1], 2 * knots[i + 1].Y - y[i + 1]); + else + secondControlPoints[i] = new Point((knots[n].X + x[n - 1]) / 2, (knots[n].Y + y[n - 1]) / 2); + } + } - double b = 2.0; - x[0] = rhs[0] / b; - for (int i = 1; i < n; i++) // Decomposition and forward substitution. - { - tmp[i] = 1 / b; - b = (i < n - 1 ? 4.0 : 3.5) - tmp[i]; - x[i] = (rhs[i] - x[i - 1]) / b; - } - for (int i = 1; i < n; i++) - x[n - i - 1] -= tmp[n - i] * x[n - i]; // Backsubstitution. + /// + /// Solves a tridiagonal system for one of coordinates (x or y) of first Bezier control points. + /// + /// Right hand side vector. + /// Solution vector. + private static double[] GetFirstControlPoints(double[] rhs) + { + int n = rhs.Length; + double[] x = new double[n]; // Solution vector. + double[] tmp = new double[n]; // Temp workspace. - return x; + double b = 2.0; + x[0] = rhs[0] / b; + for (int i = 1; i < n; i++) // Decomposition and forward substitution. + { + tmp[i] = 1 / b; + b = (i < n - 1 ? 4.0 : 3.5) - tmp[i]; + x[i] = (rhs[i] - x[i - 1]) / b; } + for (int i = 1; i < n; i++) + x[n - i - 1] -= tmp[n - i] * x[n - i]; // Backsubstitution. + + return x; } } diff --git a/src/Blazor.Diagrams.Core/Geometry/Ellipse.cs b/src/Blazor.Diagrams.Core/Geometry/Ellipse.cs index e64896b6d..771a08182 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Ellipse.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Ellipse.cs @@ -1,68 +1,67 @@ using System; using System.Collections.Generic; -namespace Blazor.Diagrams.Core.Geometry +namespace Blazor.Diagrams.Core.Geometry; + +public class Ellipse : IShape { - public class Ellipse : IShape + public Ellipse(double cx, double cy, double rx, double ry) { - public Ellipse(double cx, double cy, double rx, double ry) - { - Cx = cx; - Cy = cy; - Rx = rx; - Ry = ry; - } + Cx = cx; + Cy = cy; + Rx = rx; + Ry = ry; + } - public double Cx { get; } - public double Cy { get; } - public double Rx { get; } - public double Ry { get; } + public double Cx { get; } + public double Cy { get; } + public double Rx { get; } + public double Ry { get; } - public IEnumerable GetIntersectionsWithLine(Line line) - { - var a1 = line.Start; - var a2 = line.End; - var dir = new Point(line.End.X - line.Start.X, line.End.Y - line.Start.Y); - var diff = a1.Substract(Cx, Cy); - var mDir = new Point(dir.X / (Rx * Rx), dir.Y / (Ry * Ry)); - var mDiff = new Point(diff.X / (Rx * Rx), diff.Y / (Ry * Ry)); + public IEnumerable GetIntersectionsWithLine(Line line) + { + var a1 = line.Start; + var a2 = line.End; + var dir = new Point(line.End.X - line.Start.X, line.End.Y - line.Start.Y); + var diff = a1.Substract(Cx, Cy); + var mDir = new Point(dir.X / (Rx * Rx), dir.Y / (Ry * Ry)); + var mDiff = new Point(diff.X / (Rx * Rx), diff.Y / (Ry * Ry)); - var a = dir.Dot(mDir); - var b = dir.Dot(mDiff); - var c = diff.Dot(mDiff) - 1.0; - var d = b * b - a * c; + var a = dir.Dot(mDir); + var b = dir.Dot(mDiff); + var c = diff.Dot(mDiff) - 1.0; + var d = b * b - a * c; - if (d > 0) - { - var root = Math.Sqrt(d); - var ta = (-b - root) / a; - var tb = (-b + root) / a; + if (d > 0) + { + var root = Math.Sqrt(d); + var ta = (-b - root) / a; + var tb = (-b + root) / a; - if (ta >= 0 && 1 >= ta || tb >= 0 && 1 >= tb) - { - if (0 <= ta && ta <= 1) - yield return a1.Lerp(a2, ta); + if (ta >= 0 && 1 >= ta || tb >= 0 && 1 >= tb) + { + if (0 <= ta && ta <= 1) + yield return a1.Lerp(a2, ta); - if (0 <= tb && tb <= 1) - yield return a1.Lerp(a2, tb); - } + if (0 <= tb && tb <= 1) + yield return a1.Lerp(a2, tb); } - else + } + else + { + var t = -b / a; + if (0 <= t && t <= 1) { - var t = -b / a; - if (0 <= t && t <= 1) - { - yield return a1.Lerp(a2, t); - } + yield return a1.Lerp(a2, t); } } + } - public Point? GetPointAtAngle(double a) - { - var t = Math.Tan(a / 360 * Math.PI); - var px = Rx * (1 - Math.Pow(t, 2)) / (1 + Math.Pow(t, 2)); - var py = Ry * 2 * t / (1 + Math.Pow(t, 2)); - return new Point(Cx + px, Cy + py); - } + public Point? GetPointAtAngle(double a) + { + var t = Math.Tan(a / 360 * Math.PI); + var px = Rx * (1 - Math.Pow(t, 2)) / (1 + Math.Pow(t, 2)); + var py = Ry * 2 * t / (1 + Math.Pow(t, 2)); + return new Point(Cx + px, Cy + py); } } diff --git a/src/Blazor.Diagrams.Core/Geometry/IShape.cs b/src/Blazor.Diagrams.Core/Geometry/IShape.cs index b5402d981..05b47130f 100644 --- a/src/Blazor.Diagrams.Core/Geometry/IShape.cs +++ b/src/Blazor.Diagrams.Core/Geometry/IShape.cs @@ -1,10 +1,9 @@ using System.Collections.Generic; -namespace Blazor.Diagrams.Core.Geometry +namespace Blazor.Diagrams.Core.Geometry; + +public interface IShape { - public interface IShape - { - public IEnumerable GetIntersectionsWithLine(Line line); - public Point? GetPointAtAngle(double a); - } + public IEnumerable GetIntersectionsWithLine(Line line); + public Point? GetPointAtAngle(double a); } diff --git a/src/Blazor.Diagrams.Core/Geometry/Line.cs b/src/Blazor.Diagrams.Core/Geometry/Line.cs index d5fca90e9..45add1cd1 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Line.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Line.cs @@ -1,43 +1,42 @@ -namespace Blazor.Diagrams.Core.Geometry +namespace Blazor.Diagrams.Core.Geometry; + +public class Line { - public class Line + public Line(Point start, Point end) { - public Line(Point start, Point end) - { - Start = start; - End = end; - } + Start = start; + End = end; + } - public Point Start { get; } - public Point End { get; } + public Point Start { get; } + public Point End { get; } - public Point? GetIntersection(Line line) - { - var pt1Dir = new Point(End.X - Start.X, End.Y - Start.Y); - var pt2Dir = new Point(line.End.X - line.Start.X, line.End.Y - line.Start.Y); - var det = (pt1Dir.X * pt2Dir.Y) - (pt1Dir.Y * pt2Dir.X); - var deltaPt = new Point(line.Start.X - Start.X, line.Start.Y - Start.Y); - var alpha = (deltaPt.X * pt2Dir.Y) - (deltaPt.Y * pt2Dir.X); - var beta = (deltaPt.X * pt1Dir.Y) - (deltaPt.Y * pt1Dir.X); - - if (det == 0 || alpha * det < 0 || beta * det < 0) - return null; + public Point? GetIntersection(Line line) + { + var pt1Dir = new Point(End.X - Start.X, End.Y - Start.Y); + var pt2Dir = new Point(line.End.X - line.Start.X, line.End.Y - line.Start.Y); + var det = (pt1Dir.X * pt2Dir.Y) - (pt1Dir.Y * pt2Dir.X); + var deltaPt = new Point(line.Start.X - Start.X, line.Start.Y - Start.Y); + var alpha = (deltaPt.X * pt2Dir.Y) - (deltaPt.Y * pt2Dir.X); + var beta = (deltaPt.X * pt1Dir.Y) - (deltaPt.Y * pt1Dir.X); - if (det > 0) - { - if (alpha > det || beta > det) - return null; + if (det == 0 || alpha * det < 0 || beta * det < 0) + return null; - } - else - { - if (alpha < det || beta < det) - return null; - } + if (det > 0) + { + if (alpha > det || beta > det) + return null; - return new Point(Start.X + (alpha * pt1Dir.X / det), Start.Y + (alpha * pt1Dir.Y / det)); + } + else + { + if (alpha < det || beta < det) + return null; } - public override string ToString() => $"Line from {Start} to {End}"; + return new Point(Start.X + (alpha * pt1Dir.X / det), Start.Y + (alpha * pt1Dir.Y / det)); } + + public override string ToString() => $"Line from {Start} to {End}"; } diff --git a/src/Blazor.Diagrams.Core/Geometry/Point.cs b/src/Blazor.Diagrams.Core/Geometry/Point.cs index 088048531..272323d94 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Point.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Point.cs @@ -1,52 +1,51 @@ using System; using System.Reflection.Metadata; -namespace Blazor.Diagrams.Core.Geometry +namespace Blazor.Diagrams.Core.Geometry; + +public record Point { - public record Point + public static Point Zero { get; } = new(0, 0); + + public Point(double x, double y) + { + X = x; + Y = y; + } + + public double X { get; init; } + public double Y { get; init; } + + public double Dot(Point other) => X * other.X + Y * other.Y; + public Point Lerp(Point other, double t) + => new(X * (1.0 - t) + other.X * t, Y * (1.0 - t) + other.Y * t); + + // Maybe just make Points mutable? + public Point Add(double value) => new(X + value, Y + value); + public Point Add(double x, double y) => new(X + x, Y + y); + + public Point Substract(double value) => new(X - value, Y - value); + public Point Substract(double x, double y) => new(X - x, Y - y); + + public double DistanceTo(Point other) => Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2)); + public double DistanceTo(double x, double y) => Math.Sqrt(Math.Pow(X - x, 2) + Math.Pow(Y - y, 2)); + + public Point MoveAlongLine(Point from, double dist) + { + var x = X - from.X; + var y = Y - from.Y; + var angle = Math.Atan2(y, x); + var xOffset = Math.Cos(angle) * dist; + var yOffset = Math.Sin(angle) * dist; + return new Point(X + xOffset, Y + yOffset); + } + + public static Point operator -(Point a, Point b) => new(a.X - b.X, a.Y - b.Y); + public static Point operator +(Point a, Point b) => new(a.X + b.X, a.Y + b.Y); + + public void Deconstruct(out double x, out double y) { - public static Point Zero { get; } = new(0, 0); - - public Point(double x, double y) - { - X = x; - Y = y; - } - - public double X { get; init; } - public double Y { get; init; } - - public double Dot(Point other) => X * other.X + Y * other.Y; - public Point Lerp(Point other, double t) - => new(X * (1.0 - t) + other.X * t, Y * (1.0 - t) + other.Y * t); - - // Maybe just make Points mutable? - public Point Add(double value) => new(X + value, Y + value); - public Point Add(double x, double y) => new(X + x, Y + y); - - public Point Substract(double value) => new(X - value, Y - value); - public Point Substract(double x, double y) => new(X - x, Y - y); - - public double DistanceTo(Point other) => Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2)); - public double DistanceTo(double x, double y) => Math.Sqrt(Math.Pow(X - x, 2) + Math.Pow(Y - y, 2)); - - public Point MoveAlongLine(Point from, double dist) - { - var x = X - from.X; - var y = Y - from.Y; - var angle = Math.Atan2(y, x); - var xOffset = Math.Cos(angle) * dist; - var yOffset = Math.Sin(angle) * dist; - return new Point(X + xOffset, Y + yOffset); - } - - public static Point operator -(Point a, Point b) => new(a.X - b.X, a.Y - b.Y); - public static Point operator +(Point a, Point b) => new(a.X + b.X, a.Y + b.Y); - - public void Deconstruct(out double x, out double y) - { - x = X; - y = Y; - } + x = X; + y = Y; } } diff --git a/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs b/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs index dbea08cfa..c14309d71 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Rectangle.cs @@ -3,128 +3,127 @@ using System.Linq; using System.Text.Json.Serialization; -namespace Blazor.Diagrams.Core.Geometry +namespace Blazor.Diagrams.Core.Geometry; + +public class Rectangle : IShape { - public class Rectangle : IShape - { - public static Rectangle Zero { get; } = new(0, 0, 0, 0); + public static Rectangle Zero { get; } = new(0, 0, 0, 0); - public double Width { get; } - public double Height { get; } - public double Top { get; } - public double Right { get; } - public double Bottom { get; } - public double Left { get; } + public double Width { get; } + public double Height { get; } + public double Top { get; } + public double Right { get; } + public double Bottom { get; } + public double Left { get; } - [JsonConstructor] - public Rectangle(double left, double top, double right, double bottom) - { - Left = left; - Top = top; - Right = right; - Bottom = bottom; - Width = Math.Abs(Left - Right); - Height = Math.Abs(Top - Bottom); - } + [JsonConstructor] + public Rectangle(double left, double top, double right, double bottom) + { + Left = left; + Top = top; + Right = right; + Bottom = bottom; + Width = Math.Abs(Left - Right); + Height = Math.Abs(Top - Bottom); + } - public Rectangle(Point position, Size size) - { - ArgumentNullException.ThrowIfNull(position, nameof(position)); - ArgumentNullException.ThrowIfNull(size, nameof(size)); - - Left = position.X; - Top = position.Y; - Right = Left + size.Width; - Bottom = Top + size.Height; - Width = size.Width; - Height = size.Height; - } + public Rectangle(Point position, Size size) + { + ArgumentNullException.ThrowIfNull(position, nameof(position)); + ArgumentNullException.ThrowIfNull(size, nameof(size)); + + Left = position.X; + Top = position.Y; + Right = Left + size.Width; + Bottom = Top + size.Height; + Width = size.Width; + Height = size.Height; + } - public bool Overlap(Rectangle r) - => Left < r.Right && Right > r.Left && Top < r.Bottom && Bottom > r.Top; + public bool Overlap(Rectangle r) + => Left < r.Right && Right > r.Left && Top < r.Bottom && Bottom > r.Top; - public bool Intersects(Rectangle r) - { - var thisX = Left; - var thisY = Top; - var thisW = Width; - var thisH = Height; - var rectX = r.Left; - var rectY = r.Top; - var rectW = r.Width; - var rectH = r.Height; - return rectX < thisX + thisW && thisX < rectX + rectW && rectY < thisY + thisH && thisY < rectY + rectH; - } + public bool Intersects(Rectangle r) + { + var thisX = Left; + var thisY = Top; + var thisW = Width; + var thisH = Height; + var rectX = r.Left; + var rectY = r.Top; + var rectW = r.Width; + var rectH = r.Height; + return rectX < thisX + thisW && thisX < rectX + rectW && rectY < thisY + thisH && thisY < rectY + rectH; + } - public Rectangle Inflate(double horizontal, double vertical) - => new(Left - horizontal, Top - vertical, Right + horizontal, Bottom + vertical); + public Rectangle Inflate(double horizontal, double vertical) + => new(Left - horizontal, Top - vertical, Right + horizontal, Bottom + vertical); - public Rectangle Union(Rectangle r) - { - var x1 = Math.Min(Left, r.Left); - var x2 = Math.Max(Left + Width, r.Left + r.Width); - var y1 = Math.Min(Top, r.Top); - var y2 = Math.Max(Top + Height, r.Top + r.Height); - return new(x1, y1, x2, y2); - } + public Rectangle Union(Rectangle r) + { + var x1 = Math.Min(Left, r.Left); + var x2 = Math.Max(Left + Width, r.Left + r.Width); + var y1 = Math.Min(Top, r.Top); + var y2 = Math.Max(Top + Height, r.Top + r.Height); + return new(x1, y1, x2, y2); + } - public bool ContainsPoint(Point point) => ContainsPoint(point.X, point.Y); + public bool ContainsPoint(Point point) => ContainsPoint(point.X, point.Y); - public bool ContainsPoint(double x, double y) - => x >= Left && x <= Right && y >= Top && y <= Bottom; + public bool ContainsPoint(double x, double y) + => x >= Left && x <= Right && y >= Top && y <= Bottom; - public IEnumerable GetIntersectionsWithLine(Line line) - { - var borders = new[] { - new Line(NorthWest, NorthEast), - new Line(NorthEast, SouthEast), - new Line(SouthWest, SouthEast), - new Line(NorthWest, SouthWest) - }; - - for (var i = 0; i < borders.Length; i++) - { - var intersectionPt = borders[i].GetIntersection(line); - if (intersectionPt != null) - yield return intersectionPt; - } - } - - public Point? GetPointAtAngle(double a) + public IEnumerable GetIntersectionsWithLine(Line line) + { + var borders = new[] { + new Line(NorthWest, NorthEast), + new Line(NorthEast, SouthEast), + new Line(SouthWest, SouthEast), + new Line(NorthWest, SouthWest) + }; + + for (var i = 0; i < borders.Length; i++) { - var vx = Math.Cos(a * Math.PI / 180); - var vy = Math.Sin(a * Math.PI / 180); - var px = Left + Width / 2; - var py = Top + Height / 2; - double? t1 = (Left - px) / vx; // left - double? t2 = (Right - px) / vx; // right - double? t3 = (Top - py) / vy; // top - double? t4 = (Bottom - py) / vy; // bottom - var t = (new[] { t1, t2, t3, t4 }).Where(n => n.HasValue && double.IsFinite(n.Value) && n.Value > 0).DefaultIfEmpty(null).Min(); - if (t == null) return null; - - var x = px + t.Value * vx; - var y = py + t.Value * vy; - return new Point(x, y); + var intersectionPt = borders[i].GetIntersection(line); + if (intersectionPt != null) + yield return intersectionPt; } + } - public Point Center => new(Left + Width / 2, Top + Height / 2); - public Point NorthEast => new(Right, Top); - public Point SouthEast => new(Right, Bottom); - public Point SouthWest => new(Left, Bottom); - public Point NorthWest => new(Left, Top); - public Point East => new(Right, Top + Height / 2); - public Point North => new(Left + Width / 2, Top); - public Point South => new(Left + Width / 2, Bottom); - public Point West => new(Left, Top + Height / 2); - - public bool Equals(Rectangle? other) - { - return other != null && Left == other.Left && Right == other.Right && Top == other.Top && - Bottom == other.Bottom && Width == other.Width && Height == other.Height; - } + public Point? GetPointAtAngle(double a) + { + var vx = Math.Cos(a * Math.PI / 180); + var vy = Math.Sin(a * Math.PI / 180); + var px = Left + Width / 2; + var py = Top + Height / 2; + double? t1 = (Left - px) / vx; // left + double? t2 = (Right - px) / vx; // right + double? t3 = (Top - py) / vy; // top + double? t4 = (Bottom - py) / vy; // bottom + var t = (new[] { t1, t2, t3, t4 }).Where(n => n.HasValue && double.IsFinite(n.Value) && n.Value > 0).DefaultIfEmpty(null).Min(); + if (t == null) return null; + + var x = px + t.Value * vx; + var y = py + t.Value * vy; + return new Point(x, y); + } - public override string ToString() - => $"Rectangle(width={Width}, height={Height}, top={Top}, right={Right}, bottom={Bottom}, left={Left})"; + public Point Center => new(Left + Width / 2, Top + Height / 2); + public Point NorthEast => new(Right, Top); + public Point SouthEast => new(Right, Bottom); + public Point SouthWest => new(Left, Bottom); + public Point NorthWest => new(Left, Top); + public Point East => new(Right, Top + Height / 2); + public Point North => new(Left + Width / 2, Top); + public Point South => new(Left + Width / 2, Bottom); + public Point West => new(Left, Top + Height / 2); + + public bool Equals(Rectangle? other) + { + return other != null && Left == other.Left && Right == other.Right && Top == other.Top && + Bottom == other.Bottom && Width == other.Width && Height == other.Height; } + + public override string ToString() + => $"Rectangle(width={Width}, height={Height}, top={Top}, right={Right}, bottom={Bottom}, left={Left})"; } diff --git a/src/Blazor.Diagrams.Core/Geometry/Shapes.cs b/src/Blazor.Diagrams.Core/Geometry/Shapes.cs index 75fb58487..9bb9f2e05 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Shapes.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Shapes.cs @@ -1,38 +1,37 @@ using Blazor.Diagrams.Core.Models; -namespace Blazor.Diagrams.Core.Geometry +namespace Blazor.Diagrams.Core.Geometry; + +public static class Shapes { - public static class Shapes - { - public static IShape Rectangle(NodeModel node) => Rectangle(node.Position, node.Size!); + public static IShape Rectangle(NodeModel node) => Rectangle(node.Position, node.Size!); - public static IShape Circle(NodeModel node) => Circle(node.Position, node.Size!); + public static IShape Circle(NodeModel node) => Circle(node.Position, node.Size!); - public static IShape Ellipse(NodeModel node) => Ellipse(node.Position, node.Size!); + public static IShape Ellipse(NodeModel node) => Ellipse(node.Position, node.Size!); - public static IShape Rectangle(PortModel port) => Rectangle(port.Position, port.Size!); + public static IShape Rectangle(PortModel port) => Rectangle(port.Position, port.Size!); - public static IShape Circle(PortModel port) => Circle(port.Position, port.Size!); + public static IShape Circle(PortModel port) => Circle(port.Position, port.Size!); - public static IShape Ellipse(PortModel port) => Ellipse(port.Position, port.Size!); - - private static IShape Rectangle(Point position, Size size) => new Rectangle(position, size); + public static IShape Ellipse(PortModel port) => Ellipse(port.Position, port.Size!); + + private static IShape Rectangle(Point position, Size size) => new Rectangle(position, size); - private static IShape Circle(Point position, Size size) - { - var halfWidth = size.Width / 2; - var centerX = position.X + halfWidth; - var centerY = position.Y + size.Height / 2; - return new Ellipse(centerX, centerY, halfWidth, halfWidth); - } + private static IShape Circle(Point position, Size size) + { + var halfWidth = size.Width / 2; + var centerX = position.X + halfWidth; + var centerY = position.Y + size.Height / 2; + return new Ellipse(centerX, centerY, halfWidth, halfWidth); + } - private static IShape Ellipse(Point position, Size size) - { - var halfWidth = size.Width / 2; - var halfHeight = size.Height / 2; - var centerX = position.X + halfWidth; - var centerY = position.Y + halfHeight; - return new Ellipse(centerX, centerY, halfWidth, halfHeight); - } + private static IShape Ellipse(Point position, Size size) + { + var halfWidth = size.Width / 2; + var halfHeight = size.Height / 2; + var centerX = position.X + halfWidth; + var centerY = position.Y + halfHeight; + return new Ellipse(centerX, centerY, halfWidth, halfHeight); } } diff --git a/src/Blazor.Diagrams.Core/Geometry/Size.cs b/src/Blazor.Diagrams.Core/Geometry/Size.cs index 4207884e8..97cb89ead 100644 --- a/src/Blazor.Diagrams.Core/Geometry/Size.cs +++ b/src/Blazor.Diagrams.Core/Geometry/Size.cs @@ -1,16 +1,15 @@ -namespace Blazor.Diagrams.Core.Geometry -{ - public record Size - { - public static Size Zero { get; } = new(0, 0); +namespace Blazor.Diagrams.Core.Geometry; - public Size(double width, double height) - { - Width = width; - Height = height; - } +public record Size +{ + public static Size Zero { get; } = new(0, 0); - public double Width { get; init; } - public double Height { get; init; } + public Size(double width, double height) + { + Width = width; + Height = height; } + + public double Width { get; init; } + public double Height { get; init; } } diff --git a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs index 57d21bc69..711e19727 100644 --- a/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/BaseLayer.cs @@ -3,111 +3,110 @@ using System.Collections; using System.Collections.Generic; -namespace Blazor.Diagrams.Core +namespace Blazor.Diagrams.Core; + +public abstract class BaseLayer : IReadOnlyList where T : Model { - public abstract class BaseLayer : IReadOnlyList where T : Model + private readonly List _items = new List(); + + public event Action? Added; + public event Action? Removed; + + public BaseLayer(Diagram diagram) { - private readonly List _items = new List(); + Diagram = diagram; + } - public event Action? Added; - public event Action? Removed; + public virtual TSpecific Add(TSpecific item) where TSpecific : T + { + if (item is null) + throw new ArgumentNullException(nameof(item)); - public BaseLayer(Diagram diagram) + Diagram.Batch(() => { - Diagram = diagram; - } + _items.Add(item); + OnItemAdded(item); + Added?.Invoke(item); + }); + return item; + } - public virtual TSpecific Add(TSpecific item) where TSpecific : T - { - if (item is null) - throw new ArgumentNullException(nameof(item)); + public virtual void Add(IEnumerable items) + { + if (items is null) + throw new ArgumentNullException(nameof(items)); - Diagram.Batch(() => + Diagram.Batch(() => + { + foreach (var item in items) { _items.Add(item); OnItemAdded(item); Added?.Invoke(item); - }); - return item; - } + } + }); + } - public virtual void Add(IEnumerable items) - { - if (items is null) - throw new ArgumentNullException(nameof(items)); + public virtual void Remove(T item) + { + if (item is null) + throw new ArgumentNullException(nameof(item)); + if (_items.Remove(item)) + { Diagram.Batch(() => { - foreach (var item in items) - { - _items.Add(item); - OnItemAdded(item); - Added?.Invoke(item); - } + OnItemRemoved(item); + Removed?.Invoke(item); }); } + } - public virtual void Remove(T item) - { - if (item is null) - throw new ArgumentNullException(nameof(item)); + public virtual void Remove(IEnumerable items) + { + if (items is null) + throw new ArgumentNullException(nameof(items)); - if (_items.Remove(item)) + Diagram.Batch(() => + { + foreach (var item in items) { - Diagram.Batch(() => + if (_items.Remove(item)) { OnItemRemoved(item); Removed?.Invoke(item); - }); + } } - } - - public virtual void Remove(IEnumerable items) - { - if (items is null) - throw new ArgumentNullException(nameof(items)); + }); + } - Diagram.Batch(() => - { - foreach (var item in items) - { - if (_items.Remove(item)) - { - OnItemRemoved(item); - Removed?.Invoke(item); - } - } - }); - } + public bool Contains(T item) => _items.Contains(item); - public bool Contains(T item) => _items.Contains(item); + public void Clear() + { + if (Count == 0) + return; - public void Clear() + Diagram.Batch(() => { - if (Count == 0) - return; - - Diagram.Batch(() => + for (var i = _items.Count - 1; i >= 0; i--) { - for (var i = _items.Count - 1; i >= 0; i--) - { - var item = _items[i]; - _items.RemoveAt(i); - OnItemRemoved(item); - Removed?.Invoke(item); - } - }); - } + var item = _items[i]; + _items.RemoveAt(i); + OnItemRemoved(item); + Removed?.Invoke(item); + } + }); + } - protected virtual void OnItemAdded(T item) { } + protected virtual void OnItemAdded(T item) { } - protected virtual void OnItemRemoved(T item) { } + protected virtual void OnItemRemoved(T item) { } - public Diagram Diagram { get; } + public Diagram Diagram { get; } - public int Count => _items.Count; - public T this[int index] => _items[index]; - public IEnumerator GetEnumerator() => _items.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); - } + public int Count => _items.Count; + public T this[int index] => _items[index]; + public IEnumerator GetEnumerator() => _items.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); } diff --git a/src/Blazor.Diagrams.Core/Layers/GroupLayer.cs b/src/Blazor.Diagrams.Core/Layers/GroupLayer.cs index 31c89a9f5..9bb3b1779 100644 --- a/src/Blazor.Diagrams.Core/Layers/GroupLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/GroupLayer.cs @@ -1,50 +1,49 @@ using Blazor.Diagrams.Core.Models; using System.Linq; -namespace Blazor.Diagrams.Core.Layers +namespace Blazor.Diagrams.Core.Layers; + +public class GroupLayer : BaseLayer { - public class GroupLayer : BaseLayer + public GroupLayer(Diagram diagram) : base(diagram) { - public GroupLayer(Diagram diagram) : base(diagram) - { - } + } - public GroupModel Group(params NodeModel[] children) - { - return Add(Diagram.Options.Groups.Factory(Diagram, children)); - } + public GroupModel Group(params NodeModel[] children) + { + return Add(Diagram.Options.Groups.Factory(Diagram, children)); + } - /// - /// Removes the group AND its children - /// - public void Delete(GroupModel group) + /// + /// Removes the group AND its children + /// + public void Delete(GroupModel group) + { + Diagram.Batch(() => { - Diagram.Batch(() => - { - var children = group.Children.ToArray(); + var children = group.Children.ToArray(); - Remove(group); + Remove(group); - foreach (var child in children) + foreach (var child in children) + { + if (child is GroupModel g) + { + Delete(g); + } + else { - if (child is GroupModel g) - { - Delete(g); - } - else - { - Diagram.Nodes.Remove(child); - } + Diagram.Nodes.Remove(child); } - }); - } + } + }); + } - protected override void OnItemRemoved(GroupModel group) - { - Diagram.Links.Remove(group.PortLinks.ToArray()); - Diagram.Links.Remove(group.Links.ToArray()); - group.Ungroup(); - group.Group?.RemoveChild(group); - } + protected override void OnItemRemoved(GroupModel group) + { + Diagram.Links.Remove(group.PortLinks.ToArray()); + Diagram.Links.Remove(group.Links.ToArray()); + group.Ungroup(); + group.Group?.RemoveChild(group); } } diff --git a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs index 8d09f786b..b1e8983e4 100644 --- a/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/LinkLayer.cs @@ -2,64 +2,63 @@ using Blazor.Diagrams.Core.Models.Base; using System.Linq; -namespace Blazor.Diagrams.Core.Layers +namespace Blazor.Diagrams.Core.Layers; + +public class LinkLayer : BaseLayer { - public class LinkLayer : BaseLayer + public LinkLayer(Diagram diagram) : base(diagram) { } + + protected override void OnItemAdded(BaseLinkModel link) { - public LinkLayer(Diagram diagram) : base(diagram) { } + link.Diagram = Diagram; + HandleAnchor(link, link.Source, true); + HandleAnchor(link, link.Target, true); + link.Refresh(); - protected override void OnItemAdded(BaseLinkModel link) - { - link.Diagram = Diagram; - HandleAnchor(link, link.Source, true); - HandleAnchor(link, link.Target, true); - link.Refresh(); + link.SourceChanged += OnLinkSourceChanged; + link.TargetChanged += OnLinkTargetChanged; + } - link.SourceChanged += OnLinkSourceChanged; - link.TargetChanged += OnLinkTargetChanged; - } + protected override void OnItemRemoved(BaseLinkModel link) + { + link.Diagram = null; + HandleAnchor(link, link.Source, false); + HandleAnchor(link, link.Target, false); + link.Refresh(); - protected override void OnItemRemoved(BaseLinkModel link) - { - link.Diagram = null; - HandleAnchor(link, link.Source, false); - HandleAnchor(link, link.Target, false); - link.Refresh(); + link.SourceChanged -= OnLinkSourceChanged; + link.TargetChanged -= OnLinkTargetChanged; + + Diagram.Controls.RemoveFor(link); + Remove(link.Links.ToList()); + } - link.SourceChanged -= OnLinkSourceChanged; - link.TargetChanged -= OnLinkTargetChanged; - - Diagram.Controls.RemoveFor(link); - Remove(link.Links.ToList()); - } + private static void OnLinkSourceChanged(BaseLinkModel link, Anchor old, Anchor @new) + { + HandleAnchor(link, old, add: false); + HandleAnchor(link, @new, add: true); + } + + private static void OnLinkTargetChanged(BaseLinkModel link, Anchor old, Anchor @new) + { + HandleAnchor(link, old, add: false); + HandleAnchor(link, @new, add: true); + } - private static void OnLinkSourceChanged(BaseLinkModel link, Anchor old, Anchor @new) + private static void HandleAnchor(BaseLinkModel link, Anchor anchor, bool add) + { + if (add) { - HandleAnchor(link, old, add: false); - HandleAnchor(link, @new, add: true); + anchor.Model?.AddLink(link); } - - private static void OnLinkTargetChanged(BaseLinkModel link, Anchor old, Anchor @new) + else { - HandleAnchor(link, old, add: false); - HandleAnchor(link, @new, add: true); + anchor.Model?.RemoveLink(link); } - private static void HandleAnchor(BaseLinkModel link, Anchor anchor, bool add) + if (anchor.Model is Model model) { - if (add) - { - anchor.Model?.AddLink(link); - } - else - { - anchor.Model?.RemoveLink(link); - } - - if (anchor.Model is Model model) - { - model.Refresh(); - } + model.Refresh(); } } } diff --git a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs index 4cdd7b1ca..7a627d6a2 100644 --- a/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs +++ b/src/Blazor.Diagrams.Core/Layers/NodeLayer.cs @@ -1,18 +1,17 @@ using Blazor.Diagrams.Core.Models; using System.Linq; -namespace Blazor.Diagrams.Core.Layers +namespace Blazor.Diagrams.Core.Layers; + +public class NodeLayer : BaseLayer { - public class NodeLayer : BaseLayer - { - public NodeLayer(Diagram diagram) : base(diagram) { } + public NodeLayer(Diagram diagram) : base(diagram) { } - protected override void OnItemRemoved(NodeModel node) - { - Diagram.Links.Remove(node.PortLinks.ToList()); - Diagram.Links.Remove(node.Links.ToList()); - node.Group?.RemoveChild(node); - Diagram.Controls.RemoveFor(node); - } + protected override void OnItemRemoved(NodeModel node) + { + Diagram.Links.Remove(node.PortLinks.ToList()); + Diagram.Links.Remove(node.Links.ToList()); + node.Group?.RemoveChild(node); + Diagram.Controls.RemoveFor(node); } } diff --git a/src/Blazor.Diagrams.Core/Models/Base/Model.cs b/src/Blazor.Diagrams.Core/Models/Base/Model.cs index 8d277307f..738b45091 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/Model.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/Model.cs @@ -1,36 +1,35 @@ using System; -namespace Blazor.Diagrams.Core.Models.Base +namespace Blazor.Diagrams.Core.Models.Base; + +public abstract class Model { - public abstract class Model - { - private bool _visible = true; - - protected Model() : this(Guid.NewGuid().ToString()) { } + private bool _visible = true; + + protected Model() : this(Guid.NewGuid().ToString()) { } - protected Model(string id) - { - Id = id; - } + protected Model(string id) + { + Id = id; + } - public event Action? Changed; - public event Action? VisibilityChanged; + public event Action? Changed; + public event Action? VisibilityChanged; - public string Id { get; } - public bool Locked { get; set; } - public bool Visible + public string Id { get; } + public bool Locked { get; set; } + public bool Visible + { + get => _visible; + set { - get => _visible; - set - { - if (_visible == value) - return; + if (_visible == value) + return; - _visible = value; - VisibilityChanged?.Invoke(this); - } + _visible = value; + VisibilityChanged?.Invoke(this); } - - public virtual void Refresh() => Changed?.Invoke(this); } + + public virtual void Refresh() => Changed?.Invoke(this); } diff --git a/src/Blazor.Diagrams.Core/Models/Base/MovableModel.cs b/src/Blazor.Diagrams.Core/Models/Base/MovableModel.cs index f1aed7f34..bcd1be31d 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/MovableModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/MovableModel.cs @@ -1,31 +1,30 @@ using System; using Blazor.Diagrams.Core.Geometry; -namespace Blazor.Diagrams.Core.Models.Base +namespace Blazor.Diagrams.Core.Models.Base; + +// I'm assuming that all movable models (nodes & groups for now) are also selectable, +// I believe it makes sense since if you click to move something then you're also selecting +public abstract class MovableModel : SelectableModel { - // I'm assuming that all movable models (nodes & groups for now) are also selectable, - // I believe it makes sense since if you click to move something then you're also selecting - public abstract class MovableModel : SelectableModel + public event Action? Moved; + + public MovableModel(Point? position = null) { - public event Action? Moved; - - public MovableModel(Point? position = null) - { - Position = position ?? Point.Zero; - } + Position = position ?? Point.Zero; + } - public MovableModel(string id, Point? position = null) : base(id) - { - Position = position ?? Point.Zero; - } + public MovableModel(string id, Point? position = null) : base(id) + { + Position = position ?? Point.Zero; + } - public Point Position { get; set; } + public Point Position { get; set; } - public virtual void SetPosition(double x, double y) => Position = new Point(x, y); + public virtual void SetPosition(double x, double y) => Position = new Point(x, y); - /// - /// Only use this if you know what you're doing - /// - public void TriggerMoved() => Moved?.Invoke(this); - } + /// + /// Only use this if you know what you're doing + /// + public void TriggerMoved() => Moved?.Invoke(this); } diff --git a/src/Blazor.Diagrams.Core/Models/Base/SelectableModel.cs b/src/Blazor.Diagrams.Core/Models/Base/SelectableModel.cs index 0e3187a13..4d1894af3 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/SelectableModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/SelectableModel.cs @@ -1,29 +1,28 @@ using System; -namespace Blazor.Diagrams.Core.Models.Base +namespace Blazor.Diagrams.Core.Models.Base; + +public abstract class SelectableModel : Model { - public abstract class SelectableModel : Model - { - private int _order; + private int _order; - public event Action? OrderChanged; + public event Action? OrderChanged; - protected SelectableModel() { } + protected SelectableModel() { } - protected SelectableModel(string id) : base(id) { } + protected SelectableModel(string id) : base(id) { } - public bool Selected { get; internal set; } - public int Order + public bool Selected { get; internal set; } + public int Order + { + get => _order; + set { - get => _order; - set - { - if (value == Order) - return; + if (value == Order) + return; - _order = value; - OrderChanged?.Invoke(this); - } + _order = value; + OrderChanged?.Invoke(this); } } } diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index 88a9636fc..e9c619e2b 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -4,135 +4,134 @@ using System.Collections.Generic; using System.Linq; -namespace Blazor.Diagrams.Core.Models +namespace Blazor.Diagrams.Core.Models; + +public class GroupModel : NodeModel { - public class GroupModel : NodeModel + private readonly List _children; + + public GroupModel(IEnumerable children, byte padding = 30, bool autoSize = true) { - private readonly List _children; + _children = new List(); - public GroupModel(IEnumerable children, byte padding = 30, bool autoSize = true) - { - _children = new List(); + Size = Size.Zero; + Padding = padding; + AutoSize = autoSize; + Initialize(children); + } - Size = Size.Zero; - Padding = padding; - AutoSize = autoSize; - Initialize(children); - } + public IReadOnlyList Children => _children; + public byte Padding { get; } + public bool AutoSize { get; } - public IReadOnlyList Children => _children; - public byte Padding { get; } - public bool AutoSize { get; } + public void AddChild(NodeModel child) + { + _children.Add(child); + child.Group = this; + child.SizeChanged += OnNodeChanged; + child.Moving += OnNodeChanged; - public void AddChild(NodeModel child) + if (UpdateDimensions()) { - _children.Add(child); - child.Group = this; - child.SizeChanged += OnNodeChanged; - child.Moving += OnNodeChanged; - - if (UpdateDimensions()) - { - Refresh(); - } + Refresh(); } + } - public void RemoveChild(NodeModel child) - { - if (!_children.Remove(child)) - return; - - child.Group = null; - child.SizeChanged -= OnNodeChanged; - child.Moving -= OnNodeChanged; + public void RemoveChild(NodeModel child) + { + if (!_children.Remove(child)) + return; - if (UpdateDimensions()) - { - Refresh(); - RefreshLinks(); - } - } + child.Group = null; + child.SizeChanged -= OnNodeChanged; + child.Moving -= OnNodeChanged; - public override void SetPosition(double x, double y) + if (UpdateDimensions()) { - Console.WriteLine($"({(Group == null ? "Parent" : "Child")}) SetPosition {x:00} {y:00}"); - var deltaX = x - Position.X; - var deltaY = y - Position.Y; - base.SetPosition(x, y); - - foreach (var node in Children) - { - node.UpdatePositionSilently(deltaX, deltaY); - node.RefreshLinks(); - } - Refresh(); RefreshLinks(); } + } + + public override void SetPosition(double x, double y) + { + Console.WriteLine($"({(Group == null ? "Parent" : "Child")}) SetPosition {x:00} {y:00}"); + var deltaX = x - Position.X; + var deltaY = y - Position.Y; + base.SetPosition(x, y); - public override void UpdatePositionSilently(double deltaX, double deltaY) + foreach (var node in Children) { - Console.WriteLine($"({(Group == null ? "Parent" : "Child")}) UpdatePositionSilently {deltaX:00} {deltaY:00}"); - base.UpdatePositionSilently(deltaX, deltaY); + node.UpdatePositionSilently(deltaX, deltaY); + node.RefreshLinks(); + } - foreach (var child in Children) - child.UpdatePositionSilently(deltaX, deltaY); + Refresh(); + RefreshLinks(); + } - Refresh(); - } + public override void UpdatePositionSilently(double deltaX, double deltaY) + { + Console.WriteLine($"({(Group == null ? "Parent" : "Child")}) UpdatePositionSilently {deltaX:00} {deltaY:00}"); + base.UpdatePositionSilently(deltaX, deltaY); - public void Ungroup() - { - foreach (var child in Children) - { - child.Group = null; - child.SizeChanged -= OnNodeChanged; - child.Moving -= OnNodeChanged; - } - - _children.Clear(); - } + foreach (var child in Children) + child.UpdatePositionSilently(deltaX, deltaY); - private void Initialize(IEnumerable children) + Refresh(); + } + + public void Ungroup() + { + foreach (var child in Children) { - foreach (var child in children) - { - _children.Add(child); - child.Group = this; - child.SizeChanged += OnNodeChanged; - child.Moving += OnNodeChanged; - } - - UpdateDimensions(); + child.Group = null; + child.SizeChanged -= OnNodeChanged; + child.Moving -= OnNodeChanged; } - private void OnNodeChanged(NodeModel node) + _children.Clear(); + } + + private void Initialize(IEnumerable children) + { + foreach (var child in children) { - if (UpdateDimensions()) - { - Refresh(); - } + _children.Add(child); + child.Group = this; + child.SizeChanged += OnNodeChanged; + child.Moving += OnNodeChanged; } - private bool UpdateDimensions() + UpdateDimensions(); + } + + private void OnNodeChanged(NodeModel node) + { + if (UpdateDimensions()) { - if (Children.Count == 0) - return true; + Refresh(); + } + } - if (Children.Any(n => n.Size == null)) - return false; + private bool UpdateDimensions() + { + if (Children.Count == 0) + return true; - var bounds = Children.GetBounds(); + if (Children.Any(n => n.Size == null)) + return false; - var newPosition = new Point(bounds.Left - Padding, bounds.Top - Padding); - if (!Position.Equals(newPosition)) - { - Position = newPosition; - TriggerMoving(); - } + var bounds = Children.GetBounds(); - Size = new Size(bounds.Width + Padding * 2, bounds.Height + Padding * 2); - return true; + var newPosition = new Point(bounds.Left - Padding, bounds.Top - Padding); + if (!Position.Equals(newPosition)) + { + Position = newPosition; + TriggerMoving(); } + + Size = new Size(bounds.Width + Padding * 2, bounds.Height + Padding * 2); + return true; } } diff --git a/src/Blazor.Diagrams.Core/Models/LinkLabelModel.cs b/src/Blazor.Diagrams.Core/Models/LinkLabelModel.cs index 7744e885b..79f6c5f45 100644 --- a/src/Blazor.Diagrams.Core/Models/LinkLabelModel.cs +++ b/src/Blazor.Diagrams.Core/Models/LinkLabelModel.cs @@ -1,35 +1,34 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core.Models +namespace Blazor.Diagrams.Core.Models; + +public class LinkLabelModel : Model { - public class LinkLabelModel : Model + public LinkLabelModel(BaseLinkModel parent, string id, string content, double? distance = null, Point? offset = null) : base(id) { - public LinkLabelModel(BaseLinkModel parent, string id, string content, double? distance = null, Point? offset = null) : base(id) - { - Parent = parent; - Content = content; - Distance = distance; - Offset = offset; - } - - public LinkLabelModel(BaseLinkModel parent, string content, double? distance = null, Point? offset = null) - { - Parent = parent; - Content = content; - Distance = distance; - Offset = offset; - } + Parent = parent; + Content = content; + Distance = distance; + Offset = offset; + } - public BaseLinkModel Parent { get; } - public string Content { get; set; } - /// - /// 3 types of values are possible: - /// - A number between 0 and 1: Position relative to the link's length - /// - A positive number, greater than 1: Position away from the start - /// - A negative number, less than 0: Position away from the end - /// - public double? Distance { get; set; } - public Point? Offset { get; set; } + public LinkLabelModel(BaseLinkModel parent, string content, double? distance = null, Point? offset = null) + { + Parent = parent; + Content = content; + Distance = distance; + Offset = offset; } + + public BaseLinkModel Parent { get; } + public string Content { get; set; } + /// + /// 3 types of values are possible: + /// - A number between 0 and 1: Position relative to the link's length + /// - A positive number, greater than 1: Position away from the start + /// - A negative number, less than 0: Position away from the end + /// + public double? Distance { get; set; } + public Point? Offset { get; set; } } diff --git a/src/Blazor.Diagrams.Core/Models/LinkMarker.cs b/src/Blazor.Diagrams.Core/Models/LinkMarker.cs index dc6b9f470..aee69245f 100644 --- a/src/Blazor.Diagrams.Core/Models/LinkMarker.cs +++ b/src/Blazor.Diagrams.Core/Models/LinkMarker.cs @@ -1,31 +1,30 @@ using System; -namespace Blazor.Diagrams.Core.Models +namespace Blazor.Diagrams.Core.Models; + +public class LinkMarker { - public class LinkMarker - { - public static LinkMarker Arrow { get; } = new LinkMarker("M 0 -5 10 0 0 5 z", 10); - public static LinkMarker Circle { get; } = new LinkMarker("M 0, 0 a 5,5 0 1,0 10,0 a 5,5 0 1,0 -10,0", 10); - public static LinkMarker Square { get; } = new LinkMarker("M 0 -5 10 -5 10 5 0 5 z", 10); + public static LinkMarker Arrow { get; } = new LinkMarker("M 0 -5 10 0 0 5 z", 10); + public static LinkMarker Circle { get; } = new LinkMarker("M 0, 0 a 5,5 0 1,0 10,0 a 5,5 0 1,0 -10,0", 10); + public static LinkMarker Square { get; } = new LinkMarker("M 0 -5 10 -5 10 5 0 5 z", 10); - public LinkMarker(string path, double width) - { - Path = path; - Width = width; - } + public LinkMarker(string path, double width) + { + Path = path; + Width = width; + } - public string Path { get; } - public double Width { get; } + public string Path { get; } + public double Width { get; } - public static LinkMarker NewArrow(double width, double height) - => new LinkMarker(FormattableString.Invariant($"M 0 -{height / 2} {width} 0 0 {height / 2}"), width); + public static LinkMarker NewArrow(double width, double height) + => new LinkMarker(FormattableString.Invariant($"M 0 -{height / 2} {width} 0 0 {height / 2}"), width); - public static LinkMarker NewCircle(double r) - => new LinkMarker(FormattableString.Invariant($"M 0, 0 a {r},{r} 0 1,0 {r * 2},0 a {r},{r} 0 1,0 -{r * 2},0"), r * 2); + public static LinkMarker NewCircle(double r) + => new LinkMarker(FormattableString.Invariant($"M 0, 0 a {r},{r} 0 1,0 {r * 2},0 a {r},{r} 0 1,0 -{r * 2},0"), r * 2); - public static LinkMarker NewRectangle(double width, double height) - => new LinkMarker(FormattableString.Invariant($"M 0 -{height / 2} {width} -{height / 2} {width} {height / 2} 0 {height / 2} z"), width); + public static LinkMarker NewRectangle(double width, double height) + => new LinkMarker(FormattableString.Invariant($"M 0 -{height / 2} {width} -{height / 2} {width} {height / 2} 0 {height / 2} z"), width); - public static LinkMarker NewSquare(double size) => NewRectangle(size, size); - } + public static LinkMarker NewSquare(double size) => NewRectangle(size, size); } diff --git a/src/Blazor.Diagrams.Core/Models/LinkModel.cs b/src/Blazor.Diagrams.Core/Models/LinkModel.cs index e8814d80b..0fad9b04b 100644 --- a/src/Blazor.Diagrams.Core/Models/LinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/LinkModel.cs @@ -1,28 +1,27 @@ using Blazor.Diagrams.Core.Anchors; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core.Models +namespace Blazor.Diagrams.Core.Models; + +public class LinkModel : BaseLinkModel { - public class LinkModel : BaseLinkModel - { - public LinkModel(Anchor source, Anchor target) : base(source, target) { } + public LinkModel(Anchor source, Anchor target) : base(source, target) { } - public LinkModel(string id, Anchor source, Anchor target) : base(id, source, target) { } + public LinkModel(string id, Anchor source, Anchor target) : base(id, source, target) { } - public LinkModel(PortModel sourcePort, PortModel targetPort) - : base(new SinglePortAnchor(sourcePort), new SinglePortAnchor(targetPort)) { } + public LinkModel(PortModel sourcePort, PortModel targetPort) + : base(new SinglePortAnchor(sourcePort), new SinglePortAnchor(targetPort)) { } - public LinkModel(NodeModel sourceNode, NodeModel targetNode) - : base(new ShapeIntersectionAnchor(sourceNode), new ShapeIntersectionAnchor(targetNode)) { } + public LinkModel(NodeModel sourceNode, NodeModel targetNode) + : base(new ShapeIntersectionAnchor(sourceNode), new ShapeIntersectionAnchor(targetNode)) { } - public LinkModel(string id, PortModel sourcePort, PortModel targetPort) - : base(id, new SinglePortAnchor(sourcePort), new SinglePortAnchor(targetPort)) { } + public LinkModel(string id, PortModel sourcePort, PortModel targetPort) + : base(id, new SinglePortAnchor(sourcePort), new SinglePortAnchor(targetPort)) { } - public LinkModel(string id, NodeModel sourceNode, NodeModel targetNode) - : base(id, new ShapeIntersectionAnchor(sourceNode), new ShapeIntersectionAnchor(targetNode)) { } + public LinkModel(string id, NodeModel sourceNode, NodeModel targetNode) + : base(id, new ShapeIntersectionAnchor(sourceNode), new ShapeIntersectionAnchor(targetNode)) { } - public string? Color { get; set; } - public string? SelectedColor { get; set; } - public double Width { get; set; } = 2; - } + public string? Color { get; set; } + public string? SelectedColor { get; set; } + public double Width { get; set; } = 2; } diff --git a/src/Blazor.Diagrams.Core/Models/LinkVertexModel.cs b/src/Blazor.Diagrams.Core/Models/LinkVertexModel.cs index 508acda98..1816bd8ab 100644 --- a/src/Blazor.Diagrams.Core/Models/LinkVertexModel.cs +++ b/src/Blazor.Diagrams.Core/Models/LinkVertexModel.cs @@ -1,22 +1,21 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core.Models +namespace Blazor.Diagrams.Core.Models; + +public class LinkVertexModel : MovableModel { - public class LinkVertexModel : MovableModel + public LinkVertexModel(BaseLinkModel parent, Point? position = null) : base(position) { - public LinkVertexModel(BaseLinkModel parent, Point? position = null) : base(position) - { - Parent = parent; - } + Parent = parent; + } - public BaseLinkModel Parent { get; } + public BaseLinkModel Parent { get; } - public override void SetPosition(double x, double y) - { - base.SetPosition(x, y); - Refresh(); - Parent.Refresh(); - } + public override void SetPosition(double x, double y) + { + base.SetPosition(x, y); + Refresh(); + Parent.Refresh(); } } diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs index be5430a45..d3ec3f2df 100644 --- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs +++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs @@ -4,159 +4,159 @@ using System.Collections.Generic; using System.Linq; -namespace Blazor.Diagrams.Core.Models +namespace Blazor.Diagrams.Core.Models; + +public class NodeModel : MovableModel, IHasBounds, IHasShape, ILinkable { - public class NodeModel : MovableModel, IHasBounds, IHasShape, ILinkable - { - private readonly List _ports = new(); - private readonly List _links = new(); - private Size? _size; + private readonly List _ports = new(); + private readonly List _links = new(); + private Size? _size; - public event Action? SizeChanged; - public event Action? Moving; + public event Action? SizeChanged; + public event Action? Moving; - public NodeModel(Point? position = null) : base(position) - { - } + public NodeModel(Point? position = null) : base(position) + { + } - public NodeModel(string id, Point? position = null) : base(id, position) - { - } + public NodeModel(string id, Point? position = null) : base(id, position) + { + } - public Size? Size + public Size? Size + { + get => _size; + set { - get => _size; - set - { - if (value?.Equals(_size) == true) - return; - - _size = value; - SizeChanged?.Invoke(this); - } + if (value?.Equals(_size) == true) + return; + + _size = value; + SizeChanged?.Invoke(this); } + } + public bool FixedSize { get; init; } - public GroupModel? Group { get; internal set; } - public string? Title { get; set; } + public GroupModel? Group { get; internal set; } + public string? Title { get; set; } - public IReadOnlyList Ports => _ports; - public IReadOnlyList Links => _links; - public IEnumerable PortLinks => Ports.SelectMany(p => p.Links); + public IReadOnlyList Ports => _ports; + public IReadOnlyList Links => _links; + public IEnumerable PortLinks => Ports.SelectMany(p => p.Links); - #region Ports + #region Ports - public PortModel AddPort(PortModel port) - { - _ports.Add(port); - return port; - } + public PortModel AddPort(PortModel port) + { + _ports.Add(port); + return port; + } - public PortModel AddPort(PortAlignment alignment = PortAlignment.Bottom) - => AddPort(new PortModel(this, alignment, Position)); + public PortModel AddPort(PortAlignment alignment = PortAlignment.Bottom) + => AddPort(new PortModel(this, alignment, Position)); - public PortModel? GetPort(PortAlignment alignment) => Ports.FirstOrDefault(p => p.Alignment == alignment); + public PortModel? GetPort(PortAlignment alignment) => Ports.FirstOrDefault(p => p.Alignment == alignment); - public T? GetPort(PortAlignment alignment) where T : PortModel => (T?)GetPort(alignment); + public T? GetPort(PortAlignment alignment) where T : PortModel => (T?)GetPort(alignment); - public bool RemovePort(PortModel port) => _ports.Remove(port); + public bool RemovePort(PortModel port) => _ports.Remove(port); - #endregion + #endregion - #region Refreshing + #region Refreshing - public void RefreshAll() - { - Refresh(); - _ports.ForEach(p => p.RefreshAll()); - } + public void RefreshAll() + { + Refresh(); + _ports.ForEach(p => p.RefreshAll()); + } - public void RefreshLinks() + public void RefreshLinks() + { + foreach (var link in Links) { - foreach (var link in Links) - { - link.Refresh(); - link.RefreshLinks(); - } + link.Refresh(); + link.RefreshLinks(); } + } - public void ReinitializePorts() + public void ReinitializePorts() + { + foreach (var port in Ports) { - foreach (var port in Ports) - { - port.Initialized = false; - port.Refresh(); - } + port.Initialized = false; + port.Refresh(); } + } - #endregion + #endregion - public override void SetPosition(double x, double y) - { - var deltaX = x - Position.X; - var deltaY = y - Position.Y; - base.SetPosition(x, y); - - UpdatePortPositions(deltaX, deltaY); - Refresh(); - RefreshLinks(); - Moving?.Invoke(this); - } + public override void SetPosition(double x, double y) + { + var deltaX = x - Position.X; + var deltaY = y - Position.Y; + base.SetPosition(x, y); + + UpdatePortPositions(deltaX, deltaY); + Refresh(); + RefreshLinks(); + Moving?.Invoke(this); + } - public virtual void UpdatePositionSilently(double deltaX, double deltaY) - { - base.SetPosition(Position.X + deltaX, Position.Y + deltaY); - UpdatePortPositions(deltaX, deltaY); - Refresh(); - } + public virtual void UpdatePositionSilently(double deltaX, double deltaY) + { + base.SetPosition(Position.X + deltaX, Position.Y + deltaY); + UpdatePortPositions(deltaX, deltaY); + Refresh(); + } - public Rectangle? GetBounds() => GetBounds(false); + public Rectangle? GetBounds() => GetBounds(false); - public Rectangle? GetBounds(bool includePorts) - { - if (Size == null) - return null; - - if (!includePorts) - return new Rectangle(Position, Size); - - var leftPort = GetPort(PortAlignment.Left); - var topPort = GetPort(PortAlignment.Top); - var rightPort = GetPort(PortAlignment.Right); - var bottomPort = GetPort(PortAlignment.Bottom); - - var left = leftPort == null ? Position.X : Math.Min(Position.X, leftPort.Position.X); - var top = topPort == null ? Position.Y : Math.Min(Position.Y, topPort.Position.Y); - var right = rightPort == null - ? Position.X + Size!.Width - : Math.Max(rightPort.Position.X + rightPort.Size.Width, Position.X + Size!.Width); - var bottom = bottomPort == null - ? Position.Y + Size!.Height - : Math.Max(bottomPort.Position.Y + bottomPort.Size.Height, Position.Y + Size!.Height); - - return new Rectangle(left, top, right, bottom); - } + public Rectangle? GetBounds(bool includePorts) + { + if (Size == null) + return null; + + if (!includePorts) + return new Rectangle(Position, Size); + + var leftPort = GetPort(PortAlignment.Left); + var topPort = GetPort(PortAlignment.Top); + var rightPort = GetPort(PortAlignment.Right); + var bottomPort = GetPort(PortAlignment.Bottom); + + var left = leftPort == null ? Position.X : Math.Min(Position.X, leftPort.Position.X); + var top = topPort == null ? Position.Y : Math.Min(Position.Y, topPort.Position.Y); + var right = rightPort == null + ? Position.X + Size!.Width + : Math.Max(rightPort.Position.X + rightPort.Size.Width, Position.X + Size!.Width); + var bottom = bottomPort == null + ? Position.Y + Size!.Height + : Math.Max(bottomPort.Position.Y + bottomPort.Size.Height, Position.Y + Size!.Height); + + return new Rectangle(left, top, right, bottom); + } - public virtual IShape GetShape() => Shapes.Rectangle(this); + public virtual IShape GetShape() => Shapes.Rectangle(this); - public virtual bool CanAttachTo(ILinkable other) => other is not PortModel && other is not BaseLinkModel; + public virtual bool CanAttachTo(ILinkable other) => other is not PortModel && other is not BaseLinkModel; - private void UpdatePortPositions(double deltaX, double deltaY) + private void UpdatePortPositions(double deltaX, double deltaY) + { + // Save some JS calls and update ports directly here + foreach (var port in _ports) { - // Save some JS calls and update ports directly here - foreach (var port in _ports) - { - port.Position = new Point(port.Position.X + deltaX, port.Position.Y + deltaY); - port.RefreshLinks(); - } + port.Position = new Point(port.Position.X + deltaX, port.Position.Y + deltaY); + port.RefreshLinks(); } + } - protected void TriggerMoving() - { - Moving?.Invoke(this); - } + protected void TriggerMoving() + { + Moving?.Invoke(this); + } - void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); + void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); - void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link); - } + void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link); } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Models/PortAlignment.cs b/src/Blazor.Diagrams.Core/Models/PortAlignment.cs index 60d54e441..b8d7fad32 100644 --- a/src/Blazor.Diagrams.Core/Models/PortAlignment.cs +++ b/src/Blazor.Diagrams.Core/Models/PortAlignment.cs @@ -1,14 +1,13 @@ -namespace Blazor.Diagrams.Core.Models +namespace Blazor.Diagrams.Core.Models; + +public enum PortAlignment { - public enum PortAlignment - { - Top, - TopRight, - Right, - BottomRight, - Bottom, - BottomLeft, - Left, - TopLeft - } + Top, + TopRight, + Right, + BottomRight, + Bottom, + BottomLeft, + Left, + TopLeft } diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs index e5502432f..52608d044 100644 --- a/src/Blazor.Diagrams.Core/Models/PortModel.cs +++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs @@ -3,70 +3,69 @@ using Blazor.Diagrams.Core.Models.Base; using System.Collections.Generic; -namespace Blazor.Diagrams.Core.Models +namespace Blazor.Diagrams.Core.Models; + +public class PortModel : Model, IHasBounds, IHasShape, ILinkable { - public class PortModel : Model, IHasBounds, IHasShape, ILinkable - { - private readonly List _links = new(4); + private readonly List _links = new(4); - public PortModel(NodeModel parent, PortAlignment alignment = PortAlignment.Bottom, Point? position = null, - Size? size = null) - { - Parent = parent; - Alignment = alignment; - Position = position ?? Point.Zero; - Size = size ?? Size.Zero; - } + public PortModel(NodeModel parent, PortAlignment alignment = PortAlignment.Bottom, Point? position = null, + Size? size = null) + { + Parent = parent; + Alignment = alignment; + Position = position ?? Point.Zero; + Size = size ?? Size.Zero; + } - public PortModel(string id, NodeModel parent, PortAlignment alignment = PortAlignment.Bottom, - Point? position = null, Size? size = null) : base(id) - { - Parent = parent; - Alignment = alignment; - Position = position ?? Point.Zero; - Size = size ?? Size.Zero; - } + public PortModel(string id, NodeModel parent, PortAlignment alignment = PortAlignment.Bottom, + Point? position = null, Size? size = null) : base(id) + { + Parent = parent; + Alignment = alignment; + Position = position ?? Point.Zero; + Size = size ?? Size.Zero; + } - public NodeModel Parent { get; } - public PortAlignment Alignment { get; } - public Point Position { get; set; } - public Point MiddlePosition => new(Position.X + Size.Width / 2, Position.Y + Size.Height / 2); - public Size Size { get; set; } - public IReadOnlyList Links => _links; - /// - /// If set to false, a call to Refresh() will force the port to update its position/size using JS - /// - public bool Initialized { get; set; } + public NodeModel Parent { get; } + public PortAlignment Alignment { get; } + public Point Position { get; set; } + public Point MiddlePosition => new(Position.X + Size.Width / 2, Position.Y + Size.Height / 2); + public Size Size { get; set; } + public IReadOnlyList Links => _links; + /// + /// If set to false, a call to Refresh() will force the port to update its position/size using JS + /// + public bool Initialized { get; set; } - public void RefreshAll() - { - Refresh(); - RefreshLinks(); - } + public void RefreshAll() + { + Refresh(); + RefreshLinks(); + } - public void RefreshLinks() + public void RefreshLinks() + { + foreach (var link in Links) { - foreach (var link in Links) - { - link.Refresh(); - link.RefreshLinks(); - } + link.Refresh(); + link.RefreshLinks(); } + } - public T GetParent() where T : NodeModel => (T)Parent; + public T GetParent() where T : NodeModel => (T)Parent; - public Rectangle GetBounds() => new(Position, Size); + public Rectangle GetBounds() => new(Position, Size); - public virtual IShape GetShape() => Shapes.Circle(this); + public virtual IShape GetShape() => Shapes.Circle(this); - public virtual bool CanAttachTo(ILinkable other) - { - // Todo: remove in order to support same node links - return other is PortModel port && port != this && !port.Locked && Parent != port.Parent; - } + public virtual bool CanAttachTo(ILinkable other) + { + // Todo: remove in order to support same node links + return other is PortModel port && port != this && !port.Locked && Parent != port.Parent; + } - void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); + void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); - void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link); - } + void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link); } diff --git a/src/Blazor.Diagrams.Core/MouseEventButton.cs b/src/Blazor.Diagrams.Core/MouseEventButton.cs index 98d353a2c..c3d6894b1 100644 --- a/src/Blazor.Diagrams.Core/MouseEventButton.cs +++ b/src/Blazor.Diagrams.Core/MouseEventButton.cs @@ -1,11 +1,10 @@ -namespace Blazor.Diagrams.Core +namespace Blazor.Diagrams.Core; + +public enum MouseEventButton : long { - public enum MouseEventButton : long - { - Left = 0, - Wheel = 1, - Right = 2, - Fourth = 4, - Fifth = 5 - } + Left = 0, + Wheel = 1, + Right = 2, + Fourth = 4, + Fifth = 5 } diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerator.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerator.cs index afe745c30..113df2f90 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGenerator.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGenerator.cs @@ -2,37 +2,36 @@ using Blazor.Diagrams.Core.Models.Base; using System; -namespace Blazor.Diagrams.Core.PathGenerators +namespace Blazor.Diagrams.Core.PathGenerators; + +public abstract class PathGenerator { - public abstract class PathGenerator - { - public abstract PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target); + public abstract PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target); - protected static double AdjustRouteForSourceMarker(Point[] route, double markerWidth) - { - var angleInRadians = Math.Atan2(route[1].Y - route[0].Y, route[1].X - route[0].X) + Math.PI; - var xChange = markerWidth * Math.Cos(angleInRadians); - var yChange = markerWidth * Math.Sin(angleInRadians); - route[0] = new Point(route[0].X - xChange, route[0].Y - yChange); - return angleInRadians * 180 / Math.PI; - } + protected static double AdjustRouteForSourceMarker(Point[] route, double markerWidth) + { + var angleInRadians = Math.Atan2(route[1].Y - route[0].Y, route[1].X - route[0].X) + Math.PI; + var xChange = markerWidth * Math.Cos(angleInRadians); + var yChange = markerWidth * Math.Sin(angleInRadians); + route[0] = new Point(route[0].X - xChange, route[0].Y - yChange); + return angleInRadians * 180 / Math.PI; + } - protected static double AdjustRouteForTargetMarker(Point[] route, double markerWidth) - { - var angleInRadians = Math.Atan2(route[^1].Y - route[^2].Y, route[^1].X - route[^2].X); - var xChange = markerWidth * Math.Cos(angleInRadians); - var yChange = markerWidth * Math.Sin(angleInRadians); - route[^1] = new Point(route[^1].X - xChange, route[^1].Y - yChange); - return angleInRadians * 180 / Math.PI; - } + protected static double AdjustRouteForTargetMarker(Point[] route, double markerWidth) + { + var angleInRadians = Math.Atan2(route[^1].Y - route[^2].Y, route[^1].X - route[^2].X); + var xChange = markerWidth * Math.Cos(angleInRadians); + var yChange = markerWidth * Math.Sin(angleInRadians); + route[^1] = new Point(route[^1].X - xChange, route[^1].Y - yChange); + return angleInRadians * 180 / Math.PI; + } - protected static Point[] ConcatRouteAndSourceAndTarget(Point[] route, Point source, Point target) - { - var result = new Point[route.Length + 2]; - result[0] = source; - route.CopyTo(result, 1); - result[^1] = target; - return result; - } + protected static Point[] ConcatRouteAndSourceAndTarget(Point[] route, Point source, Point target) + { + var result = new Point[route.Length + 2]; + result[0] = source; + route.CopyTo(result, 1); + result[^1] = target; + return result; } } diff --git a/src/Blazor.Diagrams.Core/PathGenerators/PathGeneratorResult.cs b/src/Blazor.Diagrams.Core/PathGenerators/PathGeneratorResult.cs index b6eb69aa0..a20b9cb64 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/PathGeneratorResult.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/PathGeneratorResult.cs @@ -1,26 +1,25 @@ using Blazor.Diagrams.Core.Geometry; using SvgPathProperties; -namespace Blazor.Diagrams.Core +namespace Blazor.Diagrams.Core; + +public class PathGeneratorResult { - public class PathGeneratorResult + public PathGeneratorResult(SvgPath fullPath, SvgPath[] paths, double? sourceMarkerAngle = null, Point? sourceMarkerPosition = null, + double? targetMarkerAngle = null, Point? targetMarkerPosition = null) { - public PathGeneratorResult(SvgPath fullPath, SvgPath[] paths, double? sourceMarkerAngle = null, Point? sourceMarkerPosition = null, - double? targetMarkerAngle = null, Point? targetMarkerPosition = null) - { - FullPath = fullPath; - Paths = paths; - SourceMarkerAngle = sourceMarkerAngle; - SourceMarkerPosition = sourceMarkerPosition; - TargetMarkerAngle = targetMarkerAngle; - TargetMarkerPosition = targetMarkerPosition; - } - - public SvgPath FullPath { get; } - public SvgPath[] Paths { get; } - public double? SourceMarkerAngle { get; } - public Point? SourceMarkerPosition { get; } - public double? TargetMarkerAngle { get; } - public Point? TargetMarkerPosition { get; } + FullPath = fullPath; + Paths = paths; + SourceMarkerAngle = sourceMarkerAngle; + SourceMarkerPosition = sourceMarkerPosition; + TargetMarkerAngle = targetMarkerAngle; + TargetMarkerPosition = targetMarkerPosition; } + + public SvgPath FullPath { get; } + public SvgPath[] Paths { get; } + public double? SourceMarkerAngle { get; } + public Point? SourceMarkerPosition { get; } + public double? TargetMarkerAngle { get; } + public Point? TargetMarkerPosition { get; } } diff --git a/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs b/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs index 9b3162c7a..f1327e848 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs @@ -5,126 +5,125 @@ using SvgPathProperties; using System; -namespace Blazor.Diagrams.Core.PathGenerators +namespace Blazor.Diagrams.Core.PathGenerators; + +public class SmoothPathGenerator : PathGenerator { - public class SmoothPathGenerator : PathGenerator + private readonly double _margin; + + public SmoothPathGenerator(double margin = 125) + { + _margin = margin; + } + + public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target) { - private readonly double _margin; + route = ConcatRouteAndSourceAndTarget(route, source, target); + + if (route.Length > 2) + return CurveThroughPoints(route, link); - public SmoothPathGenerator(double margin = 125) + route = GetRouteWithCurvePoints(link, route); + double? sourceAngle = null; + double? targetAngle = null; + + if (link.SourceMarker != null) { - _margin = margin; + sourceAngle = AdjustRouteForSourceMarker(route, link.SourceMarker.Width); } - public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target) + if (link.TargetMarker != null) { - route = ConcatRouteAndSourceAndTarget(route, source, target); - - if (route.Length > 2) - return CurveThroughPoints(route, link); + targetAngle = AdjustRouteForTargetMarker(route, link.TargetMarker.Width); + } - route = GetRouteWithCurvePoints(link, route); - double? sourceAngle = null; - double? targetAngle = null; + var path = new SvgPath() + .AddMoveTo(route[0].X, route[0].Y) + .AddCubicBezierCurve(route[1].X, route[1].Y, route[2].X, route[2].Y, route[3].X, route[3].Y); - if (link.SourceMarker != null) - { - sourceAngle = AdjustRouteForSourceMarker(route, link.SourceMarker.Width); - } - - if (link.TargetMarker != null) - { - targetAngle = AdjustRouteForTargetMarker(route, link.TargetMarker.Width); - } + return new PathGeneratorResult(path, Array.Empty(), sourceAngle, route[0], targetAngle, route[^1]); + } - var path = new SvgPath() - .AddMoveTo(route[0].X, route[0].Y) - .AddCubicBezierCurve(route[1].X, route[1].Y, route[2].X, route[2].Y, route[3].X, route[3].Y); + private PathGeneratorResult CurveThroughPoints(Point[] route, BaseLinkModel link) + { + double? sourceAngle = null; + double? targetAngle = null; - return new PathGeneratorResult(path, Array.Empty(), sourceAngle, route[0], targetAngle, route[^1]); + if (link.SourceMarker != null) + { + sourceAngle = AdjustRouteForSourceMarker(route, link.SourceMarker.Width); } - private PathGeneratorResult CurveThroughPoints(Point[] route, BaseLinkModel link) + if (link.TargetMarker != null) { - double? sourceAngle = null; - double? targetAngle = null; + targetAngle = AdjustRouteForTargetMarker(route, link.TargetMarker.Width); + } - if (link.SourceMarker != null) - { - sourceAngle = AdjustRouteForSourceMarker(route, link.SourceMarker.Width); - } + BezierSpline.GetCurveControlPoints(route, out var firstControlPoints, out var secondControlPoints); + var paths = new SvgPath[firstControlPoints.Length]; + var fullPath = new SvgPath().AddMoveTo(route[0].X, route[0].Y); - if (link.TargetMarker != null) - { - targetAngle = AdjustRouteForTargetMarker(route, link.TargetMarker.Width); - } + for (var i = 0; i < firstControlPoints.Length; i++) + { + var cp1 = firstControlPoints[i]; + var cp2 = secondControlPoints[i]; + fullPath.AddCubicBezierCurve(cp1.X, cp1.Y, cp2.X, cp2.Y, route[i + 1].X, route[i + 1].Y); + paths[i] = new SvgPath().AddMoveTo(route[i].X, route[i].Y).AddCubicBezierCurve(cp1.X, cp1.Y, cp2.X, cp2.Y, route[i + 1].X, route[i + 1].Y); + } - BezierSpline.GetCurveControlPoints(route, out var firstControlPoints, out var secondControlPoints); - var paths = new SvgPath[firstControlPoints.Length]; - var fullPath = new SvgPath().AddMoveTo(route[0].X, route[0].Y); + // Todo: adjust marker positions based on closest control points + return new PathGeneratorResult(fullPath, paths, sourceAngle, route[0], targetAngle, route[^1]); + } - for (var i = 0; i < firstControlPoints.Length; i++) - { - var cp1 = firstControlPoints[i]; - var cp2 = secondControlPoints[i]; - fullPath.AddCubicBezierCurve(cp1.X, cp1.Y, cp2.X, cp2.Y, route[i + 1].X, route[i + 1].Y); - paths[i] = new SvgPath().AddMoveTo(route[i].X, route[i].Y).AddCubicBezierCurve(cp1.X, cp1.Y, cp2.X, cp2.Y, route[i + 1].X, route[i + 1].Y); - } + private Point[] GetRouteWithCurvePoints(BaseLinkModel link, Point[] route) + { + var cX = (route[0].X + route[1].X) / 2; + var cY = (route[0].Y + route[1].Y) / 2; + var curvePointA = GetCurvePoint(route, link.Source, route[0].X, route[0].Y, cX, cY, first: true); + var curvePointB = GetCurvePoint(route, link.Target, route[1].X, route[1].Y, cX, cY, first: false); + return new[] { route[0], curvePointA, curvePointB, route[1] }; + } - // Todo: adjust marker positions based on closest control points - return new PathGeneratorResult(fullPath, paths, sourceAngle, route[0], targetAngle, route[^1]); - } + private Point GetCurvePoint(Point[] route, Anchor anchor, double pX, double pY, double cX, double cY, bool first) + { + if (anchor is PositionAnchor) + return new Point(cX, cY); - private Point[] GetRouteWithCurvePoints(BaseLinkModel link, Point[] route) + if (anchor is SinglePortAnchor spa) { - var cX = (route[0].X + route[1].X) / 2; - var cY = (route[0].Y + route[1].Y) / 2; - var curvePointA = GetCurvePoint(route, link.Source, route[0].X, route[0].Y, cX, cY, first: true); - var curvePointB = GetCurvePoint(route, link.Target, route[1].X, route[1].Y, cX, cY, first: false); - return new[] { route[0], curvePointA, curvePointB, route[1] }; + return GetCurvePoint(pX, pY, cX, cY, spa.Port.Alignment); } - - private Point GetCurvePoint(Point[] route, Anchor anchor, double pX, double pY, double cX, double cY, bool first) + else if (anchor is ShapeIntersectionAnchor or DynamicAnchor) { - if (anchor is PositionAnchor) - return new Point(cX, cY); - - if (anchor is SinglePortAnchor spa) + if (Math.Abs(route[0].X - route[1].X) >= Math.Abs(route[0].Y - route[1].Y)) { - return GetCurvePoint(pX, pY, cX, cY, spa.Port.Alignment); - } - else if (anchor is ShapeIntersectionAnchor or DynamicAnchor) - { - if (Math.Abs(route[0].X - route[1].X) >= Math.Abs(route[0].Y - route[1].Y)) - { - return first ? new Point(cX, route[0].Y) : new Point(cX, route[1].Y); - } - else - { - return first ? new Point(route[0].X, cY) : new Point(route[1].X, cY); - } + return first ? new Point(cX, route[0].Y) : new Point(cX, route[1].Y); } else { - throw new DiagramsException($"Unhandled Anchor type {anchor.GetType().Name} when trying to find curve point"); + return first ? new Point(route[0].X, cY) : new Point(route[1].X, cY); } } - - private Point GetCurvePoint(double pX, double pY, double cX, double cY, PortAlignment? alignment) + else { - var margin = Math.Min(_margin, Math.Pow(Math.Pow(pX - cX, 2) + Math.Pow(pY - cY, 2), .5)); - return alignment switch - { - PortAlignment.Top => new Point(pX, Math.Min(pY - margin, cY)), - PortAlignment.Bottom => new Point(pX, Math.Max(pY + margin, cY)), - PortAlignment.TopRight => new Point(Math.Max(pX + margin, cX), Math.Min(pY - margin, cY)), - PortAlignment.BottomRight => new Point(Math.Max(pX + margin, cX), Math.Max(pY + margin, cY)), - PortAlignment.Right => new Point(Math.Max(pX + margin, cX), pY), - PortAlignment.Left => new Point(Math.Min(pX - margin, cX), pY), - PortAlignment.BottomLeft => new Point(Math.Min(pX - margin, cX), Math.Max(pY + margin, cY)), - PortAlignment.TopLeft => new Point(Math.Min(pX - margin, cX), Math.Min(pY - margin, cY)), - _ => new Point(cX, cY), - }; + throw new DiagramsException($"Unhandled Anchor type {anchor.GetType().Name} when trying to find curve point"); } } + + private Point GetCurvePoint(double pX, double pY, double cX, double cY, PortAlignment? alignment) + { + var margin = Math.Min(_margin, Math.Pow(Math.Pow(pX - cX, 2) + Math.Pow(pY - cY, 2), .5)); + return alignment switch + { + PortAlignment.Top => new Point(pX, Math.Min(pY - margin, cY)), + PortAlignment.Bottom => new Point(pX, Math.Max(pY + margin, cY)), + PortAlignment.TopRight => new Point(Math.Max(pX + margin, cX), Math.Min(pY - margin, cY)), + PortAlignment.BottomRight => new Point(Math.Max(pX + margin, cX), Math.Max(pY + margin, cY)), + PortAlignment.Right => new Point(Math.Max(pX + margin, cX), pY), + PortAlignment.Left => new Point(Math.Min(pX - margin, cX), pY), + PortAlignment.BottomLeft => new Point(Math.Min(pX - margin, cX), Math.Max(pY + margin, cY)), + PortAlignment.TopLeft => new Point(Math.Min(pX - margin, cX), Math.Min(pY - margin, cY)), + _ => new Point(cX, cY), + }; + } } diff --git a/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs index 460e44604..ed315984b 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/StraightPathGenerator.cs @@ -3,87 +3,86 @@ using SvgPathProperties; using System; -namespace Blazor.Diagrams.Core.PathGenerators +namespace Blazor.Diagrams.Core.PathGenerators; + +public class StraightPathGenerator : PathGenerator { - public class StraightPathGenerator : PathGenerator + private readonly double _radius; + + public StraightPathGenerator(double radius = 0) + { + _radius = radius; + } + + public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target) { - private readonly double _radius; + route = ConcatRouteAndSourceAndTarget(route, source, target); + + double? sourceAngle = null; + double? targetAngle = null; - public StraightPathGenerator(double radius = 0) + if (link.SourceMarker != null) { - _radius = radius; + sourceAngle = AdjustRouteForSourceMarker(route, link.SourceMarker.Width); } - public override PathGeneratorResult GetResult(Diagram diagram, BaseLinkModel link, Point[] route, Point source, Point target) + if (link.TargetMarker != null) { - route = ConcatRouteAndSourceAndTarget(route, source, target); - - double? sourceAngle = null; - double? targetAngle = null; - - if (link.SourceMarker != null) - { - sourceAngle = AdjustRouteForSourceMarker(route, link.SourceMarker.Width); - } - - if (link.TargetMarker != null) - { - targetAngle = AdjustRouteForTargetMarker(route, link.TargetMarker.Width); - } + targetAngle = AdjustRouteForTargetMarker(route, link.TargetMarker.Width); + } - var paths = link.Vertices.Count > 0 ? new SvgPath[route.Length - 1] : null; - var fullPath = new SvgPath().AddMoveTo(route[0].X, route[0].Y); - double? secondDist = null; - var lastPt = route[0]; + var paths = link.Vertices.Count > 0 ? new SvgPath[route.Length - 1] : null; + var fullPath = new SvgPath().AddMoveTo(route[0].X, route[0].Y); + double? secondDist = null; + var lastPt = route[0]; - for (var i = 0; i < route.Length - 1; i++) + for (var i = 0; i < route.Length - 1; i++) + { + if (_radius > 0 && i > 0) { - if (_radius > 0 && i > 0) - { - var previous = route[i - 1]; - var current = route[i]; - var next = route[i + 1]; - - double? firstDist = secondDist ?? (current.DistanceTo(previous) / 2); - secondDist = current.DistanceTo(next) / 2; + var previous = route[i - 1]; + var current = route[i]; + var next = route[i + 1]; - var p1 = -Math.Min(_radius, firstDist.Value); - var p2 = -Math.Min(_radius, secondDist.Value); + double? firstDist = secondDist ?? (current.DistanceTo(previous) / 2); + secondDist = current.DistanceTo(next) / 2; - var fp = current.MoveAlongLine(previous, p1); - var sp = current.MoveAlongLine(next, p2); + var p1 = -Math.Min(_radius, firstDist.Value); + var p2 = -Math.Min(_radius, secondDist.Value); - fullPath.AddLineTo(fp.X, fp.Y).AddQuadraticBezierCurve(current.X, current.Y, sp.X, sp.Y); + var fp = current.MoveAlongLine(previous, p1); + var sp = current.MoveAlongLine(next, p2); - if (paths != null) - { - paths[i - 1] = new SvgPath().AddMoveTo(lastPt.X, lastPt.Y).AddLineTo(fp.X, fp.Y).AddQuadraticBezierCurve(current.X, current.Y, sp.X, sp.Y); - } + fullPath.AddLineTo(fp.X, fp.Y).AddQuadraticBezierCurve(current.X, current.Y, sp.X, sp.Y); - lastPt = sp; + if (paths != null) + { + paths[i - 1] = new SvgPath().AddMoveTo(lastPt.X, lastPt.Y).AddLineTo(fp.X, fp.Y).AddQuadraticBezierCurve(current.X, current.Y, sp.X, sp.Y); + } - if (i == route.Length - 2) - { - fullPath.AddLineTo(route[^1].X, route[^1].Y); + lastPt = sp; - if (paths != null) - { - paths[i] = new SvgPath().AddMoveTo(lastPt.X, lastPt.Y).AddLineTo(route[^1].X, route[^1].Y); - } - } - } - else if (_radius == 0 || route.Length == 2) + if (i == route.Length - 2) { - fullPath.AddLineTo(route[i + 1].X, route[i + 1].Y); + fullPath.AddLineTo(route[^1].X, route[^1].Y); if (paths != null) { - paths[i] = new SvgPath().AddMoveTo(route[i].X, route[i].Y).AddLineTo(route[i + 1].X, route[i + 1].Y); + paths[i] = new SvgPath().AddMoveTo(lastPt.X, lastPt.Y).AddLineTo(route[^1].X, route[^1].Y); } } } + else if (_radius == 0 || route.Length == 2) + { + fullPath.AddLineTo(route[i + 1].X, route[i + 1].Y); - return new PathGeneratorResult(fullPath, paths ?? Array.Empty(), sourceAngle, route[0], targetAngle, route[^1]); + if (paths != null) + { + paths[i] = new SvgPath().AddMoveTo(route[i].X, route[i].Y).AddLineTo(route[i + 1].X, route[i + 1].Y); + } + } } + + return new PathGeneratorResult(fullPath, paths ?? Array.Empty(), sourceAngle, route[0], targetAngle, route[^1]); } } diff --git a/src/Blazor.Diagrams.Core/Routers/NormalRouter.cs b/src/Blazor.Diagrams.Core/Routers/NormalRouter.cs index f20a1ac31..e7e94f98c 100644 --- a/src/Blazor.Diagrams.Core/Routers/NormalRouter.cs +++ b/src/Blazor.Diagrams.Core/Routers/NormalRouter.cs @@ -2,13 +2,12 @@ using Blazor.Diagrams.Core.Models.Base; using System.Linq; -namespace Blazor.Diagrams.Core.Routers +namespace Blazor.Diagrams.Core.Routers; + +public class NormalRouter : Router { - public class NormalRouter : Router + public override Point[] GetRoute(Diagram diagram, BaseLinkModel link) { - public override Point[] GetRoute(Diagram diagram, BaseLinkModel link) - { - return link.Vertices.Select(v => v.Position).ToArray(); - } + return link.Vertices.Select(v => v.Position).ToArray(); } } diff --git a/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs b/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs index 50448e490..d37f9cd63 100644 --- a/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs +++ b/src/Blazor.Diagrams.Core/Routers/OrthogonalRouter.cs @@ -6,446 +6,445 @@ using Blazor.Diagrams.Core.Anchors; using System.Linq; -namespace Blazor.Diagrams.Core.Routers +namespace Blazor.Diagrams.Core.Routers; + +public class OrthogonalRouter : Router { - public class OrthogonalRouter : Router + private readonly Router _fallbackRouter; + private double _shapeMargin; + private double _globalMargin; + + public OrthogonalRouter(double shapeMargin = 10d, double globalMargin = 50d, Router? fallbackRouter = null) { - private readonly Router _fallbackRouter; - private double _shapeMargin; - private double _globalMargin; + _shapeMargin = shapeMargin; + _globalMargin = globalMargin; + _fallbackRouter = fallbackRouter ?? new NormalRouter(); + } - public OrthogonalRouter(double shapeMargin = 10d, double globalMargin = 50d, Router? fallbackRouter = null) + public override Point[] GetRoute(Diagram diagram, BaseLinkModel link) + { + if (!link.IsAttached) + return _fallbackRouter.GetRoute(diagram, link); + + if (link.Source is not SinglePortAnchor spa1) + return _fallbackRouter.GetRoute(diagram, link); + + if (link.Target is not SinglePortAnchor targetAnchor) + return _fallbackRouter.GetRoute(diagram, link); + + var sourcePort = spa1.Port; + if (targetAnchor == null || sourcePort.Parent.Size == null || targetAnchor.Port.Parent.Size == null) + return _fallbackRouter.GetRoute(diagram, link); + + var targetPort = targetAnchor.Port; + + var shapeMargin = _shapeMargin; + var globalBoundsMargin = _globalMargin; + var spots = new HashSet(); + var verticals = new List(); + var horizontals = new List(); + var sideA = sourcePort.Alignment; + var sideAVertical = IsVerticalSide(sideA); + var sideB = targetPort.Alignment; + var sideBVertical = IsVerticalSide(sideB); + var originA = GetPortPositionBasedOnAlignment(sourcePort); + var originB = GetPortPositionBasedOnAlignment(targetPort); + var shapeA = sourcePort.Parent.GetBounds(includePorts: true)!; + var shapeB = targetPort.Parent.GetBounds(includePorts: true)!; + var inflatedA = shapeA.Inflate(shapeMargin, shapeMargin); + var inflatedB = shapeB.Inflate(shapeMargin, shapeMargin); + + if (inflatedA.Intersects(inflatedB)) { - _shapeMargin = shapeMargin; - _globalMargin = globalMargin; - _fallbackRouter = fallbackRouter ?? new NormalRouter(); + shapeMargin = 0; + inflatedA = shapeA; + inflatedB = shapeB; } - public override Point[] GetRoute(Diagram diagram, BaseLinkModel link) - { - if (!link.IsAttached) - return _fallbackRouter.GetRoute(diagram, link); - - if (link.Source is not SinglePortAnchor spa1) - return _fallbackRouter.GetRoute(diagram, link); - - if (link.Target is not SinglePortAnchor targetAnchor) - return _fallbackRouter.GetRoute(diagram, link); - - var sourcePort = spa1.Port; - if (targetAnchor == null || sourcePort.Parent.Size == null || targetAnchor.Port.Parent.Size == null) - return _fallbackRouter.GetRoute(diagram, link); - - var targetPort = targetAnchor.Port; - - var shapeMargin = _shapeMargin; - var globalBoundsMargin = _globalMargin; - var spots = new HashSet(); - var verticals = new List(); - var horizontals = new List(); - var sideA = sourcePort.Alignment; - var sideAVertical = IsVerticalSide(sideA); - var sideB = targetPort.Alignment; - var sideBVertical = IsVerticalSide(sideB); - var originA = GetPortPositionBasedOnAlignment(sourcePort); - var originB = GetPortPositionBasedOnAlignment(targetPort); - var shapeA = sourcePort.Parent.GetBounds(includePorts: true)!; - var shapeB = targetPort.Parent.GetBounds(includePorts: true)!; - var inflatedA = shapeA.Inflate(shapeMargin, shapeMargin); - var inflatedB = shapeB.Inflate(shapeMargin, shapeMargin); - - if (inflatedA.Intersects(inflatedB)) - { - shapeMargin = 0; - inflatedA = shapeA; - inflatedB = shapeB; - } - - // Curated bounds to stick to - var bounds = inflatedA.Union(inflatedB).Inflate(globalBoundsMargin, globalBoundsMargin); + // Curated bounds to stick to + var bounds = inflatedA.Union(inflatedB).Inflate(globalBoundsMargin, globalBoundsMargin); - // Add edges to rulers - verticals.Add(inflatedA.Left); - verticals.Add(inflatedA.Right); - horizontals.Add(inflatedA.Top); - horizontals.Add(inflatedA.Bottom); - verticals.Add(inflatedB.Left); - verticals.Add(inflatedB.Right); - horizontals.Add(inflatedB.Top); - horizontals.Add(inflatedB.Bottom); + // Add edges to rulers + verticals.Add(inflatedA.Left); + verticals.Add(inflatedA.Right); + horizontals.Add(inflatedA.Top); + horizontals.Add(inflatedA.Bottom); + verticals.Add(inflatedB.Left); + verticals.Add(inflatedB.Right); + horizontals.Add(inflatedB.Top); + horizontals.Add(inflatedB.Bottom); - // Rulers at origins of shapes - (sideAVertical ? verticals : horizontals).Add(sideAVertical ? originA.X : originA.Y); - (sideBVertical ? verticals : horizontals).Add(sideBVertical ? originB.X : originB.Y); + // Rulers at origins of shapes + (sideAVertical ? verticals : horizontals).Add(sideAVertical ? originA.X : originA.Y); + (sideBVertical ? verticals : horizontals).Add(sideBVertical ? originB.X : originB.Y); - // Points of shape antennas - spots.Add(GetOriginSpot(originA, sideA, shapeMargin)); - spots.Add(GetOriginSpot(originB, sideB, shapeMargin)); + // Points of shape antennas + spots.Add(GetOriginSpot(originA, sideA, shapeMargin)); + spots.Add(GetOriginSpot(originB, sideB, shapeMargin)); - // Sort rulers - verticals.Sort(); - horizontals.Sort(); + // Sort rulers + verticals.Sort(); + horizontals.Sort(); - // Create grid - var grid = RulersToGrid(verticals, horizontals, bounds); - var gridPoints = GridToSpots(grid, new[] { inflatedA, inflatedB }); + // Create grid + var grid = RulersToGrid(verticals, horizontals, bounds); + var gridPoints = GridToSpots(grid, new[] { inflatedA, inflatedB }); - // Add to spots - spots.UnionWith(gridPoints); + // Add to spots + spots.UnionWith(gridPoints); - var ys = spots.Select(p => p.Y).Distinct().ToList(); - var xs = spots.Select(p => p.X).Distinct().ToList(); - ys.Sort(); - xs.Sort(); + var ys = spots.Select(p => p.Y).Distinct().ToList(); + var xs = spots.Select(p => p.X).Distinct().ToList(); + ys.Sort(); + xs.Sort(); - var nodes = spots.ToDictionary(p => p, p => new Node(p)); + var nodes = spots.ToDictionary(p => p, p => new Node(p)); - for (var i = 0; i < ys.Count; i++) + for (var i = 0; i < ys.Count; i++) + { + for (var j = 0; j < xs.Count; j++) { - for (var j = 0; j < xs.Count; j++) + var b = new Point(xs[j], ys[i]); + if (!nodes.ContainsKey(b)) + continue; + + if (j > 0) { - var b = new Point(xs[j], ys[i]); - if (!nodes.ContainsKey(b)) - continue; + var a = new Point(xs[j - 1], ys[i]); - if (j > 0) + if (nodes.ContainsKey(a)) { - var a = new Point(xs[j - 1], ys[i]); - - if (nodes.ContainsKey(a)) - { - nodes[a].ConnectedTo.Add(nodes[b]); - nodes[b].ConnectedTo.Add(nodes[a]); - } + nodes[a].ConnectedTo.Add(nodes[b]); + nodes[b].ConnectedTo.Add(nodes[a]); } + } - if (i > 0) - { - var a = new Point(xs[j], ys[i - 1]); + if (i > 0) + { + var a = new Point(xs[j], ys[i - 1]); - if (nodes.ContainsKey(a)) - { - nodes[a].ConnectedTo.Add(nodes[b]); - nodes[b].ConnectedTo.Add(nodes[a]); - } + if (nodes.ContainsKey(a)) + { + nodes[a].ConnectedTo.Add(nodes[b]); + nodes[b].ConnectedTo.Add(nodes[a]); } } } + } - var nodeA = nodes[GetOriginSpot(originA, sideA, shapeMargin)]; - var nodeB = nodes[GetOriginSpot(originB, sideB, shapeMargin)]; - var path = AStarPathfinder.GetPath(nodeA, nodeB); + var nodeA = nodes[GetOriginSpot(originA, sideA, shapeMargin)]; + var nodeB = nodes[GetOriginSpot(originB, sideB, shapeMargin)]; + var path = AStarPathfinder.GetPath(nodeA, nodeB); - if (path.Count > 0) - { - return path.ToArray(); - } - else - { - return _fallbackRouter.GetRoute(diagram, link); - } + if (path.Count > 0) + { + return path.ToArray(); } - - private static Grid RulersToGrid(List verticals, List horizontals, Rectangle bounds) + else { - var result = new Grid(); - verticals.Sort(); - horizontals.Sort(); + return _fallbackRouter.GetRoute(diagram, link); + } + } - var lastX = bounds.Left; - var lastY = bounds.Top; - var column = 0; - var row = 0; + private static Grid RulersToGrid(List verticals, List horizontals, Rectangle bounds) + { + var result = new Grid(); + verticals.Sort(); + horizontals.Sort(); - foreach (var y in horizontals) - { - foreach (var x in verticals) - { - result.Set(row, column++, new Rectangle(lastX, lastY, x, y)); - lastX = x; - } + var lastX = bounds.Left; + var lastY = bounds.Top; + var column = 0; + var row = 0; - // Last cell of the row - result.Set(row, column, new Rectangle(lastX, lastY, bounds.Right, y)); - lastX = bounds.Left; - lastY = y; - column = 0; - row++; + foreach (var y in horizontals) + { + foreach (var x in verticals) + { + result.Set(row, column++, new Rectangle(lastX, lastY, x, y)); + lastX = x; } + // Last cell of the row + result.Set(row, column, new Rectangle(lastX, lastY, bounds.Right, y)); lastX = bounds.Left; + lastY = y; + column = 0; + row++; + } - // Last fow of cells - foreach (var x in verticals) + lastX = bounds.Left; + + // Last fow of cells + foreach (var x in verticals) + { + result.Set(row, column++, new Rectangle(lastX, lastY, x, bounds.Bottom)); + lastX = x; + } + + // Last cell of last row + result.Set(row, column, new Rectangle(lastX, lastY, bounds.Right, bounds.Bottom)); + return result; + } + + private static HashSet GridToSpots(Grid grid, Rectangle[] obstacles) + { + bool IsInsideObstacles(Point p) + { + foreach (var obstacle in obstacles) { - result.Set(row, column++, new Rectangle(lastX, lastY, x, bounds.Bottom)); - lastX = x; + if (obstacle.ContainsPoint(p)) + return true; } - // Last cell of last row - result.Set(row, column, new Rectangle(lastX, lastY, bounds.Right, bounds.Bottom)); - return result; + return false; } - private static HashSet GridToSpots(Grid grid, Rectangle[] obstacles) + void AddIfNotInsideObstacles(HashSet list, Point p) { - bool IsInsideObstacles(Point p) + if (!IsInsideObstacles(p)) { - foreach (var obstacle in obstacles) - { - if (obstacle.ContainsPoint(p)) - return true; - } - - return false; + list.Add(p); } + } + + var gridPoints = new HashSet(); + foreach (var (row, data) in grid.Data) + { + var firstRow = row == 0; + var lastRow = row == grid.Rows - 1; - void AddIfNotInsideObstacles(HashSet list, Point p) + foreach (var (col, r) in data) { - if (!IsInsideObstacles(p)) + var firstCol = col == 0; + var lastCol = col == grid.Columns - 1; + var nw = firstCol && firstRow; + var ne = firstRow && lastCol; + var se = lastRow && lastCol; + var sw = lastRow && firstCol; + + if (nw || ne || se || sw) { - list.Add(p); + AddIfNotInsideObstacles(gridPoints, r.NorthWest); + AddIfNotInsideObstacles(gridPoints, r.NorthEast); + AddIfNotInsideObstacles(gridPoints, r.SouthWest); + AddIfNotInsideObstacles(gridPoints, r.SouthEast); } - } - - var gridPoints = new HashSet(); - foreach (var (row, data) in grid.Data) - { - var firstRow = row == 0; - var lastRow = row == grid.Rows - 1; - - foreach (var (col, r) in data) + else if (firstRow) { - var firstCol = col == 0; - var lastCol = col == grid.Columns - 1; - var nw = firstCol && firstRow; - var ne = firstRow && lastCol; - var se = lastRow && lastCol; - var sw = lastRow && firstCol; - - if (nw || ne || se || sw) - { - AddIfNotInsideObstacles(gridPoints, r.NorthWest); - AddIfNotInsideObstacles(gridPoints, r.NorthEast); - AddIfNotInsideObstacles(gridPoints, r.SouthWest); - AddIfNotInsideObstacles(gridPoints, r.SouthEast); - } - else if (firstRow) - { - AddIfNotInsideObstacles(gridPoints, r.NorthWest); - AddIfNotInsideObstacles(gridPoints, r.North); - AddIfNotInsideObstacles(gridPoints, r.NorthEast); - } - else if (lastRow) - { - AddIfNotInsideObstacles(gridPoints, r.SouthEast); - AddIfNotInsideObstacles(gridPoints, r.South); - AddIfNotInsideObstacles(gridPoints, r.SouthWest); - } - else if (firstCol) - { - AddIfNotInsideObstacles(gridPoints, r.NorthWest); - AddIfNotInsideObstacles(gridPoints, r.West); - AddIfNotInsideObstacles(gridPoints, r.SouthWest); - } - else if (lastCol) - { - AddIfNotInsideObstacles(gridPoints, r.NorthEast); - AddIfNotInsideObstacles(gridPoints, r.East); - AddIfNotInsideObstacles(gridPoints, r.SouthEast); - } - else - { - AddIfNotInsideObstacles(gridPoints, r.NorthWest); - AddIfNotInsideObstacles(gridPoints, r.North); - AddIfNotInsideObstacles(gridPoints, r.NorthEast); - AddIfNotInsideObstacles(gridPoints, r.East); - AddIfNotInsideObstacles(gridPoints, r.SouthEast); - AddIfNotInsideObstacles(gridPoints, r.South); - AddIfNotInsideObstacles(gridPoints, r.SouthWest); - AddIfNotInsideObstacles(gridPoints, r.West); - AddIfNotInsideObstacles(gridPoints, r.Center); - } + AddIfNotInsideObstacles(gridPoints, r.NorthWest); + AddIfNotInsideObstacles(gridPoints, r.North); + AddIfNotInsideObstacles(gridPoints, r.NorthEast); + } + else if (lastRow) + { + AddIfNotInsideObstacles(gridPoints, r.SouthEast); + AddIfNotInsideObstacles(gridPoints, r.South); + AddIfNotInsideObstacles(gridPoints, r.SouthWest); + } + else if (firstCol) + { + AddIfNotInsideObstacles(gridPoints, r.NorthWest); + AddIfNotInsideObstacles(gridPoints, r.West); + AddIfNotInsideObstacles(gridPoints, r.SouthWest); + } + else if (lastCol) + { + AddIfNotInsideObstacles(gridPoints, r.NorthEast); + AddIfNotInsideObstacles(gridPoints, r.East); + AddIfNotInsideObstacles(gridPoints, r.SouthEast); + } + else + { + AddIfNotInsideObstacles(gridPoints, r.NorthWest); + AddIfNotInsideObstacles(gridPoints, r.North); + AddIfNotInsideObstacles(gridPoints, r.NorthEast); + AddIfNotInsideObstacles(gridPoints, r.East); + AddIfNotInsideObstacles(gridPoints, r.SouthEast); + AddIfNotInsideObstacles(gridPoints, r.South); + AddIfNotInsideObstacles(gridPoints, r.SouthWest); + AddIfNotInsideObstacles(gridPoints, r.West); + AddIfNotInsideObstacles(gridPoints, r.Center); } } - - // Reduce repeated points and filter out those who touch shapes - return gridPoints; } - private static bool IsVerticalSide(PortAlignment alignment) - => alignment == PortAlignment.Top || alignment == PortAlignment.Bottom; - - private static Point GetOriginSpot(Point p, PortAlignment alignment, double shapeMargin) - { - return alignment switch - { - PortAlignment.Top => p.Add(0, -shapeMargin), - PortAlignment.Right => p.Add(shapeMargin, 0), - PortAlignment.Bottom => p.Add(0, shapeMargin), - PortAlignment.Left => p.Add(-shapeMargin, 0), - _ => throw new NotImplementedException() - }; - } + // Reduce repeated points and filter out those who touch shapes + return gridPoints; } - class Grid + private static bool IsVerticalSide(PortAlignment alignment) + => alignment == PortAlignment.Top || alignment == PortAlignment.Bottom; + + private static Point GetOriginSpot(Point p, PortAlignment alignment, double shapeMargin) { - public Grid() + return alignment switch { - Data = new Dictionary>(); - } + PortAlignment.Top => p.Add(0, -shapeMargin), + PortAlignment.Right => p.Add(shapeMargin, 0), + PortAlignment.Bottom => p.Add(0, shapeMargin), + PortAlignment.Left => p.Add(-shapeMargin, 0), + _ => throw new NotImplementedException() + }; + } +} - public Dictionary> Data { get; } - public double Rows { get; private set; } - public double Columns { get; private set; } +class Grid +{ + public Grid() + { + Data = new Dictionary>(); + } - public void Set(double row, double column, Rectangle rectangle) - { - Rows = Math.Max(Rows, row + 1); - Columns = Math.Max(Columns, column + 1); + public Dictionary> Data { get; } + public double Rows { get; private set; } + public double Columns { get; private set; } - if (!Data.ContainsKey(row)) - { - Data.Add(row, new Dictionary()); - } + public void Set(double row, double column, Rectangle rectangle) + { + Rows = Math.Max(Rows, row + 1); + Columns = Math.Max(Columns, column + 1); - Data[row].Add(column, rectangle); + if (!Data.ContainsKey(row)) + { + Data.Add(row, new Dictionary()); } + + Data[row].Add(column, rectangle); } +} - static class AStarPathfinder +static class AStarPathfinder +{ + public static IReadOnlyList GetPath(Node start, Node goal) { - public static IReadOnlyList GetPath(Node start, Node goal) - { - var frontier = new PriorityQueue(); - frontier.Enqueue(start, 0); + var frontier = new PriorityQueue(); + frontier.Enqueue(start, 0); - while (frontier.Count > 0) - { - var current = frontier.Dequeue(); + while (frontier.Count > 0) + { + var current = frontier.Dequeue(); - if (current.Equals(goal)) - break; + if (current.Equals(goal)) + break; - foreach (var next in current.ConnectedTo) + foreach (var next in current.ConnectedTo) + { + var newCost = current.Cost + 1.0; + if (current.Parent != null && IsChangeOfDirection(current.Parent.Position, current.Position, next.Position)) { - var newCost = current.Cost + 1.0; - if (current.Parent != null && IsChangeOfDirection(current.Parent.Position, current.Position, next.Position)) - { - newCost *= newCost; - newCost *= newCost; - } - - if (next.Cost == 0 || newCost < next.Cost) - { - next.Cost = newCost; - var priority = newCost + Heuristic(next.Position, goal.Position); - frontier.Enqueue(next, priority); - next.Parent = current; - } + newCost *= newCost; + newCost *= newCost; } - } - - var result = new List(); - var c = goal.Parent; - - while (c != null && c != start) - { - result.Insert(0, c.Position); - c = c.Parent; - } - if (c == start) - { - result = SimplifyPath(result); - - // In case of paths with one bend - if (result.Count > 2) + if (next.Cost == 0 || newCost < next.Cost) { - if (AreOnSameLine(result[^2], result[^1], goal.Position)) - { - result.RemoveAt(result.Count - 1); - } - - if (AreOnSameLine(start.Position, result[0], result[1])) - { - result.RemoveAt(0); - } + next.Cost = newCost; + var priority = newCost + Heuristic(next.Position, goal.Position); + frontier.Enqueue(next, priority); + next.Parent = current; } - - return result; - } - else - { - return Array.Empty(); } } - private static bool AreOnSameLine(Point prev, Point curr, Point next) + var result = new List(); + var c = goal.Parent; + + while (c != null && c != start) { - return (prev.X == curr.X && curr.X == next.X) || (prev.Y == curr.Y && curr.Y == next.Y); + result.Insert(0, c.Position); + c = c.Parent; } - private static List SimplifyPath(List path) + if (c == start) { - for (var i = path.Count - 2; i > 0; i--) + result = SimplifyPath(result); + + // In case of paths with one bend + if (result.Count > 2) { - var prev = path[i + 1]; - var curr = path[i]; - var next = path[i - 1]; + if (AreOnSameLine(result[^2], result[^1], goal.Position)) + { + result.RemoveAt(result.Count - 1); + } - if (AreOnSameLine(prev, curr, next)) + if (AreOnSameLine(start.Position, result[0], result[1])) { - path.RemoveAt(i); + result.RemoveAt(0); } } - return path; + return result; } - - private static bool IsChangeOfDirection(Point a, Point b, Point c) + else { - if (a.X == b.X && b.X != c.X) - return true; - - if (a.Y == b.Y && b.Y != c.Y) - return true; - - return false; + return Array.Empty(); } + } - private static double Heuristic(Point a, Point b) - { - return Math.Abs(a.X - b.X) + Math.Abs(a.Y - b.Y); - } + private static bool AreOnSameLine(Point prev, Point curr, Point next) + { + return (prev.X == curr.X && curr.X == next.X) || (prev.Y == curr.Y && curr.Y == next.Y); } - class Node + private static List SimplifyPath(List path) { - public Node(Point position) + for (var i = path.Count - 2; i > 0; i--) { - Position = position; - ConnectedTo = new List(); + var prev = path[i + 1]; + var curr = path[i]; + var next = path[i - 1]; + + if (AreOnSameLine(prev, curr, next)) + { + path.RemoveAt(i); + } } - public Point Position { get; } - public List ConnectedTo { get; } + return path; + } - public double Cost { get; internal set; } - public Node? Parent { get; internal set; } + private static bool IsChangeOfDirection(Point a, Point b, Point c) + { + if (a.X == b.X && b.X != c.X) + return true; - public override bool Equals(object? obj) - { - if (obj is not Node item) - return false; + if (a.Y == b.Y && b.Y != c.Y) + return true; - return Position.Equals(item.Position); - } + return false; + } - public override int GetHashCode() - { - return Position.GetHashCode(); - } + private static double Heuristic(Point a, Point b) + { + return Math.Abs(a.X - b.X) + Math.Abs(a.Y - b.Y); + } +} + +class Node +{ + public Node(Point position) + { + Position = position; + ConnectedTo = new List(); + } + + public Point Position { get; } + public List ConnectedTo { get; } + + public double Cost { get; internal set; } + public Node? Parent { get; internal set; } + + public override bool Equals(object? obj) + { + if (obj is not Node item) + return false; + + return Position.Equals(item.Position); + } + + public override int GetHashCode() + { + return Position.GetHashCode(); } } diff --git a/src/Blazor.Diagrams.Core/Routers/Router.cs b/src/Blazor.Diagrams.Core/Routers/Router.cs index d5a7d1d53..687d19c61 100644 --- a/src/Blazor.Diagrams.Core/Routers/Router.cs +++ b/src/Blazor.Diagrams.Core/Routers/Router.cs @@ -2,34 +2,33 @@ using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -namespace Blazor.Diagrams.Core.Routers +namespace Blazor.Diagrams.Core.Routers; + +public abstract class Router { - public abstract class Router - { - public abstract Point[] GetRoute(Diagram diagram, BaseLinkModel link); + public abstract Point[] GetRoute(Diagram diagram, BaseLinkModel link); - protected static Point GetPortPositionBasedOnAlignment(PortModel port) + protected static Point GetPortPositionBasedOnAlignment(PortModel port) + { + var pt = port.Position; + switch (port.Alignment) { - var pt = port.Position; - switch (port.Alignment) - { - case PortAlignment.Top: - return new Point(pt.X + port.Size.Width / 2, pt.Y); - case PortAlignment.TopRight: - return new Point(pt.X + port.Size.Width, pt.Y); - case PortAlignment.Right: - return new Point(pt.X + port.Size.Width, pt.Y + port.Size.Height / 2); - case PortAlignment.BottomRight: - return new Point(pt.X + port.Size.Width, pt.Y + port.Size.Height); - case PortAlignment.Bottom: - return new Point(pt.X + port.Size.Width / 2, pt.Y + port.Size.Height); - case PortAlignment.BottomLeft: - return new Point(pt.X, pt.Y + port.Size.Height); - case PortAlignment.Left: - return new Point(pt.X, pt.Y + port.Size.Height / 2); - default: - return pt; - } + case PortAlignment.Top: + return new Point(pt.X + port.Size.Width / 2, pt.Y); + case PortAlignment.TopRight: + return new Point(pt.X + port.Size.Width, pt.Y); + case PortAlignment.Right: + return new Point(pt.X + port.Size.Width, pt.Y + port.Size.Height / 2); + case PortAlignment.BottomRight: + return new Point(pt.X + port.Size.Width, pt.Y + port.Size.Height); + case PortAlignment.Bottom: + return new Point(pt.X + port.Size.Width / 2, pt.Y + port.Size.Height); + case PortAlignment.BottomLeft: + return new Point(pt.X, pt.Y + port.Size.Height); + case PortAlignment.Left: + return new Point(pt.X, pt.Y + port.Size.Height / 2); + default: + return pt; } } } diff --git a/src/Blazor.Diagrams.Core/Utils/KeysUtils.cs b/src/Blazor.Diagrams.Core/Utils/KeysUtils.cs index eefbab74c..01950b9b8 100644 --- a/src/Blazor.Diagrams.Core/Utils/KeysUtils.cs +++ b/src/Blazor.Diagrams.Core/Utils/KeysUtils.cs @@ -1,19 +1,18 @@ using System.Text; -namespace Blazor.Diagrams.Core.Utils +namespace Blazor.Diagrams.Core.Utils; + +public static class KeysUtils { - public static class KeysUtils + public static string GetStringRepresentation(bool ctrl, bool shift, bool alt, string key) { - public static string GetStringRepresentation(bool ctrl, bool shift, bool alt, string key) - { - var sb = new StringBuilder(); + var sb = new StringBuilder(); - if (ctrl) sb.Append("Ctrl+"); - if (shift) sb.Append("Shift+"); - if (alt) sb.Append("Alt+"); - sb.Append(key); + if (ctrl) sb.Append("Ctrl+"); + if (shift) sb.Append("Shift+"); + if (alt) sb.Append("Alt+"); + sb.Append(key); - return sb.ToString(); - } + return sb.ToString(); } } diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkVertexRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkVertexRenderer.cs index 023aac91f..7fa149d22 100644 --- a/src/Blazor.Diagrams/Components/Renderers/LinkVertexRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/LinkVertexRenderer.cs @@ -7,90 +7,89 @@ using Blazor.Diagrams.Core.Extensions; using Microsoft.AspNetCore.Components.Rendering; -namespace Blazor.Diagrams.Components.Renderers +namespace Blazor.Diagrams.Components.Renderers; + +public class LinkVertexRenderer : ComponentBase, IDisposable { - public class LinkVertexRenderer : ComponentBase, IDisposable - { - private bool _shouldRender = true; + private bool _shouldRender = true; - [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; - [Parameter] public LinkVertexModel Vertex { get; set; } = null!; - [Parameter] public string? Color { get; set; } - [Parameter] public string? SelectedColor { get; set; } + [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; + [Parameter] public LinkVertexModel Vertex { get; set; } = null!; + [Parameter] public string? Color { get; set; } + [Parameter] public string? SelectedColor { get; set; } - private string? ColorToUse => Vertex.Selected ? SelectedColor : Color; + private string? ColorToUse => Vertex.Selected ? SelectedColor : Color; - public void Dispose() - { - Vertex.Changed -= OnVertexChanged; - } + public void Dispose() + { + Vertex.Changed -= OnVertexChanged; + } - protected override void OnInitialized() - { - Vertex.Changed += OnVertexChanged; - } + protected override void OnInitialized() + { + Vertex.Changed += OnVertexChanged; + } - protected override bool ShouldRender() - { - if (!_shouldRender) return false; + protected override bool ShouldRender() + { + if (!_shouldRender) return false; - _shouldRender = false; - return true; - } + _shouldRender = false; + return true; + } - protected override void BuildRenderTree(RenderTreeBuilder builder) - { - var componentType = BlazorDiagram.GetComponent(Vertex); - - builder.OpenElement(0, "g"); - builder.AddAttribute(1, "class", "diagram-link-vertex"); - builder.AddAttribute(4, "cursor", "move"); - builder.AddAttribute(5, "ondblclick", value: EventCallback.Factory.Create(this, OnDoubleClick)); - builder.AddAttribute(6, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); - builder.AddAttribute(7, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); - builder.AddEventStopPropagationAttribute(8, "onpointerdown", true); - builder.AddEventStopPropagationAttribute(9, "onpointerup", true); - - if (componentType == null) - { - builder.OpenElement(10, "circle"); - builder.AddAttribute(11, "cx", Vertex.Position.X.ToInvariantString()); - builder.AddAttribute(12, "cy", Vertex.Position.Y.ToInvariantString()); - builder.AddAttribute(13, "r", "5"); - builder.AddAttribute(14, "fill", ColorToUse); - builder.CloseElement(); - } - else - { - builder.OpenComponent(15, componentType); - builder.AddAttribute(16, "Vertex", Vertex); - builder.AddAttribute(17, "Color", ColorToUse); - builder.CloseComponent(); - } + protected override void BuildRenderTree(RenderTreeBuilder builder) + { + var componentType = BlazorDiagram.GetComponent(Vertex); - builder.CloseElement(); - } + builder.OpenElement(0, "g"); + builder.AddAttribute(1, "class", "diagram-link-vertex"); + builder.AddAttribute(4, "cursor", "move"); + builder.AddAttribute(5, "ondblclick", value: EventCallback.Factory.Create(this, OnDoubleClick)); + builder.AddAttribute(6, "onpointerdown", EventCallback.Factory.Create(this, OnPointerDown)); + builder.AddAttribute(7, "onpointerup", EventCallback.Factory.Create(this, OnPointerUp)); + builder.AddEventStopPropagationAttribute(8, "onpointerdown", true); + builder.AddEventStopPropagationAttribute(9, "onpointerup", true); - private void OnVertexChanged(Model _) + if (componentType == null) { - _shouldRender = true; - InvokeAsync(StateHasChanged); + builder.OpenElement(10, "circle"); + builder.AddAttribute(11, "cx", Vertex.Position.X.ToInvariantString()); + builder.AddAttribute(12, "cy", Vertex.Position.Y.ToInvariantString()); + builder.AddAttribute(13, "r", "5"); + builder.AddAttribute(14, "fill", ColorToUse); + builder.CloseElement(); } - - private void OnPointerDown(PointerEventArgs e) + else { - BlazorDiagram.TriggerPointerDown(Vertex, e.ToCore()); + builder.OpenComponent(15, componentType); + builder.AddAttribute(16, "Vertex", Vertex); + builder.AddAttribute(17, "Color", ColorToUse); + builder.CloseComponent(); } - private void OnPointerUp(PointerEventArgs e) - { - BlazorDiagram.TriggerPointerUp(Vertex, e.ToCore()); - } + builder.CloseElement(); + } - private void OnDoubleClick(MouseEventArgs e) - { - Vertex.Parent.Vertices.Remove(Vertex); - Vertex.Parent.Refresh(); - } + private void OnVertexChanged(Model _) + { + _shouldRender = true; + InvokeAsync(StateHasChanged); + } + + private void OnPointerDown(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerDown(Vertex, e.ToCore()); + } + + private void OnPointerUp(PointerEventArgs e) + { + BlazorDiagram.TriggerPointerUp(Vertex, e.ToCore()); + } + + private void OnDoubleClick(MouseEventArgs e) + { + Vertex.Parent.Vertices.Remove(Vertex); + Vertex.Parent.Refresh(); } } diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs index 9778b46b7..0016a23f8 100644 --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs @@ -33,8 +33,10 @@ public void Dispose() Node.Changed -= OnNodeChanged; Node.VisibilityChanged -= OnVisibilityChanged; - if (_element.Id != null) + if (_element.Id != null && !Node.FixedSize) + { _ = JsRuntime.UnobserveResizes(_element); + } _reference?.Dispose(); } @@ -49,7 +51,9 @@ public void OnResize(Size size) size = new Size(size.Width / BlazorDiagram.Zoom, size.Height / BlazorDiagram.Zoom); if (Node.Size != null && Node.Size.Width.AlmostEqualTo(size.Width) && Node.Size.Height.AlmostEqualTo(size.Height)) + { return; + } Node.Size = size; Node.Refresh(); @@ -128,7 +132,11 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (firstRender || _becameVisible) { _becameVisible = false; - await JsRuntime.ObserveResizes(_element, _reference); + + if (!Node.FixedSize) + { + await JsRuntime.ObserveResizes(_element, _reference!); + } } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs index 7e13fb205..97a7460b4 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs @@ -5,193 +5,192 @@ using FluentAssertions; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Anchors +namespace Blazor.Diagrams.Core.Tests.Anchors; + +public class DynamicAnchorTests { - public class DynamicAnchorTests + [Fact] + public void GetPosition_ShouldReturnNull_WhenNodesSizeIsNull() + { + // Arrange + var node = new NodeModel(position: new Point(120, 95)) + { + Size = null + }; + var providers = new[] + { + new BoundsBasedPositionProvider(0.0, 0.0), + new BoundsBasedPositionProvider(0.5, 0.0), + new BoundsBasedPositionProvider(1.0, 0.0), + new BoundsBasedPositionProvider(0.0, 0.5), + new BoundsBasedPositionProvider(0.5, 0.5), + new BoundsBasedPositionProvider(1.0, 0.5), + new BoundsBasedPositionProvider(0.0, 1.0), + new BoundsBasedPositionProvider(0.5, 1.0), + new BoundsBasedPositionProvider(1.0, 1.0) + }; + var anchor1 = new DynamicAnchor(node, providers); + var anchor2 = new DynamicAnchor(node, providers); + var link = new LinkModel(anchor1, anchor2); + + // Act + var position = anchor1.GetPosition(link); + + // Assert + position.Should().BeNull(); + } + + [Fact] + public void GetPosition_ShouldReturnClosestPositionToOtherNodesCenter_WhenRouteIsEmpty() + { + // Arrange + var node1 = new NodeModel(position: new Point(120, 95)) + { + Size = new Size(100, 60) + }; + var node2 = new NodeModel(position: new Point(200, 60)) + { + Size = new Size(100, 60) + }; + var positions = new[] + { + new BoundsBasedPositionProvider(0.0, 0.0), // 120,95 + new BoundsBasedPositionProvider(0.5, 0.0), // 170,95 + new BoundsBasedPositionProvider(1.0, 0.0), // 220,95 + new BoundsBasedPositionProvider(0.0, 0.5), // 120,125 + new BoundsBasedPositionProvider(0.5, 0.5), // 170,125 + new BoundsBasedPositionProvider(1.0, 0.5), // 220,125 + new BoundsBasedPositionProvider(0.0, 1.0), // 120,155 + new BoundsBasedPositionProvider(0.5, 1.0), // 170,155 + new BoundsBasedPositionProvider(1.0, 1.0) // 220,155 + }; + var anchor1 = new DynamicAnchor(node1, positions); + var anchor2 = new DynamicAnchor(node2, positions); + var link = new LinkModel(anchor1, anchor2); + + // Act + var position = anchor1.GetPosition(link); + + // Assert + position.Should().NotBeNull(); + position!.X.Should().Be(220); + position.Y.Should().Be(95); + } + + [Fact] + public void GetPosition_ShouldReturnClosestPositionToOtherNodesCenterWithOffset_WhenRouteIsEmpty() { - [Fact] - public void GetPosition_ShouldReturnNull_WhenNodesSizeIsNull() - { - // Arrange - var node = new NodeModel(position: new Point(120, 95)) - { - Size = null - }; - var providers = new[] - { - new BoundsBasedPositionProvider(0.0, 0.0), - new BoundsBasedPositionProvider(0.5, 0.0), - new BoundsBasedPositionProvider(1.0, 0.0), - new BoundsBasedPositionProvider(0.0, 0.5), - new BoundsBasedPositionProvider(0.5, 0.5), - new BoundsBasedPositionProvider(1.0, 0.5), - new BoundsBasedPositionProvider(0.0, 1.0), - new BoundsBasedPositionProvider(0.5, 1.0), - new BoundsBasedPositionProvider(1.0, 1.0) - }; - var anchor1 = new DynamicAnchor(node, providers); - var anchor2 = new DynamicAnchor(node, providers); - var link = new LinkModel(anchor1, anchor2); - - // Act - var position = anchor1.GetPosition(link); - - // Assert - position.Should().BeNull(); - } - - [Fact] - public void GetPosition_ShouldReturnClosestPositionToOtherNodesCenter_WhenRouteIsEmpty() - { - // Arrange - var node1 = new NodeModel(position: new Point(120, 95)) - { - Size = new Size(100, 60) - }; - var node2 = new NodeModel(position: new Point(200, 60)) - { - Size = new Size(100, 60) - }; - var positions = new[] - { - new BoundsBasedPositionProvider(0.0, 0.0), // 120,95 - new BoundsBasedPositionProvider(0.5, 0.0), // 170,95 - new BoundsBasedPositionProvider(1.0, 0.0), // 220,95 - new BoundsBasedPositionProvider(0.0, 0.5), // 120,125 - new BoundsBasedPositionProvider(0.5, 0.5), // 170,125 - new BoundsBasedPositionProvider(1.0, 0.5), // 220,125 - new BoundsBasedPositionProvider(0.0, 1.0), // 120,155 - new BoundsBasedPositionProvider(0.5, 1.0), // 170,155 - new BoundsBasedPositionProvider(1.0, 1.0) // 220,155 - }; - var anchor1 = new DynamicAnchor(node1, positions); - var anchor2 = new DynamicAnchor(node2, positions); - var link = new LinkModel(anchor1, anchor2); - - // Act - var position = anchor1.GetPosition(link); - - // Assert - position.Should().NotBeNull(); - position!.X.Should().Be(220); - position.Y.Should().Be(95); - } - - [Fact] - public void GetPosition_ShouldReturnClosestPositionToOtherNodesCenterWithOffset_WhenRouteIsEmpty() - { - // Arrange - var node1 = new NodeModel(position: new Point(120, 95)) - { - Size = new Size(100, 60) - }; - var node2 = new NodeModel(position: new Point(200, 60)) - { - Size = new Size(100, 60) - }; - var positions = new[] - { - new BoundsBasedPositionProvider(0.0, 0.0), // 120,95 - new BoundsBasedPositionProvider(0.5, 0.0), // 170,95 - new BoundsBasedPositionProvider(1.0, 0.0, 10, -10), // 230,85 - new BoundsBasedPositionProvider(0.0, 0.5), // 120,125 - new BoundsBasedPositionProvider(0.5, 0.5), // 170,125 - new BoundsBasedPositionProvider(1.0, 0.5), // 220,125 - new BoundsBasedPositionProvider(0.0, 1.0), // 120,155 - new BoundsBasedPositionProvider(0.5, 1.0), // 170,155 - new BoundsBasedPositionProvider(1.0, 1.0) // 220,155 - }; - var anchor1 = new DynamicAnchor(node1, positions); - var anchor2 = new DynamicAnchor(node2, positions); - var link = new LinkModel(anchor1, anchor2); - - // Act - var position = anchor1.GetPosition(link); - - // Assert - position.Should().NotBeNull(); - position!.X.Should().Be(230); - position.Y.Should().Be(85); - } - - [Fact] - public void GetPosition_ShouldReturnClosestPositionToFirstVertex_WhenRouteIsNotEmpty() - { - // Arrange - var node1 = new NodeModel(position: new Point(120, 95)) - { - Size = new Size(100, 60) - }; - var node2 = new NodeModel(position: new Point(300, 60)) - { - Size = new Size(100, 60) - }; - var positions = new[] - { - new BoundsBasedPositionProvider(0.0, 0.0), // 120,95 - new BoundsBasedPositionProvider(0.5, 0.0), // 170,95 - new BoundsBasedPositionProvider(1.0, 0.0), // 220,95 - new BoundsBasedPositionProvider(0.0, 0.5), // 120,125 - new BoundsBasedPositionProvider(0.5, 0.5), // 170,125 - new BoundsBasedPositionProvider(1.0, 0.5), // 220,125 - new BoundsBasedPositionProvider(0.0, 1.0), // 120,155 - new BoundsBasedPositionProvider(0.5, 1.0), // 170,155 - new BoundsBasedPositionProvider(1.0, 1.0) // 220,155 - }; - var anchor1 = new DynamicAnchor(node1, positions); - var anchor2 = new DynamicAnchor(node2, positions); - var link = new LinkModel(anchor1, anchor2); - - // Act - var position = anchor1.GetPosition(link, new Point[] - { - new Point(280, 115) // Vertex - }); - - // Assert - position.Should().NotBeNull(); - position!.X.Should().Be(220); - position.Y.Should().Be(125); - } - - [Fact] - public void GetPosition_ShouldReturnClosestPositionToLastVertex_WhenRouteIsNotEmptyAndIsTarget() - { - // Arrange - var node1 = new NodeModel(position: new Point(120, 95)) - { - Size = new Size(100, 60) - }; - var node2 = new NodeModel(position: new Point(300, 60)) - { - Size = new Size(100, 60) - }; - var positions = new[] - { - new BoundsBasedPositionProvider(0.0, 0.0), // 300, 60 - new BoundsBasedPositionProvider(0.5, 0.0), // 350, 60 - new BoundsBasedPositionProvider(1.0, 0.0), // 400, 60 - new BoundsBasedPositionProvider(0.0, 0.5), // 300, 90 - new BoundsBasedPositionProvider(0.5, 0.5), // 350, 90 - new BoundsBasedPositionProvider(1.0, 0.5), // 400, 90 - new BoundsBasedPositionProvider(0.0, 1.0), // 300, 120 - new BoundsBasedPositionProvider(0.5, 1.0), // 350, 120 - new BoundsBasedPositionProvider(1.0, 1.0) // 400, 120 - }; - var anchor1 = new DynamicAnchor(node1, positions); - var anchor2 = new DynamicAnchor(node2, positions); - var link = new LinkModel(anchor1, anchor2); - - // Act - var position = anchor2.GetPosition(link, new Point[] - { - new Point(280, 115) // Vertex - }); - - // Assert - position.Should().NotBeNull(); - position!.X.Should().Be(300); - position.Y.Should().Be(120); - } + // Arrange + var node1 = new NodeModel(position: new Point(120, 95)) + { + Size = new Size(100, 60) + }; + var node2 = new NodeModel(position: new Point(200, 60)) + { + Size = new Size(100, 60) + }; + var positions = new[] + { + new BoundsBasedPositionProvider(0.0, 0.0), // 120,95 + new BoundsBasedPositionProvider(0.5, 0.0), // 170,95 + new BoundsBasedPositionProvider(1.0, 0.0, 10, -10), // 230,85 + new BoundsBasedPositionProvider(0.0, 0.5), // 120,125 + new BoundsBasedPositionProvider(0.5, 0.5), // 170,125 + new BoundsBasedPositionProvider(1.0, 0.5), // 220,125 + new BoundsBasedPositionProvider(0.0, 1.0), // 120,155 + new BoundsBasedPositionProvider(0.5, 1.0), // 170,155 + new BoundsBasedPositionProvider(1.0, 1.0) // 220,155 + }; + var anchor1 = new DynamicAnchor(node1, positions); + var anchor2 = new DynamicAnchor(node2, positions); + var link = new LinkModel(anchor1, anchor2); + + // Act + var position = anchor1.GetPosition(link); + + // Assert + position.Should().NotBeNull(); + position!.X.Should().Be(230); + position.Y.Should().Be(85); + } + + [Fact] + public void GetPosition_ShouldReturnClosestPositionToFirstVertex_WhenRouteIsNotEmpty() + { + // Arrange + var node1 = new NodeModel(position: new Point(120, 95)) + { + Size = new Size(100, 60) + }; + var node2 = new NodeModel(position: new Point(300, 60)) + { + Size = new Size(100, 60) + }; + var positions = new[] + { + new BoundsBasedPositionProvider(0.0, 0.0), // 120,95 + new BoundsBasedPositionProvider(0.5, 0.0), // 170,95 + new BoundsBasedPositionProvider(1.0, 0.0), // 220,95 + new BoundsBasedPositionProvider(0.0, 0.5), // 120,125 + new BoundsBasedPositionProvider(0.5, 0.5), // 170,125 + new BoundsBasedPositionProvider(1.0, 0.5), // 220,125 + new BoundsBasedPositionProvider(0.0, 1.0), // 120,155 + new BoundsBasedPositionProvider(0.5, 1.0), // 170,155 + new BoundsBasedPositionProvider(1.0, 1.0) // 220,155 + }; + var anchor1 = new DynamicAnchor(node1, positions); + var anchor2 = new DynamicAnchor(node2, positions); + var link = new LinkModel(anchor1, anchor2); + + // Act + var position = anchor1.GetPosition(link, new Point[] + { + new Point(280, 115) // Vertex + }); + + // Assert + position.Should().NotBeNull(); + position!.X.Should().Be(220); + position.Y.Should().Be(125); + } + + [Fact] + public void GetPosition_ShouldReturnClosestPositionToLastVertex_WhenRouteIsNotEmptyAndIsTarget() + { + // Arrange + var node1 = new NodeModel(position: new Point(120, 95)) + { + Size = new Size(100, 60) + }; + var node2 = new NodeModel(position: new Point(300, 60)) + { + Size = new Size(100, 60) + }; + var positions = new[] + { + new BoundsBasedPositionProvider(0.0, 0.0), // 300, 60 + new BoundsBasedPositionProvider(0.5, 0.0), // 350, 60 + new BoundsBasedPositionProvider(1.0, 0.0), // 400, 60 + new BoundsBasedPositionProvider(0.0, 0.5), // 300, 90 + new BoundsBasedPositionProvider(0.5, 0.5), // 350, 90 + new BoundsBasedPositionProvider(1.0, 0.5), // 400, 90 + new BoundsBasedPositionProvider(0.0, 1.0), // 300, 120 + new BoundsBasedPositionProvider(0.5, 1.0), // 350, 120 + new BoundsBasedPositionProvider(1.0, 1.0) // 400, 120 + }; + var anchor1 = new DynamicAnchor(node1, positions); + var anchor2 = new DynamicAnchor(node2, positions); + var link = new LinkModel(anchor1, anchor2); + + // Act + var position = anchor2.GetPosition(link, new Point[] + { + new Point(280, 115) // Vertex + }); + + // Assert + position.Should().NotBeNull(); + position!.X.Should().Be(300); + position.Y.Should().Be(120); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index bf31df407..49df661a6 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -6,440 +6,439 @@ using System.Linq; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Behaviors +namespace Blazor.Diagrams.Core.Tests.Behaviors; + +public class DragNewLinkBehaviorTests { - public class DragNewLinkBehaviorTests + [Fact] + public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnPort() { - [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) { - // 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(95); - ongoingPosition.Y.Should().Be(95); - } - - [Fact] - public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() + 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(95); + ongoingPosition.Y.Should().Be(95); + } + + [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) => { - // 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(95); - ongoingPosition.Y.Should().Be(95); - } - - [Fact] - public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() + 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) { - // 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().Be(145); - ongoingPosition.Y.Should().Be(145); - linkRefreshed.Should().BeTrue(); - } - - [Fact] - public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoomIsChanged() + 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(95); + ongoingPosition.Y.Should().Be(95); + } + + [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) { - // 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(101.6, 0.1); - ongoingPosition.Y.Should().BeApproximately(101.6, 0.1); - linkRefreshed.Should().BeTrue(); - } - - [Fact] - public void Behavior_ShouldSnapToClosestPortAndRefreshPort_WhenSnappingIsEnabledAndPortIsInRadius() + 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().Be(145); + ongoingPosition.Y.Should().Be(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) { - // 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() + 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(101.6, 0.1); + ongoingPosition.Y.Should().BeApproximately(101.6, 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) { - // 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() + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) { - // 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() + 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) { - // 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() + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) { - // 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() + 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) { - // 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() + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20) + }); + var port2 = node2.AddPort(new PortModel(node2) { - // 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() + 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) { - // 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() + 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) { - // 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); - } + 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); } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs index 1c6d16738..5df0724d8 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs @@ -4,106 +4,105 @@ using System.Threading.Tasks; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Behaviors +namespace Blazor.Diagrams.Core.Tests.Behaviors; + +public class EventsBehaviorTests { - public class EventsBehaviorTests + [Fact] + public void Behavior_ShouldNotTriggerMouseClick_WhenItsRemoved() + { + // Arrange + var diagram = new TestDiagram(); + diagram.UnregisterBehavior(); + var eventTriggered = false; + + // Act + diagram.PointerClick += (m, e) => eventTriggered = true; + diagram.TriggerPointerDown(null, 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 + eventTriggered.Should().BeFalse(); + } + + [Fact] + public void Behavior_ShouldTriggerMouseClick_WhenMouseDownThenUpWithoutMove() + { + // Arrange + var diagram = new TestDiagram(); + var eventTriggered = false; + + // Act + diagram.PointerClick += (m, e) => eventTriggered = true; + diagram.TriggerPointerDown(null, 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 + eventTriggered.Should().BeTrue(); + } + + [Fact] + public void Behavior_ShouldNotTriggerMouseClick_WhenMouseMoves() + { + // Arrange + var diagram = new TestDiagram(); + var eventTriggered = false; + + // Act + diagram.PointerClick += (m, e) => eventTriggered = true; + diagram.TriggerPointerDown(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, 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 + eventTriggered.Should().BeFalse(); + } + + [Fact] + public void Behavior_ShouldTriggerMouseDoubleClick_WhenTwoMouseClicksHappenWithinTime() + { + // Arrange + var diagram = new TestDiagram(); + var eventTriggered = false; + + // Act + diagram.PointerDoubleClick += (m, e) => eventTriggered = true; + diagram.TriggerPointerClick(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerClick(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + eventTriggered.Should().BeTrue(); + } + + [Fact] + public async Task Behavior_ShouldNotTriggerMouseDoubleClick_WhenTimeExceeds500() { - [Fact] - public void Behavior_ShouldNotTriggerMouseClick_WhenItsRemoved() - { - // Arrange - var diagram = new TestDiagram(); - diagram.UnregisterBehavior(); - var eventTriggered = false; - - // Act - diagram.PointerClick += (m, e) => eventTriggered = true; - diagram.TriggerPointerDown(null, 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 - eventTriggered.Should().BeFalse(); - } - - [Fact] - public void Behavior_ShouldTriggerMouseClick_WhenMouseDownThenUpWithoutMove() - { - // Arrange - var diagram = new TestDiagram(); - var eventTriggered = false; - - // Act - diagram.PointerClick += (m, e) => eventTriggered = true; - diagram.TriggerPointerDown(null, 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 - eventTriggered.Should().BeTrue(); - } - - [Fact] - public void Behavior_ShouldNotTriggerMouseClick_WhenMouseMoves() - { - // Arrange - var diagram = new TestDiagram(); - var eventTriggered = false; - - // Act - diagram.PointerClick += (m, e) => eventTriggered = true; - diagram.TriggerPointerDown(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, 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 - eventTriggered.Should().BeFalse(); - } - - [Fact] - public void Behavior_ShouldTriggerMouseDoubleClick_WhenTwoMouseClicksHappenWithinTime() - { - // Arrange - var diagram = new TestDiagram(); - var eventTriggered = false; - - // Act - diagram.PointerDoubleClick += (m, e) => eventTriggered = true; - diagram.TriggerPointerClick(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerClick(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - eventTriggered.Should().BeTrue(); - } - - [Fact] - public async Task Behavior_ShouldNotTriggerMouseDoubleClick_WhenTimeExceeds500() - { - // Arrange - var diagram = new TestDiagram(); - var eventTriggered = false; - - // Act - diagram.PointerDoubleClick += (m, e) => eventTriggered = true; - diagram.TriggerPointerClick(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - await Task.Delay(520); - diagram.TriggerPointerClick(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - eventTriggered.Should().BeFalse(); - } - - [Fact] - public void Behavior_ShouldTriggerMouseClick_OnlyWhenMouseDownWasAlsoTriggered_Issue204() - { - // Arrange - var diagram = new TestDiagram(); - var eventTriggered = false; - - // Act - diagram.PointerClick += (m, e) => eventTriggered = true; - diagram.TriggerPointerUp(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - eventTriggered.Should().BeFalse(); - } + // Arrange + var diagram = new TestDiagram(); + var eventTriggered = false; + + // Act + diagram.PointerDoubleClick += (m, e) => eventTriggered = true; + diagram.TriggerPointerClick(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + await Task.Delay(520); + diagram.TriggerPointerClick(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + eventTriggered.Should().BeFalse(); + } + + [Fact] + public void Behavior_ShouldTriggerMouseClick_OnlyWhenMouseDownWasAlsoTriggered_Issue204() + { + // Arrange + var diagram = new TestDiagram(); + var eventTriggered = false; + + // Act + diagram.PointerClick += (m, e) => eventTriggered = true; + diagram.TriggerPointerUp(null, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + eventTriggered.Should().BeFalse(); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs index 0bc9f3c0e..42fe887b7 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs @@ -4,87 +4,86 @@ using System.Threading.Tasks; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Behaviors +namespace Blazor.Diagrams.Core.Tests.Behaviors; + +public class KeyboardShortcutsBehaviorTests { - public class KeyboardShortcutsBehaviorTests + [Theory] + [InlineData("A", true, true, true)] + [InlineData("B", true, false, false)] + [InlineData("C", true, true, false)] + [InlineData("D", true, false, true)] + [InlineData("E", false, false, true)] + [InlineData("F", false, true, true)] + [InlineData("G", false, false, false)] + public void Behavior_ShouldExecuteAction_WhenCombinationIsPressed(string key, bool ctrl, bool shift, bool alt) { - [Theory] - [InlineData("A", true, true, true)] - [InlineData("B", true, false, false)] - [InlineData("C", true, true, false)] - [InlineData("D", true, false, true)] - [InlineData("E", false, false, true)] - [InlineData("F", false, true, true)] - [InlineData("G", false, false, false)] - public void Behavior_ShouldExecuteAction_WhenCombinationIsPressed(string key, bool ctrl, bool shift, bool alt) + // Arrange + var diagram = new TestDiagram(); + var ksb = diagram.GetBehavior()!; + var executed = false; + + ksb.SetShortcut(key, ctrl, shift, alt, d => { - // Arrange - var diagram = new TestDiagram(); - var ksb = diagram.GetBehavior()!; - var executed = false; + executed = true; + return ValueTask.CompletedTask; + }); - ksb.SetShortcut(key, ctrl, shift, alt, d => - { - executed = true; - return ValueTask.CompletedTask; - }); + // Act + diagram.TriggerKeyDown(new KeyboardEventArgs(key, key, 0, ctrl, shift, alt)); - // Act - diagram.TriggerKeyDown(new KeyboardEventArgs(key, key, 0, ctrl, shift, alt)); + // Assert + executed.Should().BeTrue(); + } - // Assert - executed.Should().BeTrue(); - } + [Fact] + public void Behavior_ShouldDoNothing_WhenRemoved() + { + // Arrange + var diagram = new TestDiagram(); + var ksb = diagram.GetBehavior()!; + diagram.UnregisterBehavior(); + var executed = false; - [Fact] - public void Behavior_ShouldDoNothing_WhenRemoved() + ksb.SetShortcut("A", false, false, false, d => { - // Arrange - var diagram = new TestDiagram(); - var ksb = diagram.GetBehavior()!; - diagram.UnregisterBehavior(); - var executed = false; + executed = true; + return ValueTask.CompletedTask; + }); - ksb.SetShortcut("A", false, false, false, d => - { - executed = true; - return ValueTask.CompletedTask; - }); + // Act + diagram.TriggerKeyDown(new KeyboardEventArgs("A", "A", 0, false, false, false)); - // Act - diagram.TriggerKeyDown(new KeyboardEventArgs("A", "A", 0, false, false, false)); + // Assert + executed.Should().BeFalse(); + } - // Assert - executed.Should().BeFalse(); - } + [Fact] + public void SetShortcut_ShouldOverride() + { + // Arrange + var diagram = new TestDiagram(); + var ksb = diagram.GetBehavior()!; + var executed1 = false; + var executed2 = false; - [Fact] - public void SetShortcut_ShouldOverride() + ksb.SetShortcut("A", false, false, false, d => { - // Arrange - var diagram = new TestDiagram(); - var ksb = diagram.GetBehavior()!; - var executed1 = false; - var executed2 = false; + executed1 = true; + return ValueTask.CompletedTask; + }); - ksb.SetShortcut("A", false, false, false, d => - { - executed1 = true; - return ValueTask.CompletedTask; - }); - - ksb.SetShortcut("A", false, false, false, d => - { - executed2 = true; - return ValueTask.CompletedTask; - }); + ksb.SetShortcut("A", false, false, false, d => + { + executed2 = true; + return ValueTask.CompletedTask; + }); - // Act - diagram.TriggerKeyDown(new KeyboardEventArgs("A", "A", 0, false, false, false)); + // Act + diagram.TriggerKeyDown(new KeyboardEventArgs("A", "A", 0, false, false, false)); - // Assert - executed1.Should().BeFalse(); - executed2.Should().BeTrue(); - } + // Assert + executed1.Should().BeFalse(); + executed2.Should().BeTrue(); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs index 9a1839c52..3a30fc040 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs @@ -5,130 +5,129 @@ using System.Threading.Tasks; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Behaviors +namespace Blazor.Diagrams.Core.Tests.Behaviors; + +public class KeyboardShortcutsDefaultsTests { - public class KeyboardShortcutsDefaultsTests + [Fact] + public async Task DeleteSelection_ShouldNotDeleteModel_WhenItsLocked() { - [Fact] - public async Task DeleteSelection_ShouldNotDeleteModel_WhenItsLocked() + // Arrange + var diagram = new TestDiagram(); + diagram.Nodes.Add(new NodeModel { - // Arrange - var diagram = new TestDiagram(); - diagram.Nodes.Add(new NodeModel - { - Selected = true, - Locked = true - }); - - // Act - await KeyboardShortcutsDefaults.DeleteSelection(diagram); - - // Assert - diagram.Nodes.Count.Should().Be(1); - } - - [Fact] - public async Task DeleteSelection_ShouldTakeIntoAccountGroupConstraint() + Selected = true, + Locked = true + }); + + // Act + await KeyboardShortcutsDefaults.DeleteSelection(diagram); + + // Assert + diagram.Nodes.Count.Should().Be(1); + } + + [Fact] + public async Task DeleteSelection_ShouldTakeIntoAccountGroupConstraint() + { + // Arrange + var funcCalled = false; + var diagram = new TestDiagram(); + diagram.Options.Constraints.ShouldDeleteGroup = _ => { - // Arrange - var funcCalled = false; - var diagram = new TestDiagram(); - diagram.Options.Constraints.ShouldDeleteGroup = _ => - { - funcCalled = true; - return ValueTask.FromResult(false); - }; - diagram.Groups.Add(new GroupModel(Array.Empty()) - { - Selected = true - }); - - // Act - await KeyboardShortcutsDefaults.DeleteSelection(diagram); - - // Assert - funcCalled.Should().BeTrue(); - diagram.Groups.Count.Should().Be(1); - } - - [Fact] - public async Task DeleteSelection_ShouldTakeIntoAccountNodeConstraint() + funcCalled = true; + return ValueTask.FromResult(false); + }; + diagram.Groups.Add(new GroupModel(Array.Empty()) { - // Arrange - var funcCalled = false; - var diagram = new TestDiagram(); - diagram.Options.Constraints.ShouldDeleteNode = _ => - { - funcCalled = true; - return ValueTask.FromResult(false); - }; - diagram.Nodes.Add(new NodeModel - { - Selected = true - }); - - // Act - await KeyboardShortcutsDefaults.DeleteSelection(diagram); - - // Assert - funcCalled.Should().BeTrue(); - diagram.Nodes.Count.Should().Be(1); - } - - [Fact] - public async Task DeleteSelection_ShouldTakeIntoAccountLinkConstraint() + Selected = true + }); + + // Act + await KeyboardShortcutsDefaults.DeleteSelection(diagram); + + // Assert + funcCalled.Should().BeTrue(); + diagram.Groups.Count.Should().Be(1); + } + + [Fact] + public async Task DeleteSelection_ShouldTakeIntoAccountNodeConstraint() + { + // Arrange + var funcCalled = false; + var diagram = new TestDiagram(); + diagram.Options.Constraints.ShouldDeleteNode = _ => { - // Arrange - var funcCalled = false; - var diagram = new TestDiagram(); - diagram.Options.Constraints.ShouldDeleteLink = _ => - { - funcCalled = true; - return ValueTask.FromResult(false); - }; - diagram.Nodes.Add(new NodeModel[] - { - new NodeModel(), - new NodeModel() - }); - diagram.Links.Add(new LinkModel(diagram.Nodes[0], diagram.Nodes[1]) - { - Selected = true - }); - - // Act - await KeyboardShortcutsDefaults.DeleteSelection(diagram); - - // Assert - funcCalled.Should().BeTrue(); - diagram.Links.Count.Should().Be(1); - } - - [Fact] - public async Task DeleteSelection_ShouldResultInSingleRefresh() + funcCalled = true; + return ValueTask.FromResult(false); + }; + diagram.Nodes.Add(new NodeModel { - // Arrange - var diagram = new TestDiagram(); - diagram.Nodes.Add(new NodeModel[] - { - new NodeModel { Selected = true }, - new NodeModel { Selected = true } - }); - diagram.Links.Add(new LinkModel(diagram.Nodes[0], diagram.Nodes[1]) - { - Selected = true - }); - - var refreshes = 0; - diagram.Changed += () => refreshes++; - - // Act - await KeyboardShortcutsDefaults.DeleteSelection(diagram); - - // Assert - diagram.Nodes.Count.Should().Be(0); - diagram.Links.Count.Should().Be(0); - refreshes.Should().Be(1); - } + Selected = true + }); + + // Act + await KeyboardShortcutsDefaults.DeleteSelection(diagram); + + // Assert + funcCalled.Should().BeTrue(); + diagram.Nodes.Count.Should().Be(1); + } + + [Fact] + public async Task DeleteSelection_ShouldTakeIntoAccountLinkConstraint() + { + // Arrange + var funcCalled = false; + var diagram = new TestDiagram(); + diagram.Options.Constraints.ShouldDeleteLink = _ => + { + funcCalled = true; + return ValueTask.FromResult(false); + }; + diagram.Nodes.Add(new NodeModel[] + { + new NodeModel(), + new NodeModel() + }); + diagram.Links.Add(new LinkModel(diagram.Nodes[0], diagram.Nodes[1]) + { + Selected = true + }); + + // Act + await KeyboardShortcutsDefaults.DeleteSelection(diagram); + + // Assert + funcCalled.Should().BeTrue(); + diagram.Links.Count.Should().Be(1); + } + + [Fact] + public async Task DeleteSelection_ShouldResultInSingleRefresh() + { + // Arrange + var diagram = new TestDiagram(); + diagram.Nodes.Add(new NodeModel[] + { + new NodeModel { Selected = true }, + new NodeModel { Selected = true } + }); + diagram.Links.Add(new LinkModel(diagram.Nodes[0], diagram.Nodes[1]) + { + Selected = true + }); + + var refreshes = 0; + diagram.Changed += () => refreshes++; + + // Act + await KeyboardShortcutsDefaults.DeleteSelection(diagram); + + // Assert + diagram.Nodes.Count.Should().Be(0); + diagram.Links.Count.Should().Be(0); + refreshes.Should().Be(1); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs index 492f139b8..5493e51fa 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs @@ -3,196 +3,195 @@ using System; using Xunit; -namespace Blazor.Diagrams.Core.Tests +namespace Blazor.Diagrams.Core.Tests; + +public class DiagramOrderingTests { - public class DiagramOrderingTests + [Fact] + public void GetMinOrder_ShouldReturnZeroWhenNoModelsHaveBeenAdded() + { + // Arrange + var diagram = new TestDiagram(); + + // Act + var minOrder = diagram.GetMinOrder(); + + // Assert + minOrder.Should().Be(0); + } + + [Fact] + public void GetMinOrder_ShouldReturnCorrectValue() + { + // Arrange + var diagram = new TestDiagram(); + diagram.Nodes.Add(new NodeModel()); + diagram.Groups.Add(new GroupModel(Array.Empty())); + diagram.Links.Add(new LinkModel(diagram.Nodes[0], diagram.Groups[0])); + + // Act + var minOrder = diagram.GetMinOrder(); + + // Assert + minOrder.Should().Be(1); + } + + [Fact] + public void GetMaxOrder_ShouldReturnZeroWhenNoModelsHaveBeenAdded() + { + // Arrange + var diagram = new TestDiagram(); + + // Act + var maxOrder = diagram.GetMaxOrder(); + + // Assert + maxOrder.Should().Be(0); + } + + [Fact] + public void GetMaxOrder_ShouldReturnCorrectValue() + { + // Arrange + var diagram = new TestDiagram(); + diagram.Nodes.Add(new NodeModel()); + diagram.Groups.Add(new GroupModel(Array.Empty())); + diagram.Links.Add(new LinkModel(diagram.Nodes[0], diagram.Groups[0])); + + // Act + var maxOrder = diagram.GetMaxOrder(); + + // Assert + maxOrder.Should().Be(3); + } + + [Fact] + public void Diagram_ShouldReSortWhenModelOrderChanges() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); // 1 + var node2 = diagram.Nodes.Add(new NodeModel()); // 2 + + // Act + node1.Order = 10; + + // Assert + diagram.OrderedSelectables[0].Should().Be(node2); + diagram.OrderedSelectables[1].Should().Be(node1); + } + + [Fact] + public void Diagram_ShouldRefreshOnceWhenModelOrderChanges() { - [Fact] - public void GetMinOrder_ShouldReturnZeroWhenNoModelsHaveBeenAdded() - { - // Arrange - var diagram = new TestDiagram(); - - // Act - var minOrder = diagram.GetMinOrder(); - - // Assert - minOrder.Should().Be(0); - } - - [Fact] - public void GetMinOrder_ShouldReturnCorrectValue() - { - // Arrange - var diagram = new TestDiagram(); - diagram.Nodes.Add(new NodeModel()); - diagram.Groups.Add(new GroupModel(Array.Empty())); - diagram.Links.Add(new LinkModel(diagram.Nodes[0], diagram.Groups[0])); - - // Act - var minOrder = diagram.GetMinOrder(); - - // Assert - minOrder.Should().Be(1); - } - - [Fact] - public void GetMaxOrder_ShouldReturnZeroWhenNoModelsHaveBeenAdded() - { - // Arrange - var diagram = new TestDiagram(); - - // Act - var maxOrder = diagram.GetMaxOrder(); - - // Assert - maxOrder.Should().Be(0); - } - - [Fact] - public void GetMaxOrder_ShouldReturnCorrectValue() - { - // Arrange - var diagram = new TestDiagram(); - diagram.Nodes.Add(new NodeModel()); - diagram.Groups.Add(new GroupModel(Array.Empty())); - diagram.Links.Add(new LinkModel(diagram.Nodes[0], diagram.Groups[0])); - - // Act - var maxOrder = diagram.GetMaxOrder(); - - // Assert - maxOrder.Should().Be(3); - } - - [Fact] - public void Diagram_ShouldReSortWhenModelOrderChanges() - { - // Arrange - var diagram = new TestDiagram(); - var node1 = diagram.Nodes.Add(new NodeModel()); // 1 - var node2 = diagram.Nodes.Add(new NodeModel()); // 2 - - // Act - node1.Order = 10; - - // Assert - diagram.OrderedSelectables[0].Should().Be(node2); - diagram.OrderedSelectables[1].Should().Be(node1); - } - - [Fact] - public void Diagram_ShouldRefreshOnceWhenModelOrderChanges() - { - // Arrange - var diagram = new TestDiagram(); - var node1 = diagram.Nodes.Add(new NodeModel()); // 1 - var node2 = diagram.Nodes.Add(new NodeModel()); // 2 - var refreshes = 0; - diagram.Changed += () => refreshes++; - - // Act - node1.Order = 10; - - // Assert - refreshes.Should().Be(1); - } - - [Fact] - public void SendToBack_ShouldInsertAtZeroAndFixOrders() - { - // Arrange - var diagram = new TestDiagram(); - var node1 = diagram.Nodes.Add(new NodeModel()); - var node2 = diagram.Nodes.Add(new NodeModel()); - var node3 = diagram.Nodes.Add(new NodeModel()); - - // Act - diagram.SendToBack(node3); - - // Assert - diagram.OrderedSelectables[0].Should().Be(node3); - diagram.OrderedSelectables[0].Order.Should().Be(1); - - diagram.OrderedSelectables[1].Should().Be(node1); - diagram.OrderedSelectables[1].Order.Should().Be(2); - - diagram.OrderedSelectables[2].Should().Be(node2); - diagram.OrderedSelectables[2].Order.Should().Be(3); - } - - [Fact] - public void SendToFront_ShouldAddAndFixOrder() - { - // Arrange - var diagram = new TestDiagram(); - var node1 = diagram.Nodes.Add(new NodeModel()); - var node2 = diagram.Nodes.Add(new NodeModel()); - var node3 = diagram.Nodes.Add(new NodeModel()); - - // Act - diagram.SendToFront(node1); - - // Assert - diagram.OrderedSelectables[0].Should().Be(node2); - diagram.OrderedSelectables[0].Order.Should().Be(2); - - diagram.OrderedSelectables[1].Should().Be(node3); - diagram.OrderedSelectables[1].Order.Should().Be(3); - - diagram.OrderedSelectables[2].Should().Be(node1); - diagram.OrderedSelectables[2].Order.Should().Be(4); - } - - [Fact] - public void Diagram_ShouldRefreshOnceWhenMultipleModelsWereRemoved() - { - // Arrange - var diagram = new TestDiagram(); - var node1 = diagram.Nodes.Add(new NodeModel()); - var node2 = diagram.Nodes.Add(new NodeModel()); - var link = diagram.Links.Add(new LinkModel(node1, node2)); - var refreshes = 0; - diagram.Changed += () => refreshes++; - - // Act - diagram.Nodes.Remove(node1); - - // Assert - refreshes.Should().Be(1); - } - - [Fact] - public void Diagram_ShouldNotUpdateOrders_WhenSuspendSortingIsTrue() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SuspendSorting = true; - var node1 = diagram.Nodes.Add(new NodeModel()); // 1 - var node2 = diagram.Nodes.Add(new NodeModel()); // 2 - - // Act - node1.Order = 10; - - // Assert - diagram.OrderedSelectables[0].Should().Be(node1); - diagram.OrderedSelectables[1].Should().Be(node2); - } - - [Fact] - public void RefreshOrders_ShouldSortModels() - { - // Arrange - var diagram = new TestDiagram(); - var node1 = diagram.Nodes.Add(new NodeModel() { Order = 10 }); - var node2 = diagram.Nodes.Add(new NodeModel() { Order = 5 }); - - // Act - diagram.RefreshOrders(); - - // Assert - diagram.OrderedSelectables[0].Should().Be(node2); - diagram.OrderedSelectables[1].Should().Be(node1); - } + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); // 1 + var node2 = diagram.Nodes.Add(new NodeModel()); // 2 + var refreshes = 0; + diagram.Changed += () => refreshes++; + + // Act + node1.Order = 10; + + // Assert + refreshes.Should().Be(1); + } + + [Fact] + public void SendToBack_ShouldInsertAtZeroAndFixOrders() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); + var node2 = diagram.Nodes.Add(new NodeModel()); + var node3 = diagram.Nodes.Add(new NodeModel()); + + // Act + diagram.SendToBack(node3); + + // Assert + diagram.OrderedSelectables[0].Should().Be(node3); + diagram.OrderedSelectables[0].Order.Should().Be(1); + + diagram.OrderedSelectables[1].Should().Be(node1); + diagram.OrderedSelectables[1].Order.Should().Be(2); + + diagram.OrderedSelectables[2].Should().Be(node2); + diagram.OrderedSelectables[2].Order.Should().Be(3); + } + + [Fact] + public void SendToFront_ShouldAddAndFixOrder() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); + var node2 = diagram.Nodes.Add(new NodeModel()); + var node3 = diagram.Nodes.Add(new NodeModel()); + + // Act + diagram.SendToFront(node1); + + // Assert + diagram.OrderedSelectables[0].Should().Be(node2); + diagram.OrderedSelectables[0].Order.Should().Be(2); + + diagram.OrderedSelectables[1].Should().Be(node3); + diagram.OrderedSelectables[1].Order.Should().Be(3); + + diagram.OrderedSelectables[2].Should().Be(node1); + diagram.OrderedSelectables[2].Order.Should().Be(4); + } + + [Fact] + public void Diagram_ShouldRefreshOnceWhenMultipleModelsWereRemoved() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); + var node2 = diagram.Nodes.Add(new NodeModel()); + var link = diagram.Links.Add(new LinkModel(node1, node2)); + var refreshes = 0; + diagram.Changed += () => refreshes++; + + // Act + diagram.Nodes.Remove(node1); + + // Assert + refreshes.Should().Be(1); + } + + [Fact] + public void Diagram_ShouldNotUpdateOrders_WhenSuspendSortingIsTrue() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SuspendSorting = true; + var node1 = diagram.Nodes.Add(new NodeModel()); // 1 + var node2 = diagram.Nodes.Add(new NodeModel()); // 2 + + // Act + node1.Order = 10; + + // Assert + diagram.OrderedSelectables[0].Should().Be(node1); + diagram.OrderedSelectables[1].Should().Be(node2); + } + + [Fact] + public void RefreshOrders_ShouldSortModels() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel() { Order = 10 }); + var node2 = diagram.Nodes.Add(new NodeModel() { Order = 5 }); + + // Act + diagram.RefreshOrders(); + + // Assert + diagram.OrderedSelectables[0].Should().Be(node2); + diagram.OrderedSelectables[1].Should().Be(node1); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs index b8e7d4da3..85a812dfc 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs @@ -4,123 +4,122 @@ using System; using Xunit; -namespace Blazor.Diagrams.Core.Tests +namespace Blazor.Diagrams.Core.Tests; + +public class DiagramTests { - public class DiagramTests + [Fact] + public void GetScreenPoint_ShouldReturnCorrectPoint() { - [Fact] - public void GetScreenPoint_ShouldReturnCorrectPoint() - { - // Arrange - var diagram = new TestDiagram(); - - // Act - diagram.SetZoom(1.234); - diagram.UpdatePan(50, 50); - diagram.SetContainer(new Rectangle(30, 65, 1000, 793)); - var pt = diagram.GetScreenPoint(100, 200); - - // Assert - pt.X.Should().Be(203.4); // 2*X + panX + left - pt.Y.Should().Be(361.8); // 2*Y + panY + top - } - - [Fact] - public void ZoomToFit_ShouldUseSelectedNodesIfAny() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(new Point(0, 0), new Size(1080, 768))); - diagram.Nodes.Add(new NodeModel(new Point(50, 50)) - { - Size = new Size(100, 80) - }); - diagram.SelectModel(diagram.Nodes[0], true); - - // Act - diagram.ZoomToFit(10); - - // Assert - diagram.Zoom.Should().BeApproximately(7.68, 0.001); - diagram.Pan.X.Should().Be(-307.2); - diagram.Pan.Y.Should().Be(-307.2); - } - - [Fact] - public void ZoomToFit_ShouldUseNodesWhenNoneSelected() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(new Point(0, 0), new Size(1080, 768))); - diagram.Nodes.Add(new NodeModel(new Point(50, 50)) - { - Size = new Size(100, 80) - }); - - // Act - diagram.ZoomToFit(10); - - // Assert - diagram.Zoom.Should().BeApproximately(7.68, 0.001); - diagram.Pan.X.Should().Be(-307.2); - diagram.Pan.Y.Should().Be(-307.2); - } - - [Fact] - public void ZoomToFit_ShouldTriggerAppropriateEvents() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(new Point(0, 0), new Size(1080, 768))); - diagram.Nodes.Add(new NodeModel(new Point(50, 50)) - { - Size = new Size(100, 80) - }); - - var refreshes = 0; - var zoomChanges = 0; - var panChanges = 0; - - // Act - diagram.Changed += () => refreshes++; - diagram.ZoomChanged += () => zoomChanges++; - diagram.PanChanged += () => panChanges++; - diagram.ZoomToFit(10); - - // Assert - refreshes.Should().Be(1); - zoomChanges.Should().Be(1); - panChanges.Should().Be(1); - } - - [Theory] - [InlineData(0.001)] - [InlineData(0.1)] - public void Zoom_ShoulClampToMinimumValue(double zoomValue) + // Arrange + var diagram = new TestDiagram(); + + // Act + diagram.SetZoom(1.234); + diagram.UpdatePan(50, 50); + diagram.SetContainer(new Rectangle(30, 65, 1000, 793)); + var pt = diagram.GetScreenPoint(100, 200); + + // Assert + pt.X.Should().Be(203.4); // 2*X + panX + left + pt.Y.Should().Be(361.8); // 2*Y + panY + top + } + + [Fact] + public void ZoomToFit_ShouldUseSelectedNodesIfAny() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(new Point(0, 0), new Size(1080, 768))); + diagram.Nodes.Add(new NodeModel(new Point(50, 50)) { - var diagram = new TestDiagram(); - diagram.SetZoom(zoomValue); - Assert.Equal(diagram.Zoom, diagram.Options.Zoom.Minimum); - } - - [Theory] - [InlineData(0)] - [InlineData(-0.1)] - [InlineData(-0.00001)] - public void Zoom_ThrowExceptionWhenLessThan0(double zoomValue) + Size = new Size(100, 80) + }); + diagram.SelectModel(diagram.Nodes[0], true); + + // Act + diagram.ZoomToFit(10); + + // Assert + diagram.Zoom.Should().BeApproximately(7.68, 0.001); + diagram.Pan.X.Should().Be(-307.2); + diagram.Pan.Y.Should().Be(-307.2); + } + + [Fact] + public void ZoomToFit_ShouldUseNodesWhenNoneSelected() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(new Point(0, 0), new Size(1080, 768))); + diagram.Nodes.Add(new NodeModel(new Point(50, 50)) { - var diagram = new TestDiagram(); - Assert.Throws(() => diagram.SetZoom(zoomValue)); - } - - [Theory] - [InlineData(0)] - [InlineData(-0.1)] - [InlineData(-0.00001)] - public void ZoomOptions_ThrowExceptionWhenLessThan0(double zoomValue) + Size = new Size(100, 80) + }); + + // Act + diagram.ZoomToFit(10); + + // Assert + diagram.Zoom.Should().BeApproximately(7.68, 0.001); + diagram.Pan.X.Should().Be(-307.2); + diagram.Pan.Y.Should().Be(-307.2); + } + + [Fact] + public void ZoomToFit_ShouldTriggerAppropriateEvents() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(new Point(0, 0), new Size(1080, 768))); + diagram.Nodes.Add(new NodeModel(new Point(50, 50)) { - var diagram = new TestDiagram(); - Assert.Throws(() => diagram.Options.Zoom.Minimum = zoomValue); - } + Size = new Size(100, 80) + }); + + var refreshes = 0; + var zoomChanges = 0; + var panChanges = 0; + + // Act + diagram.Changed += () => refreshes++; + diagram.ZoomChanged += () => zoomChanges++; + diagram.PanChanged += () => panChanges++; + diagram.ZoomToFit(10); + + // Assert + refreshes.Should().Be(1); + zoomChanges.Should().Be(1); + panChanges.Should().Be(1); + } + + [Theory] + [InlineData(0.001)] + [InlineData(0.1)] + public void Zoom_ShoulClampToMinimumValue(double zoomValue) + { + var diagram = new TestDiagram(); + diagram.SetZoom(zoomValue); + Assert.Equal(diagram.Zoom, diagram.Options.Zoom.Minimum); + } + + [Theory] + [InlineData(0)] + [InlineData(-0.1)] + [InlineData(-0.00001)] + public void Zoom_ThrowExceptionWhenLessThan0(double zoomValue) + { + var diagram = new TestDiagram(); + Assert.Throws(() => diagram.SetZoom(zoomValue)); + } + + [Theory] + [InlineData(0)] + [InlineData(-0.1)] + [InlineData(-0.00001)] + public void ZoomOptions_ThrowExceptionWhenLessThan0(double zoomValue) + { + var diagram = new TestDiagram(); + Assert.Throws(() => diagram.Options.Zoom.Minimum = zoomValue); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Extensions/DiagramExtensionsTests.cs b/tests/Blazor.Diagrams.Core.Tests/Extensions/DiagramExtensionsTests.cs index 3ecc98a65..534790cfc 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Extensions/DiagramExtensionsTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Extensions/DiagramExtensionsTests.cs @@ -3,47 +3,46 @@ using Blazor.Diagrams.Core.Models; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Extensions +namespace Blazor.Diagrams.Core.Tests.Extensions; + +public class DiagramExtensionsTests { - public class DiagramExtensionsTests + [Fact] + public void GetBounds_ShouldReturnZeroRectangle_WhenNodesAreEmpty() { - [Fact] - public void GetBounds_ShouldReturnZeroRectangle_WhenNodesAreEmpty() - { - // Arrange - var nodes = new NodeModel[0]; + // Arrange + var nodes = new NodeModel[0]; - // Act - var bounds = nodes.GetBounds(); + // Act + var bounds = nodes.GetBounds(); - // Assert - Assert.True(Rectangle.Zero.Equals(bounds)); - } + // Assert + Assert.True(Rectangle.Zero.Equals(bounds)); + } - [Fact] - public void GetBounds_ShouldReturnCorrectBounds() + [Fact] + public void GetBounds_ShouldReturnCorrectBounds() + { + // Arrange + var nodes = new NodeModel[] { - // Arrange - var nodes = new NodeModel[] + new NodeModel + { + Position = new Point(10, 10), + Size = new Size(100, 100) + }, + new NodeModel { - new NodeModel - { - Position = new Point(10, 10), - Size = new Size(100, 100) - }, - new NodeModel - { - Position = new Point(200, 200), - Size = new Size(100, 100) - }, - }; + Position = new Point(200, 200), + Size = new Size(100, 100) + }, + }; - // Act - var bounds = nodes.GetBounds(); + // Act + var bounds = nodes.GetBounds(); - // Assert - var expected = new Rectangle(10, 10, 300, 300); - Assert.True(expected.Equals(bounds)); - } + // Assert + var expected = new Rectangle(10, 10, 300, 300); + Assert.True(expected.Equals(bounds)); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Extensions/DoubleExtensionsTests.cs b/tests/Blazor.Diagrams.Core.Tests/Extensions/DoubleExtensionsTests.cs index c454a415c..d2bc5bbf9 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Extensions/DoubleExtensionsTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Extensions/DoubleExtensionsTests.cs @@ -1,21 +1,20 @@ using Xunit; using Blazor.Diagrams.Core.Extensions; -namespace Blazor.Diagrams.Core.Tests.Extensions +namespace Blazor.Diagrams.Core.Tests.Extensions; + +public class DoubleExtensionsTests { - public class DoubleExtensionsTests + [Theory] + [InlineData(5, 10, 0.1, false)] + [InlineData(1.1, 1.2, 0.01, false)] + [InlineData(10, 10, 0.0001, true)] + [InlineData(10.35, 10.35, 0.0001, true)] + [InlineData(1.659, 1.660, 0.0001, false)] + [InlineData(1.65999, 1.65998, 0.0001, true)] + [InlineData(1.65999, 1.6599998, 0.0001, true)] + public void AlmostEqualTo(double num1, double num2, double tolerance, bool expected) { - [Theory] - [InlineData(5, 10, 0.1, false)] - [InlineData(1.1, 1.2, 0.01, false)] - [InlineData(10, 10, 0.0001, true)] - [InlineData(10.35, 10.35, 0.0001, true)] - [InlineData(1.659, 1.660, 0.0001, false)] - [InlineData(1.65999, 1.65998, 0.0001, true)] - [InlineData(1.65999, 1.6599998, 0.0001, true)] - public void AlmostEqualTo(double num1, double num2, double tolerance, bool expected) - { - Assert.Equal(expected, num1.AlmostEqualTo(num2, tolerance)); - } + Assert.Equal(expected, num1.AlmostEqualTo(num2, tolerance)); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Geometry/PointTests.cs b/tests/Blazor.Diagrams.Core.Tests/Geometry/PointTests.cs index ab15e59a8..104103578 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Geometry/PointTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Geometry/PointTests.cs @@ -2,20 +2,19 @@ using FluentAssertions; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Geometry +namespace Blazor.Diagrams.Core.Tests.Geometry; + +public class PointTests { - public class PointTests + [Theory] + [InlineData(0, 0, 0, 0, 0)] + [InlineData(-7, -4, 17, 6.5, 26.196374)] + [InlineData(5, 10, 33, 98, 92.347171)] + [InlineData(5.5, 2.7, 6.5, 47.2, 44.511235)] + public void DistanceTo(double x1, double y1, double x2, double y2, double expected) { - [Theory] - [InlineData(0, 0, 0, 0, 0)] - [InlineData(-7, -4, 17, 6.5, 26.196374)] - [InlineData(5, 10, 33, 98, 92.347171)] - [InlineData(5.5, 2.7, 6.5, 47.2, 44.511235)] - public void DistanceTo(double x1, double y1, double x2, double y2, double expected) - { - var pt1 = new Point(x1, y1); - var pt2 = new Point(x2, y2); - pt1.DistanceTo(pt2).Should().BeApproximately(expected, 0.0001); - } + var pt1 = new Point(x1, y1); + var pt2 = new Point(x2, y2); + pt1.DistanceTo(pt2).Should().BeApproximately(expected, 0.0001); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs b/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs index 877d33cc0..c17398c6b 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs @@ -3,173 +3,172 @@ using System; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Layers +namespace Blazor.Diagrams.Core.Tests.Layers; + +public class GroupLayerTests { - public class GroupLayerTests + [Fact] + public void Group_ShouldCallFactoryThenAddMethod() { - [Fact] - public void Group_ShouldCallFactoryThenAddMethod() + // Arrange + var diagram = new TestDiagram(); + var factoryCalled = false; + + diagram.Options.Groups.Factory = (_, children) => { - // Arrange - var diagram = new TestDiagram(); - var factoryCalled = false; + factoryCalled = true; + return new GroupModel(children); + }; - diagram.Options.Groups.Factory = (_, children) => - { - factoryCalled = true; - return new GroupModel(children); - }; + // Act + diagram.Groups.Group(Array.Empty()); - // Act - diagram.Groups.Group(Array.Empty()); + // Assert + factoryCalled.Should().BeTrue(); + } - // Assert - factoryCalled.Should().BeTrue(); - } + [Fact] + public void Remove_ShouldRemoveAllPortLinks() + { + // Arrange + var diagram = new TestDiagram(); + var group = diagram.Groups.Add(new GroupModel(Array.Empty())); + var groupPort = group.AddPort(PortAlignment.Top); + var node = diagram.Nodes.Add(new NodeModel()); + var nodePort = node.AddPort(PortAlignment.Top); + diagram.Links.Add(new LinkModel(groupPort, nodePort)); + + // Act + diagram.Groups.Remove(group); + + // Assert + diagram.Links.Should().BeEmpty(); + } - [Fact] - public void Remove_ShouldRemoveAllPortLinks() - { - // Arrange - var diagram = new TestDiagram(); - var group = diagram.Groups.Add(new GroupModel(Array.Empty())); - var groupPort = group.AddPort(PortAlignment.Top); - var node = diagram.Nodes.Add(new NodeModel()); - var nodePort = node.AddPort(PortAlignment.Top); - diagram.Links.Add(new LinkModel(groupPort, nodePort)); - - // Act - diagram.Groups.Remove(group); - - // Assert - diagram.Links.Should().BeEmpty(); - } - - [Fact] - public void Remove_ShouldRemoveAllLinks() - { - // Arrange - var diagram = new TestDiagram(); - var group = diagram.Groups.Add(new GroupModel(Array.Empty())); - var node = diagram.Nodes.Add(new NodeModel()); - diagram.Links.Add(new LinkModel(group, node)); + [Fact] + public void Remove_ShouldRemoveAllLinks() + { + // Arrange + var diagram = new TestDiagram(); + var group = diagram.Groups.Add(new GroupModel(Array.Empty())); + var node = diagram.Nodes.Add(new NodeModel()); + diagram.Links.Add(new LinkModel(group, node)); - // Act - diagram.Groups.Remove(group); + // Act + diagram.Groups.Remove(group); - // Assert - diagram.Links.Should().BeEmpty(); - } + // Assert + diagram.Links.Should().BeEmpty(); + } - [Fact] - public void Remove_ShouldRemoveItselfFromParentGroup() - { - // Arrange - var diagram = new TestDiagram(); - var group1 = diagram.Groups.Add(new GroupModel(Array.Empty())); - var group2 = diagram.Groups.Add(new GroupModel(new[] { group1 })); + [Fact] + public void Remove_ShouldRemoveItselfFromParentGroup() + { + // Arrange + var diagram = new TestDiagram(); + var group1 = diagram.Groups.Add(new GroupModel(Array.Empty())); + var group2 = diagram.Groups.Add(new GroupModel(new[] { group1 })); - // Act - diagram.Groups.Remove(group1); + // Act + diagram.Groups.Remove(group1); - // Assert - group2.Children.Should().BeEmpty(); - group1.Group.Should().BeNull(); - } + // Assert + group2.Children.Should().BeEmpty(); + group1.Group.Should().BeNull(); + } - [Fact] - public void Remove_ShouldUngroup() - { - // Arrange - var diagram = new TestDiagram(); - var node = diagram.Nodes.Add(new NodeModel()); - var group = diagram.Groups.Add(new GroupModel(new[] { node })); + [Fact] + public void Remove_ShouldUngroup() + { + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel()); + var group = diagram.Groups.Add(new GroupModel(new[] { node })); - // Act - diagram.Groups.Remove(group); + // Act + diagram.Groups.Remove(group); - // Assert - group.Children.Should().BeEmpty(); - node.Group.Should().BeNull(); - } + // Assert + group.Children.Should().BeEmpty(); + node.Group.Should().BeNull(); + } - [Fact] - public void Delete_ShouldDeleteChildGroup() - { - // Arrange - var diagram = new TestDiagram(); - var group1 = diagram.Groups.Add(new GroupModel(Array.Empty())); - var group2 = diagram.Groups.Add(new GroupModel(new[] { group1 })); + [Fact] + public void Delete_ShouldDeleteChildGroup() + { + // Arrange + var diagram = new TestDiagram(); + var group1 = diagram.Groups.Add(new GroupModel(Array.Empty())); + var group2 = diagram.Groups.Add(new GroupModel(new[] { group1 })); - // Act - diagram.Groups.Delete(group2); + // Act + diagram.Groups.Delete(group2); - // Assert - diagram.Groups.Should().BeEmpty(); - } + // Assert + diagram.Groups.Should().BeEmpty(); + } - [Fact] - public void Delete_ShouldRemoveChild() - { - // Arrange - var diagram = new TestDiagram(); - var node = diagram.Nodes.Add(new NodeModel()); - var group = diagram.Groups.Add(new GroupModel(new[] { node })); + [Fact] + public void Delete_ShouldRemoveChild() + { + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel()); + var group = diagram.Groups.Add(new GroupModel(new[] { node })); - // Act - diagram.Groups.Delete(group); + // Act + diagram.Groups.Delete(group); - // Assert - diagram.Groups.Should().BeEmpty(); - diagram.Nodes.Should().BeEmpty(); - } + // Assert + diagram.Groups.Should().BeEmpty(); + diagram.Nodes.Should().BeEmpty(); + } - [Fact] - public void Add_ShouldRefreshDiagramOnce() - { - // Arrange - var diagram = new TestDiagram(); - var refreshes = 0; - diagram.Changed += () => refreshes++; + [Fact] + public void Add_ShouldRefreshDiagramOnce() + { + // Arrange + var diagram = new TestDiagram(); + var refreshes = 0; + diagram.Changed += () => refreshes++; - // Act - var group = diagram.Groups.Add(new GroupModel(Array.Empty())); + // Act + var group = diagram.Groups.Add(new GroupModel(Array.Empty())); - // Assert - refreshes.Should().Be(1); - } + // Assert + refreshes.Should().Be(1); + } - [Fact] - public void Remove_ShouldRefreshDiagramOnce() - { - // Arrange - var diagram = new TestDiagram(); - var group = diagram.Groups.Add(new GroupModel(Array.Empty())); - var refreshes = 0; - diagram.Changed += () => refreshes++; + [Fact] + public void Remove_ShouldRefreshDiagramOnce() + { + // Arrange + var diagram = new TestDiagram(); + var group = diagram.Groups.Add(new GroupModel(Array.Empty())); + var refreshes = 0; + diagram.Changed += () => refreshes++; - // Act - diagram.Groups.Remove(group); + // Act + diagram.Groups.Remove(group); - // Assert - refreshes.Should().Be(1); - } + // Assert + refreshes.Should().Be(1); + } - [Fact] - public void Delete_ShouldRefreshDiagramOnce() - { - // Arrange - var diagram = new TestDiagram(); - var node = diagram.Nodes.Add(new NodeModel()); - var group = diagram.Groups.Add(new GroupModel(new[] { node })); - var refreshes = 0; - diagram.Changed += () => refreshes++; - - // Act - diagram.Groups.Delete(group); - - // Assert - refreshes.Should().Be(1); - } + [Fact] + public void Delete_ShouldRefreshDiagramOnce() + { + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel()); + var group = diagram.Groups.Add(new GroupModel(new[] { node })); + var refreshes = 0; + diagram.Changed += () => refreshes++; + + // Act + diagram.Groups.Delete(group); + + // Assert + refreshes.Should().Be(1); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Layers/NodeLayerTests.cs b/tests/Blazor.Diagrams.Core.Tests/Layers/NodeLayerTests.cs index 9e2331b0b..7c5718ef4 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Layers/NodeLayerTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Layers/NodeLayerTests.cs @@ -2,106 +2,105 @@ using FluentAssertions; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Layers +namespace Blazor.Diagrams.Core.Tests.Layers; + +public class NodeLayerTests { - public class NodeLayerTests + [Fact] + public void Remove_ShouldRemoveAllPortLinks() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); + var nodePort1 = node1.AddPort(PortAlignment.Top); + var node2 = diagram.Nodes.Add(new NodeModel()); + var nodePort2 = node2.AddPort(PortAlignment.Top); + diagram.Links.Add(new LinkModel(nodePort1, nodePort2)); + + // Act + diagram.Nodes.Remove(node1); + + // Assert + diagram.Links.Should().BeEmpty(); + } + + [Fact] + public void Remove_ShouldRemoveAllLinks() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); + var node2 = diagram.Nodes.Add(new NodeModel()); + diagram.Links.Add(new LinkModel(node1, node2)); + + // Act + diagram.Nodes.Remove(node1); + + // Assert + diagram.Links.Should().BeEmpty(); + } + + [Fact] + public void Remove_ShouldRemoveItselfFromParentGroup() + { + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel()); + var group = diagram.Groups.Add(new GroupModel(new[] { node })); + + // Act + diagram.Nodes.Remove(node); + + // Assert + group.Children.Should().BeEmpty(); + node.Group.Should().BeNull(); + } + + [Fact] + public void Add_ShouldRefreshDiagramOnce() + { + // Arrange + var diagram = new TestDiagram(); + var refreshes = 0; + diagram.Changed += () => refreshes++; + + // Act + var node = diagram.Nodes.Add(new NodeModel()); + + // Assert + refreshes.Should().Be(1); + } + + [Fact] + public void Remove_ShouldRefreshDiagramOnce() + { + // Arrange + var diagram = new TestDiagram(); + var node1 = diagram.Nodes.Add(new NodeModel()); + var node2 = diagram.Nodes.Add(new NodeModel()); + diagram.Links.Add(new LinkModel(node1, node2)); + var refreshes = 0; + diagram.Changed += () => refreshes++; + + // Act + diagram.Nodes.Remove(node1); + + // Assert + refreshes.Should().Be(1); + } + + [Fact] + public void Remove_ShouldRemoveControls() { - [Fact] - public void Remove_ShouldRemoveAllPortLinks() - { - // Arrange - var diagram = new TestDiagram(); - var node1 = diagram.Nodes.Add(new NodeModel()); - var nodePort1 = node1.AddPort(PortAlignment.Top); - var node2 = diagram.Nodes.Add(new NodeModel()); - var nodePort2 = node2.AddPort(PortAlignment.Top); - diagram.Links.Add(new LinkModel(nodePort1, nodePort2)); - - // Act - diagram.Nodes.Remove(node1); - - // Assert - diagram.Links.Should().BeEmpty(); - } - - [Fact] - public void Remove_ShouldRemoveAllLinks() - { - // Arrange - var diagram = new TestDiagram(); - var node1 = diagram.Nodes.Add(new NodeModel()); - var node2 = diagram.Nodes.Add(new NodeModel()); - diagram.Links.Add(new LinkModel(node1, node2)); - - // Act - diagram.Nodes.Remove(node1); - - // Assert - diagram.Links.Should().BeEmpty(); - } - - [Fact] - public void Remove_ShouldRemoveItselfFromParentGroup() - { - // Arrange - var diagram = new TestDiagram(); - var node = diagram.Nodes.Add(new NodeModel()); - var group = diagram.Groups.Add(new GroupModel(new[] { node })); - - // Act - diagram.Nodes.Remove(node); - - // Assert - group.Children.Should().BeEmpty(); - node.Group.Should().BeNull(); - } - - [Fact] - public void Add_ShouldRefreshDiagramOnce() - { - // Arrange - var diagram = new TestDiagram(); - var refreshes = 0; - diagram.Changed += () => refreshes++; - - // Act - var node = diagram.Nodes.Add(new NodeModel()); - - // Assert - refreshes.Should().Be(1); - } - - [Fact] - public void Remove_ShouldRefreshDiagramOnce() - { - // Arrange - var diagram = new TestDiagram(); - var node1 = diagram.Nodes.Add(new NodeModel()); - var node2 = diagram.Nodes.Add(new NodeModel()); - diagram.Links.Add(new LinkModel(node1, node2)); - var refreshes = 0; - diagram.Changed += () => refreshes++; - - // Act - diagram.Nodes.Remove(node1); - - // Assert - refreshes.Should().Be(1); - } - - [Fact] - public void Remove_ShouldRemoveControls() - { - // Arrange - var diagram = new TestDiagram(); - var node = diagram.Nodes.Add(new NodeModel()); - var controls = diagram.Controls.AddFor(node); - - // Act - diagram.Nodes.Remove(node); - - // Assert - diagram.Controls.GetFor(node).Should().BeNull(); - } + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel()); + var controls = diagram.Controls.AddFor(node); + + // Act + diagram.Nodes.Remove(node); + + // Assert + diagram.Controls.GetFor(node).Should().BeNull(); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs index 577843a1d..c4566e861 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs @@ -7,94 +7,93 @@ using FluentAssertions; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Models.Base +namespace Blazor.Diagrams.Core.Tests.Models.Base; + +public class BaseLinkModelTests { - public class BaseLinkModelTests + [Fact] + public void SetSource_ShouldChangePropertiesAndTriggerEvent() { - [Fact] - public void SetSource_ShouldChangePropertiesAndTriggerEvent() + // Arrange + var link = new LinkModel(sourcePort: new PortModel(null), targetPort: null); + var parent = new NodeModel(); + var port = new PortModel(parent); + var sp = new SinglePortAnchor(port); + var eventsTriggered = 0; + Anchor? oldSp = null; + Anchor? newSp = null; + BaseLinkModel? linkInstance = null; + + // Act + link.SourceChanged += (l, o, n) => { - // Arrange - var link = new LinkModel(sourcePort: new PortModel(null), targetPort: null); - var parent = new NodeModel(); - var port = new PortModel(parent); - var sp = new SinglePortAnchor(port); - var eventsTriggered = 0; - Anchor? oldSp = null; - Anchor? newSp = null; - BaseLinkModel? linkInstance = null; + eventsTriggered++; + linkInstance = l; + oldSp = o; + newSp = n; + }; - // Act - link.SourceChanged += (l, o, n) => - { - eventsTriggered++; - linkInstance = l; - oldSp = o; - newSp = n; - }; + link.SetSource(sp); - link.SetSource(sp); + // Assert + eventsTriggered.Should().Be(1); + link.Source.Should().BeSameAs(sp); + oldSp.Should().NotBeNull(); + newSp.Should().BeSameAs(sp); + linkInstance.Should().BeSameAs(link); + link.Source.Model.Should().BeSameAs(port); + } - // Assert - eventsTriggered.Should().Be(1); - link.Source.Should().BeSameAs(sp); - oldSp.Should().NotBeNull(); - newSp.Should().BeSameAs(sp); - linkInstance.Should().BeSameAs(link); - link.Source.Model.Should().BeSameAs(port); - } + [Fact] + public void SetTarget_ShouldChangePropertiesAndTriggerEvent() + { + // Arrange + var link = new LinkModel(new SinglePortAnchor(null), new PositionAnchor(Point.Zero)); + var parent = new NodeModel(); + var port = new PortModel(parent); + var tp = new SinglePortAnchor(port); + var eventsTriggered = 0; + Anchor? oldTp = null; + Anchor? newTp = null; + BaseLinkModel? linkInstance = null; - [Fact] - public void SetTarget_ShouldChangePropertiesAndTriggerEvent() + // Act + link.TargetChanged += (l, o, n) => { - // Arrange - var link = new LinkModel(new SinglePortAnchor(null), new PositionAnchor(Point.Zero)); - var parent = new NodeModel(); - var port = new PortModel(parent); - var tp = new SinglePortAnchor(port); - var eventsTriggered = 0; - Anchor? oldTp = null; - Anchor? newTp = null; - BaseLinkModel? linkInstance = null; + eventsTriggered++; + linkInstance = l; + oldTp = o; + newTp = n; + }; - // Act - link.TargetChanged += (l, o, n) => - { - eventsTriggered++; - linkInstance = l; - oldTp = o; - newTp = n; - }; + link.SetTarget(tp); - link.SetTarget(tp); - - // Assert - eventsTriggered.Should().Be(1); - link.Target.Should().BeSameAs(tp); - oldTp.Should().BeOfType(); - newTp.Should().BeSameAs(tp); - linkInstance.Should().BeSameAs(link); - link.Target!.Model.Should().BeSameAs(port); - } + // Assert + eventsTriggered.Should().Be(1); + link.Target.Should().BeSameAs(tp); + oldTp.Should().BeOfType(); + newTp.Should().BeSameAs(tp); + linkInstance.Should().BeSameAs(link); + link.Target!.Model.Should().BeSameAs(port); + } - [Fact] - public void GetBounds_ShouldReturnPathBBox() - { - // Arrange - var link = new LinkModel(new PositionAnchor(new Point(10, 5)), new PositionAnchor(new Point(100, 80))); - link.Diagram = new TestDiagram(); - link.PathGenerator = new StraightPathGenerator(); - link.Router = new NormalRouter(); + [Fact] + public void GetBounds_ShouldReturnPathBBox() + { + // Arrange + var link = new LinkModel(new PositionAnchor(new Point(10, 5)), new PositionAnchor(new Point(100, 80))); + link.Diagram = new TestDiagram(); + link.PathGenerator = new StraightPathGenerator(); + link.Router = new NormalRouter(); - // Act - link.Refresh(); - var bounds = link.GetBounds()!; + // Act + link.Refresh(); + var bounds = link.GetBounds()!; - // Assert - bounds.Left.Should().Be(10); - bounds.Top.Should().Be(5); - bounds.Width.Should().Be(90); - bounds.Height.Should().Be(75); - } + // Assert + bounds.Left.Should().Be(10); + bounds.Top.Should().Be(5); + bounds.Width.Should().Be(90); + bounds.Height.Should().Be(75); } } diff --git a/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs index fb8128020..9c4678099 100644 --- a/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs +++ b/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs @@ -8,128 +8,127 @@ using System.Threading.Tasks; using Xunit; -namespace Blazor.Diagrams.Tests.Components +namespace Blazor.Diagrams.Tests.Components; + +public class LinkVertexWidgetTests { - public class LinkVertexWidgetTests + [Fact] + public void ShouldRenderCircle() + { + // Arrange + using var ctx = new TestContext(); + var node1 = new NodeModel(); + var node2 = new NodeModel(); + var link = new LinkModel(node1, node2); + var vertex = new LinkVertexModel(link, new Point(10.5, 20)); + link.Vertices.Add(vertex); + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.Vertex, vertex) + .Add(n => n.Color, "red") + .Add(n => n.SelectedColor, "blue") + .Add(n => n.BlazorDiagram, new BlazorDiagram())); + + // Assert + cut.MarkupMatches(""); + } + + [Fact] + public void ShouldRenderCircleWithSelectedColor_WhenVertexIsSelected() + { + // Arrange + using var ctx = new TestContext(); + var node1 = new NodeModel(); + var node2 = new NodeModel(); + var link = new LinkModel(node1, node2); + var vertex = new LinkVertexModel(link, new Point(10.5, 20)); + link.Vertices.Add(vertex); + vertex.Selected = true; + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.Vertex, vertex) + .Add(n => n.Color, "red") + .Add(n => n.SelectedColor, "blue") + .Add(n => n.BlazorDiagram, new BlazorDiagram())); + + // Assert + cut.MarkupMatches(""); + } + + [Fact] + public void ShouldRerender_WhenVertexIsRefreshed() + { + // Arrange + using var ctx = new TestContext(); + var node1 = new NodeModel(); + var node2 = new NodeModel(); + var link = new LinkModel(node1, node2); + var vertex = new LinkVertexModel(link, new Point(10.5, 20)); + link.Vertices.Add(vertex); + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.Vertex, vertex) + .Add(n => n.Color, "red") + .Add(n => n.SelectedColor, "blue") + .Add(n => n.BlazorDiagram, new BlazorDiagram())); + + // Assert + cut.RenderCount.Should().Be(1); + vertex.Refresh(); + cut.RenderCount.Should().Be(2); + } + + [Fact] + public async Task ShouldDeleteItselfAndRefreshParent_WhenDoubleClicked() + { + // Arrange + using var ctx = new TestContext(); + var node1 = new NodeModel(); + var node2 = new NodeModel(); + var link = new LinkModel(node1, node2); + int linkRefreshes = 0; + var vertex = new LinkVertexModel(link, new Point(10.5, 20)); + link.Vertices.Add(vertex); + link.Changed += _ => linkRefreshes++; + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.Vertex, vertex) + .Add(n => n.Color, "red") + .Add(n => n.SelectedColor, "blue") + .Add(n => n.BlazorDiagram, new BlazorDiagram())); + + await cut.Find("circle").DoubleClickAsync(new MouseEventArgs()); + + // Assert + link.Vertices.Should().BeEmpty(); + linkRefreshes.Should().Be(1); + } + + [Fact] + public void ShouldUseCustomComponent_WhenProvided() { - [Fact] - public void ShouldRenderCircle() - { - // Arrange - using var ctx = new TestContext(); - var node1 = new NodeModel(); - var node2 = new NodeModel(); - var link = new LinkModel(node1, node2); - var vertex = new LinkVertexModel(link, new Point(10.5, 20)); - link.Vertices.Add(vertex); - - // Act - var cut = ctx.RenderComponent(parameters => parameters - .Add(n => n.Vertex, vertex) - .Add(n => n.Color, "red") - .Add(n => n.SelectedColor, "blue") - .Add(n => n.BlazorDiagram, new BlazorDiagram())); - - // Assert - cut.MarkupMatches(""); - } - - [Fact] - public void ShouldRenderCircleWithSelectedColor_WhenVertexIsSelected() - { - // Arrange - using var ctx = new TestContext(); - var node1 = new NodeModel(); - var node2 = new NodeModel(); - var link = new LinkModel(node1, node2); - var vertex = new LinkVertexModel(link, new Point(10.5, 20)); - link.Vertices.Add(vertex); - vertex.Selected = true; - - // Act - var cut = ctx.RenderComponent(parameters => parameters - .Add(n => n.Vertex, vertex) - .Add(n => n.Color, "red") - .Add(n => n.SelectedColor, "blue") - .Add(n => n.BlazorDiagram, new BlazorDiagram())); - - // Assert - cut.MarkupMatches(""); - } - - [Fact] - public void ShouldRerender_WhenVertexIsRefreshed() - { - // Arrange - using var ctx = new TestContext(); - var node1 = new NodeModel(); - var node2 = new NodeModel(); - var link = new LinkModel(node1, node2); - var vertex = new LinkVertexModel(link, new Point(10.5, 20)); - link.Vertices.Add(vertex); - - // Act - var cut = ctx.RenderComponent(parameters => parameters - .Add(n => n.Vertex, vertex) - .Add(n => n.Color, "red") - .Add(n => n.SelectedColor, "blue") - .Add(n => n.BlazorDiagram, new BlazorDiagram())); - - // Assert - cut.RenderCount.Should().Be(1); - vertex.Refresh(); - cut.RenderCount.Should().Be(2); - } - - [Fact] - public async Task ShouldDeleteItselfAndRefreshParent_WhenDoubleClicked() - { - // Arrange - using var ctx = new TestContext(); - var node1 = new NodeModel(); - var node2 = new NodeModel(); - var link = new LinkModel(node1, node2); - int linkRefreshes = 0; - var vertex = new LinkVertexModel(link, new Point(10.5, 20)); - link.Vertices.Add(vertex); - link.Changed += _ => linkRefreshes++; - - // Act - var cut = ctx.RenderComponent(parameters => parameters - .Add(n => n.Vertex, vertex) - .Add(n => n.Color, "red") - .Add(n => n.SelectedColor, "blue") - .Add(n => n.BlazorDiagram, new BlazorDiagram())); - - await cut.Find("circle").DoubleClickAsync(new MouseEventArgs()); - - // Assert - link.Vertices.Should().BeEmpty(); - linkRefreshes.Should().Be(1); - } - - [Fact] - public void ShouldUseCustomComponent_WhenProvided() - { - // Arrange - using var ctx = new TestContext(); - var diagram = new BlazorDiagram(); - diagram.RegisterComponent(); - var node1 = new NodeModel(); - var node2 = new NodeModel(); - var link = new LinkModel(node1, node2); - var vertex = new LinkVertexModel(link, new Point(10.5, 20)); - link.Vertices.Add(vertex); - - // Act - var cut = ctx.RenderComponent(parameters => parameters - .Add(n => n.Vertex, vertex) - .Add(n => n.Color, "red") - .Add(n => n.SelectedColor, "blue") - .Add(n => n.BlazorDiagram, diagram)); - - // Assert - cut.MarkupMatches(""); - } + // Arrange + using var ctx = new TestContext(); + var diagram = new BlazorDiagram(); + diagram.RegisterComponent(); + var node1 = new NodeModel(); + var node2 = new NodeModel(); + var link = new LinkModel(node1, node2); + var vertex = new LinkVertexModel(link, new Point(10.5, 20)); + link.Vertices.Add(vertex); + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.Vertex, vertex) + .Add(n => n.Color, "red") + .Add(n => n.SelectedColor, "blue") + .Add(n => n.BlazorDiagram, diagram)); + + // Assert + cut.MarkupMatches(""); } } diff --git a/tests/Blazor.Diagrams.Tests/Components/NodeWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/NodeWidgetTests.cs index 9e8ba3239..dfabb012d 100644 --- a/tests/Blazor.Diagrams.Tests/Components/NodeWidgetTests.cs +++ b/tests/Blazor.Diagrams.Tests/Components/NodeWidgetTests.cs @@ -9,29 +9,28 @@ using Xunit; -namespace Blazor.Diagrams.Tests.Components +namespace Blazor.Diagrams.Tests.Components; + +public class NodeWidgetTests { - public class NodeWidgetTests + [Fact] + public void DefaultNodeWidget_ShouldHaveSingleClassAndNoPorts_WhenItHasNoPortsAndNoSelectionNorGroup() { - [Fact] - public void DefaultNodeWidget_ShouldHaveSingleClassAndNoPorts_WhenItHasNoPortsAndNoSelectionNorGroup() - { - // Arrange - using var ctx = new TestContext(); - var node = new NodeModel(Point.Zero); - - // Act - var cut = ctx.RenderComponent(parameters => parameters - .Add(n => n.Node, node)); - - // Assert - var content = cut.Find("div.default-node"); - content.ClassList.Should().ContainSingle(); - content.ClassList[0].Should().Be("default-node"); - content.TextContent.Trim().Should().Be("Title"); - - var ports = cut.FindComponents(); - ports.Should().BeEmpty(); - } + // Arrange + using var ctx = new TestContext(); + var node = new NodeModel(Point.Zero); + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.Node, node)); + + // Assert + var content = cut.Find("div.default-node"); + content.ClassList.Should().ContainSingle(); + content.ClassList[0].Should().Be("default-node"); + content.TextContent.Trim().Should().Be("Title"); + + var ports = cut.FindComponents(); + ports.Should().BeEmpty(); } } diff --git a/tests/Blazor.Diagrams.Tests/Components/SvgNodeWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/SvgNodeWidgetTests.cs index e13500b4a..64634c5dd 100644 --- a/tests/Blazor.Diagrams.Tests/Components/SvgNodeWidgetTests.cs +++ b/tests/Blazor.Diagrams.Tests/Components/SvgNodeWidgetTests.cs @@ -3,21 +3,20 @@ using Bunit; using Xunit; -namespace Blazor.Diagrams.Tests.Components +namespace Blazor.Diagrams.Tests.Components; + +public class SvgNodeWidgetTests { - public class SvgNodeWidgetTests + [Fact] + public void ShouldRenderSimpleRect() { - [Fact] - public void ShouldRenderSimpleRect() - { - // Arrange - using var ctx = new TestContext(); + // Arrange + using var ctx = new TestContext(); - // Act - var cut = ctx.RenderComponent(); + // Act + var cut = ctx.RenderComponent(); - // Assert - cut.MarkupMatches(""); - } + // Assert + cut.MarkupMatches(""); } } diff --git a/tests/Blazor.Diagrams.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Tests/DiagramTests.cs index 90a93fa91..a61abc64b 100644 --- a/tests/Blazor.Diagrams.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Tests/DiagramTests.cs @@ -5,81 +5,80 @@ using Microsoft.AspNetCore.Components; using Xunit; -namespace Blazor.Diagrams.Tests +namespace Blazor.Diagrams.Tests; + +public class DiagramTests { - public class DiagramTests + [Fact] + public void GetComponentForModel_ShouldReturnComponentType_WhenModelTypeWasRegistered() + { + // Arrange + var diagram = new BlazorDiagram(); + diagram.RegisterComponent(); + + // Act + var componentType = diagram.GetComponent(); + + // Assert + componentType.Should().Be(typeof(NodeWidget)); + } + + [Fact] + public void GetComponentForModel_ShouldReturnNull_WhenModelTypeWasNotRegistered() + { + // Arrange + var diagram = new BlazorDiagram(); + + // Act + var componentType = diagram.GetComponent(); + + // Assert + componentType.Should().BeNull(); + } + + [Fact] + public void GetComponentForModel_ShouldReturnComponentType_WhenInheritedModelTypeWasRegistered() + { + // Arrange + var diagram = new BlazorDiagram(); + diagram.RegisterComponent(); + + // Act + var componentType = diagram.GetComponent(); + + // Assert + componentType.Should().Be(typeof(NodeWidget)); + } + + [Fact] + public void GetComponentForModel_ShouldReturnSpecificComponentType_WhenInheritedAndSpecificModelTypeWasRegistered() + { + // Arrange + var diagram = new BlazorDiagram(); + diagram.RegisterComponent(); + diagram.RegisterComponent(); + + // Act + var componentType = diagram.GetComponent(); + + // Assert + componentType.Should().Be(typeof(CustomWidget)); + } + + [Fact] + public void GetComponentForModel_ShouldReturnNull_WhenCheckSubclassesIsFalse() { - [Fact] - public void GetComponentForModel_ShouldReturnComponentType_WhenModelTypeWasRegistered() - { - // Arrange - var diagram = new BlazorDiagram(); - diagram.RegisterComponent(); - - // Act - var componentType = diagram.GetComponent(); - - // Assert - componentType.Should().Be(typeof(NodeWidget)); - } - - [Fact] - public void GetComponentForModel_ShouldReturnNull_WhenModelTypeWasNotRegistered() - { - // Arrange - var diagram = new BlazorDiagram(); - - // Act - var componentType = diagram.GetComponent(); - - // Assert - componentType.Should().BeNull(); - } - - [Fact] - public void GetComponentForModel_ShouldReturnComponentType_WhenInheritedModelTypeWasRegistered() - { - // Arrange - var diagram = new BlazorDiagram(); - diagram.RegisterComponent(); - - // Act - var componentType = diagram.GetComponent(); - - // Assert - componentType.Should().Be(typeof(NodeWidget)); - } - - [Fact] - public void GetComponentForModel_ShouldReturnSpecificComponentType_WhenInheritedAndSpecificModelTypeWasRegistered() - { - // Arrange - var diagram = new BlazorDiagram(); - diagram.RegisterComponent(); - diagram.RegisterComponent(); - - // Act - var componentType = diagram.GetComponent(); - - // Assert - componentType.Should().Be(typeof(CustomWidget)); - } - - [Fact] - public void GetComponentForModel_ShouldReturnNull_WhenCheckSubclassesIsFalse() - { - // Arrange - var diagram = new BlazorDiagram(); - diagram.RegisterComponent(); - - // Act - var componentType = diagram.GetComponent(false); - - // Assert - componentType.Should().BeNull(); - } - - private class CustomModel : Model { } - private class CustomWidget : ComponentBase { } + // Arrange + var diagram = new BlazorDiagram(); + diagram.RegisterComponent(); + + // Act + var componentType = diagram.GetComponent(false); + + // Assert + componentType.Should().BeNull(); } + + private class CustomModel : Model { } + private class CustomWidget : ComponentBase { } } From 41bc4a9550cc238f3eda19ec34f412cc2614de23 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 3 Jul 2023 14:06:59 +0100 Subject: [PATCH 170/193] Add Nodes SVG Customization doc --- .../Nodes/GingerbreadWidget.razor | 8 ++ .../Nodes/GingerbreadWidget.razor.css | 8 ++ .../Nodes/SvgCustomization.razor | 120 +++++++++--------- 3 files changed, 77 insertions(+), 59 deletions(-) diff --git a/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor b/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor index 8e6ee9c89..c8ca4d3c4 100644 --- a/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor +++ b/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor @@ -14,6 +14,14 @@ + + + + + + + + @code { diff --git a/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor.css b/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor.css index 385a601e0..1570b5f56 100644 --- a/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor.css +++ b/site/Site/Components/Documentation/Nodes/GingerbreadWidget.razor.css @@ -17,3 +17,11 @@ stroke-width: 35px; stroke-linecap: round; } + +.gingerbread .hand { + fill: #cd803d; +} + + .gingerbread .hand:hover { + fill: #75441a; + } diff --git a/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor b/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor index 57961438d..bfe21bf04 100644 --- a/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor +++ b/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor @@ -21,18 +21,15 @@
    GingerbreadNode.cs
    
     using Blazor.Diagrams.Core.Geometry;
    -using Blazor.Diagrams.Core.Models;
    +using Blazor.Diagrams.Models;
     
     namespace YourNamespace;
     
    -public class AddTwoNumbersNode : NodeModel
    +public class GingerbreadNode : SvgNodeModel
     {
    -    public AddTwoNumbersNode(Point? position = null) : base(position) { }
    +    public GingerbreadNode(Point? position = null) : base(position) { }
     
    -    public double FirstNumber { get; set; }
    -    public double SecondNumber { get; set; }
    -
    -    // Here, you can put whatever you want, such as a method that does the addition
    +    // Here, you can put whatever you want
     }
     
    @@ -42,76 +39,77 @@ public class AddTwoNumbersNode : NodeModel Let's create a UI component to control how the node looks like:

    -
    AddTwoNumbersWidget.razor
    +
    GingerbreadWidget.razor
    
     @@using Blazor.Diagrams.Components.Renderers;
     @@using Site.Models.Nodes;
     
    -<div>
    -    <h5 class="card-title">Add</h5>
    -    <input type="number" class="form-control" @@bind-value="Node.FirstNumber" placeholder="Number 1" />
    -    <input type="number" class="form-control" @@bind-value="Node.SecondNumber" placeholder="Number 2" />
    +<g class="gingerbread">
    +    <circle class="body" cx="60" cy="30" r="30" />
     
    -    @@foreach (var port in Node.Ports)
    -    {
    -        // In case you have any ports to show
    -        // IMPORTANT: You are always in charge of rendering ports
    -        <PortRenderer @@key="port" Port="port" />
    -    }
    -</div>
    +    <circle class="eye" cx="48" cy="25" r="3" />
    +    <circle class="eye" cx="72" cy="25" r="3" />
    +    <rect class="mouth" x="50" y="40" width="20" height="5" rx="2" />
    +
    +    <line class="limb" x1="20" y1="70" x2="100" y2="70" />
    +    <line class="limb" x1="35" y1="130" x2="60" y2="65" />
    +    <line class="limb" x1="85" y1="130" x2="60" y2="65" />
    +
    +    <circle class="button" cx="60" cy="70" r="5" />
    +    <circle class="button" cx="60" cy="90" r="5" />
    +
    +    <PortRenderer @@key="'l'" Port="Node.GetPort(PortAlignment.Left)">
    +        <circle class="hand" cx="20" cy="70" r="17.5" />
    +    </PortRenderer>
    +
    +    <PortRenderer @@key="'r'" Port="Node.GetPort(PortAlignment.Right)">
    +        <circle class="hand" cx="100" cy="70" r="17.5" />
    +    </PortRenderer>
    +</g>
     
     @@code {
         // This gets filled by the library
    -    [Parameter] public AddTwoNumbersNode Node { get; set; } = null!;
    +    [Parameter] public GingerbreadNode Node { get; set; } = null!;
     }
     
    Let's also style our component! -
    AddTwoNumbersWidget.razor.css
    +
    GingerbreadWidget.razor.css
    
    -div {
    -    width: 230px;
    -    outline: 1px solid black;
    -    padding: 20px;
    -    box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2), 0 6px 20px 0 rgba(0, 0, 0, 0.19);
    +.gingerbread .body {
    +    fill: #cd803d;
     }
     
    -    div > h5 {
    -        font-weight: 600;
    -        text-transform: uppercase;
    -        margin-bottom: 10px;
    -    }
    +.gingerbread .eye {
    +    fill: white;
    +}
     
    -    div > input[type=number] {
    -        padding: 3px;
    -        border-radius: 3px;
    -        border: 1px solid black;
    -        margin-bottom: 8px;
    -    }
    +.gingerbread .mouth {
    +    fill: none;
    +    stroke: white;
    +    stroke-width: 2px;
    +}
     
    -::deep .diagram-port {
    -    position: absolute;
    -    width: 30px;
    -    height: 20px;
    -    background-color: black;
    -    left: 50%;
    -    transform: translate(-50%, -50%);
    +.gingerbread .limb {
    +    stroke: #cd803d;
    +    stroke-width: 35px;
    +    stroke-linecap: round;
     }
     
    -    ::deep .diagram-port.top {
    -        border-top-left-radius: 50%;
    -        border-top-right-radius: 50%;
    -        top: -10px;
    -    }
    +.gingerbread .hand {
    +    fill: #cd803d;
    +}
     
    -    ::deep .diagram-port.bottom {
    -        border-bottom-left-radius: 50%;
    -        border-bottom-right-radius: 50%;
    -        bottom: -30px;
    +    .gingerbread .hand:hover {
    +        fill: #75441a;
         }
     
    +

    + This SVG example is based from this article. +

    +

    Displaying

    @@ -125,14 +123,18 @@ protected override void OnInitialized() { base.OnInitialized(); - Diagram.RegisterComponent<AddTwoNumbersNode, AddTwoNumbersWidget>(); + Diagram.RegisterComponent<GingerbreadNode, GingerbreadWidget>(); - var node = Diagram.Nodes.Add(new AddTwoNumbersNode(new Point(80, 80))); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Bottom); + var node = Diagram.Nodes.Add(new GingerbreadNode(new Point(80, 80))); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); }

    +

    + Grabbing his hands will create a link! +

    +
    @@ -152,7 +154,7 @@ protected override void OnInitialized() Diagram.RegisterComponent(); var node = Diagram.Nodes.Add(new GingerbreadNode(new Point(80, 80))); - node.AddPort(PortAlignment.Top); - node.AddPort(PortAlignment.Bottom); + node.AddPort(PortAlignment.Left); + node.AddPort(PortAlignment.Right); } } \ No newline at end of file From a0447b226ef66e99ca5b1d1294c30e7f37775d8f Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 4 Jul 2023 10:31:33 +0100 Subject: [PATCH 171/193] Add ordering doc and fix navigation buttons --- .../Documentation/Diagram/Behaviors.razor | 4 +- .../Pages/Documentation/Diagram/Options.razor | 4 +- .../Documentation/Diagram/Ordering.razor | 69 +++++++++++++++++++ .../Nodes/SvgCustomization.razor | 4 +- site/Site/Static/Documentation.cs | 1 + 5 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 site/Site/Pages/Documentation/Diagram/Ordering.razor diff --git a/site/Site/Pages/Documentation/Diagram/Behaviors.razor b/site/Site/Pages/Documentation/Diagram/Behaviors.razor index 7a0c20e93..540daba7e 100644 --- a/site/Site/Pages/Documentation/Diagram/Behaviors.razor +++ b/site/Site/Pages/Documentation/Diagram/Behaviors.razor @@ -116,5 +116,5 @@ Diagram.RegisterBehavior(new MySelectionBehavior(Diagram)); \ No newline at end of file + NextTitle="Ordering" + NextLink="/documentation/diagram-ordering" /> \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Diagram/Options.razor b/site/Site/Pages/Documentation/Diagram/Options.razor index f74f01c67..36ec9ce1b 100644 --- a/site/Site/Pages/Documentation/Diagram/Options.razor +++ b/site/Site/Pages/Documentation/Diagram/Options.razor @@ -278,7 +278,7 @@ - \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Diagram/Ordering.razor b/site/Site/Pages/Documentation/Diagram/Ordering.razor new file mode 100644 index 000000000..25c4ab119 --- /dev/null +++ b/site/Site/Pages/Documentation/Diagram/Ordering.razor @@ -0,0 +1,69 @@ +@page "/documentation/diagram-ordering" +@layout DocumentationLayout +@inherits DocumentationPage + +Diagram Ordering - Documentation - Blazor Diagrams + +

    Diagram Ordering

    + +

    + Since 3.0, Blazor Diagrams supports ordering between models out of the box! +

    + +

    Overview

    + +
      +
    • + All selectable models have an Order property (number). +
    • +
    • + Diagram keeps an ordered list of all selectable models called OrderedSelectables. +
    • +
    • + When a new model is added to the diagram, it's placed at the end of the list with the Order property set to lastOrder + 1. +
    • +
    • + When the Order property changes on any of the models, everything is re-ordered. +
    • +
    + +

    Demonstration

    + +
    
    +var diagram = new BlazorDiagram();
    +var node1 = diagram.Nodes.Add(new NodeModel());
    +var node2 = diagram.Nodes.Add(new NodeModel());
    +
    +Console.WriteLine(node1.Order); // 1
    +Console.WriteLine(node2.Order); // 2
    +
    +node1.Order = 10;
    +// diagram.OrderedSelectables[0] will be node2 (Order 2)
    +// diagram.OrderedSelectables[1] will be node1 (Order 10)
    +
    +diagram.SendToFront(node2);
    +
    +Console.WriteLine(node1.Order); // 10
    +Console.WriteLine(node2.Order); // 11
    +
    +diagram.SendToBack(node2);
    +
    +Console.WriteLine(node1.Order); // 2
    +Console.WriteLine(node2.Order); // 1
    +
    +// Setting the order for 2 models will sort the list twice
    +// We can avoid that by suspending the sorting until we're finished
    +diagram.SuspendSorting = true;
    +node1.Order = 100;
    +node2.Order = 200;
    +diagram.SuspendSorting = false;
    +diagram.RefreshOrders();
    +
    +// diagram.OrderedSelectables[0] will be node1 (Order 100)
    +// diagram.OrderedSelectables[1] will be node2 (Order 200)
    +
    + + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor b/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor index bfe21bf04..9b70b7259 100644 --- a/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor +++ b/site/Site/Pages/Documentation/Nodes/SvgCustomization.razor @@ -141,8 +141,8 @@ protected override void OnInitialized()
    - + @code { private BlazorDiagram Diagram { get; set; } = new(); diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs index 3ad2fb3b1..58657827f 100644 --- a/site/Site/Static/Documentation.cs +++ b/site/Site/Static/Documentation.cs @@ -20,6 +20,7 @@ public static class Documentation { new MenuItem("Overview", "/documentation/diagram"), new MenuItem("Behaviors", "/documentation/diagram-behaviors"), + new MenuItem("Ordering", "/documentation/diagram-ordering"), new MenuItem("Options", "/documentation/diagram-options"), new MenuItem("Keyboard Shortcuts", "/documentation/keyboard-shortcuts"), new MenuItem("API", "/documentation/diagram-api"), From 8805ffba1072dc46779eddc71376951e415ebab5 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 4 Jul 2023 15:58:18 +0100 Subject: [PATCH 172/193] Ugly documentation overview page placeholder --- site/Site/Pages/Documentation/Index.razor | 19 +- site/Site/wwwroot/css/app.css | 560 ++++++++++++---------- 2 files changed, 312 insertions(+), 267 deletions(-) diff --git a/site/Site/Pages/Documentation/Index.razor b/site/Site/Pages/Documentation/Index.razor index a04f46c7a..95f186f0e 100644 --- a/site/Site/Pages/Documentation/Index.razor +++ b/site/Site/Pages/Documentation/Index.razor @@ -3,4 +3,21 @@ Documentation - Blazor Diagrams -Overview \ No newline at end of file +
    + @foreach (var group in Documentation.Menu.Groups) + { +
    +
    + @group.Title +
    +
    + @foreach (var item in group.Children) + { + + @item.Title + + } +
    +
    + } +
    \ No newline at end of file diff --git a/site/Site/wwwroot/css/app.css b/site/Site/wwwroot/css/app.css index 8e3536eb1..2938775c2 100644 --- a/site/Site/wwwroot/css/app.css +++ b/site/Site/wwwroot/css/app.css @@ -1,5 +1,5 @@ /* -! tailwindcss v3.1.8 | MIT License | https://tailwindcss.com +! tailwindcss v3.3.2 | MIT License | https://tailwindcss.com */ /* @@ -30,6 +30,8 @@ 2. Prevent adjustments of font size after orientation changes in iOS. 3. Use a more readable tab size. 4. Use the user's configured `sans` font-family by default. +5. Use the user's configured `sans` font-feature-settings by default. +6. Use the user's configured `sans` font-variation-settings by default. */ html { @@ -44,6 +46,10 @@ html { /* 3 */ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; /* 4 */ + font-feature-settings: normal; + /* 5 */ + font-variation-settings: normal; + /* 6 */ } /* @@ -410,54 +416,13 @@ video { height: auto; } -*, ::before, ::after { - --tw-border-spacing-x: 0; - --tw-border-spacing-y: 0; - --tw-translate-x: 0; - --tw-translate-y: 0; - --tw-rotate: 0; - --tw-skew-x: 0; - --tw-skew-y: 0; - --tw-scale-x: 1; - --tw-scale-y: 1; - --tw-pan-x: ; - --tw-pan-y: ; - --tw-pinch-zoom: ; - --tw-scroll-snap-strictness: proximity; - --tw-ordinal: ; - --tw-slashed-zero: ; - --tw-numeric-figure: ; - --tw-numeric-spacing: ; - --tw-numeric-fraction: ; - --tw-ring-inset: ; - --tw-ring-offset-width: 0px; - --tw-ring-offset-color: #fff; - --tw-ring-color: rgb(59 130 246 / 0.5); - --tw-ring-offset-shadow: 0 0 #0000; - --tw-ring-shadow: 0 0 #0000; - --tw-shadow: 0 0 #0000; - --tw-shadow-colored: 0 0 #0000; - --tw-blur: ; - --tw-brightness: ; - --tw-contrast: ; - --tw-grayscale: ; - --tw-hue-rotate: ; - --tw-invert: ; - --tw-saturate: ; - --tw-sepia: ; - --tw-drop-shadow: ; - --tw-backdrop-blur: ; - --tw-backdrop-brightness: ; - --tw-backdrop-contrast: ; - --tw-backdrop-grayscale: ; - --tw-backdrop-hue-rotate: ; - --tw-backdrop-invert: ; - --tw-backdrop-opacity: ; - --tw-backdrop-saturate: ; - --tw-backdrop-sepia: ; +/* Make elements with the HTML hidden attribute stay hidden by default */ + +[hidden] { + display: none; } -::-webkit-backdrop { +*, ::before, ::after { --tw-border-spacing-x: 0; --tw-border-spacing-y: 0; --tw-translate-x: 0; @@ -471,6 +436,9 @@ video { --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; @@ -518,6 +486,9 @@ video { --tw-pan-y: ; --tw-pinch-zoom: ; --tw-scroll-snap-strictness: proximity; + --tw-gradient-from-position: ; + --tw-gradient-via-position: ; + --tw-gradient-to-position: ; --tw-ordinal: ; --tw-slashed-zero: ; --tw-numeric-figure: ; @@ -621,7 +592,7 @@ tr { border-bottom-width: 1px; } -td, th { +td, th { padding-left: 1.5rem; padding-right: 1.5rem; padding-top: 1rem; @@ -662,7 +633,6 @@ td, th { } .sticky { - position: -webkit-sticky; position: sticky; } @@ -671,38 +641,38 @@ td, th { right: 0px; } -.top-0 { - top: 0px; +.-left-3 { + left: -0.75rem; } -.left-0 { - left: 0px; +.-right-3 { + right: -0.75rem; } .bottom-0 { bottom: 0px; } -.top-1\/2 { - top: 50%; -} - -.-right-3 { - right: -0.75rem; +.left-0 { + left: 0px; } -.-left-3 { - left: -0.75rem; +.top-0 { + top: 0px; } -.z-30 { - z-index: 30; +.top-1\/2 { + top: 50%; } .z-20 { z-index: 20; } +.z-30 { + z-index: 30; +} + .z-\[60\] { z-index: 60; } @@ -716,6 +686,16 @@ td, th { margin-right: auto; } +.my-0 { + margin-top: 0px; + margin-bottom: 0px; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + .my-4 { margin-top: 1rem; margin-bottom: 1rem; @@ -726,82 +706,76 @@ td, th { margin-bottom: 1.5rem; } -.my-2 { - margin-top: 0.5rem; - margin-bottom: 0.5rem; +.-ml-px { + margin-left: -1px; } -.my-0 { - margin-top: 0px; - margin-bottom: 0px; +.mb-10 { + margin-bottom: 2.5rem; } -.mb-6 { - margin-bottom: 1.5rem; +.mb-12 { + margin-bottom: 3rem; } -.mt-2 { - margin-top: 0.5rem; +.mb-2 { + margin-bottom: 0.5rem; } -.mr-2 { - margin-right: 0.5rem; +.mb-3 { + margin-bottom: 0.75rem; } -.mt-0 { - margin-top: 0px; +.mb-4 { + margin-bottom: 1rem; } -.mr-3 { - margin-right: 0.75rem; +.mb-6 { + margin-bottom: 1.5rem; } -.ml-auto { - margin-left: auto; +.mb-8 { + margin-bottom: 2rem; } -.mb-3 { - margin-bottom: 0.75rem; +.ml-0 { + margin-left: 0px; } .ml-0\.5 { margin-left: 0.125rem; } -.ml-0 { - margin-left: 0px; -} - -.-ml-px { - margin-left: -1px; +.ml-2 { + margin-left: 0.5rem; } -.mt-4 { - margin-top: 1rem; +.ml-auto { + margin-left: auto; } -.mt-10 { - margin-top: 2.5rem; +.mr-2 { + margin-right: 0.5rem; } -.ml-2 { - margin-left: 0.5rem; +.mr-3 { + margin-right: 0.75rem; } -.mb-8 { - margin-bottom: 2rem; +.mt-0 { + margin-top: 0px; } -.mb-4 { - margin-bottom: 1rem; +.mt-10 { + margin-top: 2.5rem; } -.mb-12 { - margin-bottom: 3rem; +.mt-2 { + margin-top: 0.5rem; } -.mb-2 { - margin-bottom: 0.5rem; +.mt-4 { + margin-top: 1rem; } .block { @@ -832,60 +806,56 @@ td, th { display: none; } -.h-8 { - height: 2rem; +.h-1 { + height: 0.25rem; } -.h-6 { - height: 1.5rem; +.h-4 { + height: 1rem; } .h-5 { height: 1.25rem; } -.h-4 { - height: 1rem; +.h-6 { + height: 1.5rem; } -.h-1 { - height: 0.25rem; +.h-8 { + height: 2rem; } .h-full { height: 100%; } -.w-full { - width: 100%; -} - -.w-6 { - width: 1.5rem; +.w-1\/6 { + width: 16.666667%; } -.w-8 { - width: 2rem; +.w-4 { + width: 1rem; } .w-5 { width: 1.25rem; } -.w-80 { - width: 20rem; +.w-6 { + width: 1.5rem; } -.w-4 { - width: 1rem; +.w-8 { + width: 2rem; } -.w-1\/6 { - width: 16.666667%; +.w-80 { + width: 20rem; } -.max-w-\[90rem\] { - max-width: 90rem; +.w-full { + width: 100%; } .max-w-3xl { @@ -896,6 +866,10 @@ td, th { max-width: 64rem; } +.max-w-\[90rem\] { + max-width: 90rem; +} + .flex-1 { flex: 1 1 0%; } @@ -918,6 +892,10 @@ td, th { transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); } +.cursor-pointer { + cursor: pointer; +} + .list-inside { list-style-position: inside; } @@ -930,6 +908,10 @@ td, th { grid-template-columns: repeat(4, minmax(0, 1fr)); } +.grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); +} + .flex-row { flex-direction: row; } @@ -971,12 +953,6 @@ td, th { column-gap: 0.5rem; } -.space-y-8 > :not([hidden]) ~ :not([hidden]) { - --tw-space-y-reverse: 0; - margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse))); - margin-bottom: calc(2rem * var(--tw-space-y-reverse)); -} - .space-y-1 > :not([hidden]) ~ :not([hidden]) { --tw-space-y-reverse: 0; margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse))); @@ -989,20 +965,30 @@ td, th { margin-bottom: calc(0.5rem * var(--tw-space-y-reverse)); } +.space-y-8 > :not([hidden]) ~ :not([hidden]) { + --tw-space-y-reverse: 0; + margin-top: calc(2rem * calc(1 - var(--tw-space-y-reverse))); + margin-bottom: calc(2rem * var(--tw-space-y-reverse)); +} + .overflow-y-auto { overflow-y: auto; } -.rounded-md { - border-radius: 0.375rem; +.rounded { + border-radius: 0.25rem; } .rounded-full { border-radius: 9999px; } -.rounded { - border-radius: 0.25rem; +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded-md { + border-radius: 0.375rem; } .rounded-l-md { @@ -1020,6 +1006,11 @@ td, th { border-top-right-radius: 0.25rem; } +.rounded-t-lg { + border-top-left-radius: 0.5rem; + border-top-right-radius: 0.5rem; +} + .border { border-width: 1px; } @@ -1029,24 +1020,39 @@ td, th { border-bottom-width: 1px; } +.border-b { + border-bottom-width: 1px; +} + +.border-l { + border-left-width: 1px; +} + .border-l-2 { border-left-width: 2px; } +.border-r { + border-right-width: 1px; +} + .border-t { border-top-width: 1px; } -.border-r { - border-right-width: 1px; +.border-black { + --tw-border-opacity: 1; + border-color: rgb(0 0 0 / var(--tw-border-opacity)); } -.border-l { - border-left-width: 1px; +.border-gray-100 { + --tw-border-opacity: 1; + border-color: rgb(243 244 246 / var(--tw-border-opacity)); } -.border-b { - border-bottom-width: 1px; +.border-palette-main { + --tw-border-opacity: 1; + border-color: rgb(64 186 189 / var(--tw-border-opacity)); } .border-slate-100 { @@ -1054,33 +1060,28 @@ td, th { border-color: rgb(241 245 249 / var(--tw-border-opacity)); } -.border-transparent { - border-color: transparent; -} - .border-slate-200 { --tw-border-opacity: 1; border-color: rgb(226 232 240 / var(--tw-border-opacity)); } -.border-palette-main { - --tw-border-opacity: 1; - border-color: rgb(64 186 189 / var(--tw-border-opacity)); +.border-transparent { + border-color: transparent; } -.border-gray-100 { +.border-slate-400 { --tw-border-opacity: 1; - border-color: rgb(243 244 246 / var(--tw-border-opacity)); + border-color: rgb(148 163 184 / var(--tw-border-opacity)); } -.border-black { +.border-slate-600 { --tw-border-opacity: 1; - border-color: rgb(0 0 0 / var(--tw-border-opacity)); + border-color: rgb(71 85 105 / var(--tw-border-opacity)); } -.bg-white { - --tw-bg-opacity: 1; - background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +.border-slate-50 { + --tw-border-opacity: 1; + border-color: rgb(248 250 252 / var(--tw-border-opacity)); } .bg-gray-100 { @@ -1093,6 +1094,11 @@ td, th { background-color: rgb(55 65 81 / var(--tw-bg-opacity)); } +.bg-white { + --tw-bg-opacity: 1; + background-color: rgb(255 255 255 / var(--tw-bg-opacity)); +} + .fill-current { fill: currentColor; } @@ -1101,36 +1107,41 @@ td, th { padding: 0.25rem; } -.p-4 { - padding: 1rem; -} - .p-2 { padding: 0.5rem; } +.p-4 { + padding: 1rem; +} + .p-6 { padding: 1.5rem; } +.px-3 { + padding-left: 0.75rem; + padding-right: 0.75rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + .px-8 { padding-left: 2rem; padding-right: 2rem; } -.py-6 { - padding-top: 1.5rem; - padding-bottom: 1.5rem; -} - -.py-2 { - padding-top: 0.5rem; - padding-bottom: 0.5rem; +.py-0 { + padding-top: 0px; + padding-bottom: 0px; } -.px-4 { - padding-left: 1rem; - padding-right: 1rem; +.py-1 { + padding-top: 0.25rem; + padding-bottom: 0.25rem; } .py-10 { @@ -1138,9 +1149,9 @@ td, th { padding-bottom: 2.5rem; } -.py-1 { - padding-top: 0.25rem; - padding-bottom: 0.25rem; +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; } .py-4 { @@ -1148,9 +1159,9 @@ td, th { padding-bottom: 1rem; } -.px-3 { - padding-left: 0.75rem; - padding-right: 0.75rem; +.py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; } .py-8 { @@ -1158,9 +1169,8 @@ td, th { padding-bottom: 2rem; } -.py-0 { - padding-top: 0px; - padding-bottom: 0px; +.pb-10 { + padding-bottom: 2.5rem; } .pl-4 { @@ -1171,6 +1181,10 @@ td, th { padding-right: 1rem; } +.pr-6 { + padding-right: 1.5rem; +} + .pt-12 { padding-top: 3rem; } @@ -1183,14 +1197,6 @@ td, th { padding-top: 1.5rem; } -.pb-10 { - padding-bottom: 2.5rem; -} - -.pr-6 { - padding-right: 1.5rem; -} - .text-center { text-align: center; } @@ -1204,9 +1210,9 @@ td, th { line-height: 2rem; } -.text-sm { - font-size: 0.875rem; - line-height: 1.25rem; +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; } .text-5xl { @@ -1214,9 +1220,9 @@ td, th { line-height: 1; } -.text-3xl { - font-size: 1.875rem; - line-height: 2.25rem; +.text-sm { + font-size: 0.875rem; + line-height: 1.25rem; } .text-xs { @@ -1228,10 +1234,6 @@ td, th { font-weight: 700; } -.font-semibold { - font-weight: 600; -} - .font-medium { font-weight: 500; } @@ -1240,10 +1242,18 @@ td, th { font-weight: 400; } +.font-semibold { + font-weight: 600; +} + .uppercase { text-transform: uppercase; } +.leading-none { + line-height: 1; +} + .leading-normal { line-height: 1.5; } @@ -1252,10 +1262,6 @@ td, th { line-height: 1.25; } -.leading-none { - line-height: 1; -} - .tracking-normal { letter-spacing: 0em; } @@ -1265,14 +1271,14 @@ td, th { color: rgb(0 0 0 / var(--tw-text-opacity)); } -.text-pink-600 { +.text-gray-500 { --tw-text-opacity: 1; - color: rgb(219 39 119 / var(--tw-text-opacity)); + color: rgb(107 114 128 / var(--tw-text-opacity)); } -.text-gray-500 { +.text-gray-600 { --tw-text-opacity: 1; - color: rgb(107 114 128 / var(--tw-text-opacity)); + color: rgb(75 85 99 / var(--tw-text-opacity)); } .text-gray-800 { @@ -1280,59 +1286,57 @@ td, th { color: rgb(31 41 55 / var(--tw-text-opacity)); } -.text-white { +.text-palette-main { --tw-text-opacity: 1; - color: rgb(255 255 255 / var(--tw-text-opacity)); + color: rgb(64 186 189 / var(--tw-text-opacity)); } -.text-pink-800 { +.text-pink-600 { --tw-text-opacity: 1; - color: rgb(157 23 77 / var(--tw-text-opacity)); + color: rgb(219 39 119 / var(--tw-text-opacity)); } -.text-slate-700 { +.text-pink-800 { --tw-text-opacity: 1; - color: rgb(51 65 85 / var(--tw-text-opacity)); + color: rgb(157 23 77 / var(--tw-text-opacity)); } -.text-slate-900 { +.text-slate-500 { --tw-text-opacity: 1; - color: rgb(15 23 42 / var(--tw-text-opacity)); + color: rgb(100 116 139 / var(--tw-text-opacity)); } -.text-slate-500 { +.text-slate-700 { --tw-text-opacity: 1; - color: rgb(100 116 139 / var(--tw-text-opacity)); + color: rgb(51 65 85 / var(--tw-text-opacity)); } -.text-palette-main { +.text-slate-900 { --tw-text-opacity: 1; - color: rgb(64 186 189 / var(--tw-text-opacity)); + color: rgb(15 23 42 / var(--tw-text-opacity)); } -.text-gray-600 { +.text-white { --tw-text-opacity: 1; - color: rgb(75 85 99 / var(--tw-text-opacity)); + color: rgb(255 255 255 / var(--tw-text-opacity)); } .underline { - -webkit-text-decoration-line: underline; - text-decoration-line: underline; + text-decoration-line: underline; } .no-underline { - -webkit-text-decoration-line: none; - text-decoration-line: none; -} - -.opacity-75 { - opacity: 0.75; + text-decoration-line: none; } .opacity-25 { opacity: 0.25; } +.opacity-75 { + opacity: 0.75; +} + .shadow { --tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color); @@ -1355,9 +1359,9 @@ td, th { } .transition { - transition-property: color, background-color, border-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-text-decoration-color, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter; transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter; - transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-text-decoration-color, -webkit-backdrop-filter; + transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter; transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); transition-duration: 150ms; } @@ -1488,14 +1492,19 @@ td, th { background-color: rgb(64 186 189 / var(--tw-bg-opacity)); } -.hover\:text-pink-500:hover { - --tw-text-opacity: 1; - color: rgb(236 72 153 / var(--tw-text-opacity)); +.hover\:bg-gray-300:hover { + --tw-bg-opacity: 1; + background-color: rgb(209 213 219 / var(--tw-bg-opacity)); } -.hover\:text-gray-900:hover { +.hover\:bg-gray-200:hover { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + +.hover\:text-gray-600:hover { --tw-text-opacity: 1; - color: rgb(17 24 39 / var(--tw-text-opacity)); + color: rgb(75 85 99 / var(--tw-text-opacity)); } .hover\:text-gray-800:hover { @@ -1503,14 +1512,19 @@ td, th { color: rgb(31 41 55 / var(--tw-text-opacity)); } +.hover\:text-gray-900:hover { + --tw-text-opacity: 1; + color: rgb(17 24 39 / var(--tw-text-opacity)); +} + .hover\:text-palette-main:hover { --tw-text-opacity: 1; color: rgb(64 186 189 / var(--tw-text-opacity)); } -.hover\:text-gray-600:hover { +.hover\:text-pink-500:hover { --tw-text-opacity: 1; - color: rgb(75 85 99 / var(--tw-text-opacity)); + color: rgb(236 72 153 / var(--tw-text-opacity)); } .hover\:text-slate-900:hover { @@ -1524,13 +1538,11 @@ td, th { } .hover\:underline:hover { - -webkit-text-decoration-line: underline; - text-decoration-line: underline; + text-decoration-line: underline; } .hover\:no-underline:hover { - -webkit-text-decoration-line: none; - text-decoration-line: none; + text-decoration-line: none; } .focus\:outline-none:focus { @@ -1538,31 +1550,31 @@ td, th { outline-offset: 2px; } -.dark .dark\:border-gray-700 { +:is(.dark .dark\:border-gray-700) { --tw-border-opacity: 1; border-color: rgb(55 65 81 / var(--tw-border-opacity)); } -.dark .dark\:border-slate-800 { - --tw-border-opacity: 1; - border-color: rgb(30 41 59 / var(--tw-border-opacity)); +:is(.dark .dark\:border-slate-200\/5) { + border-color: rgb(226 232 240 / 0.05); } -.dark .dark\:border-slate-200\/5 { - border-color: rgb(226 232 240 / 0.05); +:is(.dark .dark\:border-slate-800) { + --tw-border-opacity: 1; + border-color: rgb(30 41 59 / var(--tw-border-opacity)); } -.dark .dark\:bg-slate-900 { +:is(.dark .dark\:bg-gray-800) { --tw-bg-opacity: 1; - background-color: rgb(15 23 42 / var(--tw-bg-opacity)); + background-color: rgb(31 41 55 / var(--tw-bg-opacity)); } -.dark .dark\:bg-gray-800 { +:is(.dark .dark\:bg-slate-900) { --tw-bg-opacity: 1; - background-color: rgb(31 41 55 / var(--tw-bg-opacity)); + background-color: rgb(15 23 42 / var(--tw-bg-opacity)); } -.dark .dark\:text-slate-200 { +:is(.dark .dark\:text-slate-200) { --tw-text-opacity: 1; color: rgb(226 232 240 / var(--tw-text-opacity)); } @@ -1576,6 +1588,10 @@ td, th { display: flex; } + .sm\:grid-cols-12 { + grid-template-columns: repeat(12, minmax(0, 1fr)); + } + .sm\:px-6 { padding-left: 1.5rem; padding-right: 1.5rem; @@ -1615,6 +1631,18 @@ td, th { grid-template-columns: repeat(5, minmax(0, 1fr)); } + .md\:grid-cols-6 { + grid-template-columns: repeat(6, minmax(0, 1fr)); + } + + .md\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .md\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + .md\:flex-row { flex-direction: row; } @@ -1634,14 +1662,6 @@ td, th { } @media (min-width: 1024px) { - .lg\:top-0 { - top: 0px; - } - - .lg\:right-auto { - right: auto; - } - .lg\:bottom-0 { bottom: 0px; } @@ -1650,6 +1670,14 @@ td, th { left: max(0px,calc(50% - 45rem)); } + .lg\:right-auto { + right: auto; + } + + .lg\:top-0 { + top: 0px; + } + .lg\:z-10 { z-index: 10; } @@ -1700,16 +1728,16 @@ td, th { padding-left: 22rem; } - .lg\:text-4xl { - font-size: 2.25rem; - line-height: 2.5rem; - } - .lg\:text-3xl { font-size: 1.875rem; line-height: 2.25rem; } + .lg\:text-4xl { + font-size: 2.25rem; + line-height: 2.5rem; + } + .lg\:leading-6 { line-height: 1.5rem; } From d7e52a92d9ba2f120a6891b2940227589be937f3 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 4 Jul 2023 23:14:57 +0100 Subject: [PATCH 173/193] Remove useless Console Writelines from GroupModel --- src/Blazor.Diagrams.Core/Models/GroupModel.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index e9c619e2b..c78977064 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -55,7 +55,6 @@ public void RemoveChild(NodeModel child) public override void SetPosition(double x, double y) { - Console.WriteLine($"({(Group == null ? "Parent" : "Child")}) SetPosition {x:00} {y:00}"); var deltaX = x - Position.X; var deltaY = y - Position.Y; base.SetPosition(x, y); @@ -72,7 +71,6 @@ public override void SetPosition(double x, double y) public override void UpdatePositionSilently(double deltaX, double deltaY) { - Console.WriteLine($"({(Group == null ? "Parent" : "Child")}) UpdatePositionSilently {deltaX:00} {deltaY:00}"); base.UpdatePositionSilently(deltaX, deltaY); foreach (var child in Children) From 561e60ad62887d33ecb791b8880ee52315a7299f Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 4 Jul 2023 23:15:13 +0100 Subject: [PATCH 174/193] Add autoSize argument to SvgGroupModel ctor --- src/Blazor.Diagrams/Models/SvgGroupModel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blazor.Diagrams/Models/SvgGroupModel.cs b/src/Blazor.Diagrams/Models/SvgGroupModel.cs index d470541e5..49b2c063c 100644 --- a/src/Blazor.Diagrams/Models/SvgGroupModel.cs +++ b/src/Blazor.Diagrams/Models/SvgGroupModel.cs @@ -5,7 +5,7 @@ namespace Blazor.Diagrams.Models; public class SvgGroupModel : GroupModel { - public SvgGroupModel(IEnumerable children, byte padding = 30) : base(children, padding) + public SvgGroupModel(IEnumerable children, byte padding = 30, bool autoSize = true) : base(children, padding, autoSize) { } } \ No newline at end of file From 79893b3dd2885db507f2d61ace3cd35f2d53f32a Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Tue, 4 Jul 2023 23:15:38 +0100 Subject: [PATCH 175/193] Add Groups Customization (SVG) doc --- .../Nodes/ArithmeticContainerWidget.razor | 20 +++ .../Nodes/ArithmeticContainerWidget.razor.css | 35 ++++ .../Nodes/GingerbreadHouseWidget.razor | 29 ++++ site/Site/Models/Nodes/ArithmeticContainer.cs | 10 ++ site/Site/Models/Nodes/GingerbreadHouse.cs | 11 ++ .../Documentation/Groups/Customization.razor | 164 +++++++++++++++++- .../Groups/Customization.razor.css | 5 + .../Site/Pages/Documentation/Groups/SVG.razor | 4 +- .../Groups/SvgCustomization.razor | 133 ++++++++++++++ .../Groups/SvgCustomization.razor.css | 5 + .../Documentation/Nodes/Customization.razor | 2 +- site/Site/Static/Documentation.cs | 2 + 12 files changed, 415 insertions(+), 5 deletions(-) create mode 100644 site/Site/Components/Documentation/Nodes/ArithmeticContainerWidget.razor create mode 100644 site/Site/Components/Documentation/Nodes/ArithmeticContainerWidget.razor.css create mode 100644 site/Site/Components/Documentation/Nodes/GingerbreadHouseWidget.razor create mode 100644 site/Site/Models/Nodes/ArithmeticContainer.cs create mode 100644 site/Site/Models/Nodes/GingerbreadHouse.cs create mode 100644 site/Site/Pages/Documentation/Groups/Customization.razor.css create mode 100644 site/Site/Pages/Documentation/Groups/SvgCustomization.razor create mode 100644 site/Site/Pages/Documentation/Groups/SvgCustomization.razor.css diff --git a/site/Site/Components/Documentation/Nodes/ArithmeticContainerWidget.razor b/site/Site/Components/Documentation/Nodes/ArithmeticContainerWidget.razor new file mode 100644 index 000000000..77cca8b89 --- /dev/null +++ b/site/Site/Components/Documentation/Nodes/ArithmeticContainerWidget.razor @@ -0,0 +1,20 @@ +@using Site.Models.Nodes; + +
    +
    + @Group.Title +
    + + @* This is required and it's what renders the children *@ + + + @foreach (var port in Group.Ports) + { + + } +
    + +@code { + // This gets filled by the library + [Parameter] public ArithmeticContainer Group { get; set; } = null!; +} diff --git a/site/Site/Components/Documentation/Nodes/ArithmeticContainerWidget.razor.css b/site/Site/Components/Documentation/Nodes/ArithmeticContainerWidget.razor.css new file mode 100644 index 000000000..1d1207e8a --- /dev/null +++ b/site/Site/Components/Documentation/Nodes/ArithmeticContainerWidget.razor.css @@ -0,0 +1,35 @@ +.arithmetic-container { + width: 100%; + height: 100%; + border: 2px dashed black; +} + + .arithmetic-container .title { + position: absolute; + right: 0; + padding: 8px; + text-align: right; + border-left: 2px dashed black; + border-bottom: 2px dashed black; + } + +::deep .diagram-port { + position: absolute; + width: 30px; + height: 20px; + background-color: black; + left: 50%; + transform: translate(-50%, -50%); +} + + ::deep .diagram-port.top { + border-top-left-radius: 50%; + border-top-right-radius: 50%; + top: -10px; + } + + ::deep .diagram-port.bottom { + border-bottom-left-radius: 50%; + border-bottom-right-radius: 50%; + bottom: -30px; + } diff --git a/site/Site/Components/Documentation/Nodes/GingerbreadHouseWidget.razor b/site/Site/Components/Documentation/Nodes/GingerbreadHouseWidget.razor new file mode 100644 index 000000000..a00d4c9cd --- /dev/null +++ b/site/Site/Components/Documentation/Nodes/GingerbreadHouseWidget.razor @@ -0,0 +1,29 @@ +@using Site.Models.Nodes; + +@{ + var halfWidth = Group.Size!.Width / 2; +} + + + + + +@* This is required and it's what renders the children *@ + + +@foreach (var port in Group.Ports) +{ + +} + +@code { + // This gets filled by the library + [Parameter] public GingerbreadHouse Group { get; set; } = null!; +} \ No newline at end of file diff --git a/site/Site/Models/Nodes/ArithmeticContainer.cs b/site/Site/Models/Nodes/ArithmeticContainer.cs new file mode 100644 index 000000000..e4d7e59dc --- /dev/null +++ b/site/Site/Models/Nodes/ArithmeticContainer.cs @@ -0,0 +1,10 @@ +using Blazor.Diagrams.Core.Models; + +namespace Site.Models.Nodes; + +public class ArithmeticContainer : GroupModel +{ + public ArithmeticContainer(IEnumerable children, byte padding = 30, bool autoSize = true) : base(children, padding, autoSize) + { + } +} diff --git a/site/Site/Models/Nodes/GingerbreadHouse.cs b/site/Site/Models/Nodes/GingerbreadHouse.cs new file mode 100644 index 000000000..233797e43 --- /dev/null +++ b/site/Site/Models/Nodes/GingerbreadHouse.cs @@ -0,0 +1,11 @@ +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Models; + +namespace Site.Models.Nodes; + +public class GingerbreadHouse : SvgGroupModel +{ + public GingerbreadHouse(IEnumerable children, byte padding = 30, bool autoSize = true) : base(children, padding, autoSize) + { + } +} diff --git a/site/Site/Pages/Documentation/Groups/Customization.razor b/site/Site/Pages/Documentation/Groups/Customization.razor index aaba04be2..70fcdd807 100644 --- a/site/Site/Pages/Documentation/Groups/Customization.razor +++ b/site/Site/Pages/Documentation/Groups/Customization.razor @@ -1,4 +1,6 @@ @page "/documentation/groups-customization" +@using Site.Components.Documentation.Nodes; +@using Site.Models.Nodes; @layout DocumentationLayout @inherits DocumentationPage @@ -7,6 +9,162 @@

    Groups Customization

    - In Blazor Diagrams, Groups are a way to group nodes together.
    - Groups can also contain other groups, so you can create hierarchies. -

    \ No newline at end of file + Customizing groups in Blazor Diagrams is very easy!
    + This tutorial is based on the Nodes Customization one, make sure you look at that one first. +

    + +

    Creating a model

    + +

    + Let's assume that we want to create a new group that represents a container of arithmetic operations: +

    + +
    ArithmeticContainer.cs
    +
    
    +namespace YourNamespace;
    +
    +public class ArithmeticContainer : GroupModel
    +{
    +    public ArithmeticContainer(IEnumerable<NodeModel> children, byte padding = 30, bool autoSize = true) : base(children, padding, autoSize)
    +    {
    +    }
    +}
    +
    + +

    Creating a component

    + +

    + Let's create a UI component to control how the group looks like:
    +

    + +
    ArithmeticContainerWidget.razor
    +
    
    +@@using Site.Models.Nodes;
    +
    +<div class="arithmetic-container">
    +    <div class="title">
    +        @@Group.Title
    +    </div>
    +
    +    @@* This is required and it's what renders the children *@@
    +    <GroupNodes Group="Group" />
    +
    +    @@foreach (var port in Group.Ports)
    +    {
    +        <PortRenderer Port="port" Class="group-port"></PortRenderer>
    +    }
    +</div>
    +
    +@@code {
    +    // This gets filled by the library
    +    [Parameter] public ArithmeticContainer Group { get; set; } = null!;
    +}
    +
    + +Let's also style our component! + +
    ArithmeticContainerWidget.razor.css
    +
    
    +.arithmetic-container {
    +    width: 100%;
    +    height: 100%;
    +    border: 2px dashed black;
    +}
    +
    +    .arithmetic-container .title {
    +        position: absolute;
    +        right: 0;
    +        padding: 8px;
    +        text-align: right;
    +        border-left: 2px dashed black;
    +        border-bottom: 2px dashed black;
    +    }
    +
    +::deep .diagram-port {
    +    position: absolute;
    +    width: 30px;
    +    height: 20px;
    +    background-color: black;
    +    left: 50%;
    +    transform: translate(-50%, -50%);
    +}
    +
    +    ::deep .diagram-port.top {
    +        border-top-left-radius: 50%;
    +        border-top-right-radius: 50%;
    +        top: -10px;
    +    }
    +
    +    ::deep .diagram-port.bottom {
    +        border-bottom-left-radius: 50%;
    +        border-bottom-right-radius: 50%;
    +        bottom: -30px;
    +    }
    +
    + +

    Displaying

    + +

    + All we have to do now is register our new creation! +

    + +
    
    +private BlazorDiagram Diagram { get; set; } = new();
    +
    +protected override void OnInitialized()
    +{
    +    base.OnInitialized();
    +
    +    Diagram.RegisterComponent<AddTwoNumbersNode, AddTwoNumbersWidget>();
    +    Diagram.RegisterComponent<ArithmeticContainer, ArithmeticContainerWidget>();
    +
    +    var node1 = Diagram.Nodes.Add(new AddTwoNumbersNode(new Point(120, 140)));
    +    node1.AddPort(PortAlignment.Top);
    +    node1.AddPort(PortAlignment.Bottom);
    +
    +    var node2 = Diagram.Nodes.Add(new AddTwoNumbersNode(new Point(370, 340)));
    +    node2.AddPort(PortAlignment.Top);
    +    node2.AddPort(PortAlignment.Bottom);
    +
    +    var group = Diagram.Groups.Add(new ArithmeticContainer(new[] { node1, node2 }, padding: 50));
    +    group.AddPort(PortAlignment.Top);
    +    group.AddPort(PortAlignment.Bottom);
    +    group.Title = "First Container";
    +}
    +
    + +
    + + + +
    + + + +@code { + private BlazorDiagram Diagram { get; set; } = new(); + + protected override void OnInitialized() + { + base.OnInitialized(); + + Diagram.RegisterComponent(); + Diagram.RegisterComponent(); + + var node1 = Diagram.Nodes.Add(new AddTwoNumbersNode(new Point(120, 140))); + node1.AddPort(PortAlignment.Top); + node1.AddPort(PortAlignment.Bottom); + + var node2 = Diagram.Nodes.Add(new AddTwoNumbersNode(new Point(370, 340))); + node2.AddPort(PortAlignment.Top); + node2.AddPort(PortAlignment.Bottom); + + var group = Diagram.Groups.Add(new ArithmeticContainer(new[] { node1, node2 }, padding: 50)); + group.AddPort(PortAlignment.Top); + group.AddPort(PortAlignment.Bottom); + group.Title = "First Container"; + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Groups/Customization.razor.css b/site/Site/Pages/Documentation/Groups/Customization.razor.css new file mode 100644 index 000000000..3204db388 --- /dev/null +++ b/site/Site/Pages/Documentation/Groups/Customization.razor.css @@ -0,0 +1,5 @@ +.diagram-container { + width: 100%; + height: 650px; + border: 1px solid black; +} diff --git a/site/Site/Pages/Documentation/Groups/SVG.razor b/site/Site/Pages/Documentation/Groups/SVG.razor index 9de6e054f..2285e25f4 100644 --- a/site/Site/Pages/Documentation/Groups/SVG.razor +++ b/site/Site/Pages/Documentation/Groups/SVG.razor @@ -57,4 +57,6 @@ var group = new SvgGroupModel(new[] { node1, node2 }); \ No newline at end of file + PreviousLink="/documentation/groups" + NextTitle="Customization" + NextLink="/documentation/groups-customization" /> \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Groups/SvgCustomization.razor b/site/Site/Pages/Documentation/Groups/SvgCustomization.razor new file mode 100644 index 000000000..16e5be339 --- /dev/null +++ b/site/Site/Pages/Documentation/Groups/SvgCustomization.razor @@ -0,0 +1,133 @@ +@page "/documentation/groups-customization-svg" +@using Site.Components.Documentation.Nodes; +@using Site.Models.Nodes; +@layout DocumentationLayout +@inherits DocumentationPage + +SVG Groups Customization - Documentation - Blazor Diagrams + +

    SVG Groups Customization

    + +

    + Creating a custom SVG group is as easy as a HTML one, let's go!
    + This tutorial is based on the SVG Nodes Customization one, make sure you look at that one first. +
    +
    + Don't be jealous of my arts. +

    + +

    Creating a model

    + +

    + Let's assume that we want to create a new group that represents a house for gingerbreads: +

    + +
    GingerbreadHouse.cs
    +
    
    +namespace YourNamespace;
    +
    +public class GingerbreadHouse : SvgGroupModel
    +{
    +    public GingerbreadHouse(IEnumerable<NodeModel> children, byte padding = 30, bool autoSize = true) : base(children, padding, autoSize)
    +    {
    +    }
    +}
    +
    + +

    Creating a component

    + +

    + Let's create a UI component to control how the group looks like:
    +

    + +
    GingerbreadHouseWidget.razor
    +
    
    +@@using YourNamspace;
    +
    +@@{
    +    var halfWidth = Group.Size!.Width / 2;
    +}
    +
    +<path d="m @@halfWidth -100 l @@halfWidth 100 l -@@Group.Size.Width 0 z"
    +      fill="transparent"
    +      stroke="black"
    +      stroke-width="2" />
    +
    +<rect width="@@Group.Size!.Width"
    +      height="@@Group.Size!.Height"
    +      fill="transparent"
    +      stroke="black"
    +      stroke-width="2"></rect>
    +
    +@@* This is required and it's what renders the children *@@
    +<GroupNodes Group="Group" />
    +
    +@@foreach (var port in Group.Ports)
    +{
    +    <PortRenderer Port="port" Class="group-port"></PortRenderer>
    +}
    +
    +@@code {
    +    // This gets filled by the library
    +    [Parameter] public GingerbreadHouse Group { get; set; } = null!;
    +}
    +
    + +

    Displaying

    + +

    + All we have to do now is register our new creation! +

    + +
    
    +private BlazorDiagram Diagram { get; set; } = new();
    +
    +protected override void OnInitialized()
    +{
    +    base.OnInitialized();
    +
    +    Diagram.RegisterComponent<GingerbreadNode, GingerbreadWidget>();
    +    Diagram.RegisterComponent<GingerbreadHouse, GingerbreadHouseWidget>();
    +
    +    var node1 = Diagram.Nodes.Add(new GingerbreadNode(new Point(160, 300)));
    +    node1.AddPort(PortAlignment.Left);
    +    node1.AddPort(PortAlignment.Right);
    +
    +    var node2 = Diagram.Nodes.Add(new GingerbreadNode(new Point(410, 350)));
    +    node2.AddPort(PortAlignment.Left);
    +    node2.AddPort(PortAlignment.Right);
    +
    +    var group = Diagram.Groups.Add(new GingerbreadHouse(new[] { node1, node2 }, padding: 50));
    +}
    +
    + +
    + + + +
    + + + +@code { + private BlazorDiagram Diagram { get; set; } = new(); + + protected override void OnInitialized() + { + base.OnInitialized(); + + Diagram.RegisterComponent(); + Diagram.RegisterComponent(); + + var node1 = Diagram.Nodes.Add(new GingerbreadNode(new Point(160, 300))); + node1.AddPort(PortAlignment.Left); + node1.AddPort(PortAlignment.Right); + + var node2 = Diagram.Nodes.Add(new GingerbreadNode(new Point(410, 350))); + node2.AddPort(PortAlignment.Left); + node2.AddPort(PortAlignment.Right); + + var group = Diagram.Groups.Add(new GingerbreadHouse(new[] { node1, node2 }, padding: 50)); + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Groups/SvgCustomization.razor.css b/site/Site/Pages/Documentation/Groups/SvgCustomization.razor.css new file mode 100644 index 000000000..3204db388 --- /dev/null +++ b/site/Site/Pages/Documentation/Groups/SvgCustomization.razor.css @@ -0,0 +1,5 @@ +.diagram-container { + width: 100%; + height: 650px; + border: 1px solid black; +} diff --git a/site/Site/Pages/Documentation/Nodes/Customization.razor b/site/Site/Pages/Documentation/Nodes/Customization.razor index 7b685b8c9..b9120ac88 100644 --- a/site/Site/Pages/Documentation/Nodes/Customization.razor +++ b/site/Site/Pages/Documentation/Nodes/Customization.razor @@ -48,7 +48,7 @@ public class AddTwoNumbersNode : NodeModel
    AddTwoNumbersWidget.razor
    
     @@using Blazor.Diagrams.Components.Renderers;
    -@@using Site.Models.Nodes;
    +@@using YourNamespace;
     
     <div>
         <h5 class="card-title">Add</h5>
    diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs
    index 58657827f..24cd31eee 100644
    --- a/site/Site/Static/Documentation.cs
    +++ b/site/Site/Static/Documentation.cs
    @@ -36,6 +36,8 @@ public static class Documentation
             {
                 new MenuItem("Overview", "/documentation/groups"),
                 new MenuItem("SVG", "/documentation/groups-svg"),
    +            new MenuItem("Customization", "/documentation/groups-customization"),
    +            new MenuItem("Customization (SVG)", "/documentation/groups-customization-svg")
             })
         });
     }
    
    From c92d2e707cd2bbaec1b38744d27f07f92896d256 Mon Sep 17 00:00:00 2001
    From: Haytam Zanid 
    Date: Wed, 5 Jul 2023 08:56:07 +0100
    Subject: [PATCH 176/193] Rename FixedSize to ControlledSize
    
    ---
     src/Blazor.Diagrams.Core/Models/NodeModel.cs             | 2 +-
     src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs | 4 ++--
     2 files changed, 3 insertions(+), 3 deletions(-)
    
    diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
    index d3ec3f2df..053c5b3d8 100644
    --- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs
    +++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
    @@ -35,7 +35,7 @@ public Size? Size
                 SizeChanged?.Invoke(this);
             }
         }
    -    public bool FixedSize { get; init; }
    +    public bool ControlledSize { get; init; }
     
         public GroupModel? Group { get; internal set; }
         public string? Title { get; set; }
    diff --git a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs
    index 0016a23f8..2dbe58d02 100644
    --- a/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs
    +++ b/src/Blazor.Diagrams/Components/Renderers/NodeRenderer.cs
    @@ -33,7 +33,7 @@ public void Dispose()
             Node.Changed -= OnNodeChanged;
             Node.VisibilityChanged -= OnVisibilityChanged;
     
    -        if (_element.Id != null && !Node.FixedSize)
    +        if (_element.Id != null && !Node.ControlledSize)
             {
                 _ = JsRuntime.UnobserveResizes(_element);
             }
    @@ -133,7 +133,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender)
             {
                 _becameVisible = false;
     
    -            if (!Node.FixedSize)
    +            if (!Node.ControlledSize)
                 {
                     await JsRuntime.ObserveResizes(_element, _reference!);
                 }
    
    From 6506f4479b28fe6b84e8e6a0e7bcc656085923bf Mon Sep 17 00:00:00 2001
    From: Haytam Zanid 
    Date: Wed, 5 Jul 2023 08:56:13 +0100
    Subject: [PATCH 177/193] Some cleanup
    
    ---
     samples/ServerSide/Program.cs                             | 8 --------
     src/Blazor.Diagrams.Core/DiagramsException.cs             | 8 ++++++++
     .../Positions/LinkPathPositionProvider.cs                 | 5 +++--
     src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs  | 4 ++--
     src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs  | 3 +++
     src/Blazor.Diagrams/Components/SvgNodeWidget.razor        | 2 +-
     .../Behaviors/DragNewLinkBehaviorTests.cs                 | 2 +-
     .../Positions/ShapeAnglePositionProviderTests.cs          | 2 +-
     8 files changed, 19 insertions(+), 15 deletions(-)
    
    diff --git a/samples/ServerSide/Program.cs b/samples/ServerSide/Program.cs
    index 86fdb3e83..a6591ca8d 100644
    --- a/samples/ServerSide/Program.cs
    +++ b/samples/ServerSide/Program.cs
    @@ -1,13 +1,5 @@
    -using System;
    -using System.Collections.Generic;
    -using System.IO;
    -using System.Linq;
    -using System.Threading.Tasks;
    -using Microsoft.AspNetCore;
     using Microsoft.AspNetCore.Hosting;
    -using Microsoft.Extensions.Configuration;
     using Microsoft.Extensions.Hosting;
    -using Microsoft.Extensions.Logging;
     
     namespace ServerSide;
     
    diff --git a/src/Blazor.Diagrams.Core/DiagramsException.cs b/src/Blazor.Diagrams.Core/DiagramsException.cs
    index f56cdc0c4..fab0335b9 100644
    --- a/src/Blazor.Diagrams.Core/DiagramsException.cs
    +++ b/src/Blazor.Diagrams.Core/DiagramsException.cs
    @@ -7,4 +7,12 @@ public class DiagramsException : Exception
         public DiagramsException(string? message) : base(message)
         {
         }
    +
    +    public DiagramsException() : base()
    +    {
    +    }
    +
    +    public DiagramsException(string? message, Exception? innerException) : base(message, innerException)
    +    {
    +    }
     }
    diff --git a/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs b/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs
    index 1890e3ca2..a9ae71342 100644
    --- a/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs
    +++ b/src/Blazor.Diagrams.Core/Positions/LinkPathPositionProvider.cs
    @@ -1,5 +1,6 @@
     using Blazor.Diagrams.Core.Geometry;
     using Blazor.Diagrams.Core.Models.Base;
    +using System;
     
     namespace Blazor.Diagrams.Core.Positions;
     
    @@ -29,11 +30,11 @@ public LinkPathPositionProvider(double distance, double offsetX = 0, double offs
             {
                 >= 0 and <= 1 => Distance * totalLength,
                 > 1 => Distance,
    -            < 0 => totalLength + Distance
    +            < 0 => totalLength + Distance,
    +            _ => throw new NotImplementedException()
             };
     
             var pt = link.PathGeneratorResult.FullPath.GetPointAtLength(length);
             return new Point(pt.X + OffsetX, pt.Y + OffsetY);
    -
         }
     }
    \ No newline at end of file
    diff --git a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs
    index 8cbe42d9d..305530a6a 100644
    --- a/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs
    +++ b/src/Blazor.Diagrams/Components/Renderers/LinkRenderer.cs
    @@ -12,9 +12,9 @@ public class LinkRenderer : ComponentBase, IDisposable
     {
         private bool _shouldRender = true;
     
    -    [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; }
    +    [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!;
     
    -    [Parameter] public BaseLinkModel Link { get; set; }
    +    [Parameter] public BaseLinkModel Link { get; set; } = null!;
     
         public void Dispose()
         {
    diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs
    index c7f0c7997..bbd4786f1 100644
    --- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs
    +++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs
    @@ -117,6 +117,9 @@ private void OnPointerUp(PointerEventArgs e)
     
         private async Task UpdateDimensions()
         {
    +        if (BlazorDiagram.Container == null)
    +            return;
    +
             _updatingDimensions = true;
             var zoom = BlazorDiagram.Zoom;
             var pan = BlazorDiagram.Pan;
    diff --git a/src/Blazor.Diagrams/Components/SvgNodeWidget.razor b/src/Blazor.Diagrams/Components/SvgNodeWidget.razor
    index c1b7af8d9..8fdc6e3ed 100644
    --- a/src/Blazor.Diagrams/Components/SvgNodeWidget.razor
    +++ b/src/Blazor.Diagrams/Components/SvgNodeWidget.razor
    @@ -3,6 +3,6 @@
     @code {
     
         [Parameter]
    -    public NodeModel Node { get; set; }
    +    public NodeModel Node { get; set; } = 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 49df661a6..76d27bc66 100644
    --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
    +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
    @@ -48,7 +48,7 @@ public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort()
             diagram.Options.Links.Factory = (d, s, ta) =>
             {
                 factoryCalled = true;
    -            return new LinkModel(new SinglePortAnchor(s as PortModel), ta);
    +            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)
    diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/ShapeAnglePositionProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/ShapeAnglePositionProviderTests.cs
    index 63b5790a2..205cad5b8 100644
    --- a/tests/Blazor.Diagrams.Core.Tests/Positions/ShapeAnglePositionProviderTests.cs
    +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/ShapeAnglePositionProviderTests.cs
    @@ -39,7 +39,7 @@ public void GetPosition_ShouldUseOffset_WhenProvided()
             var position = provider.GetPosition(nodeMock.Object);
     
             // Assert
    -        position.X.Should().Be(105);
    +        position!.X.Should().Be(105);
             position.Y.Should().Be(40);
         }
     }
    \ No newline at end of file
    
    From 8b3bb632a89d9c509cd94b72b77a62d57564ae5a Mon Sep 17 00:00:00 2001
    From: Haytam Zanid 
    Date: Wed, 5 Jul 2023 09:02:09 +0100
    Subject: [PATCH 178/193] Remove unused namespaces
    
    ---
     docs/CustomNodesLinks/Pages/Error.cshtml.cs                | 6 +-----
     docs/Diagram-Demo/Pages/Error.cshtml.cs                    | 6 +-----
     docs/Layouts/Pages/Error.cshtml.cs                         | 6 +-----
     samples/SharedDemo/Demos/Simple.razor.cs                   | 1 -
     src/Blazor.Diagrams.Core/Anchors/Anchor.cs                 | 2 +-
     src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs         | 4 +---
     .../Controls/Default/DragNewLinkControl.cs                 | 1 -
     src/Blazor.Diagrams.Core/Geometry/Point.cs                 | 1 -
     src/Blazor.Diagrams.Core/Models/GroupModel.cs              | 1 -
     src/Blazor.Diagrams.Core/Models/PortModel.cs               | 7 +++----
     src/Blazor.Diagrams/Components/LinkWidget.razor.cs         | 1 -
     11 files changed, 8 insertions(+), 28 deletions(-)
    
    diff --git a/docs/CustomNodesLinks/Pages/Error.cshtml.cs b/docs/CustomNodesLinks/Pages/Error.cshtml.cs
    index 889588be6..99ccc5af5 100644
    --- a/docs/CustomNodesLinks/Pages/Error.cshtml.cs
    +++ b/docs/CustomNodesLinks/Pages/Error.cshtml.cs
    @@ -1,8 +1,4 @@
    -using System;
    -using System.Collections.Generic;
    -using System.Diagnostics;
    -using System.Linq;
    -using System.Threading.Tasks;
    +using System.Diagnostics;
     using Microsoft.AspNetCore.Mvc;
     using Microsoft.AspNetCore.Mvc.RazorPages;
     using Microsoft.Extensions.Logging;
    diff --git a/docs/Diagram-Demo/Pages/Error.cshtml.cs b/docs/Diagram-Demo/Pages/Error.cshtml.cs
    index a924eac35..555cbe763 100644
    --- a/docs/Diagram-Demo/Pages/Error.cshtml.cs
    +++ b/docs/Diagram-Demo/Pages/Error.cshtml.cs
    @@ -1,8 +1,4 @@
    -using System;
    -using System.Collections.Generic;
    -using System.Diagnostics;
    -using System.Linq;
    -using System.Threading.Tasks;
    +using System.Diagnostics;
     using Microsoft.AspNetCore.Mvc;
     using Microsoft.AspNetCore.Mvc.RazorPages;
     using Microsoft.Extensions.Logging;
    diff --git a/docs/Layouts/Pages/Error.cshtml.cs b/docs/Layouts/Pages/Error.cshtml.cs
    index ab885857f..2c40ebbbe 100644
    --- a/docs/Layouts/Pages/Error.cshtml.cs
    +++ b/docs/Layouts/Pages/Error.cshtml.cs
    @@ -1,8 +1,4 @@
    -using System;
    -using System.Collections.Generic;
    -using System.Diagnostics;
    -using System.Linq;
    -using System.Threading.Tasks;
    +using System.Diagnostics;
     using Microsoft.AspNetCore.Mvc;
     using Microsoft.AspNetCore.Mvc.RazorPages;
     using Microsoft.Extensions.Logging;
    diff --git a/samples/SharedDemo/Demos/Simple.razor.cs b/samples/SharedDemo/Demos/Simple.razor.cs
    index b1a995b98..316fd4c20 100644
    --- a/samples/SharedDemo/Demos/Simple.razor.cs
    +++ b/samples/SharedDemo/Demos/Simple.razor.cs
    @@ -1,5 +1,4 @@
     using Blazor.Diagrams;
    -using Blazor.Diagrams.Core;
     using Blazor.Diagrams.Core.Geometry;
     using Blazor.Diagrams.Core.Models;
     using Blazor.Diagrams.Core.PathGenerators;
    diff --git a/src/Blazor.Diagrams.Core/Anchors/Anchor.cs b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs
    index 53f100fdf..b8fa6fe86 100644
    --- a/src/Blazor.Diagrams.Core/Anchors/Anchor.cs
    +++ b/src/Blazor.Diagrams.Core/Anchors/Anchor.cs
    @@ -7,7 +7,7 @@ namespace Blazor.Diagrams.Core.Anchors;
     
     public abstract class Anchor
     {
    -    public Anchor(ILinkable? model = null)
    +    protected Anchor(ILinkable? model = null)
         {
             Model = model;
         }
    diff --git a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs
    index ee6cca73e..3da507b75 100644
    --- a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs
    +++ b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs
    @@ -1,6 +1,4 @@
    -using Blazor.Diagrams.Core.Geometry;
    -
    -using Blazor.Diagrams.Core.Events;
    +using Blazor.Diagrams.Core.Events;
     
     using System;
     
    diff --git a/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs
    index bb4e6498b..3caa413a0 100644
    --- a/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs
    +++ b/src/Blazor.Diagrams.Core/Controls/Default/DragNewLinkControl.cs
    @@ -1,5 +1,4 @@
     using System.Threading.Tasks;
    -using Blazor.Diagrams.Core.Anchors;
     using Blazor.Diagrams.Core.Behaviors;
     using Blazor.Diagrams.Core.Events;
     using Blazor.Diagrams.Core.Geometry;
    diff --git a/src/Blazor.Diagrams.Core/Geometry/Point.cs b/src/Blazor.Diagrams.Core/Geometry/Point.cs
    index 272323d94..6361aab90 100644
    --- a/src/Blazor.Diagrams.Core/Geometry/Point.cs
    +++ b/src/Blazor.Diagrams.Core/Geometry/Point.cs
    @@ -1,5 +1,4 @@
     using System;
    -using System.Reflection.Metadata;
     
     namespace Blazor.Diagrams.Core.Geometry;
     
    diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs
    index c78977064..041d872ec 100644
    --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs
    +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs
    @@ -1,6 +1,5 @@
     using Blazor.Diagrams.Core.Extensions;
     using Blazor.Diagrams.Core.Geometry;
    -using System;
     using System.Collections.Generic;
     using System.Linq;
     
    diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs
    index 52608d044..fbb4ddc4e 100644
    --- a/src/Blazor.Diagrams.Core/Models/PortModel.cs
    +++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs
    @@ -1,5 +1,4 @@
    -using System;
    -using Blazor.Diagrams.Core.Geometry;
    +using Blazor.Diagrams.Core.Geometry;
     using Blazor.Diagrams.Core.Models.Base;
     using System.Collections.Generic;
     
    @@ -30,11 +29,11 @@ public PortModel(string id, NodeModel parent, PortAlignment alignment = PortAlig
         public NodeModel Parent { get; }
         public PortAlignment Alignment { get; }
         public Point Position { get; set; }
    -    public Point MiddlePosition => new(Position.X + Size.Width / 2, Position.Y + Size.Height / 2);
    +    public Point MiddlePosition => new(Position.X + (Size.Width / 2), Position.Y + (Size.Height / 2));
         public Size Size { get; set; }
         public IReadOnlyList Links => _links;
         /// 
    -    /// If set to false, a call to Refresh() will force the port to update its position/size using JS
    +    /// If set to false, a call to Refresh() will force the port to update its position/size
         /// 
         public bool Initialized { get; set; }
     
    diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs
    index 6eb3a51a1..66cf6a72a 100644
    --- a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs
    +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs
    @@ -2,7 +2,6 @@
     using Blazor.Diagrams.Extensions;
     using Microsoft.AspNetCore.Components;
     using Microsoft.AspNetCore.Components.Web;
    -using System;
     
     namespace Blazor.Diagrams.Components;
     
    
    From c6dab668b87c9047752b35e70b71ef72da75ab73 Mon Sep 17 00:00:00 2001
    From: Haytam Zanid 
    Date: Wed, 12 Jul 2023 08:39:19 +0100
    Subject: [PATCH 179/193] Avoid rendering link selection helper while dragging
     link
    
    ---
     .../Components/LinkWidget.razor               | 21 +++++++++++--------
     1 file changed, 12 insertions(+), 9 deletions(-)
    
    diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor b/src/Blazor.Diagrams/Components/LinkWidget.razor
    index bf4a88205..a117990c1 100644
    --- a/src/Blazor.Diagrams/Components/LinkWidget.razor
    +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor
    @@ -12,17 +12,20 @@
           fill="none"
           stroke="@color" />
     
    -@if (Link.Vertices.Count == 0)
    +@if (Link.IsAttached)
     {
    -    @GetSelectionHelperPath(color, d, 0)
    -}
    -else
    -{
    -    @for (var i = 0; i < result.Paths.Length; i++)
    +    @if (Link.Vertices.Count == 0)
    +    {
    +        @GetSelectionHelperPath(color, d, 0)
    +    }
    +    else
         {
    -        d = result.Paths[i].ToString();
    -        var index = i;
    -        @GetSelectionHelperPath(color, d, index)
    +        @for (var i = 0; i < result.Paths.Length; i++)
    +        {
    +            d = result.Paths[i].ToString();
    +            var index = i;
    +            @GetSelectionHelperPath(color, d, index)
    +        }
         }
     }
     
    
    From f078a686b51ea33742c2f0037db88dc062ad4e6b Mon Sep 17 00:00:00 2001
    From: Haytam Zanid 
    Date: Wed, 12 Jul 2023 08:41:07 +0100
    Subject: [PATCH 180/193] Rename Point.Substract to Subtract (duh)
    
    ---
     .../Behaviors/DragMovablesBehavior.cs                     | 4 ----
     src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs | 8 ++++----
     src/Blazor.Diagrams.Core/Geometry/Ellipse.cs              | 2 +-
     src/Blazor.Diagrams.Core/Geometry/Point.cs                | 4 ++--
     4 files changed, 7 insertions(+), 11 deletions(-)
    
    diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
    index 692a83dd1..db5aa2c0d 100644
    --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
    +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
    @@ -100,10 +100,6 @@ private double ApplyGridSize(double n)
                 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);
         }
     
    diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs
    index e50b5a9d9..b54f329ab 100644
    --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs
    +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs
    @@ -23,7 +23,7 @@ public void StartFrom(ILinkable source, double clientX, double clientY)
             if (_ongoingLink != null)
                 return;
     
    -        _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5));
    +        _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(clientX, clientY).Subtract(5));
             _ongoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor);
             if (_ongoingLink == null)
                 return;
    @@ -36,7 +36,7 @@ public void StartFrom(BaseLinkModel link, double clientX, double clientY)
             if (_ongoingLink != null)
                 return;
     
    -        _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(clientX, clientY).Substract(5));
    +        _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(clientX, clientY).Subtract(5));
             _ongoingLink = link;
             _ongoingLink.SetTarget(_targetPositionAnchor);
             _ongoingLink.Refresh();
    @@ -56,7 +56,7 @@ private void OnPointerDown(Model? model, MouseEventArgs e)
                 if (port.Locked)
                     return;
     
    -            _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5));
    +            _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Subtract(5));
                 _ongoingLink = Diagram.Options.Links.Factory(Diagram, port, _targetPositionAnchor);
                 if (_ongoingLink == null)
                     return;
    @@ -71,7 +71,7 @@ private void OnPointerMove(Model? model, MouseEventArgs e)
             if (_ongoingLink == null || model != null)
                 return;
     
    -        _targetPositionAnchor!.SetPosition(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Substract(5));
    +        _targetPositionAnchor!.SetPosition(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Subtract(5));
     
             if (Diagram.Options.Links.EnableSnapping)
             {
    diff --git a/src/Blazor.Diagrams.Core/Geometry/Ellipse.cs b/src/Blazor.Diagrams.Core/Geometry/Ellipse.cs
    index 771a08182..72db92dbc 100644
    --- a/src/Blazor.Diagrams.Core/Geometry/Ellipse.cs
    +++ b/src/Blazor.Diagrams.Core/Geometry/Ellipse.cs
    @@ -23,7 +23,7 @@ public IEnumerable GetIntersectionsWithLine(Line line)
             var a1 = line.Start;
             var a2 = line.End;
             var dir = new Point(line.End.X - line.Start.X, line.End.Y - line.Start.Y);
    -        var diff = a1.Substract(Cx, Cy);
    +        var diff = a1.Subtract(Cx, Cy);
             var mDir = new Point(dir.X / (Rx * Rx), dir.Y / (Ry * Ry));
             var mDiff = new Point(diff.X / (Rx * Rx), diff.Y / (Ry * Ry));
     
    diff --git a/src/Blazor.Diagrams.Core/Geometry/Point.cs b/src/Blazor.Diagrams.Core/Geometry/Point.cs
    index 6361aab90..c02de02b5 100644
    --- a/src/Blazor.Diagrams.Core/Geometry/Point.cs
    +++ b/src/Blazor.Diagrams.Core/Geometry/Point.cs
    @@ -23,8 +23,8 @@ public Point Lerp(Point other, double t)
         public Point Add(double value) => new(X + value, Y + value);
         public Point Add(double x, double y) => new(X + x, Y + y);
     
    -    public Point Substract(double value) => new(X - value, Y - value);
    -    public Point Substract(double x, double y) => new(X - x, Y - y);
    +    public Point Subtract(double value) => new(X - value, Y - value);
    +    public Point Subtract(double x, double y) => new(X - x, Y - y);
     
         public double DistanceTo(Point other) => Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2));
         public double DistanceTo(double x, double y) => Math.Sqrt(Math.Pow(X - x, 2) + Math.Pow(Y - y, 2));
    
    From 9fb3ee37685614b68707294eba79234e4665057a Mon Sep 17 00:00:00 2001
    From: Haytam Zanid 
    Date: Thu, 20 Jul 2023 10:47:15 +0100
    Subject: [PATCH 181/193] Fix mouse overlapping dragged link
    
    ---
     .../Behaviors/DragNewLinkBehavior.cs          | 99 +++++++++++--------
     src/Blazor.Diagrams.Core/Geometry/Point.cs    | 14 ++-
     src/Blazor.Diagrams.Core/Models/PortModel.cs  |  2 +-
     .../Components/LinkWidget.razor               |  7 +-
     4 files changed, 79 insertions(+), 43 deletions(-)
    
    diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs
    index b54f329ab..31d0c5ede 100644
    --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs
    +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs
    @@ -3,14 +3,16 @@
     using Blazor.Diagrams.Core.Events;
     using System.Linq;
     using Blazor.Diagrams.Core.Anchors;
    +using Blazor.Diagrams.Core.Geometry;
     
     namespace Blazor.Diagrams.Core.Behaviors;
     
     public class DragNewLinkBehavior : Behavior
     {
    -    private BaseLinkModel? _ongoingLink;
         private PositionAnchor? _targetPositionAnchor;
     
    +    public BaseLinkModel? OngoingLink { get; private set; }
    +
         public DragNewLinkBehavior(Diagram diagram) : base(diagram)
         {
             Diagram.PointerDown += OnPointerDown;
    @@ -20,27 +22,27 @@ public DragNewLinkBehavior(Diagram diagram) : base(diagram)
     
         public void StartFrom(ILinkable source, double clientX, double clientY)
         {
    -        if (_ongoingLink != null)
    +        if (OngoingLink != null)
                 return;
     
    -        _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(clientX, clientY).Subtract(5));
    -        _ongoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor);
    -        if (_ongoingLink == null)
    +        _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY));
    +        OngoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor);
    +        if (OngoingLink == null)
                 return;
     
    -        Diagram.Links.Add(_ongoingLink);
    +        Diagram.Links.Add(OngoingLink);
         }
     
         public void StartFrom(BaseLinkModel link, double clientX, double clientY)
         {
    -        if (_ongoingLink != null)
    +        if (OngoingLink != null)
                 return;
     
    -        _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(clientX, clientY).Subtract(5));
    -        _ongoingLink = link;
    -        _ongoingLink.SetTarget(_targetPositionAnchor);
    -        _ongoingLink.Refresh();
    -        _ongoingLink.RefreshLinks();
    +        _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY));
    +        OngoingLink = link;
    +        OngoingLink.SetTarget(_targetPositionAnchor);
    +        OngoingLink.Refresh();
    +        OngoingLink.RefreshLinks();
         }
     
         private void OnPointerDown(Model? model, MouseEventArgs e)
    @@ -48,7 +50,7 @@ private void OnPointerDown(Model? model, MouseEventArgs e)
             if (e.Button != (int)MouseEventButton.Left)
                 return;
     
    -        _ongoingLink = null;
    +        OngoingLink = null;
             _targetPositionAnchor = null;
     
             if (model is PortModel port)
    @@ -56,79 +58,98 @@ private void OnPointerDown(Model? model, MouseEventArgs e)
                 if (port.Locked)
                     return;
     
    -            _targetPositionAnchor = new PositionAnchor(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Subtract(5));
    -            _ongoingLink = Diagram.Options.Links.Factory(Diagram, port, _targetPositionAnchor);
    -            if (_ongoingLink == null)
    +            _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);
    +            OngoingLink.SetTarget(_targetPositionAnchor);
    +            Diagram.Links.Add(OngoingLink);
             }
         }
     
         private void OnPointerMove(Model? model, MouseEventArgs e)
         {
    -        if (_ongoingLink == null || model != null)
    +        if (OngoingLink == null || model != null)
                 return;
     
    -        _targetPositionAnchor!.SetPosition(Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY).Subtract(5));
    +        _targetPositionAnchor!.SetPosition(CalculateTargetPosition(e.ClientX, e.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.RefreshLinks();
    +        OngoingLink.Refresh();
    +        OngoingLink.RefreshLinks();
         }
     
         private void OnPointerUp(Model? model, MouseEventArgs e)
         {
    -        if (_ongoingLink == null)
    +        if (OngoingLink == null)
                 return;
     
    -        if (_ongoingLink.IsAttached) // Snapped already
    +        if (OngoingLink.IsAttached) // Snapped already
             {
    -            _ongoingLink.TriggerTargetAttached();
    -            _ongoingLink = null;
    +            OngoingLink.TriggerTargetAttached();
    +            OngoingLink = null;
                 return;
             }
     
    -        if (model is ILinkable linkable && (_ongoingLink.Source.Model == null || _ongoingLink.Source.Model.CanAttachTo(linkable)))
    +        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();
    +            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);
    +            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;
             }
     
    -        _ongoingLink = null;
    +        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)
    +        if (OngoingLink is null || _targetPositionAnchor is null)
                 return null;
     
             PortModel? nearestSnapPort = null;
             var nearestSnapPortDistance = double.PositiveInfinity;
     
    -        var position = _targetPositionAnchor!.GetPosition(_ongoingLink)!;
    +        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 <= Diagram.Options.Links.SnappingRadius && (OngoingLink.Source.Model?.CanAttachTo(port) != false))
                 {
                     if (distance < nearestSnapPortDistance)
                     {
    diff --git a/src/Blazor.Diagrams.Core/Geometry/Point.cs b/src/Blazor.Diagrams.Core/Geometry/Point.cs
    index c02de02b5..7bb02b514 100644
    --- a/src/Blazor.Diagrams.Core/Geometry/Point.cs
    +++ b/src/Blazor.Diagrams.Core/Geometry/Point.cs
    @@ -15,16 +15,28 @@ public Point(double x, double y)
         public double X { get; init; }
         public double Y { get; init; }
     
    +    public double Length => Math.Sqrt(Dot(this));
    +
         public double Dot(Point other) => X * other.X + Y * other.Y;
         public Point Lerp(Point other, double t)
             => new(X * (1.0 - t) + other.X * t, Y * (1.0 - t) + other.Y * t);
     
    -    // Maybe just make Points mutable?
         public Point Add(double value) => new(X + value, Y + value);
         public Point Add(double x, double y) => new(X + x, Y + y);
     
         public Point Subtract(double value) => new(X - value, Y - value);
         public Point Subtract(double x, double y) => new(X - x, Y - y);
    +    public Point Subtract(Point other) => new(X - other.X, Y - other.Y);
    +
    +    public Point Divide(Point other) => new(X / other.X, Y / other.Y);
    +
    +    public Point Multiply(double value) => new(X * value, Y * value);
    +
    +    public Point Normalize()
    +    {
    +        var length = Length;
    +        return new Point(X / length, Y / length);
    +    }
     
         public double DistanceTo(Point other) => Math.Sqrt(Math.Pow(X - other.X, 2) + Math.Pow(Y - other.Y, 2));
         public double DistanceTo(double x, double y) => Math.Sqrt(Math.Pow(X - x, 2) + Math.Pow(Y - y, 2));
    diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs
    index fbb4ddc4e..a0c850816 100644
    --- a/src/Blazor.Diagrams.Core/Models/PortModel.cs
    +++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs
    @@ -61,7 +61,7 @@ public void RefreshLinks()
         public virtual bool CanAttachTo(ILinkable other)
         {
             // Todo: remove in order to support same node links
    -        return other is PortModel port && port != this && !port.Locked && Parent != port.Parent; 
    +        return other is PortModel port && port != this && !port.Locked && Parent != port.Parent;
         }
     
         void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link);
    diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor b/src/Blazor.Diagrams/Components/LinkWidget.razor
    index a117990c1..60ea5b2f9 100644
    --- a/src/Blazor.Diagrams/Components/LinkWidget.razor
    +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor
    @@ -1,9 +1,12 @@
    -@{
    +@using Blazor.Diagrams.Core.Behaviors;
    +
    +@{
         var color = Link.Selected ? Link.SelectedColor ?? BlazorDiagram.Options.Links.DefaultSelectedColor : Link.Color ?? BlazorDiagram.Options.Links.DefaultColor;
         var result = Link.PathGeneratorResult;
         if (result == null)
             return;
     
    +    var dnlb = BlazorDiagram.GetBehavior();
         var d = result.FullPath.ToString();
     }
     
    @@ -12,7 +15,7 @@
           fill="none"
           stroke="@color" />
     
    -@if (Link.IsAttached)
    +@if (dnlb!.OngoingLink == null || dnlb.OngoingLink != Link)
     {
         @if (Link.Vertices.Count == 0)
         {
    
    From eac192ebfa7675850e758b8e5521f8aa90e306a7 Mon Sep 17 00:00:00 2001
    From: Haytam Zanid 
    Date: Sat, 22 Jul 2023 20:51:09 +0100
    Subject: [PATCH 182/193] Add Ports customization doc
    
    ---
     site/Site/Models/Ports/MyCustomPortModel.cs   | 32 +++++++++
     .../Pages/Documentation/Groups/Overview.razor |  2 +-
     .../Pages/Documentation/Nodes/Overview.razor  |  2 +-
     .../Documentation/Ports/Customization.razor   | 58 +++++++++++++++++
     .../Pages/Documentation/Ports/Overview.razor  | 65 +++++++++++++++++++
     site/Site/Static/Documentation.cs             |  5 ++
     site/Site/wwwroot/css/app.css                 | 56 ++++------------
     7 files changed, 174 insertions(+), 46 deletions(-)
     create mode 100644 site/Site/Models/Ports/MyCustomPortModel.cs
     create mode 100644 site/Site/Pages/Documentation/Ports/Customization.razor
     create mode 100644 site/Site/Pages/Documentation/Ports/Overview.razor
    
    diff --git a/site/Site/Models/Ports/MyCustomPortModel.cs b/site/Site/Models/Ports/MyCustomPortModel.cs
    new file mode 100644
    index 000000000..d030743bb
    --- /dev/null
    +++ b/site/Site/Models/Ports/MyCustomPortModel.cs
    @@ -0,0 +1,32 @@
    +using Blazor.Diagrams.Core.Geometry;
    +using Blazor.Diagrams.Core.Models;
    +using Blazor.Diagrams.Core.Models.Base;
    +
    +namespace Site.Models.Ports;
    +
    +public class MyCustomPortModel : PortModel
    +{
    +    public bool In { get; set; }
    +
    +    public MyCustomPortModel(NodeModel parent, bool @in, PortAlignment alignment = PortAlignment.Bottom, Point? position = null, Size? size = null) : base(parent, alignment, position, size)
    +    {
    +        In = @in;
    +    }
    +
    +    public MyCustomPortModel(string id, NodeModel parent, bool @in, PortAlignment alignment = PortAlignment.Bottom, Point? position = null, Size? size = null) : base(id, parent, alignment, position, size)
    +    {
    +        In = @in;
    +    }
    +
    +    public override bool CanAttachTo(ILinkable other)
    +    {
    +        if (!base.CanAttachTo(other)) // default constraints
    +            return false;
    +
    +        if (other is not MyCustomPortModel otherPort)
    +            return false;
    +
    +        // Only link Ins with Outs
    +        return In != otherPort.In;
    +    }
    +}
    diff --git a/site/Site/Pages/Documentation/Groups/Overview.razor b/site/Site/Pages/Documentation/Groups/Overview.razor
    index 32a67a2ee..1b712c5a1 100644
    --- a/site/Site/Pages/Documentation/Groups/Overview.razor
    +++ b/site/Site/Pages/Documentation/Groups/Overview.razor
    @@ -14,7 +14,7 @@
     

    Structure

    - The component GroupRenderer generates the follolwing structure: + The internal component GroupRenderer generates the follolwing structure:

    
    diff --git a/site/Site/Pages/Documentation/Nodes/Overview.razor b/site/Site/Pages/Documentation/Nodes/Overview.razor
    index fbdf5b5be..a0b8a6ba9 100644
    --- a/site/Site/Pages/Documentation/Nodes/Overview.razor
    +++ b/site/Site/Pages/Documentation/Nodes/Overview.razor
    @@ -13,7 +13,7 @@
     

    Structure

    - The component NodeRenderer generates the follolwing structure: + The internal component NodeRenderer generates the follolwing structure:

    
    diff --git a/site/Site/Pages/Documentation/Ports/Customization.razor b/site/Site/Pages/Documentation/Ports/Customization.razor
    new file mode 100644
    index 000000000..e4acec747
    --- /dev/null
    +++ b/site/Site/Pages/Documentation/Ports/Customization.razor
    @@ -0,0 +1,58 @@
    +@page "/documentation/ports-customization"
    +@layout DocumentationLayout
    +@inherits DocumentationPage
    +
    +Ports Customization - Documentation - Blazor Diagrams
    +
    +

    Ports Customization

    + +

    + Unulike for other models, customization in this page refers to the model itself, not the UI.
    + How the ports are represented in the UI is managed by the parent (node or group), no registration is needed. +

    + +

    Creating a model

    + +

    + Let's assume that we want to create ports that either receive (In) or send (Out).
    + The constraint is that In ports only link to Out ports, you can't link two ports with the same type: +

    + +
    
    +public class MyCustomPortModel : PortModel
    +{
    +    public bool In { get; set; }
    +
    +    public MyCustomPortModel(NodeModel parent, bool @@in, PortAlignment alignment = PortAlignment.Bottom, Point? position = null, Size? size = null) : base(parent, alignment, position, size)
    +    {
    +        In = @@in;
    +    }
    +
    +    public MyCustomPortModel(string id, NodeModel parent, bool @@in, PortAlignment alignment = PortAlignment.Bottom, Point? position = null, Size? size = null) : base(id, parent, alignment, position, size)
    +    {
    +        In = @@in;
    +    }
    +
    +    public override bool CanAttachTo(ILinkable other)
    +    {
    +        if (!base.CanAttachTo(other)) // default constraints
    +            return false;
    +
    +        if (other is not MyCustomPortModel otherPort)
    +            return false;
    +
    +        // Only link Ins with Outs
    +        return In != otherPort.In;
    +    }
    +}
    +
    + +

    Using the model

    + +
    
    +var receiverPort = myNode.AddPort(new MyCustomPortModel(myNode, true, PortAlignment.Top));
    +var senderPort = myNode.AddPort(new MyCustomPortModel(myNode, false, PortAlignment.Top));
    +
    + + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Ports/Overview.razor b/site/Site/Pages/Documentation/Ports/Overview.razor new file mode 100644 index 000000000..42ce990b0 --- /dev/null +++ b/site/Site/Pages/Documentation/Ports/Overview.razor @@ -0,0 +1,65 @@ +@page "/documentation/ports" +@layout DocumentationLayout +@inherits DocumentationPage + +Ports - Documentation - Blazor Diagrams + +

    Ports

    + +

    + Ports are locations/endpoints on your nodes/groups from where a link can be created and/or attached. +

    + +

    Structure

    + +

    + The internal component PortRenderer generates the follolwing structures:
    + The classes that the element can have (beside diagram-port) are:
    +

      +
    • The alignment (e.g. top, bottom)
    • +
    • If the port contains ingoing/outgoing links, has-links
    • +
    • Extra classes you provide using the Class parameter
    • +
    +

    + +

    When the parent is HTML

    + +
    
    +<div class="diagram-port ..."
    +     data-port-id="28e9606d-08dd-47d5-a4c7-b25e541bcf1n">
    +    <!-- YOUR CONTENT WILL BE HERE -->
    +</div>
    +
    + +

    When the parent is SVG

    + +
    
    +<g class="diagram-port ..."
    +   data-port-id="28e9606d-08dd-47d5-a4c7-b25e541bcf1n">
    +    <!-- YOUR CONTENT WILL BE HERE -->
    +</g>
    +
    + +

    Shape

    + +Nodes can have a specific shape, which by default is a rectangle. It is used for two things at the moment: +
      +
    • ShapeAnglePositionProvider: To return a position for the given angle.
    • +
    • SinglePortAnchor: When UseShapeAndAlignment is true.
    • +
    +
    + +

    + You can change the shape of your node by overriding the GetShape method in your custom model. +

    + +

    Creating a port

    + +
    
    +var port = myNode.AddPort(PortAlignment.Top);
    +// OR
    +var port = myNode.AddPort(new PortModel(myNode, PortAlignment.Top));
    +
    + + \ No newline at end of file diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs index 24cd31eee..07180d500 100644 --- a/site/Site/Static/Documentation.cs +++ b/site/Site/Static/Documentation.cs @@ -32,6 +32,11 @@ public static class Documentation new MenuItem("Customization", "/documentation/nodes-customization"), new MenuItem("Customization (SVG)", "/documentation/nodes-customization-svg") }), + new MenuGroup("Ports", new List + { + new MenuItem("Overview", "/documentation/ports"), + new MenuItem("Customization", "/documentation/ports-customization") + }), new MenuGroup("Groups", new List { new MenuItem("Overview", "/documentation/groups"), diff --git a/site/Site/wwwroot/css/app.css b/site/Site/wwwroot/css/app.css index 2938775c2..da403a538 100644 --- a/site/Site/wwwroot/css/app.css +++ b/site/Site/wwwroot/css/app.css @@ -750,6 +750,10 @@ td, th { margin-left: 0.5rem; } +.ml-4 { + margin-left: 1rem; +} + .ml-auto { margin-left: auto; } @@ -908,10 +912,6 @@ td, th { grid-template-columns: repeat(4, minmax(0, 1fr)); } -.grid-cols-12 { - grid-template-columns: repeat(12, minmax(0, 1fr)); -} - .flex-row { flex-direction: row; } @@ -1069,21 +1069,6 @@ td, th { border-color: transparent; } -.border-slate-400 { - --tw-border-opacity: 1; - border-color: rgb(148 163 184 / var(--tw-border-opacity)); -} - -.border-slate-600 { - --tw-border-opacity: 1; - border-color: rgb(71 85 105 / var(--tw-border-opacity)); -} - -.border-slate-50 { - --tw-border-opacity: 1; - border-color: rgb(248 250 252 / var(--tw-border-opacity)); -} - .bg-gray-100 { --tw-bg-opacity: 1; background-color: rgb(243 244 246 / var(--tw-bg-opacity)); @@ -1487,19 +1472,14 @@ td, th { border-color: rgb(148 163 184 / var(--tw-border-opacity)); } -.hover\:bg-palette-main:hover { - --tw-bg-opacity: 1; - background-color: rgb(64 186 189 / var(--tw-bg-opacity)); -} - -.hover\:bg-gray-300:hover { +.hover\:bg-gray-200:hover { --tw-bg-opacity: 1; - background-color: rgb(209 213 219 / var(--tw-bg-opacity)); + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); } -.hover\:bg-gray-200:hover { +.hover\:bg-palette-main:hover { --tw-bg-opacity: 1; - background-color: rgb(229 231 235 / var(--tw-bg-opacity)); + background-color: rgb(64 186 189 / var(--tw-bg-opacity)); } .hover\:text-gray-600:hover { @@ -1588,10 +1568,6 @@ td, th { display: flex; } - .sm\:grid-cols-12 { - grid-template-columns: repeat(12, minmax(0, 1fr)); - } - .sm\:px-6 { padding-left: 1.5rem; padding-right: 1.5rem; @@ -1627,22 +1603,14 @@ td, th { width: 40%; } - .md\:grid-cols-5 { - grid-template-columns: repeat(5, minmax(0, 1fr)); - } - - .md\:grid-cols-6 { - grid-template-columns: repeat(6, minmax(0, 1fr)); - } - - .md\:grid-cols-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } - .md\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } + .md\:grid-cols-5 { + grid-template-columns: repeat(5, minmax(0, 1fr)); + } + .md\:flex-row { flex-direction: row; } From 4a19d958861a8e75d4e2a80687fc93f87b4f06cb Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 22 Jul 2023 21:00:05 +0100 Subject: [PATCH 183/193] Correct ports overview doc --- site/Site/Pages/Documentation/Ports/Overview.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/Site/Pages/Documentation/Ports/Overview.razor b/site/Site/Pages/Documentation/Ports/Overview.razor index 42ce990b0..03c9fe999 100644 --- a/site/Site/Pages/Documentation/Ports/Overview.razor +++ b/site/Site/Pages/Documentation/Ports/Overview.razor @@ -42,7 +42,7 @@

    Shape

    -Nodes can have a specific shape, which by default is a rectangle. It is used for two things at the moment: +Ports can have a specific shape, which by default is a circle. It is used for two things at the moment:
    • ShapeAnglePositionProvider: To return a position for the given angle.
    • SinglePortAnchor: When UseShapeAndAlignment is true.
    • From 69295503a6f640e6a863ac2abbbd4fd7ac57f85f Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 11 Aug 2023 11:47:15 +0100 Subject: [PATCH 184/193] Add AddLabel method to links and fix SmoothPathGenerator not working with LinkAnchor --- .../Anchors/ShapeIntersectionAnchor.cs | 2 +- src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs | 7 +++++++ .../PathGenerators/SmoothPathGenerator.cs | 2 +- .../Blazor.Diagrams.Core.Tests.csproj | 2 +- tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj | 2 +- 5 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs index 8790e401f..1ddddcef8 100644 --- a/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs +++ b/src/Blazor.Diagrams.Core/Anchors/ShapeIntersectionAnchor.cs @@ -34,7 +34,7 @@ public ShapeIntersectionAnchor(NodeModel model) : base(model) var line = new Line(pt, nodeCenter); var intersections = Node.GetShape().GetIntersectionsWithLine(line); - return GetClosestPointTo(intersections, pt); // Todo: use Offset + return GetClosestPointTo(intersections, pt); } public override Point? GetPlainPosition() => Node.GetBounds()?.Center ?? null; diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index da51dd783..066e9ca6f 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -55,6 +55,13 @@ public void RefreshLinks() } } + public LinkLabelModel AddLabel(string content, double? distance = null, Point? offset = null) + { + var label = new LinkLabelModel(this, content, distance, offset); + Labels.Add(label); + return label; + } + public void SetSource(Anchor anchor) { ArgumentNullException.ThrowIfNull(anchor, nameof(anchor)); diff --git a/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs b/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs index f1327e848..89126ef98 100644 --- a/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs +++ b/src/Blazor.Diagrams.Core/PathGenerators/SmoothPathGenerator.cs @@ -93,7 +93,7 @@ private Point GetCurvePoint(Point[] route, Anchor anchor, double pX, double pY, { return GetCurvePoint(pX, pY, cX, cY, spa.Port.Alignment); } - else if (anchor is ShapeIntersectionAnchor or DynamicAnchor) + else if (anchor is ShapeIntersectionAnchor or DynamicAnchor or LinkAnchor) { if (Math.Abs(route[0].X - route[1].X) >= Math.Abs(route[0].Y - route[1].Y)) { diff --git a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj index 85db1b8cb..0e1f0d2ff 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj +++ b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj @@ -10,7 +10,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj index bed2d3311..dead3f03e 100644 --- a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj +++ b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj @@ -12,7 +12,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all From 6efe9b4da05bfe2896a02174b0a774c18ef73c8c Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 11 Aug 2023 11:47:35 +0100 Subject: [PATCH 185/193] Add links Overview and Anchors documentation --- site/Site/Models/Documentation/Menu.cs | 2 +- .../Documentation/Diagram/Overview.razor | 2 +- .../Pages/Documentation/Groups/Overview.razor | 2 +- .../Site/Pages/Documentation/Groups/SVG.razor | 2 +- site/Site/Pages/Documentation/Index.razor | 11 + .../Pages/Documentation/Links/Anchors.razor | 251 ++++++++++++++++++ .../Documentation/Links/Anchors.razor.css | 4 + .../Pages/Documentation/Links/Overview.razor | 43 +++ .../Pages/Documentation/Nodes/Overview.razor | 2 +- site/Site/Pages/Documentation/Nodes/SVG.razor | 2 +- .../Documentation/Ports/Customization.razor | 2 +- .../Pages/Documentation/Ports/Overview.razor | 2 +- site/Site/Shared/DocumentationLayout.razor | 12 +- site/Site/Static/Documentation.cs | 15 +- site/Site/Static/Icons.cs | 15 +- site/Site/wwwroot/css/app.css | 4 + 16 files changed, 355 insertions(+), 16 deletions(-) create mode 100644 site/Site/Pages/Documentation/Links/Anchors.razor create mode 100644 site/Site/Pages/Documentation/Links/Anchors.razor.css create mode 100644 site/Site/Pages/Documentation/Links/Overview.razor diff --git a/site/Site/Models/Documentation/Menu.cs b/site/Site/Models/Documentation/Menu.cs index 7d4d1f779..28f11039b 100644 --- a/site/Site/Models/Documentation/Menu.cs +++ b/site/Site/Models/Documentation/Menu.cs @@ -2,6 +2,6 @@ public record Menu(IEnumerable Items, IEnumerable Groups); -public record MenuGroup(string Title, IEnumerable Children); +public record MenuGroup(string Title, IEnumerable Children, string? Icon = null); public record MenuItem(string Title, string Link, string? Icon = null); diff --git a/site/Site/Pages/Documentation/Diagram/Overview.razor b/site/Site/Pages/Documentation/Diagram/Overview.razor index 4eeb9ac7f..140833ee8 100644 --- a/site/Site/Pages/Documentation/Diagram/Overview.razor +++ b/site/Site/Pages/Documentation/Diagram/Overview.razor @@ -8,7 +8,7 @@

      Structure

      -The component DiagramCanvas generates the follolwing structure: +The component DiagramCanvas generates the following structure:
      
       <div class="diagram-canvas" tabindex="-1">
      diff --git a/site/Site/Pages/Documentation/Groups/Overview.razor b/site/Site/Pages/Documentation/Groups/Overview.razor
      index 1b712c5a1..ef089a760 100644
      --- a/site/Site/Pages/Documentation/Groups/Overview.razor
      +++ b/site/Site/Pages/Documentation/Groups/Overview.razor
      @@ -14,7 +14,7 @@
       

      Structure

      - The internal component GroupRenderer generates the follolwing structure: + The internal component GroupRenderer generates the following structure:

      
      diff --git a/site/Site/Pages/Documentation/Groups/SVG.razor b/site/Site/Pages/Documentation/Groups/SVG.razor
      index 2285e25f4..798453cbd 100644
      --- a/site/Site/Pages/Documentation/Groups/SVG.razor
      +++ b/site/Site/Pages/Documentation/Groups/SVG.razor
      @@ -14,7 +14,7 @@
       

      Structure

      - The component GroupRenderer generates the follolwing structure: + The component GroupRenderer generates the following structure:

      
      diff --git a/site/Site/Pages/Documentation/Index.razor b/site/Site/Pages/Documentation/Index.razor
      index 95f186f0e..08ac2968f 100644
      --- a/site/Site/Pages/Documentation/Index.razor
      +++ b/site/Site/Pages/Documentation/Index.razor
      @@ -8,6 +8,17 @@
           {
               
      + @if (group.Icon != null) + { + + + + } @group.Title
      diff --git a/site/Site/Pages/Documentation/Links/Anchors.razor b/site/Site/Pages/Documentation/Links/Anchors.razor new file mode 100644 index 000000000..82dc136fd --- /dev/null +++ b/site/Site/Pages/Documentation/Links/Anchors.razor @@ -0,0 +1,251 @@ +@page "/documentation/links-anchors" +@using Blazor.Diagrams.Core.Anchors; +@using Blazor.Diagrams.Core.Controls.Default; +@using Blazor.Diagrams.Core.PathGenerators; +@using Blazor.Diagrams.Core.Positions; +@layout DocumentationLayout +@inherits DocumentationPage + +Anchors - Documentation - Blazor Diagrams + +

      Anchors

      + +

      + An anchor represents the exact position where on an ILinkable (node, group, port, ...) a link should connect.
      + Links have two anchors, Source and Target. +

      + +

      Position Anchor

      + +

      + PositionAnchor is the most basic anchor. It wraps a Point position and is meant to be used for static and/or known positions (without calculations). + This anchor was created for internal use, so that links couldn't have null as a target, but you can use it if you see fit. +

      + +

      Usage

      + +
      
      +var paSourceAnchor = new PositionAnchor(new Point(50, 40));
      +var paTargetAnchor = new PositionAnchor(new Point(300, 70));
      +Diagram.Links.Add(new LinkModel(paSourceAnchor, paTargetAnchor));
      +
      + +
      + + + +
      + +

      Single Port Anchor

      + +

      + SinglePortAnchor calculates a position on the specified port based on the chosen options. +

      + +

      Options

      + + + + + + + + + + + + + + + + + + + + + +
      NameDefaultDescription
      MiddleIfNoMarkerfalseIf the corresponding side doesn't have a Marker, the center of the port will be used as the position
      UseShapeAndAlignmenttrueUsing the port's shape, the point at an angle (depending on the alignment) will be used as the position
      + +

      + If both options are false, the point at the boundary (depending on the alignment) will be used as the position. +

      + +

      Usage

      + +

      + For the purpose of this example, the SVG layer will be rendered on top of the HTML one. +

      + +
      
      +var spaSourceAnchor = new SinglePortAnchor(spaPort)
      +{
      +    MiddleIfNoMarker = false,
      +    UseShapeAndAlignment = true
      +};
      +Diagram.Links.Add(new LinkModel(spaSourceAnchor, someTargetAnchor));
      +// OR
      +Diagram.Links.Add(new LinkModel(sourcePort, targetPort));
      +
      + +
      + + + +
      + +

      Shape Intersection Anchor

      + +

      + ShapeIntersectionAnchor calculates the position as the intersection a line going from the other end to the center of the specified node. + This anchor is used to create port-less links that take into account the node's shape. +

      + +

      Usage

      + +
      
      +var sourceAnchor = new ShapeIntersectionAnchor(firstNode);
      +var targetAnchor = new ShapeIntersectionAnchor(secondNode);
      +Diagram.Links.Add(new LinkModel(sourceAnchor, targetAnchor));
      +// OR
      +Diagram.Links.Add(new LinkModel(firstNode, secondNode));
      +
      + +
      + + + +
      + +

      Link Anchor

      + +

      + LinkAnchor calculates the position along the given link based on the chosen options. +

      + +

      Usage

      + +
      
      +var sourceAnchor = new LinkAnchor(otherLink, distance: 0.5, offsetX: 0, offsetY: 0);
      +Diagram.Links.Add(new LinkModel(sourceAnchor, someTargetAnchor));
      +
      + +
      + + + +
      + +

      Dynamic Anchor

      + +

      + DynamicAnchor chooses the closest position from the given calculated positions.
      + You can check out the list of position providers here. +

      + +

      Usage

      + +
      
      +var sourceAnchor = new DynamicAnchor(someNode, new[]
      +{
      +    new BoundsBasedPositionProvider(0.5, 0), // Center top
      +    new BoundsBasedPositionProvider(1, 0.5), // Center right
      +    new BoundsBasedPositionProvider(0.5, 1), // Center bottom
      +    new BoundsBasedPositionProvider(0, 0.5), // Center left
      +});
      +Diagram.Links.Add(new LinkModel(daSourceAnchor, someTargetAnchor));
      +
      + +
      + + + +
      + + + +@code { + private BlazorDiagram _paDiagram = new(); + private BlazorDiagram _spaDiagram = new(); + private BlazorDiagram _siaDiagram = new(); + private BlazorDiagram _laDiagram = new(); + private BlazorDiagram _daDiagram = new(); + + protected override void OnInitialized() + { + _paDiagram.Options.Zoom.Enabled = false; + _spaDiagram.Options.Zoom.Enabled = false; + _siaDiagram.Options.Zoom.Enabled = false; + _laDiagram.Options.Zoom.Enabled = false; + _daDiagram.Options.Zoom.Enabled = false; + + // Position Anchor + var paSourceAnchor = new PositionAnchor(new Point(50, 40)); + var paTargetAnchor = new PositionAnchor(new Point(300, 70)); + _paDiagram.Links.Add(new LinkModel(paSourceAnchor, paTargetAnchor)); + + // Single Port Anchor + _spaDiagram.Options.LinksLayerOrder = 5; + SetupSpaSample(60, true, false, "MiddleIfNoMarker = true"); + SetupSpaSample(160, false, true, "UseShapeAndAlignment = true"); + SetupSpaSample(260, false, false, "Fallback / Default"); + + // Shape Intersection Anchor + _siaDiagram.RegisterComponent(); + var siaNode1 = _siaDiagram.Nodes.Add(new ColoredNodeModel("Rectangle", false, "color1", new Point(100, 100))); + var siaNode2 = _siaDiagram.Nodes.Add(new ColoredNodeModel("Circle", true, "color2", new Point(350, 150))); + _siaDiagram.Links.Add(new LinkModel(siaNode1, siaNode2)); + + // Link Anchor + _laDiagram.Options.Links.RequireTarget = false; + var laNode1 = _laDiagram.Nodes.Add(new NodeModel(new Point(50, 100))); + var laNode2 = _laDiagram.Nodes.Add(new NodeModel(new Point(350, 250))); + var laPort1 = laNode1.AddPort(PortAlignment.Right); + var laPort2 = laNode2.AddPort(PortAlignment.Top); + var laBaseLink = _laDiagram.Links.Add(new LinkModel(laPort1, laPort2)); + + var laChildLink1 = _laDiagram.Links.Add(new LinkModel(new LinkAnchor(laBaseLink, 0.5), new PositionAnchor(new Point(600, 50)))); + laChildLink1.PathGenerator = new StraightPathGenerator(); + laChildLink1.AddLabel("Distance = 0.5"); + laChildLink1.TargetMarker = LinkMarker.Arrow; + + var laChildLink2 = _laDiagram.Links.Add(new LinkModel(new LinkAnchor(laBaseLink, 0.1, 0, 10), new PositionAnchor(new Point(100, 300)))); + laChildLink2.PathGenerator = new StraightPathGenerator(); + laChildLink2.AddLabel("Distance = 0.1, OffsetY = 10"); + laChildLink2.TargetMarker = LinkMarker.Arrow; + + var laChildLink3 = _laDiagram.Links.Add(new LinkModel(new LinkAnchor(laBaseLink, 0.9, 10), new PositionAnchor(new Point(650, 250)))); + laChildLink3.PathGenerator = new StraightPathGenerator(); + laChildLink3.AddLabel("Distance = 0.9, OffsetX = 10"); + laChildLink3.TargetMarker = LinkMarker.Arrow; + + // Dynamic Anchor + var daNode = _daDiagram.Nodes.Add(new NodeModel(new Point(50, 160))); + var daSourceAnchor = new DynamicAnchor(daNode, new[] + { + new BoundsBasedPositionProvider(0.5, 0), // Center top + new BoundsBasedPositionProvider(1, 0.5), // Center right + new BoundsBasedPositionProvider(0.5, 1), // Center bottom + new BoundsBasedPositionProvider(0, 0.5), // Center left + }); + var daLink = _daDiagram.Links.Add(new LinkModel(daSourceAnchor, new PositionAnchor(new Point(350, 200)))); + daLink.PathGenerator = new StraightPathGenerator(); + daLink.SourceMarker = LinkMarker.Arrow; + daLink.TargetMarker = LinkMarker.Arrow; + } + + private void SetupSpaSample(double y, bool middleIfNoMarker, bool useShapeAndAlignment, string title) + { + var spaNode = _spaDiagram.Nodes.Add(new NodeModel(new Point(50, y))); + var spaPort = spaNode.AddPort(PortAlignment.TopRight); + var spaSourceAnchor = new SinglePortAnchor(spaPort) + { + MiddleIfNoMarker = middleIfNoMarker, + UseShapeAndAlignment = useShapeAndAlignment + }; + var spaLink = _spaDiagram.Links.Add(new LinkModel(spaSourceAnchor, new PositionAnchor(new Point(600, y - 10)))); + spaLink.PathGenerator = new StraightPathGenerator(); + spaLink.Color = "red"; + spaLink.Labels.Add(new LinkLabelModel(spaLink, title)); + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Links/Anchors.razor.css b/site/Site/Pages/Documentation/Links/Anchors.razor.css new file mode 100644 index 000000000..4a1f68263 --- /dev/null +++ b/site/Site/Pages/Documentation/Links/Anchors.razor.css @@ -0,0 +1,4 @@ +.diagram-container { + border: 1px solid black; + caret-color: transparent; +} diff --git a/site/Site/Pages/Documentation/Links/Overview.razor b/site/Site/Pages/Documentation/Links/Overview.razor new file mode 100644 index 000000000..b0e9bca44 --- /dev/null +++ b/site/Site/Pages/Documentation/Links/Overview.razor @@ -0,0 +1,43 @@ +@page "/documentation/links" +@layout DocumentationLayout +@inherits DocumentationPage + +Links - Documentation - Blazor Diagrams + +

      Links

      + +

      + Links (also called Edges) are relationships between nodes, groups, ports and even other links!
      + They are always rendered in the SVG layer. +

      + +

      Structure

      + +

      + The internal component LinkRenderer generates the following structure: +

      + +
      
      +<g class="diagram-link"
      +   data-link-id="9566f945-cc88-46bc-8d50-1ec15502f6fb">
      +    <!-- YOUR CONTENT WILL BE HERE -->
      +</g>
      +
      + +

      + The classes that the g can have (beside diagram-link) are: attached. +

      + +

      Creating a link

      + +
      
      +var link = Diagram.Links.Add(new LinkModel(node1, node2));
      +// OR
      +var link = Diagram.Links.Add(new LinkModel(port1, port2));
      +// OR
      +var link = Diagram.Links.Add(new LinkModel(anchor1, anchor2));
      +
      + + + \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Nodes/Overview.razor b/site/Site/Pages/Documentation/Nodes/Overview.razor index a0b8a6ba9..cf7bef3e6 100644 --- a/site/Site/Pages/Documentation/Nodes/Overview.razor +++ b/site/Site/Pages/Documentation/Nodes/Overview.razor @@ -13,7 +13,7 @@

      Structure

      - The internal component NodeRenderer generates the follolwing structure: + The internal component NodeRenderer generates the following structure:

      
      diff --git a/site/Site/Pages/Documentation/Nodes/SVG.razor b/site/Site/Pages/Documentation/Nodes/SVG.razor
      index 0fa507a30..9a852427c 100644
      --- a/site/Site/Pages/Documentation/Nodes/SVG.razor
      +++ b/site/Site/Pages/Documentation/Nodes/SVG.razor
      @@ -14,7 +14,7 @@
       

      Structure

      - The component NodeRenderer generates the follolwing structure: + The component NodeRenderer generates the following structure:

      
      diff --git a/site/Site/Pages/Documentation/Ports/Customization.razor b/site/Site/Pages/Documentation/Ports/Customization.razor
      index e4acec747..bbfb782db 100644
      --- a/site/Site/Pages/Documentation/Ports/Customization.razor
      +++ b/site/Site/Pages/Documentation/Ports/Customization.razor
      @@ -7,7 +7,7 @@
       

      Ports Customization

      - Unulike for other models, customization in this page refers to the model itself, not the UI.
      + Unlike for other models, customization in this page refers to the model itself, not the UI.
      How the ports are represented in the UI is managed by the parent (node or group), no registration is needed.

      diff --git a/site/Site/Pages/Documentation/Ports/Overview.razor b/site/Site/Pages/Documentation/Ports/Overview.razor index 03c9fe999..b104209fc 100644 --- a/site/Site/Pages/Documentation/Ports/Overview.razor +++ b/site/Site/Pages/Documentation/Ports/Overview.razor @@ -13,7 +13,7 @@

      Structure

      - The internal component PortRenderer generates the follolwing structures:
      + The internal component PortRenderer generates the following structures:
      The classes that the element can have (beside diagram-port) are:

      • The alignment (e.g. top, bottom)
      • diff --git a/site/Site/Shared/DocumentationLayout.razor b/site/Site/Shared/DocumentationLayout.razor index 106e8b8dc..8f0e5a781 100644 --- a/site/Site/Shared/DocumentationLayout.razor +++ b/site/Site/Shared/DocumentationLayout.razor @@ -83,7 +83,17 @@ @foreach (var group in Documentation.Menu.Groups) {
      • -
        @group.Title
        +
        + + + + @group.Title +
          @foreach (var item in group.Children) { diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs index 07180d500..0f117872a 100644 --- a/site/Site/Static/Documentation.cs +++ b/site/Site/Static/Documentation.cs @@ -15,7 +15,7 @@ public static class Documentation new MenuItem("Installation", "/documentation/installation"), new MenuItem("Diagram Creation", "/documentation/diagram-creation"), new MenuItem("Display", "/documentation/display"), - }), + }, Icon: Icons.Awards), new MenuGroup("Diagram", new List { new MenuItem("Overview", "/documentation/diagram"), @@ -24,25 +24,30 @@ public static class Documentation new MenuItem("Options", "/documentation/diagram-options"), new MenuItem("Keyboard Shortcuts", "/documentation/keyboard-shortcuts"), new MenuItem("API", "/documentation/diagram-api"), - }), + }, Icon: Icons.ListTree), new MenuGroup("Nodes", new List { new MenuItem("Overview", "/documentation/nodes"), new MenuItem("SVG", "/documentation/nodes-svg"), new MenuItem("Customization", "/documentation/nodes-customization"), new MenuItem("Customization (SVG)", "/documentation/nodes-customization-svg") - }), + }, Icon: Icons.Square), new MenuGroup("Ports", new List { new MenuItem("Overview", "/documentation/ports"), new MenuItem("Customization", "/documentation/ports-customization") - }), + }, Icon: Icons.Circle), + new MenuGroup("Links", new List + { + new MenuItem("Overview", "/documentation/links"), + new MenuItem("Anchors", "/documentation/links-anchors"), + }, Icon: Icons.Link), new MenuGroup("Groups", new List { new MenuItem("Overview", "/documentation/groups"), new MenuItem("SVG", "/documentation/groups-svg"), new MenuItem("Customization", "/documentation/groups-customization"), new MenuItem("Customization (SVG)", "/documentation/groups-customization-svg") - }) + }, Icon: Icons.Ratio) }); } diff --git a/site/Site/Static/Icons.cs b/site/Site/Static/Icons.cs index 2e0df5f81..87689327f 100644 --- a/site/Site/Static/Icons.cs +++ b/site/Site/Static/Icons.cs @@ -1,8 +1,19 @@ -namespace Site.Static; +using System.IO; + +namespace Site.Static; public static class Icons { public static readonly string Github = "M8 0C3.58 0 0 3.58 0 8C0 11.54 2.29 14.53 5.47 15.59C5.87 15.66 6.02 15.42 6.02 15.21C6.02 15.02 6.01 14.39 6.01 13.72C4 14.09 3.48 13.23 3.32 12.78C3.23 12.55 2.84 11.84 2.5 11.65C2.22 11.5 1.82 11.13 2.49 11.12C3.12 11.11 3.57 11.7 3.72 11.94C4.44 13.15 5.59 12.81 6.05 12.6C6.12 12.08 6.33 11.73 6.56 11.53C4.78 11.33 2.92 10.64 2.92 7.58C2.92 6.71 3.23 5.99 3.74 5.43C3.66 5.23 3.38 4.41 3.82 3.31C3.82 3.31 4.49 3.1 6.02 4.13C6.66 3.95 7.34 3.86 8.02 3.86C8.7 3.86 9.38 3.95 10.02 4.13C11.55 3.09 12.22 3.31 12.22 3.31C12.66 4.41 12.38 5.23 12.3 5.43C12.81 5.99 13.12 6.7 13.12 7.58C13.12 10.65 11.25 11.33 9.47 11.53C9.76 11.78 10.01 12.26 10.01 13.01C10.01 14.08 10 14.94 10 15.21C10 15.42 10.15 15.67 10.55 15.59C13.71 14.53 16 11.53 16 8C16 3.58 12.42 0 8 0Z"; public static readonly string BookOpen = "M12 6.042A8.967 8.967 0 006 3.75c-1.052 0-2.062.18-3 .512v14.25A8.987 8.987 0 016 18c2.305 0 4.408.867 6 2.292m0-14.25a8.966 8.966 0 016-2.292c1.052 0 2.062.18 3 .512v14.25A8.987 8.987 0 0018 18a8.967 8.967 0 00-6 2.292m0-14.25v14.25"; public static readonly string FolderOpen = "M3.75 9.776c.112-.017.227-.026.344-.026h15.812c.117 0 .232.009.344.026m-16.5 0a2.25 2.25 0 00-1.883 2.542l.857 6a2.25 2.25 0 002.227 1.932H19.05a2.25 2.25 0 002.227-1.932l.857-6a2.25 2.25 0 00-1.883-2.542m-16.5 0V6A2.25 2.25 0 016 3.75h3.879a1.5 1.5 0 011.06.44l2.122 2.12a1.5 1.5 0 001.06.44H18A2.25 2.25 0 0120.25 9v.776"; -} + + // From CSS.gg + public static readonly string Link = "M6 8C6.74028 8 7.38663 7.5978 7.73244 7H14C15.1046 7 16 7.89543 16 9C16 10.1046 15.1046 11 14 11H10C7.79086 11 6 12.7909 6 15C6 17.2091 7.79086 19 10 19H16.2676C16.6134 19.5978 17.2597 20 18 20C19.1046 20 20 19.1046 20 18C20 16.8954 19.1046 16 18 16C17.2597 16 16.6134 16.4022 16.2676 17H10C8.89543 17 8 16.1046 8 15C8 13.8954 8.89543 13 10 13H14C16.2091 13 18 11.2091 18 9C18 6.79086 16.2091 5 14 5H7.73244C7.38663 4.4022 6.74028 4 6 4C4.89543 4 4 4.89543 4 6C4 7.10457 4.89543 8 6 8Z"; + public static readonly string Awards = "M19 9C19 11.3787 17.8135 13.4804 16 14.7453V22H13.4142L12 20.5858L10.5858 22H8V14.7453C6.18652 13.4804 5 11.3787 5 9C5 5.13401 8.13401 2 12 2C15.866 2 19 5.13401 19 9ZM17 9C17 11.7614 14.7614 14 12 14C9.23858 14 7 11.7614 7 9C7 6.23858 9.23858 4 12 4C14.7614 4 17 6.23858 17 9ZM10 19.7573L12 17.7573L14 19.7574V15.7101C13.3663 15.8987 12.695 16 12 16C11.305 16 10.6337 15.8987 10 15.7101V19.7573Z"; + public static readonly string ListTree = "M9 1H1V9H9V6H11V20H15V23H23V15H15V18H13V6H15V9H23V1H15V4H9V1ZM21 3H17V7H21V3ZM17 17H21V21H17V17Z"; + public static readonly string Square = "M17 7H7V17H17V7ZM4 4V20H20V4H4Z"; + public static readonly string Circle = "M12 17C14.7614 17 17 14.7614 17 12C17 9.23858 14.7614 7 12 7C9.23858 7 7 9.23858 7 12C7 14.7614 9.23858 17 12 17ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20Z"; + public static readonly string Ratio = "M4 2C1.79086 2 0 3.79086 0 6V18C0 20.2091 1.79086 22 4 22H20C22.2091 22 24 20.2091 24 18V6C24 3.79086 22.2091 2 20 2H4ZM20 4H4C2.89543 4 2 4.89543 2 6V18C2 19.1046 2.89543 20 4 20H20C21.1046 20 22 19.1046 22 18V6C22 4.89543 21.1046 4 20 4Z"; + +} \ No newline at end of file diff --git a/site/Site/wwwroot/css/app.css b/site/Site/wwwroot/css/app.css index da403a538..7aa4bdac6 100644 --- a/site/Site/wwwroot/css/app.css +++ b/site/Site/wwwroot/css/app.css @@ -758,6 +758,10 @@ td, th { margin-left: auto; } +.mr-1 { + margin-right: 0.25rem; +} + .mr-2 { margin-right: 0.5rem; } From 3b25b328183a240bb1b9eb0ea336ab37012e9c7f Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 13 Aug 2023 18:48:21 +0100 Subject: [PATCH 186/193] Add AddVertex method to links --- src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index 066e9ca6f..acba5848e 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -62,6 +62,13 @@ public LinkLabelModel AddLabel(string content, double? distance = null, Point? o return label; } + public LinkVertexModel AddVertex(Point? position = null) + { + var vertex = new LinkVertexModel(this, position); + Vertices.Add(vertex); + return vertex; + } + public void SetSource(Anchor anchor) { ArgumentNullException.ThrowIfNull(anchor, nameof(anchor)); From 4849b4d2ea490c3e0db9f50872673963ab4f3842 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 13 Aug 2023 18:56:49 +0100 Subject: [PATCH 187/193] Document link routers and path generators --- .../Pages/Documentation/Links/Anchors.razor | 4 +- .../Documentation/Links/PathGenerators.razor | 89 +++++++++++++++ .../Links/PathGenerators.razor.css | 4 + .../Pages/Documentation/Links/Routers.razor | 106 ++++++++++++++++++ .../Documentation/Links/Routers.razor.css | 4 + site/Site/Static/Documentation.cs | 2 + 6 files changed, 208 insertions(+), 1 deletion(-) create mode 100644 site/Site/Pages/Documentation/Links/PathGenerators.razor create mode 100644 site/Site/Pages/Documentation/Links/PathGenerators.razor.css create mode 100644 site/Site/Pages/Documentation/Links/Routers.razor create mode 100644 site/Site/Pages/Documentation/Links/Routers.razor.css diff --git a/site/Site/Pages/Documentation/Links/Anchors.razor b/site/Site/Pages/Documentation/Links/Anchors.razor index 82dc136fd..fcd3e0ad0 100644 --- a/site/Site/Pages/Documentation/Links/Anchors.razor +++ b/site/Site/Pages/Documentation/Links/Anchors.razor @@ -162,7 +162,9 @@ Diagram.Links.Add(new LinkModel(daSourceAnchor, someTargetAnchor));
      + PreviousLink="/documentation/links" + NextTitle="Routers" + NextLink="/documentation/links-routers" /> @code { private BlazorDiagram _paDiagram = new(); diff --git a/site/Site/Pages/Documentation/Links/PathGenerators.razor b/site/Site/Pages/Documentation/Links/PathGenerators.razor new file mode 100644 index 000000000..90eb1097a --- /dev/null +++ b/site/Site/Pages/Documentation/Links/PathGenerators.razor @@ -0,0 +1,89 @@ +@page "/documentation/links-path-generators" +@using Blazor.Diagrams.Core.PathGenerators; +@using Blazor.Diagrams.Core.Routers; +@layout DocumentationLayout +@inherits DocumentationPage + +Path Generators - Documentation - Blazor Diagrams + +

      Path Generators

      + +

      + A PathGenerator is responsible of creating the SVG path for the given route. +

      + +

      Smooth Path Generator

      + +

      + The SmoothPathGenerator generates a Cubic Bezier Curve.
      + It is the default path generator. +

      + +

      Usage

      + +
      
      +var link = Diagram.Links.Add(new LinkModel(sourceAnchor, targetAnchor));
      +link.PathGenerator = new SmoothPathGenerator();
      +
      + +
      + + + +
      + +

      Straight Path Generator

      + +

      + The StraightPathGenerator generates straight lines. +

      + +

      Usage

      + +
      
      +var link = Diagram.Links.Add(new LinkModel(sourceAnchor, targetAnchor));
      +link.PathGenerator = new StraightPathGenerator();
      +
      + +
      + + + +
      + + + +@code { + private BlazorDiagram _smpgDiagram = new(); + private BlazorDiagram _stpgDiagram = new(); + + protected override void OnInitialized() + { + _smpgDiagram.Options.Zoom.Enabled = false; + _stpgDiagram.Options.Zoom.Enabled = false; + + // Smooth Path Generator + var smpgNode1 = _smpgDiagram.Nodes.Add(new NodeModel(new Point(150, 50))); + var smpgNode2 = _smpgDiagram.Nodes.Add(new NodeModel(new Point(450, 110))); + var smpgBottomPort1 = smpgNode1.AddPort(PortAlignment.Bottom); + var smpgRightPort1 = smpgNode1.AddPort(PortAlignment.Right); + var smpgBottomPort2 = smpgNode2.AddPort(PortAlignment.Bottom); + var smpgLeftPort2 = smpgNode2.AddPort(PortAlignment.Left); + _smpgDiagram.Links.Add(new LinkModel(smpgBottomPort1, smpgBottomPort2)); + _smpgDiagram.Links.Add(new LinkModel(smpgRightPort1, smpgLeftPort2)); + + // Straight Path Generator + var stpgNode1 = _stpgDiagram.Nodes.Add(new NodeModel(new Point(150, 50))); + var stpgNode2 = _stpgDiagram.Nodes.Add(new NodeModel(new Point(450, 110))); + var stpgBottomPort1 = stpgNode1.AddPort(PortAlignment.BottomRight); + var stpgRightPort1 = stpgNode1.AddPort(PortAlignment.Right); + var stpgBottomPort2 = stpgNode2.AddPort(PortAlignment.BottomLeft); + var stpgLeftPort2 = stpgNode2.AddPort(PortAlignment.Left); + _stpgDiagram.Links.Add(new LinkModel(stpgBottomPort1, stpgBottomPort2)).PathGenerator = new StraightPathGenerator(); + _stpgDiagram.Links.Add(new LinkModel(stpgRightPort1, stpgLeftPort2)).PathGenerator = new StraightPathGenerator(); + + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Links/PathGenerators.razor.css b/site/Site/Pages/Documentation/Links/PathGenerators.razor.css new file mode 100644 index 000000000..4a1f68263 --- /dev/null +++ b/site/Site/Pages/Documentation/Links/PathGenerators.razor.css @@ -0,0 +1,4 @@ +.diagram-container { + border: 1px solid black; + caret-color: transparent; +} diff --git a/site/Site/Pages/Documentation/Links/Routers.razor b/site/Site/Pages/Documentation/Links/Routers.razor new file mode 100644 index 000000000..177e94d8d --- /dev/null +++ b/site/Site/Pages/Documentation/Links/Routers.razor @@ -0,0 +1,106 @@ +@page "/documentation/links-routers" +@using Blazor.Diagrams.Core.PathGenerators; +@using Blazor.Diagrams.Core.Routers; +@layout DocumentationLayout +@inherits DocumentationPage + +Routers - Documentation - Blazor Diagrams + +

      Routers

      + +

      + A Router is responsible of creating the route that the link will go through.
      + It takes as input the list of vertices (if any) and outputs a list of new computed points. +

      + +

      Normal Router

      + +

      + The NormalRouter is the default router.
      + It simply returns the list of user-created vertices as is to ensure the link goes through them. +

      + +

      Usage

      + +
      
      +var link = Diagram.Links.Add(new LinkModel(sourceAnchor, targetAnchor));
      +link.Router = new NormalRouter();
      +
      + +
      + + + +
      + +

      Orthogonal Router

      + +

      + The OrthogonalRouter is a router that ensures an orthogonal path.
      + It will add as many points as necessary to create that route using a simplified A* algorithm.
      +
      + Limitations: +

        +
      • Only supports links between 2 ports
      • +
      • Ignores user-defined Vertices
      • +
      +

      + +

      Usage

      + +
      
      +var link = Diagram.Links.Add(new LinkModel(sourcePort, targetPort));
      +link.Router = new OrthogonalRouter();
      +
      + +
      + + + +
      + + + +@code { + private BlazorDiagram _nrDiagram = new(); + private BlazorDiagram _orDiagram = new(); + + protected override void OnInitialized() + { + _nrDiagram.Options.Zoom.Enabled = false; + _orDiagram.Options.Zoom.Enabled = false; + + // Normal Router + var node1 = _nrDiagram.Nodes.Add(new NodeModel(new Point(150, 50))); + var node2 = _nrDiagram.Nodes.Add(new NodeModel(new Point(450, 150))); + _nrDiagram.Links.Add(new LinkModel(node1, node2) + { + PathGenerator = new StraightPathGenerator() + }); + _nrDiagram.Links.Add(new LinkModel(node1, node2) + { + PathGenerator = new StraightPathGenerator() + }).AddVertex(new Point(200, 190)); + + // Orthogonal Router + var orNode1 = _orDiagram.Nodes.Add(new NodeModel(new Point(100, 110))); + var orNode2 = _orDiagram.Nodes.Add(new NodeModel(new Point(500, 110))); + var orTopPort1 = orNode1.AddPort(PortAlignment.Top); + var orRightPort1 = orNode1.AddPort(PortAlignment.Right); + var orTopPort2 = orNode2.AddPort(PortAlignment.Top); + var orRightPort2 = orNode2.AddPort(PortAlignment.Right); + _orDiagram.Links.Add(new LinkModel(orTopPort1, orTopPort2) + { + PathGenerator = new StraightPathGenerator(), + Router = new OrthogonalRouter() + }); + _orDiagram.Links.Add(new LinkModel(orRightPort1, orRightPort2) + { + PathGenerator = new StraightPathGenerator(), + Router = new OrthogonalRouter() + }); + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Links/Routers.razor.css b/site/Site/Pages/Documentation/Links/Routers.razor.css new file mode 100644 index 000000000..4a1f68263 --- /dev/null +++ b/site/Site/Pages/Documentation/Links/Routers.razor.css @@ -0,0 +1,4 @@ +.diagram-container { + border: 1px solid black; + caret-color: transparent; +} diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs index 0f117872a..bb4d382aa 100644 --- a/site/Site/Static/Documentation.cs +++ b/site/Site/Static/Documentation.cs @@ -41,6 +41,8 @@ public static class Documentation { new MenuItem("Overview", "/documentation/links"), new MenuItem("Anchors", "/documentation/links-anchors"), + new MenuItem("Routers", "/documentation/links-routers"), + new MenuItem("Path Generators", "/documentation/links-path-generators"), }, Icon: Icons.Link), new MenuGroup("Groups", new List { From d76fb679669e549f24b99d5fe254c21a88fd73af Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 13 Aug 2023 23:29:32 +0100 Subject: [PATCH 188/193] Document link markers and vertices --- .../Documentation/MyVertexWidget.razor | 8 ++ .../Pages/Documentation/Links/Markers.razor | 130 ++++++++++++++++++ .../Documentation/Links/Markers.razor.css | 4 + .../Documentation/Links/PathGenerators.razor | 2 +- .../Pages/Documentation/Links/Vertices.razor | 122 ++++++++++++++++ .../Documentation/Links/Vertices.razor.css | 4 + site/Site/Static/Documentation.cs | 2 + .../Site/wwwroot/img/ExampleSvgPathEditor.png | Bin 0 -> 32810 bytes 8 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 site/Site/Components/Documentation/MyVertexWidget.razor create mode 100644 site/Site/Pages/Documentation/Links/Markers.razor create mode 100644 site/Site/Pages/Documentation/Links/Markers.razor.css create mode 100644 site/Site/Pages/Documentation/Links/Vertices.razor create mode 100644 site/Site/Pages/Documentation/Links/Vertices.razor.css create mode 100644 site/Site/wwwroot/img/ExampleSvgPathEditor.png diff --git a/site/Site/Components/Documentation/MyVertexWidget.razor b/site/Site/Components/Documentation/MyVertexWidget.razor new file mode 100644 index 000000000..2cbc118a4 --- /dev/null +++ b/site/Site/Components/Documentation/MyVertexWidget.razor @@ -0,0 +1,8 @@ + + + +@code { + [Parameter] public LinkVertexModel Vertex { get; set; } = null!; + [Parameter] public string Color { get; set; } = null!; +} diff --git a/site/Site/Pages/Documentation/Links/Markers.razor b/site/Site/Pages/Documentation/Links/Markers.razor new file mode 100644 index 000000000..5fca1483b --- /dev/null +++ b/site/Site/Pages/Documentation/Links/Markers.razor @@ -0,0 +1,130 @@ +@page "/documentation/links-markers" +@using Blazor.Diagrams.Core.PathGenerators; +@using Blazor.Diagrams.Core.Routers; +@layout DocumentationLayout +@inherits DocumentationPage + +Link Markers - Documentation - Blazor Diagrams + +

      Link Markers

      + +

      + A LinkMarker is a SVG path that can be drawn at the beginning and/or at the end of a link.
      + They are automatically rotated to fit the direction. +

      + +

      Usage

      + +

      + You can simply use the provided markers: +

      + +
      
      +yourLink.SourceMarker = LinkMarker.Arrow;
      +// OR
      +yourLink.TargetMarker = LinkMarker.Circle;
      +// OR
      +yourLink.TargetMarker = LinkMarker.Square;
      +
      + +
      + + + +
      + +

      Customization

      + +

      + You can either customize the predefined shapes: +

      + +
      
      +yourLink.SourceMarker = LinkMarker.NewCircle(10);
      +yourLink.SourceMarker = LinkMarker.NewSquare(20);
      +yourLink.TargetMarker = LinkMarker.NewArrow(20, 30);
      +yourLink.TargetMarker = LinkMarker.NewRectangle(20, 30);
      +
      + +
      + + + +
      + +

      + Or provide your own SVG paths:
      + Requirements: +

        +
      • Know the width of your path
      • +
      • Your path should not go to the negative side
      • +
      +

      + +
      
      +yourLink.SourceMarker = new LinkMarker("M 0 -8 L 3 -8 3 8 0 8 z M 4 -8 7 -8 7 8 4 8 z M 8 -8 16 0 8 8 z", 16);
      +yourLink.TargetMarker = new LinkMarker("M 0 -8 L 8 -8 4 0 8 8 0 8 4 0 z", 8);
      +
      + +
      + + + +
      + +

      + You can use the SvgPathEditor app to easily create these. +

      + + + + + +@code { + private BlazorDiagram _sampleDiagram = new(); + private BlazorDiagram _cpDiagram = new(); + private BlazorDiagram _cDiagram = new(); + + protected override void OnInitialized() + { + _sampleDiagram.Options.Zoom.Enabled = false; + _cpDiagram.Options.Zoom.Enabled = false; + _cDiagram.Options.Zoom.Enabled = false; + + // Sample + var node1 = _sampleDiagram.Nodes.Add(new NodeModel(new Point(150, 50))); + var node2 = _sampleDiagram.Nodes.Add(new NodeModel(new Point(450, 150))); + var link = _sampleDiagram.Links.Add(new LinkModel(node1, node2) + { + PathGenerator = new StraightPathGenerator() + }); + + link.SourceMarker = LinkMarker.Arrow; + link.TargetMarker = LinkMarker.Square; + + // Customize Predefined + var cpNode1 = _cpDiagram.Nodes.Add(new NodeModel(new Point(150, 50))); + var cpNode2 = _cpDiagram.Nodes.Add(new NodeModel(new Point(450, 150))); + var cpLink = _cpDiagram.Links.Add(new LinkModel(cpNode1, cpNode2) + { + PathGenerator = new StraightPathGenerator() + }); + + cpLink.SourceMarker = LinkMarker.NewCircle(10); + cpLink.TargetMarker = LinkMarker.NewRectangle(20, 30); + + // Customize + var cNode1 = _cDiagram.Nodes.Add(new NodeModel(new Point(150, 110))); + var cNode2 = _cDiagram.Nodes.Add(new NodeModel(new Point(450, 110))); + var cLink = _cDiagram.Links.Add(new LinkModel(cNode1, cNode2) + { + PathGenerator = new StraightPathGenerator() + }); + + cLink.SourceMarker = new LinkMarker("M 0 -8 L 3 -8 3 8 0 8 z M 4 -8 7 -8 7 8 4 8 z M 8 -8 16 0 8 8 z", 16); + cLink.TargetMarker = new LinkMarker("M 0 -8 L 8 -8 4 0 8 8 0 8 4 0 z", 8); + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Links/Markers.razor.css b/site/Site/Pages/Documentation/Links/Markers.razor.css new file mode 100644 index 000000000..4a1f68263 --- /dev/null +++ b/site/Site/Pages/Documentation/Links/Markers.razor.css @@ -0,0 +1,4 @@ +.diagram-container { + border: 1px solid black; + caret-color: transparent; +} diff --git a/site/Site/Pages/Documentation/Links/PathGenerators.razor b/site/Site/Pages/Documentation/Links/PathGenerators.razor index 90eb1097a..4b78263ce 100644 --- a/site/Site/Pages/Documentation/Links/PathGenerators.razor +++ b/site/Site/Pages/Documentation/Links/PathGenerators.razor @@ -9,7 +9,7 @@

      Path Generators

      - A PathGenerator is responsible of creating the SVG path for the given route. + A PathGenerator is responsible of creating the SVG path for a given route.

      Smooth Path Generator

      diff --git a/site/Site/Pages/Documentation/Links/Vertices.razor b/site/Site/Pages/Documentation/Links/Vertices.razor new file mode 100644 index 000000000..3147a3fbf --- /dev/null +++ b/site/Site/Pages/Documentation/Links/Vertices.razor @@ -0,0 +1,122 @@ +@page "/documentation/links-vertices" +@using Blazor.Diagrams.Core.PathGenerators; +@using Blazor.Diagrams.Core.Routers; +@layout DocumentationLayout +@inherits DocumentationPage + +Link Vertices - Documentation - Blazor Diagrams + +

      Link Vertices

      + +

      + A LinkVertexModel are user-defined points for the link to pass through. +

      + +

      Usage

      + +

      Programmatically

      + +
      
      +yourLink.Vertices.Add(new LinkVertexModel(new Point(50, 200)));
      +// OR
      +yourLink.AddVertex(new Point(50, 200));
      +
      + +
      + + + +
      + +

      Interactively

      + +
      
      +yourLink.Segmentable = true;
      +// Clicking a link will now create vertices
      +// Double clicking a vertex deletes it
      +
      + +
      + + + +
      + +

      Customization

      + +

      + If you need any additional data, feel free to create a separate model (class) and inherit LinkVertexModel.
      + In all cases, you will need to register a custom Blazor component to be used. +

      + +
      
      +Diagram.RegisterComponent<LinkVertexModel, MyVertexWidget>();
      +
      + + +
      MyVertexWidget.razor
      +
      
      +<circle cx="@@Vertex.Position.X" cy="@@Vertex.Position.Y" r="5" fill="red"></circle>
      +<circle cx="@@Vertex.Position.X" cy="@@Vertex.Position.Y" r="10"
      +        fill="none" stroke="red" stroke-width="4"></circle>
      +
      +@@code {
      +    [Parameter] public LinkVertexModel Vertex { get; set; } = null!;
      +    [Parameter] public string Color { get; set; } = null!;
      +}
      +
      + +
      + + + +
      + + + +@code { + private BlazorDiagram _sampleDiagram = new(); + private BlazorDiagram _interactiveDiagram = new(); + private BlazorDiagram _cDiagram = new(); + + protected override void OnInitialized() + { + _sampleDiagram.Options.Zoom.Enabled = false; + _interactiveDiagram.Options.Zoom.Enabled = false; + _cDiagram.Options.Zoom.Enabled = false; + + // Sample + var node1 = _sampleDiagram.Nodes.Add(new NodeModel(new Point(150, 50))); + var node2 = _sampleDiagram.Nodes.Add(new NodeModel(new Point(450, 150))); + var link1 = _sampleDiagram.Links.Add(new LinkModel(node1, node2) + { + PathGenerator = new StraightPathGenerator() + }); + link1.AddVertex(new Point(50, 250)); + var link2 = _sampleDiagram.Links.Add(new LinkModel(node1, node2)); + link2.AddVertex(new Point(400, 70)); + link2.AddVertex(new Point(600, 40)); + + // Interactive + var inode1 = _interactiveDiagram.Nodes.Add(new NodeModel(new Point(150, 50))); + var inode2 = _interactiveDiagram.Nodes.Add(new NodeModel(new Point(450, 150))); + var ilink1 = _interactiveDiagram.Links.Add(new LinkModel(inode1, inode2) + { + PathGenerator = new StraightPathGenerator() + }).Segmentable = true; + + // Sample + _cDiagram.RegisterComponent(); + var cnode1 = _cDiagram.Nodes.Add(new NodeModel(new Point(150, 50))); + var cnode2 = _cDiagram.Nodes.Add(new NodeModel(new Point(450, 150))); + var clink1 = _cDiagram.Links.Add(new LinkModel(cnode1, cnode2) + { + PathGenerator = new StraightPathGenerator() + }); + clink1.AddVertex(new Point(50, 250)); + var clink2 = _cDiagram.Links.Add(new LinkModel(cnode1, cnode2)); + clink2.AddVertex(new Point(400, 70)); + clink2.AddVertex(new Point(600, 40)); + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Links/Vertices.razor.css b/site/Site/Pages/Documentation/Links/Vertices.razor.css new file mode 100644 index 000000000..4a1f68263 --- /dev/null +++ b/site/Site/Pages/Documentation/Links/Vertices.razor.css @@ -0,0 +1,4 @@ +.diagram-container { + border: 1px solid black; + caret-color: transparent; +} diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs index bb4d382aa..23cc1087f 100644 --- a/site/Site/Static/Documentation.cs +++ b/site/Site/Static/Documentation.cs @@ -43,6 +43,8 @@ public static class Documentation new MenuItem("Anchors", "/documentation/links-anchors"), new MenuItem("Routers", "/documentation/links-routers"), new MenuItem("Path Generators", "/documentation/links-path-generators"), + new MenuItem("Markers", "/documentation/links-markers"), + new MenuItem("Vertices", "/documentation/links-vertices"), }, Icon: Icons.Link), new MenuGroup("Groups", new List { diff --git a/site/Site/wwwroot/img/ExampleSvgPathEditor.png b/site/Site/wwwroot/img/ExampleSvgPathEditor.png new file mode 100644 index 0000000000000000000000000000000000000000..ad325b404c8387197a70a27db3b16f685ed68d78 GIT binary patch literal 32810 zcmeFac~p~U*FI|Bwp#1JtG+ENQx%n35sC;DnQFC8sI=CK2q9GzRE8iSgegfY4z(iG z0g(B%R;_}GfDBHrr6_fVdhdtu_vw+F{IFA|%&tTJyvM-&wSUSK5`FXfwL4=2#hQc* z2kwC^B&|%?&r3aK8!xeX~qbW(1-vuP6l&nWN-r@WtFi}tfs|Naa;^|ePQX#j; zb+Z?1PwKaS`3!r8M(zP z(UHEZf&=z$Mdl(=6@Bm0*}pd7ut&?$4S<&DX{7goBL z;Y25-7zYuy=c0V<5f+uwZeG$p-=P4D%87k)N-Wy$v?1ApeV{5>wJV5FGkCy0)H!@1 zp3FS*{h2v^ts-7GE&a!!yL)DZM&kEJVyC)hWh>BUhST`_F4=B2CeUK6#1W-(>c4-5;iPTRC&eRI-QNXWNsbhZ0{E1v|;}~GHv_*sK)mK z&dl-KmF{VmeJ7?gbu9Sq$(}hcy=44#hx`1PWY-9aihTZ-0r>QRcA{~(vV4^<+#*^Ul zzmL~1*L=?Z?E09?dt0}!5ARydBPTj-=>Il|NQ4bkJTI7Wy?(4Is<`@aazlJDp}=8r z_oD903q>O3EwbyT8xH%9484$v)?2dg+_}iOTOrCay)+;qgmbI;KlfVpeU&Zl<55$x z@_Ft9LtfpYHC6KXWLNf?8gj`R_P3VoFe@G7VP#5!T3A}R(7Iy}d!DTI%$7!bTiXjk z>$#639^FE2;$c5(|I5veZN{}2!he(BiNSx`^#?L01hT`{k6c4{!D+_z6d$O@BU=V>c*@G)*`zGPOv0KxPJK-6RBw&9FoHu@~E^b|IJRbJ>i1(RNq< z=tWU|9-9FNxOrF5-NIJf+5G$cxN8fo;Y=5!7&)T3XCPqoEn<8Nr&v}^+f^68dGkit z?s58!9x$@ke)%(&_m;2VC{+J>j2Sb0?wq#zM0f9onGyB9!80X$XGXNbclaWhDOsLW zWu27mSmBC%Sup9R|9$-b$@R$$vgCAb+WCjgyl>!BYiTK+hefvf=k`e4^xK~93*nJf zh=G9Vwm)HK5P^T(%?$^Co7TJ*ew8`8-F@g!fFkA<=Nk{t%v>+O1DV_qPvhp?pmEu? zwYi%sAqU77UpPfwXkBa)f+p9GEhLfsy-KjCz+-buVb7ReazMbt`h!j#>_UjQts(?s z?81)kGnaqG^<1z2wMbYQ#t!2hE~HdbH$_6kPdRDK3VHLE_qE_cod&0Oez$J_H8QO= znOaz6)ji5Idss*jU1VZW)S~nZe&H^H4?1XN$iDOpy!%bjqm*mHRvbUTw-t9KrP>y@ z=jY5u*$s!qB3D7GUx`KapMTP;Ma{o_cV-R}wKYKwopj72WaPQekf5Y6k&;6W0@3!F0GYzMEg}Yxv2o{v+d_cBnY%fNXI67srZgGbJg8 z-T%tt;Y1{vIUr}?e;@yUWPMWNB11O*QQUj{_;G;E@0IVZI|1KMG;T+|4fYh1PoM6) zDHN`y0Z ziaNIDETi<+mjVZeY*A&LJUt}Ht^TXldM_=bo7{@ysMUh+Q*{n(r(gov3>#>L1t5b8 z!I4k9ll?rksn>88_&~3lgq4s~N;}%Ux2%z!o7-xL<`cli80pIjGA_IjI{go)=l|*S z|Nq8GD|Prn4)UzCM}b6Le)FW6fL&XkfJIx2%n*>_|5@BIbgzL?x+sk@dMqfYfgkPQUq z&h#L%=+L#5=EtcKquNhPJlSVFD5kGe{k{G9hw0jtDDD z7mggOX8sY?v2X8Q1oz0yTq2-ebw3+d{{73_eOaW)BBGBRsmAr(S~P9wDxB&CamNPEg zf3^p-D~25kYBdC&g3ux^=W*2!Nn<|Qxo-G$-a|$8_nj4--Dr78S2dPLhO?1%)4$wV8ple=s*+R|xC(QyO;S;vVI2X}?o0mC zknU9peP+BziAp9qVi%7)l!Cv&>t@(aRdj~r_oxRb+JUA$&MiEhhUb#%F|u1(g9;#N zhFeG)iVk<+&^Gx!dt#CDlGWHHS%T`~yNHIW}aa8%lG^;cA6Cb$&}W_r+Bi)%6^f7@#3QE?TXPdEDR47z*kfTkf;y-A)ya`iB)87m{{#C$AviPvb7 zFIg>Ghndb8aENvh$Um*i$Z%OUji(*K=*nDGgV-jaaMOr{AZnUByLxGFfm9llp2~K} z_ym%qEr7{xr{Dw>!+)1K6X)?5*tCRn?g9t9?PG9R{XmdLf7zv46=Wht1N|TDHUD#VLK{5qY46?bASx=$j)n1P zm#KoRo50M3vL%9(q|uYiBD-R{cE2u8)U1SIj``-4HVR22TH$?>tCiX7qVCH-b;AeJ zZyhn{#oVtSI2AM&)l7)ukVj%o-W1nnpxw@?nhcW>7vwcpCa42GlJBVF} zX288pt{7cTdjRuCV?`Q15sv6DlJBO1WQL*eDqczvf zMFYP~vplFpPVS1(&eb{X` zzAvlV6t+;jbZNV3l`%$Aivo{Dolr1{gN!V+`W`wgFMizlGb!T%>5B-JiS-|0O_7(52 zK~GJMv#&M7H!-MUX(wMeL=!ks)-p~R39JR2d-L}i=t#$@iZ7?SWc0RPSy~{qPIV2T zsWuonTOhmMco?@tShh}Llj;^ysj5CPL4-|&Eh7;&U826O8KB7?VXwOVvQyKY^k#sY zcw~8vI7LQVoa&VyxXQ)FSF38DsIXqE!>Au9EbtsZE_;2)UY%AWo^iM|dHMwJa2kp^ zeNHs+6lCwnjUCn@m2zcId(>MNe?d~h{G@ErLe5!Sqf?KlF+ z->SHC=guPLM5cW`ACFDN@6WyA*kdHcLn2Lsju|QN`JB4>9CNrze9cHHB|LEGH(&QE z+jrf13S6is>S}_l@~3xww6l;q;%`;N33EdtO^gqF?e#eKds7Pg`mvR>3_w8=XTgb! zx_;ir^GarZzCGht^GFe!0^h?fn*TkCOPFQm3_O+rTKX=_xOZ=;!-+H$-dBGHLgV}W z5D7u#*t)BoeX9CY)BYb{WfRouJMY!z&aWA`Q82ZWM%Y*q-dz@z{0PDXcDUU)12hGQ ze*XkuTb%gVwK*iMvPH$yX=SuutEuxtQ6($qaUhy9SC%`cy2~04VvihE!Ev;c7Nmx; zR#2*oQM>^n!ZHB#oR39#=N$E43{@7;U~|9MGZ5~WI4x9G&El-DEK8uKORgfH4!?}h zBHMfdnedyNCp;Fpxu#9anuP*JImsMCAR|5LPXNr!%wb#$BGBv!WItp&?2AL0G94CU z5`jR6a1IYkV*oS_B8MiIk~?|3rs4k;&PWkW-Ml)bDCZQaii{ywMda|qyJNx&;_ZGt zZBM(c?49Pdn``PIs0?=X3v}9mgp)X-o0V0#7q=WklK=g_NxGt)Jq?0+%oZs<~5AkpwZkBU@vnrTi#b4oiH#$}zr_cQNi31T7`HJ>oA?{7dc}5$m zH%r(!9&uhP=opJDtlM%+dMNSj1x<|ajU>$Lx4Zj8{E|w%gwv@kGBYsq$+fS%XS{8a z+uR<|!o<aB-s=#<<_Wv4uxZl z(2Q`loh#-bWr881WvBI~tjFh3HV+!3^J|OY;$3S&z1^SgA908k{r2^839YqoeMsP^ z`IgBm5it+s4uokxzm`9xu?bkcUG8R;Kw<#R4ou;jG`n>@nvL)_X890G0H;2A@{G?u zwY5nuN3@{Vc^LV|wPoQ|F~6_prdY7y(PR`m18u`q3qldBp2qs{$m8RtEh%bQNF>6n$l_v7#(Z_};zE2$8}FJs;F_%cehfTH1$z3n2trKE1jv8)P@1 z{jH0AkrzeI@O}?Q06XktSSnCDg`qJ6a2pjz`W~dj?s46)zjzOwn1gGnYFp`F@6U#J zZ|gx*!b%huH-i#!`n%y_n(Rr*nu_x9?hYgdCI#So{JSD4^}in5>FG!Xf#OC1IU?gn z57WEHO#>_b!|_Q|e7j_%eh)j0iIh7*ck?_FvNl=~qHj~bswGHk6c9gr^GXJ7^hiz> zDM%YAQaA~!^BAk)OWg8b5ZRW6gTgrwBSW~WI4*>6 zD3;;ZUr`=AS0orE0jNM-7uM!7y`#1%%y^^qwW2Ts=D z&NEL12g@9OYRu!Xx09+|CX!_4 z=X)~itPu<4Krm1Qk0(+WtnE>^sH-ZZo|3Zl`UTLouV3fK$c@^?2U??rK`l3iZ@lY5 zNp8zRD%m&veaYRm#>%`E4&&ta-=RNA3=4d8yjUM?kr>w9D={8#c#8t+N0Nq2iBzYR zV$jbEa5=^T^o@soEV&r)FxP7cbu;AcHzW_3ir?BAZ4-3^=VT~i-?+wX$;F&!4Ze^t3g!D@ zF4V8iA^C*uO80*Q^_1;Ge=e+?uN&6I?qL_9bv{;9TuM)6-mQq9$`fmz{dqp)(^cTV zh-13|xJ{YkH&pFk@G@wU(R3PcW(N6QM#dn_SWBilrv~0)waQc3|3gy|4N!o$>xTZ^ z#MWaw$92JF0uU?@$aY(Bl*cJK3oQ1jM#L>0H~j&(u*oQKrUDixXL?H~bAb9;pt4Dx zt)S~Pe8QYl)d4z$xem|lg~OhWpoo^K-WOr0lGY?#L745E@b%1 z+a6p_PMjcnB5G8Bsn;w}We%M$ z>TvSse^p7AFbBm4V(5aZgUTOYUUA4KUR1?x@U5g{jy+rm`mH;d` zJLDx^s*)BIzNdYAdVx-JcwDGw_($rVo1jah!veYk;`nJTKy34{tSpG~|E9h1#CBAtP0VRFZzP zyRMjC6C9g&7{ZgvL&;^@)73xoEzb0|I^A-u;&LSwmx)`C)Gyd3_a(I_ibK(%=$R&g z=}3}>3WEdJIxDJ^Uo^r0>+vQ-TtsBr%kcV+jkk+533Rrtg#+$BIbG^E##LvlyBlRG z8zOZb7)^&P`)4_eV#PdcAOC&Ahzg0=i$Z#l+5sUOQ-r-K>V3fw5;Jby5DFt-y*L&X zEgz=1c+a4$D?2AeRq}b;(*bEpJ4>$Bq+ao5jydd{+v6>g0l75x^8v&agt&t&q_q}q zF@pzFsZ6`7!o-v>Ca=n;9H5SN$=fN!(nCN6-j8kZ;|EwqZ?9upex3L{q)p%v3nw7dM?p@8n1Xg5(|Ngir^b#iKJ!lJg}+2erJu=<~J;?9O) zrcE2xDRiam^)~h0pRo>DxF%GOCCu&jE>}N3VT)<*-ELc6ex<>!*R!uAKb)q8_e#~XW&6OEwF448me$T>C-c0x5 ze}3W3W4S+6OMpNuVIs;qQph@1m|O_0l&i_OtKA0Zh_J$IxJOl~+NY`MUNUL#Pnsbs z3{93sS5>= zW%EW@IyI~O2%$mYF?9F6jH+>pVFBl4Q>J7vU%B)8fa?rHu1;v`l{}4|Az6P&{`B(6 zsiF|CZgs0v-CDv^&7eP@F+BW_vfR6T>&~F5_}1+im=E&eZ+H}FP`qK3SV8E0-)c9F z!|#>)$N$n2E^9kJeq^WOS3)^4k5*SBFs2;+Dg7ZbAKU60e!;FL%6wrVoK<uZ4Xy=$a)%&wB%2E6y&h04yq57pR(iWR_Pr9x5*%NNtLqB}w!7Livl;=r6)06&NZ% zoiE+?6Gcl)>!l5M_;*WRe!%P)s6NVe<-c~5+n1D=*L|R%kDBV9bV^YxD&XL{SeEo^ zub{`SgwXBR_HbuP-bX$#Aau-UAz%aQ91m5s5&RpMk)%_QNM=`pavpo8*Vc^9EU!fw zT^-a#wHDpw7)&_;qF0}j4_P7nLK%B6C8ho8Le2_yWImo7cX=vadutn|*RvV)MPQc@ zf+-|Yao(|qde@G!7g-wYRK-8jmA4WH{ye`uLLd}ES>X1V!wCQgUDyBhB=62E`EhQe zSo~$J=De5kJd23Ysy5XUMmj^vF}ABR$uyH2nic0xDleS-K+&&?s1lbMKI!+p%FQAw zE@^1ykaU*mBK|TD%XSW*;AyKr`VlP+B@!S1+)Eo9DCqvq3=%2dXE&r@R8>qV>A%;3 z7}%SB@kZW^CutOSdYn$hLvu~f9yAX!Tc3Wu7Yubs4xJ^b{@s`LEgE>Q9Y5hFB~k1y z#8P2Frj51mcUvi6oKy38=0WY)E&Bs~ju|~VKW&Ad*5{SGlK{lCZ&icAlE6Rn_2S#0 z-gS9ND(zC7Gx}!*POumF4g6#b80j`FId~bYJ&9osB2gAXSf@4qS2J3pcF0Uh8qN&K zLaR#A2_p%>sgjD$^sJ*{_}B`^XJ@En^ynSa0oG&fC?ngR@XVV<i~WLdouEvuc@$Z|`;lK)&DFDW$YtXiQyw6G|!VsVRD>Z0u?Bb?prtVcw}RQKtw2NQIaYzL2~H* zm}ffk`GrcIcWt(}Th#Q8X7p(!R+G(zK@PP9#TEJPi@km;!`>@%(|!9MyNceU>n!UY zSC6$*B6KYeR`7pOWKKc|8wAkuGm_ z%dixc$y_By%2ZFmnz>cX`#^kiZ2j*aMDKHL%h7qps;^(`@|l-u{-1IlQ6(#$Jx`$I zNRzDKwIQtpdM2Py3_|?^CR*3$MJ7GQvhqf>fx|a^J*wPazJEUp&RMVI^JvR!Ax1Jo zufM@tcQYL&y|`?J@=VHgteeIVJ+}2o;p8RC8bdP5NjmLF=*bxp8hakMJj&18>`_4m zehKfzHa#X%gG3Z#CO$s_GN8V!MT#&@S$WM(jdXzOIoFV;^zeSAPiukZ5zV~v$h#eYa*9G^P4L{a);!y?T5xyN`~F^||#{8CF6Y(iGm z0!0~5M|%D}WmhD_J5Y8mMq`EgO@!7BqtUZDKbEh@kULuy=802Xqo(K($uHsh=7j8k zc{G`Kaz;Q@<0744)2bn`o7Lk*0lPut3E7EjY0Y)njbkoG$ zvn!JKm#Q1N`6_C@Ke@L(->x{S2LH*kjjN_h8g-<$9e)dt5FWY^egJ48Tuq5<3GHHJ zF@|sQ#%`V@1nhlz6R4wZvhRQKOD&zqjD}lqt1wC5Bw5%bB`T^iD5b3n#En*key&xy zUVne5$+mX=s=}8Uv}W{Ny7f41=U9a&wA+j)re2^EDRKSuD$cFOpbeg zCKB`L_HlU%nhRW3=+}i1G}mX+)q#?Hi9wq|=}=D}1p-N@md}#Bhg{F79jTa0#-!RF zdMN3XV>!bzp-@fpG`t)XHWGW%R8Q3=?Z1kUt8wIODjYZQPX+AW{W*vt{pzz9fV4ZY z`5$oGzJ>b}J$Vu;0#7DK+6pLlrZ&)V=J&>y4CNNct>p4Efx@G6UamMid z7j2>STn=c^mCvqbcWD%}Fcp7rHs~$E?Cfd@&3-ZLejd-h|}V_%<_J5pAxdk0Sa8c#0{FoxdM0 zOeGCdw8LA1eK1OEk9I{&TFGaV`QH$N!Ph*zK$4Tot#oQ@S!!2?;eDSn=hSKb?&}Oy z#n=D{!8ZYS-I-w@_;I?4IV?j5E;;<)eyve|v-=3*POQJ`;pHO1lMfUGMGuFK zuA$n?27-185zCMrMH9HAxVcx-&?^Xbs*qdPU=sVxR$TX}Av~Ue{<|&QKuf~$%K%sQ zVE?(CfIHuZQ}jR}*dRctm{#04XzuY{rTW|KOoSn~0ED0ddZ0T_1G+OaB+oCR#17fj z#^+?N$Klf5AX7?p1h=+bI*GAdM|42Yt$-xV2M&QB<9`~d35)^({@IlbC$wh37kQ=b zjh?czGM;(Lgp$F5EP6_k^K4m0hCVhJrJBq?S5b2kB-E2$yjkgKb@2&U^gY)BI|X0@RK+`a}SUVr)yWRjl{VJ9XNh45u8a5+KV1;n*s4_3gg3 z4_PXsgtYajn=bb$O_^~)A-bB)9{JiX{ilm!+SuicGNc&Zxf;z7SanM<7Vg%;z*eKI%sLj}7aYFI4bp&7RL=DxOYgbJ?PK z2sMrrX*6IQC>-)@&xDMeVf!sV?xd#w+!rr}BLPU(uo-+w_t=w^Bv)8EPip@e*s6Ok zt6+Dz_TDo(q+oxge1PXf*3A-UO2DZ#Yg(8u@_8F2x!{|(=eqQ-FRS`PcF7YFBF!`mKUZ8!2_(F%g^vCq4j;5kFEAmh{StTn&w6 z>}kvOzhm@n!$l$vMs5UV>Z=CPrAZqa$%SpXykgyeFTL{Iqmx|Xz%|dd?f2X1Qsy|p zEzw4RVZ*sv{s=)f!QeXL`YxVM0vnYtBO|2mZm9;(+tqIOft|24l z`{juP{pWrB;{TR|mTz*8W#b8{z&V=hFiGDkp&^k|Z-Gj~%k|c3D9sbDmVvd}S*Mr( z$C|2?=8^4>&h}fxXkGRaD!P&SVz#Ge)YU~^)4^`3_dhkQFI`iCEn5*nJ^2W+0^md) zpoMMx9Yq*!=kG>i;vbrnhS(PfkH+#>urLU`hgCSn+AYfBF9DDL6VNEbE#vyn#spr^ zJ58G#GR{z=O7g)xRuBIgR|LRTAU*jpaI#cXsIIsu`gG%h~qBe^-y8Q`{!GXh7Y}Z*2rI~eE+MB zV;`JXyN;H$t|?F}^dZ^+-P&s{s_@bX%CpweC+);rpCZNCxM~zN1s&aXZ)36q$yqJ) zeB9f%KWSgJOCMwPFYNYJg|+H26ey{JuO~lIb|-S+x;^^i8`x|plUA%ah2qI<9s`pt zi*lX!@{NMX)*8)==?gH_8DOjV#-x?FR^1QTaGRC!A^B6RRpP%a^t^PejYOq!v_@j!M za}Yu^d|rA9EZ`H)Jl;q+uk6L*TUV4~D3;Rhk}(B!dMZ{Aco91igs4^3?(BB015!ea zKbLd%s2dWWZ(CW726>GHt%xIx`rlFIN)tIo`D zookwGO-StRA3?Ixp`(^UrIERFO;SAvtrO@+w?U^@-YX7xKwJ-1he*jS&$ZbgJ~)w% zb%Aj~pR5*eHm3X70~zVx^y(TRPAHqqL{&K{LHHcV5w9Mt6Z5bis&IJm1=I8ZFKx3U z>$c)YLev($AOR@7`rk>Qx4Mz0YJ2C+oxI$`vm~e5A8Q~Sk^6B3W@!&%q^0JR!lhoE zO-(p7K z>$0ChYd+EdrQO){;9B{BZMm@{xp{_bMc)ath=Z0S^-xIa50lsP#8kz4&&DOpF+N($ zPaE`5-(($FhFnIOuTwVdxKSsW(R)RsRi$I1bNsDu>aHQ2GbzZHh2w8PJp`c^7&H~uzbw^IjWn@i6>)ruNvIPd9? zDwDHcwIWTy{eNz~1AURQh3ODOfg^mXUxh#wU~7GFh*?0} zP9<}~C?YV6TrXlfNwcsUbOfoYE$stOwHU1vk$OOd^y3#Vu0;_DPw46lU2*ZB&&0s1 zv@GIfFd)~=OI)`!@do9GAUpfLXB{)(egAVMBe26=^Wq_Jk0ojjI3iV93@Z{lOBLt~ ziYdl2{^?^#I(|e+u(If0A+VEYyD{_{&E_4YCUa;f9H68bw6kZfc_4#MGo)1*bW0|< zIe;t^19Cl`ZBHCJy<&L)&r{XOaz3fzNJ@##dr)-*VJ_ZM24v%^WP=&Y+yoLxjG_l$ z&e+{i)s;H-T0;TAa=jo!k<$87+_Lm>b93U;mHBYGbK9A}l^#_ES*ad1&AnOxsBP?S9dR`u+Y<`W4AHe9(=6ZsWcN7hm33bHqMNSiUf~zOn`={`>)BJA zD+)KEA&zXXNta=OST5-M7q+kl;U5&&^aaI4Yk^w;fWmJgqsmlzux@D;?{Coq98BP! zQ0|`JFFoGOCYWInI1VcI7~pN z=PUDnj(YC&*r*jag7}IUISQa0iK4I36Lu4=cYv=Gg?M6!ih$4=v~!IoxiVpw{Pg;E zdVYh`llG183yzb9LE8aA6frJ=$msg825g8|;&AJz7z=$#gNPnT1V-R?)TzyPRF3(| z<}J(EK+8carvaTJ0?#OBi3}nUVv_(-&Mv^pIYsw}ed0G+^6Q*i^2^d)`|T zQf(F6`^(6;2XQ|OP4<{4s#ra_dY_n0md&4(td!H=a?3NuulpkaqZPV|?zA!aW-6W0g!KUgNJ)Iu*F?RU#W zu(L!oArLnL06921kTyDWHexJ6v^l6n;WwMW;bM$L^2?YTk!>CnV;*}W-=m@?G% z?K@Sl{suK&zt*5{0UaSg?Bd;_#eEtniG)@1Gs^S#Madek-M0S%VcN1(QaW9k1O-ys zpLw8Mtuv~90r~p3;@J9C-|vK1`fI5)Fr2z_#E|RPW5Fv{8d8lVD9TByau-;A9*1`S zrq(-cs^XHdv>;>jn+pn?xW7G-`4tlfCVQsIO23xpSEG6 z=Ywihk2C`DItVbj5ll8r`=I+T$mJ5186@S;s&RF|p`yw=I>aAD0IK+n+iXq!roj7~ z6I|jL2qKf8qiUq<&RQwYj_kiH^i)sIA_i$Yj= zf>5-oYm2kI+e-&l{GU~R0q&Zgd%09>h>Evv$y9y68RJJn?xx)V3F6Xe?$g!<#6LSA z&Sz32h|?xWU*b^o+K`*wh?IwDLv==Nh&RXpB24`#SblLn^fnHS>h9WT+nZ*7RilBX zn?2Z(PT>VrurEP4Tw48mU|q*5ktqI8vfCn)zLNc>HVcVVA+pi{d403 zWiwqIq0cYS>*5ra^xqr+#Vu7lOpp}Vn)p)m$XRw>wH-2Fp-#33nZAI2z_OnUf-HC5>7_yi+3e+8m5B9dn2_>`yP%yzknsP%d9uH@bXjPX3NN z@T~{(&dr(^5mC*;;W4$`e!keL+h_UX2g_LS?K%&Z z*Wt(aEQLb(tt$@tmrrXL9lC9nzmA|@F1Z`HVi^|+%4EmZ&lOknWbXt(dif|2T@{0! z5A!;SJ)%Hg$wmSKXFPD<#4P^8WHrqHnKQi$h$^TuwqLZkhO{pj<|1Sc!>j{idBMkd z)RC7cPjwvgy;?#K4&xVJ%W-J%=N1>tsyUpzDPZu?5fq&Mj+@9g z4A->$nq5yVq9lZIPs}m{1Gd{X?ze9hVRF7l1(;({Yzmqu34^eg)c?;9=)e?64rKo( z2c-EocP0c2L;ON$I$ALu%KV&8X6N-qF@ulV!r*wCTgQmIekA6OLciwYkv;dp&(P|n zJ1S_j$+wlB-c;D190az;vIF~Iwm?M9aEIQW7;e|PKmw`x8sd1+^mm0)!jq5~YeA+4 zhr+G5d<6?k@8)>J5>zL$(wBX$dnhTO}w++G4_)K z%ftk(9u%?iIdF+(%m$)gR6}EL3ZjRt9LxY7aox?`uJpaO@bm_#ek?Y!Ir`CACCd7ZzDt8Nt>>F z!ID9>kezN<1c0Q|Hp~)$EeNZ)b{ugH?Q)#+2GU-;-0?!=FRMb$0PnZcDR&Br}s=&NMN&#gLv?; z3!6vQnRrBh?WHdtU+YMk>f*Sf7BU60y+em%j6VC<+U$ct+zu(TN_<`$Ow`_Q(DQ~4 zP?EjUi!Y$gmH;?#a^Xa-tJ>cFj!OxsGbPr!oq5KlfH$u3F~l+un-p1$R3uBj*6RaD z4K_a{BzrruCVsMlHc2}#nk|7o8@nE!%whLyN*S{040Fw}&_WfY{6Xwr7M<@uNxFf3 zFkY{??m`v@$=l?LC%;9h42AZacLYsc@=`!0eaVhZ;rJzne6H8HCVdX(;@;NE5PBRq zoc$xpOW*!cU{@!(F?68)vU9))$afYQ8%JL0hXP~8ucU!`jo40eM(uM8$ZCzSRAcy0OwRh-w#JT0vE%czFiDoHK*U? z6NvGXX4155fh@9Y6dEVzJGi*8X-Z(&3_~6DCd3u0ZfIAqnb)5hFtaF#f%9%7ghL*2 z{CVDo(3^QGuP_KK0m6LJS~)PLAP6m4I4=QyaX$1&fy0vyf5qxl&#{AUMjkIg5|=R9 z%U`Zn);nhhnfuQ}kn4I&f9_TkQOW1W41|JI-5}Z)h=)YEHk*A95?1JLFdIVIK$gpc zVtTHg1&RDNb;*%LTeFd4?hAItWWgLLa6DilT)>66-=>hZ{S39bP(o#4+Yz{XzVxD@9SYh#T`=6JuOn^J=Pg{f-;a$_q(=b^K?($FuxQ6< z*oyoaPoA8%Kcw$u2MG*D6s8B+Nf8hNTybmLNFNwkT@*as*Kz2(@Zuk}5MfqeoV8PZ zk<;0r(y@4STmV8U%g5OCOGftt!zozQKODJ>jTF)(QVm=$_j#ma!RF`$I`C~i@d|=$& ze|p8QgS@!oIF+`f!iF8@OCt9mtj~lkX41KRX2x;73LAtA^)BV}@^`)-$fSRsgb$8f>)~3GOMmL?p@W=-WT7_~VQYpSLU?}Xp)jcHZ#0@i;0pavvA+t# z<%n_mF2}dsISADd+b}(qrHQR8-t}T2oF^35AoDYYs`0Lci76iwxBW#0;+YaaTR$p( zc1${J*w8)pwi^QvKp8frM;Z62`8!_|PBJYAvIH*?t2D>!TkYRy2Z!VeK89|*Lei6j zu0moeltXXc*$0Xi7v2N?Y!ezm)dVJ0J)0oJMN-go;-Nmr&iCY*xqy8`8~=bJKBL$< zg0nltI2_brq%zn|KNT(6`hyKX>xXgYr>>a0L)mZ}ar{<3eIj_@Y7{woWK`7#f3{)1 zRd+N5bp$Sxu|OYZZxIM#%m(mrx%*M7v6bHSj?incdY*?tjnu%r)Yj-w7f z`{BZ;n%xQ}MPp}VhN$7qA3(_kusFqJ*I!`;U>NCUK-#l=Ar8*IQ5`ff6xASFYh$=^ z!czMu(4*#$kRwI7;UEnFE83rhjC=90_9YDhXgbK&z(;amChL*bj4#-Vq{}lL4bZuSXjYy_3pB)F z8|22e&DZfB2fKjN8G-M?u@{Au*%)Kd>*FXB$Vc5&0ysPYc}D#7X2mXn=F2dOdeMLD z#xd4g9-n{0sw*n#9N>AC)6W;Y1q|k|Yc`Ke)LIrARG0K*Tce4Cj9Yp1~l==KR9!yt4>=joiFNFym~!cWVdm7%1pC6bBXL%~;Gr zwMP0XzTNNEe{CiL=0wK<%n7nHSdk%m&r8$^6}d_R<1Z+KR-Hc3!3v&TvCrWfMV`eg zDK#TqBj?T)35H=FMoww7-!nr1i4wp73Ao8u2KyW!G5vT5vLh}0??USb%Ys^#GG|qN zmog{R=~aed+c!5BikSMLfzNDGmy>P~nCUAow~7#pOEW~1p>yY6-_&LcIdSfttUyI2 zyHunfM)O8F-wf1(rFUx>VmoA!^%EoX%-buKI7E>+^GAl|F z9utX1b|8C~jyj@0o^QT)*DM6bX}Z|7vVc^*68x6O>_Hw=bAWk^ipZIWx%5@GC=+Of z?sDltRv7xoy-)akQoV~19tQo~?$n)mcgM$j!fbNHErE>so|tORxwUs4lnurNZWsfO z#bUkBx5EIdhWDa?B}|9oio$*hw47|&7w3P3bnoXE3_#%9zX1B{C$xOi2VrgDl1s(L zDKw(7k@Jh7vYC4crh%-?pRt(qq92hVA&dWU1mK0)=@0Nu8#qzSn=RIe}Co^GKhS@J&qA!0m^hc6|hNZO+;hIGZOL*X|g9|1W`?MJ~<)Rv?;llFintGe!0q|$RQ)lSQnBRE`q+RXoT${h44I%Y zbT7l%O5HmR36B9rqy|ZSGM8%^+pFhtu_&xMA_%JI{!H&2b(%qKkfLwjY-q zC(h@SnTA$woUxTV&h**RrACuOEX+kEKc^^d0;?sa9Cd%UIVmboaKn5r<()tgx~kq+ix9_ zNq{a6(Pq-?t%VrmN1I3VijqFszw-G5+CZi!#Ynk5)fzFzUP{rI zN3Jk>L{RJD;!^(N*q5$fJ7c&Y5&8{n{Sx%Sii(QcK`{1eVsK3g@CuXv7Xs(-u78>h z*kH7O>rST)VV$eEm4YKH^+RFOog~PR!x{?W>PgIm&qoT`*l2A!p{)DoU zF&2+trLu^gJ)Wj_VNA+DhztoLcU6pdVK~gWf_X)LDRYz`-i4l&utUVI?>tQj`d$v5 zR>g#HBN}V4PIZPXw+jj=nDq|-8v)}YwntV;gJ~1HVaR)Eu5r}WAp4d>l^vrF2oOX2 zh6VG4$7B7xLPc$JOZY)NjNxmjx>0=^5J*dQ`EBV}lO$mT73|KN2@}5v2*cH#3xj=N z-UKwQ2!t->As@j*)7ir>AmpA7TJ{hwG-;F#jMFsv2uMtFI6~Nbh7dT;KLZS^&D{iZ zfxuzrQqDvgXOGTEPpYc4-zRJ>6qSKbH;k!{1{5-v1C-Sx{fsZDyH~)BFZFtG97do% zmTYWNErOZ#$n2h6(cJkk60GY`i7V2z0VBQT)zHJ*N$#WpVsf?4Wj}(u8u~JrePtWO zHl%wN{(BP9(xn#0QOD_geSG?oQMYs{H5kEDdp55M>0^<)UrN)^MZ&{9`ZcO)4;uw^i#wh#dTY$!6_FyNr zdyxW*n5TFoJ==^$Ui$UhYmMaLc^a0T4mO})7UdB~KdT72#h3im)|BH2u_}LTqI>i} z$CV$QEkT;Zf64foSZBul&eMtz5Yu@;d@W4PVQB(NAV$hOVK!YamqtiHJUA6f7oC8Y zEZc*y2e+AH%^{u-IkA0byfMd!|8>B5Kd!xn5;zy1!|mWo#8R<1mGSO;OFdXZhM^h-FbXYNZQ` z;}M$oMe2~b6-^7bW9YIK^c|iqp-B5pmrKkv9!zJzr|}TayFU4PP?8ehzw@{>_rIO@ zp`5W<1?3OYspS9_6fjE9KwQ>VMl8Ug9R1+P%PLdK^MPvq%1}wBp0W*v?(t-t265n} zaa^B|ASS4|=d~xh9Lw$kMN{^Io@yz>4HPkotK-8~ns}*G1TdjAjHc$>Qwn>2Dc7hl z_E1P7UNVc>-VJEea#{Q|4>NIo;GJ(~XH0;{~BZd7v zy*3u(rQEco=CP{d=+T`@4a&nyP}!NwMnA>&+?DS|F*rUDtjtXqtV&C&X!~yXf11|i zMn7ouo{?t2lN>?p3Hgmyw{)5J)9F&(#eE z?zr~e0EB3h@3;sd^bTag*Tx%T@m4T2!Has%?V*2bq%i7K;?yYe_sarxV=k*Hu&tQj z^?7>Hv8{c#ua{2lMK3-vjT;png-oH%%l@Jld;Q+;!j(}gwc=?T6?8B3ZO z@GTmc6#;f8GXrkzF7EsJ`@HSLF7~^KAGts&4D-4XdaW{!wcbJOLh~?FeeZGnw$Lc# zF?C3`?m*ggG$t-gc*UYX6gCKN|EIUBs$RwpTYNuC--`=$@$&PkPepxNZ4)|Hg6+op zMBg^HJJn2}he0Em%&uIo(uhXyTg?mi3U-6yQnli^Kfk~sU2R3cR1kz$)^B^v3OwP{ z8;p<}!D!O-vbRaY{b4c_r#=`iKN;t%PuwOfuW-qY!t^qz9p`*LvI4_~K%Te*%1>MN z*cif@M-@3GbP22vOgZCwiRc?G`bjq~4!;b*HQ!mQtSNu??V&CB4*ytDPl|f=6H`dkuI4}_tWojm;9E50 z%Tc?WbP?ZG)HusVT}HSZS)7wmY)817{IG9#=6<>OXt)WmcHnF1;3|kzNMz_{DN3EM zxUZlZ^W{heMt9lqHWBc#o4xo>5kdYI=GJu;hiz(l*HQ^RM`M zHd*V9NDXb#@f|sUHKU(r00Yp6>j*i6qE;kNOoa96#0=tEwN|^buMf)WC4>nyS`KcD zKGH3f0`suLX~WjndPp@=MebzLIU0LI-nV=+SzCJ{;pQD9tA>0-qzf8Zvu7EwcOmJlO{sM25 z=~IEYS$M@t7dTaaT7KKVf4<-wU5`zPHt%}T1?7N&bFd8+-6Xb5#W9^nH1hVZG;w2`Svpp9ZpgKDZGNp`&6#e1$T z;E<6fCjmk7I#!fsb5M!e7bCE&445lS40)7O2z( z?gsrk(!c1k>i>VL98?KqzZaS(QzRA)*MUz-J|G?&&C&u#nNDKDYnu3wWGW!v!AX#lW&a9eJkWX1fYx6AJ;#gqkg&Kru?86`+++yz2U&F+M?^{)ah;K z-f258E@CbY{fE+GoC4OSx{VF#sASZ+@0JP4+G;K+10*5iJOEduS1J#~ZG zK~faPxntGL>}|pxRrrEN%`EF6b$cLV6fA1i(R@-`*I)m+?jt&AXs4I)7bMQIch^S7 z;)|Q3_oBQMloN6lc~)SOxjDYuvtd^F zgv$XIFX*YYu?blaiD|XPTc@imZ@z`%IHQ5<%`ZSL8l92SKOUr+J#oq;;VAKy^W>4~ zJb47n6KVZrhUoHx8heqJ4VE??KY1^tnfA@XghOn;!p0CyUzFoppKZ}o6vn;}?G*=ip^xQ%=LlTU(>Wo_90=c5Eu8x^~$&K|hZf7wHNr?hRTOm;s4V@pz@|RWT zUoIHNxs@>Uos!A!Qg4l+KX7~?sw!tO(Qa;N1og#)(MSwZCk-`5|J?r;SpKR`i6uw3 nP(CiY#hZIRHAOl-yVCR5#$PrkJ{Vqse|vU$>@42#`PY8~vf Date: Mon, 14 Aug 2023 12:09:06 +0100 Subject: [PATCH 189/193] Document diagram widgets --- .../Pages/Documentation/Links/Vertices.razor | 2 +- .../Pages/Documentation/Widgets/Grid.razor | 106 +++++++++++++++ .../Documentation/Widgets/Grid.razor.css | 4 + .../Documentation/Widgets/Navigator.razor | 127 ++++++++++++++++++ .../Documentation/Widgets/Navigator.razor.css | 4 + .../Documentation/Widgets/SelectionBox.razor | 73 ++++++++++ .../Widgets/SelectionBox.razor.css | 4 + site/Site/Static/Documentation.cs | 8 +- site/Site/Static/Icons.cs | 3 + .../Widgets/SelectionBoxWidget.razor.cs | 2 +- 10 files changed, 330 insertions(+), 3 deletions(-) create mode 100644 site/Site/Pages/Documentation/Widgets/Grid.razor create mode 100644 site/Site/Pages/Documentation/Widgets/Grid.razor.css create mode 100644 site/Site/Pages/Documentation/Widgets/Navigator.razor create mode 100644 site/Site/Pages/Documentation/Widgets/Navigator.razor.css create mode 100644 site/Site/Pages/Documentation/Widgets/SelectionBox.razor create mode 100644 site/Site/Pages/Documentation/Widgets/SelectionBox.razor.css diff --git a/site/Site/Pages/Documentation/Links/Vertices.razor b/site/Site/Pages/Documentation/Links/Vertices.razor index 3147a3fbf..18852cce0 100644 --- a/site/Site/Pages/Documentation/Links/Vertices.razor +++ b/site/Site/Pages/Documentation/Links/Vertices.razor @@ -9,7 +9,7 @@

      Link Vertices

      - A LinkVertexModel are user-defined points for the link to pass through. + LinkVertexModel are user-defined points for the link to pass through.

      Usage

      diff --git a/site/Site/Pages/Documentation/Widgets/Grid.razor b/site/Site/Pages/Documentation/Widgets/Grid.razor new file mode 100644 index 000000000..9e601f677 --- /dev/null +++ b/site/Site/Pages/Documentation/Widgets/Grid.razor @@ -0,0 +1,106 @@ +@page "/documentation/grid-widget" +@using Blazor.Diagrams.Core.PathGenerators; +@using Blazor.Diagrams.Core.Routers; +@layout DocumentationLayout +@inherits DocumentationPage + +Grid Widget - Documentation - Blazor Diagrams + +

      Grid Widget

      + +

      + A GridWidget is simply a customizable background with a grid pattern.
      + It adjusts itself based on the current panning / zoom. +

      + +

      Options

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameTypeDefault
      Sizedouble20
      ZoomThresholddouble0
      ModeGridMode (Line or Point)GridMode.Line
      BackgroundColorstringrgb(241 241 241)
      + +

      Usage

      + +
      
      +<CascadingValue Value="_diagram" IsFixed="true">
      +	<DiagramCanvas>
      +		<Widgets>
      +			<GridWidget Size="30" Mode="GridMode.Line" BackgroundColor="white" />
      +		</Widgets>
      +	</DiagramCanvas>
      +</CascadingValue>
      +
      + +
      + + + + + + + +
      + + +
      +
      + + + +@code { + private BlazorDiagram _diagram = new(); + private bool _gridPoints; + + public bool GridPoints + { + get => _gridPoints; + set + { + _gridPoints = value; + _diagram.Refresh(); + } + } + + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + + var node1 = _diagram.Nodes.Add(new NodeModel(new Point(100, 100))); + var node2 = _diagram.Nodes.Add(new NodeModel(new Point(500, 150))); + _diagram.Links.Add(new LinkModel(node1, node2) + { + PathGenerator = new StraightPathGenerator() + }); + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Widgets/Grid.razor.css b/site/Site/Pages/Documentation/Widgets/Grid.razor.css new file mode 100644 index 000000000..4a1f68263 --- /dev/null +++ b/site/Site/Pages/Documentation/Widgets/Grid.razor.css @@ -0,0 +1,4 @@ +.diagram-container { + border: 1px solid black; + caret-color: transparent; +} diff --git a/site/Site/Pages/Documentation/Widgets/Navigator.razor b/site/Site/Pages/Documentation/Widgets/Navigator.razor new file mode 100644 index 000000000..cb6341762 --- /dev/null +++ b/site/Site/Pages/Documentation/Widgets/Navigator.razor @@ -0,0 +1,127 @@ +@page "/documentation/navigator-widget" +@using Blazor.Diagrams.Core.PathGenerators; +@using Blazor.Diagrams.Core.Routers; +@layout DocumentationLayout +@inherits DocumentationPage + +Navigator Widget - Documentation - Blazor Diagrams + +

      Navigator Widget

      + +

      + A NavigatorWidget (also called a Minimap) is a simplified view of the whole diagram. +

      + +

      Options

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameTypeDefault
      UseNodeShapebooltrue
      Widthdouble0
      Heightdouble0
      Margindouble5
      NodeColorstring#40babd
      GroupColorstring#9fd0d1
      ViewStrokeColorstring#40babd
      ViewStrokeWidthdouble4
      Classstring
      Stylestring
      + +

      Usage

      + +
      
      +<CascadingValue Value="_diagram" IsFixed="true">
      +	<DiagramCanvas>
      +		<Widgets>
      +			<NavigatorWidget Width="200" Height="120"
      +								Class="border border-black bg-white absolute"
      +								Style="bottom: 15px; right: 15px;" />
      +		</Widgets>
      +	</DiagramCanvas>
      +</CascadingValue>
      +
      + +
      + + + + + + + +
      + + + +@code { + private BlazorDiagram _diagram = new(); + + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + + var node1 = _diagram.Nodes.Add(new NodeModel(new Point(100, 50))); + var node2 = _diagram.Nodes.Add(new NodeModel(new Point(400, 150))); + var node3 = _diagram.Nodes.Add(new NodeModel(new Point(250, -150))); + _diagram.Links.Add(new LinkModel(node1, node2) + { + PathGenerator = new StraightPathGenerator() + }); + _diagram.Links.Add(new LinkModel(node3, node2) + { + PathGenerator = new StraightPathGenerator() + }); + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Widgets/Navigator.razor.css b/site/Site/Pages/Documentation/Widgets/Navigator.razor.css new file mode 100644 index 000000000..4a1f68263 --- /dev/null +++ b/site/Site/Pages/Documentation/Widgets/Navigator.razor.css @@ -0,0 +1,4 @@ +.diagram-container { + border: 1px solid black; + caret-color: transparent; +} diff --git a/site/Site/Pages/Documentation/Widgets/SelectionBox.razor b/site/Site/Pages/Documentation/Widgets/SelectionBox.razor new file mode 100644 index 000000000..7651ee5dd --- /dev/null +++ b/site/Site/Pages/Documentation/Widgets/SelectionBox.razor @@ -0,0 +1,73 @@ +@page "/documentation/selection-box-widget" +@using Blazor.Diagrams.Core.PathGenerators; +@using Blazor.Diagrams.Core.Routers; +@layout DocumentationLayout +@inherits DocumentationPage + +Selection Box Widget - Documentation - Blazor Diagrams + +

      Selection Box Widget

      + +

      + A SelectionBoxWidget lets you hold SHIFT and click+drag to show a selection box that selects all models intersecting with it. +

      + +

      Options

      + + + + + + + + + + + + + + + + +
      NameTypeDefault
      Backgroundstringrgb(110 159 212 / 25%)
      + +

      Usage

      + +
      
      +<CascadingValue Value="_diagram" IsFixed="true">
      +	<DiagramCanvas>
      +		<Widgets>
      +			<SelectionBoxWidget />
      +		</Widgets>
      +	</DiagramCanvas>
      +</CascadingValue>
      +
      + +
      + + + + + + + +
      + + + +@code { + private BlazorDiagram _diagram = new(); + + protected override void OnInitialized() + { + _diagram.Options.Zoom.Enabled = false; + + var node1 = _diagram.Nodes.Add(new NodeModel(new Point(100, 100))); + var node2 = _diagram.Nodes.Add(new NodeModel(new Point(500, 150))); + _diagram.Links.Add(new LinkModel(node1, node2) + { + PathGenerator = new StraightPathGenerator() + }); + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Widgets/SelectionBox.razor.css b/site/Site/Pages/Documentation/Widgets/SelectionBox.razor.css new file mode 100644 index 000000000..4a1f68263 --- /dev/null +++ b/site/Site/Pages/Documentation/Widgets/SelectionBox.razor.css @@ -0,0 +1,4 @@ +.diagram-container { + border: 1px solid black; + caret-color: transparent; +} diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs index 23cc1087f..c34bcca8a 100644 --- a/site/Site/Static/Documentation.cs +++ b/site/Site/Static/Documentation.cs @@ -52,6 +52,12 @@ public static class Documentation new MenuItem("SVG", "/documentation/groups-svg"), new MenuItem("Customization", "/documentation/groups-customization"), new MenuItem("Customization (SVG)", "/documentation/groups-customization-svg") - }, Icon: Icons.Ratio) + }, Icon: Icons.Ratio), + new MenuGroup("Diagram Widgets", new List + { + new MenuItem("Navigator", "/documentation/navigator-widget"), + new MenuItem("Grid", "/documentation/grid-widget"), + new MenuItem("Selection Box", "/documentation/selection-box-widget"), + }, Icon: Icons.Components) }); } diff --git a/site/Site/Static/Icons.cs b/site/Site/Static/Icons.cs index 87689327f..dcae2c1f5 100644 --- a/site/Site/Static/Icons.cs +++ b/site/Site/Static/Icons.cs @@ -16,4 +16,7 @@ public static class Icons public static readonly string Circle = "M12 17C14.7614 17 17 14.7614 17 12C17 9.23858 14.7614 7 12 7C9.23858 7 7 9.23858 7 12C7 14.7614 9.23858 17 12 17ZM12 20C16.4183 20 20 16.4183 20 12C20 7.58172 16.4183 4 12 4C7.58172 4 4 7.58172 4 12C4 16.4183 7.58172 20 12 20Z"; public static readonly string Ratio = "M4 2C1.79086 2 0 3.79086 0 6V18C0 20.2091 1.79086 22 4 22H20C22.2091 22 24 20.2091 24 18V6C24 3.79086 22.2091 2 20 2H4ZM20 4H4C2.89543 4 2 4.89543 2 6V18C2 19.1046 2.89543 20 4 20H20C21.1046 20 22 19.1046 22 18V6C22 4.89543 21.1046 4 20 4Z"; + // Custom + public static readonly string Components = "M 0 0 v 8 h 8 V 0 H 0 Z M 10 0 L 18 0 V 8 H 10 Z M 0 10 H 8 V 18 H 0 Z M 10 10 H 18 V 18 H 10 Z"; + } \ No newline at end of file diff --git a/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor.cs index dbf3f24a3..738fabf1a 100644 --- a/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor.cs @@ -14,7 +14,7 @@ public partial class SelectionBoxWidget : IDisposable [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; - [Parameter] public string Background { get; set; } = "rgb(110 159 212 / 25%);"; + [Parameter] public string Background { get; set; } = "rgb(110 159 212 / 25%)"; public void Dispose() { From 8aeba9d6c106a1e97fa0568af5b6ebe55b2104f6 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 14 Aug 2023 14:14:28 +0100 Subject: [PATCH 190/193] Document controls --- .../Controls/AlertControlWidget.razor | 17 ++ .../NodeInformationControlWidget.razor | 20 ++ site/Site/Models/Controls/AlertControl.cs | 34 +++ .../Models/Controls/NodeInformationControl.cs | 19 ++ .../Controls/Customization.razor | 172 +++++++++++++++ .../Controls/Customization.razor.css | 4 + .../Documentation/Controls/Overview.razor | 202 ++++++++++++++++++ .../Documentation/Controls/Overview.razor.css | 4 + .../Pages/Documentation/Links/Anchors.razor | 2 +- site/Site/Static/Documentation.cs | 7 +- site/Site/Static/Icons.cs | 1 + 11 files changed, 480 insertions(+), 2 deletions(-) create mode 100644 site/Site/Components/Documentation/Controls/AlertControlWidget.razor create mode 100644 site/Site/Components/Documentation/Controls/NodeInformationControlWidget.razor create mode 100644 site/Site/Models/Controls/AlertControl.cs create mode 100644 site/Site/Models/Controls/NodeInformationControl.cs create mode 100644 site/Site/Pages/Documentation/Controls/Customization.razor create mode 100644 site/Site/Pages/Documentation/Controls/Customization.razor.css create mode 100644 site/Site/Pages/Documentation/Controls/Overview.razor create mode 100644 site/Site/Pages/Documentation/Controls/Overview.razor.css diff --git a/site/Site/Components/Documentation/Controls/AlertControlWidget.razor b/site/Site/Components/Documentation/Controls/AlertControlWidget.razor new file mode 100644 index 000000000..0a7698c99 --- /dev/null +++ b/site/Site/Components/Documentation/Controls/AlertControlWidget.razor @@ -0,0 +1,17 @@ +@using Blazor.Diagrams.Core.Models.Base; +@using Site.Models.Controls; + +
      + ! +
      + +@code +{ + [Parameter] + public AlertControl Control { get; set; } = null!; + + [Parameter] + public Model Model { get; set; } = null!; + + public NodeModel Node => (Model as NodeModel)!; +} \ No newline at end of file diff --git a/site/Site/Components/Documentation/Controls/NodeInformationControlWidget.razor b/site/Site/Components/Documentation/Controls/NodeInformationControlWidget.razor new file mode 100644 index 000000000..7ad4bbdc9 --- /dev/null +++ b/site/Site/Components/Documentation/Controls/NodeInformationControlWidget.razor @@ -0,0 +1,20 @@ +@using Blazor.Diagrams.Core.Models.Base; +@using Site.Models.Controls; + +
      +
      Width: @Node.Size.Width
      +
      Height: @Node.Size.Height
      +
      Ports: @Node.Ports.Count
      +
      Links: @(Node.Links.Count + Node.PortLinks.Count())
      +
      + +@code +{ + [Parameter] + public NodeInformationControl Control { get; set; } = null!; + + [Parameter] + public Model Model { get; set; } = null!; + + public NodeModel Node => (Model as NodeModel)!; +} \ No newline at end of file diff --git a/site/Site/Models/Controls/AlertControl.cs b/site/Site/Models/Controls/AlertControl.cs new file mode 100644 index 000000000..19363168e --- /dev/null +++ b/site/Site/Models/Controls/AlertControl.cs @@ -0,0 +1,34 @@ +using Blazor.Diagrams.Core; +using Blazor.Diagrams.Core.Controls; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using Microsoft.JSInterop; + +namespace Site.Models.Controls; + +public class AlertControl : ExecutableControl +{ + private readonly IJSRuntime _jSRuntime; + + public AlertControl(IJSRuntime jSRuntime) + { + _jSRuntime = jSRuntime; + } + + public override Point? GetPosition(Model model) + { + // Fixed at top-right + var node = (model as NodeModel)!; + if (node.Size == null) + return null; + + return node.Position.Add(node.Size.Width, -15); + } + + public override async ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) + { + await _jSRuntime.InvokeVoidAsync("alert", "Some Alert??"); + } +} diff --git a/site/Site/Models/Controls/NodeInformationControl.cs b/site/Site/Models/Controls/NodeInformationControl.cs new file mode 100644 index 000000000..39ed61891 --- /dev/null +++ b/site/Site/Models/Controls/NodeInformationControl.cs @@ -0,0 +1,19 @@ +using Blazor.Diagrams.Core.Controls; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Site.Models.Controls; + +public class NodeInformationControl : Control +{ + public override Point? GetPosition(Model model) + { + // We want the information to be under the node + var node = (model as NodeModel)!; + if (node.Size == null) + return null; + + return node.Position.Add(0, node.Size!.Height + 10); + } +} diff --git a/site/Site/Pages/Documentation/Controls/Customization.razor b/site/Site/Pages/Documentation/Controls/Customization.razor new file mode 100644 index 000000000..fd33dcf7a --- /dev/null +++ b/site/Site/Pages/Documentation/Controls/Customization.razor @@ -0,0 +1,172 @@ +@page "/documentation/controls-customization" +@using Blazor.Diagrams.Core.Controls; +@using Blazor.Diagrams.Core.PathGenerators; +@using Blazor.Diagrams.Core.Routers; +@using Site.Components.Documentation.Controls; +@using Site.Models.Controls; +@layout DocumentationLayout +@inherits DocumentationPage + +Controls Customization - Documentation - Blazor Diagrams + +

      Controls Customization

      + +

      + Customizing controls is done the same way as any other customization.
      +

        +
      • + For completely new controls, you create a model class and its accompanying widget. +
      • +
      • For existing controls, you simply register a new widget for them.
      • +
      + In this document, we will only talk about new controls. +

      + +

      Simple Control

      + +

      + Let's say we want to show some information about a node when we hover on it: +

      + +
      NodeInformationControl.cs
      +
      
      +public class NodeInformationControl : Control
      +{
      +    public override Point? GetPosition(Model model)
      +    {
      +        // We want the information to be under the node
      +        var node = (model as NodeModel)!;
      +        if (node.Size == null)
      +            return null;
      +
      +        return node.Position.Add(0, node.Size!.Height + 10);
      +    }
      +}
      +
      + +
      NodeInformationControlWidget.razor
      +
      
      +@@using Blazor.Diagrams.Core.Models.Base;
      +@@using YourNamespace;
      +
      +<div style="background-color: #eee; width: @@(Node.Size!.Width)px; padding: 5px;">
      +    <div>Width: @@Node.Size.Width</div>
      +    <div>Height: @@Node.Size.Height</div>
      +    <div>Ports: @@Node.Ports.Count</div>
      +    <div>Links: @@(Node.Links.Count + Node.PortLinks.Count())</div>
      +</div>
      +
      +@@code
      +{
      +    [Parameter]
      +    public NodeInformationControl Control { get; set; } = null!;
      +
      +    [Parameter]
      +    public Model Model { get; set; } = null!;
      +
      +    public NodeModel Node => (Model as NodeModel)!;
      +}
      +
      + +
      + + + +
      + +

      Executable Control

      + +

      + Let's say we want to show an alert button (I'm out of ideas): +

      + +
      AlertControl.cs
      +
      
      +public class AlertControl : ExecutableControl
      +{
      +    private readonly IJSRuntime _jSRuntime;
      +
      +    public AlertControl(IJSRuntime jSRuntime)
      +    {
      +        _jSRuntime = jSRuntime;
      +    }
      +
      +    public override Point? GetPosition(Model model)
      +    {
      +        // Fixed at top-right
      +        var node = (model as NodeModel)!;
      +        if (node.Size == null)
      +            return null;
      +
      +        return node.Position.Add(node.Size.Width + 10, -10);
      +    }
      +
      +    public override async ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e)
      +    {
      +        await _jSRuntime.InvokeVoidAsync("alert", "Some Alert??");
      +    }
      +}
      +
      + +
      AlertControl.Widget.razor
      +
      
      +@@using Blazor.Diagrams.Core.Models.Base;
      +@@using YourNamespace;
      +
      +<div class="rounded-full p-1 text-center text-white"
      +     style="background-color: red; width: 30px; height: 30px;">
      +    !
      +</div>
      +
      +@@code
      +{
      +    [Parameter]
      +    public AlertControl Control { get; set; } = null!;
      +
      +    [Parameter]
      +    public Model Model { get; set; } = null!;
      +
      +    public NodeModel Node => (Model as NodeModel)!;
      +}
      +
      + +
      + + + +
      + + + +@code { + private BlazorDiagram _cDiagram = new(); + private BlazorDiagram _ecDiagram = new(); + + protected override void OnInitialized() + { + _cDiagram.Options.Zoom.Enabled = false; + _ecDiagram.Options.Zoom.Enabled = false; + + // Simple Control + _cDiagram.RegisterComponent(); + var cNode1 = _cDiagram.Nodes.Add(new NodeModel(new Point(150, 70))); + var cNode2 = _cDiagram.Nodes.Add(new NodeModel(new Point(450, 100))); + cNode1.Title = "Select me"; + cNode2.Title = "Hover on me"; + _cDiagram.Controls.AddFor(cNode1, ControlsType.OnSelection).Add(new NodeInformationControl()); + _cDiagram.Controls.AddFor(cNode2, ControlsType.OnHover).Add(new NodeInformationControl()); + _cDiagram.SelectModel(cNode1, false); + + // Executable Control + _ecDiagram.RegisterComponent(); + var ecNode1 = _ecDiagram.Nodes.Add(new NodeModel(new Point(150, 70))); + var ecNode2 = _ecDiagram.Nodes.Add(new NodeModel(new Point(450, 100))); + ecNode1.Title = "Select me"; + ecNode2.Title = "Select me"; + _ecDiagram.Controls.AddFor(ecNode1).Add(new AlertControl(JSRuntime)); + _ecDiagram.Controls.AddFor(ecNode2).Add(new AlertControl(JSRuntime)); + _ecDiagram.SelectModel(ecNode1, false); + _ecDiagram.SelectModel(ecNode2, false); + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Controls/Customization.razor.css b/site/Site/Pages/Documentation/Controls/Customization.razor.css new file mode 100644 index 000000000..4a1f68263 --- /dev/null +++ b/site/Site/Pages/Documentation/Controls/Customization.razor.css @@ -0,0 +1,4 @@ +.diagram-container { + border: 1px solid black; + caret-color: transparent; +} diff --git a/site/Site/Pages/Documentation/Controls/Overview.razor b/site/Site/Pages/Documentation/Controls/Overview.razor new file mode 100644 index 000000000..e75ed4254 --- /dev/null +++ b/site/Site/Pages/Documentation/Controls/Overview.razor @@ -0,0 +1,202 @@ +@page "/documentation/controls" +@using Blazor.Diagrams.Core.Controls.Default; +@using Blazor.Diagrams.Core.Controls; +@using Blazor.Diagrams.Core.PathGenerators; +@using Blazor.Diagrams.Core.Positions; +@using Blazor.Diagrams.Core.Routers; +@layout DocumentationLayout +@inherits DocumentationPage + +Controls - Documentation - Blazor Diagrams + +

      Controls

      + +

      + Controls are "extra" UI element that show when models are selected or hovered on.
      + The internal ControlsLayerRenderer component is responsible of rendering them in the appropriate layer, which means + you can have HTML controls as well as SVG controls based on the parent's layer.
      + This feature is still somewhat new and might be improved based on feedback. +

      + +

      Types

      + +

      Control

      + +

      + A Control is just a UI element with no behavior (e.g. BoundaryControl). +

      + +

      Executable Control

      + +

      + A ExecutableControl is a clickable UI element with some attached behavior (e.g. RemoveControl).
      + For now, executable controls work best using ControlsType.OnSelection. +

      + +

      Demonstration

      + +
      
      +// Initialize
      +var node1Controls = Diagram.Controls.AddFor(Node1); // OnSelection default
      +var node2Controls = Diagram.Controls.AddFor(Node2, ControlsType.OnHover);
      +
      +// Add
      +node1Controls.Add(new SomeControl());
      +node2Controls.Add(new SomeControl());
      +
      +// Get the controls whenever
      +node1Controls = Diagram.Controls.GetFor(Node1);
      +node2Controls = Diagram.Controls.GetFor(Node2);
      +
      +// Manually control visibility
      +node1Controls.Show();
      +node2Controls.Hide();
      +
      +// Remove all controls for a model
      +Diagram.Controls.RemoveFor(Node1);
      +
      + +

      Out of the box controls

      + +The library comes with a couple of controls: + +

      Boundary Control

      + +

      + The BoundaryControl simply shows a border alongside the boundary of the model based on GetBounds. +

      + +
      
      +Diagram.Controls.AddFor(SomeModel).Add(new BoundaryControl());
      +
      + +
      + + + +
      + +

      Remove Control

      + +

      + The RemoveControl adds a deletion button positioned using Position Providers. +

      + +
      
      +Diagram.Controls.AddFor(SomeModel)
      +	.Add(new RemoveControl(0.5, 0)); // BoundsBasedPositionProvider (top center)
      +// OR
      +Diagram.Controls.AddFor(SomeModel)
      +	.Add(new RemoveControl(somePositionProvider));
      +
      + +
      + + + +
      + +

      Arrow Head Control

      + +

      + The ArrowHeadControl adds a draggable arrow head on links to enable changing the source and/or target interactively. +

      + +
      
      +Diagram.Controls.AddFor(SomeModel).Add(new ArrowHeadControl(source: true));
      +Diagram.Controls.AddFor(SomeModel).Add(new ArrowHeadControl(source: false)); // Target
      +Diagram.Controls.AddFor(SomeModel).Add(new ArrowHeadControl(true, customLinkMarker));
      +
      + +
      + + + +
      + +

      Drag New Link Control

      + +

      + The DragNewLinkControl adds a link creation button positioned using Position Providers.
      + This is mainly for port-less links, the source anchor will be a ShapeIntersectionAnchor by default. +

      + +
      
      +Diagram.Controls.AddFor(SomeModel).Add(new DragNewLinkControl(1, 0.5, offsetX: 20));
      +Diagram.Controls.AddFor(SomeModel).Add(new DragNewLinkControl(0, 0.5, offsetX: -20));
      +// OR
      +Diagram.Controls.AddFor(SomeModel)
      +	.Add(new DragNewLinkControl(somePositionProvider));
      +
      + +
      + + + +
      + + + +@code { + private BlazorDiagram _bDiagram = new(); + private BlazorDiagram _rDiagram = new(); + private BlazorDiagram _ahDiagram = new(); + private BlazorDiagram _dnlDiagram = new(); + + protected override void OnInitialized() + { + _bDiagram.Options.Zoom.Enabled = false; + _rDiagram.Options.Zoom.Enabled = false; + _ahDiagram.Options.Zoom.Enabled = false; + _dnlDiagram.Options.Zoom.Enabled = false; + + // Boundary Control + var bNode1 = _bDiagram.Nodes.Add(new NodeModel(new Point(100, 100))); + var bNode2 = _bDiagram.Nodes.Add(new NodeModel(new Point(500, 150))); + var bLink1 = _bDiagram.Links.Add(new LinkModel(bNode1, bNode2)); + bNode1.Title = "Select me"; + bNode2.Title = "Hover on me"; + bLink1.AddLabel("Select me", distance: 0.5); + _bDiagram.Controls.AddFor(bNode1, ControlsType.OnSelection).Add(new BoundaryControl()); + _bDiagram.Controls.AddFor(bNode2, ControlsType.OnHover).Add(new BoundaryControl()); + _bDiagram.Controls.AddFor(bLink1, ControlsType.OnSelection).Add(new BoundaryControl()); + _bDiagram.SelectModel(bNode1, false); + _bDiagram.SelectModel(bLink1, false); + + // Remove Control + var rNode1 = _rDiagram.Nodes.Add(new NodeModel(new Point(100, 100))); + var rNode2 = _rDiagram.Nodes.Add(new NodeModel(new Point(500, 150))); + var rLink1 = _rDiagram.Links.Add(new LinkModel(rNode1, rNode2)); + rNode1.Title = "Select me"; + rNode2.Title = "Select me"; + rLink1.AddLabel("Select me", distance: 0.5); + _rDiagram.Controls.AddFor(rNode1, ControlsType.OnSelection).Add(new RemoveControl(0.5, 0)); + _rDiagram.Controls.AddFor(rNode2, ControlsType.OnSelection).Add(new RemoveControl(0.5, 1)); + _rDiagram.Controls.AddFor(rLink1, ControlsType.OnSelection).Add(new RemoveControl(new LinkPathPositionProvider(0.8, 0, -10))); + _rDiagram.SelectModel(rNode1, false); + _rDiagram.SelectModel(rNode2, false); + _rDiagram.SelectModel(rLink1, false); + + // Arrow Head Control + _ahDiagram.Options.Links.RequireTarget = false; + var ahNode1 = _ahDiagram.Nodes.Add(new NodeModel(new Point(100, 100))); + var ahNode2 = _ahDiagram.Nodes.Add(new NodeModel(new Point(500, 150))); + var ahLink1 = _ahDiagram.Links.Add(new LinkModel(ahNode1, ahNode2)); + ahLink1.AddLabel("Select me", distance: 0.5); + _ahDiagram.Controls.AddFor(ahLink1).Add(new ArrowHeadControl(true)) + .Add(new ArrowHeadControl(false, LinkMarker.NewCircle(10))); + _ahDiagram.SelectModel(ahLink1, false); + + // Drag New Link Control + _dnlDiagram.Options.Links.RequireTarget = false; + var dnlNode1 = _dnlDiagram.Nodes.Add(new NodeModel(new Point(100, 100))); + var dnlNode2 = _dnlDiagram.Nodes.Add(new NodeModel(new Point(500, 150))); + dnlNode1.Title = "Select me"; + dnlNode2.Title = "Select me"; + _dnlDiagram.Controls.AddFor(dnlNode1).Add(new DragNewLinkControl(1, 0.5, offsetX: 20)); + _dnlDiagram.Controls.AddFor(dnlNode2).Add(new DragNewLinkControl(0, 0.5, offsetX: -20)); + _dnlDiagram.SelectModel(dnlNode1, false); + _dnlDiagram.SelectModel(dnlNode2, false); + } +} \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Controls/Overview.razor.css b/site/Site/Pages/Documentation/Controls/Overview.razor.css new file mode 100644 index 000000000..4a1f68263 --- /dev/null +++ b/site/Site/Pages/Documentation/Controls/Overview.razor.css @@ -0,0 +1,4 @@ +.diagram-container { + border: 1px solid black; + caret-color: transparent; +} diff --git a/site/Site/Pages/Documentation/Links/Anchors.razor b/site/Site/Pages/Documentation/Links/Anchors.razor index fcd3e0ad0..7991bc70b 100644 --- a/site/Site/Pages/Documentation/Links/Anchors.razor +++ b/site/Site/Pages/Documentation/Links/Anchors.razor @@ -139,7 +139,7 @@ Diagram.Links.Add(new LinkModel(sourceAnchor, someTargetAnchor));

      DynamicAnchor chooses the closest position from the given calculated positions.
      - You can check out the list of position providers here. + You can check out the list of position providers here.

      Usage

      diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs index c34bcca8a..081d52a30 100644 --- a/site/Site/Static/Documentation.cs +++ b/site/Site/Static/Documentation.cs @@ -58,6 +58,11 @@ public static class Documentation new MenuItem("Navigator", "/documentation/navigator-widget"), new MenuItem("Grid", "/documentation/grid-widget"), new MenuItem("Selection Box", "/documentation/selection-box-widget"), - }, Icon: Icons.Components) + }, Icon: Icons.Components), + new MenuGroup("Controls", new List + { + new MenuItem("Overview", "/documentation/controls"), + new MenuItem("Customization", "/documentation/controls-customization"), + }, Icon: Icons.Controls) }); } diff --git a/site/Site/Static/Icons.cs b/site/Site/Static/Icons.cs index dcae2c1f5..6b2544338 100644 --- a/site/Site/Static/Icons.cs +++ b/site/Site/Static/Icons.cs @@ -18,5 +18,6 @@ public static class Icons // Custom public static readonly string Components = "M 0 0 v 8 h 8 V 0 H 0 Z M 10 0 L 18 0 V 8 H 10 Z M 0 10 H 8 V 18 H 0 Z M 10 10 H 18 V 18 H 10 Z"; + public static readonly string Controls = "M1 0h2v24h-2zM7 0h2v24h-2zM13 0h2V24h-2zM0 3h4v2h-4zM6 19h4v2h-4zM12 10h4v2h-4z"; } \ No newline at end of file From d737ee7230b8fc4430dcf365f188a0fcfe7069ac Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 14 Aug 2023 14:53:46 +0100 Subject: [PATCH 191/193] Document position providers --- .../Documentation/Controls/Overview.razor | 4 +- .../Pages/Documentation/Links/Anchors.razor | 2 +- .../Misc/PositionProviders.razor | 125 ++++++++++++++++++ site/Site/Static/Documentation.cs | 6 +- .../img/BoundsBasedPositionProvider.png | Bin 0 -> 13086 bytes .../wwwroot/img/LinkPathPositionProvider.png | Bin 0 -> 10800 bytes .../img/ShapeAnglePositionProvider.png | Bin 0 -> 7525 bytes 7 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 site/Site/Pages/Documentation/Misc/PositionProviders.razor create mode 100644 site/Site/wwwroot/img/BoundsBasedPositionProvider.png create mode 100644 site/Site/wwwroot/img/LinkPathPositionProvider.png create mode 100644 site/Site/wwwroot/img/ShapeAnglePositionProvider.png diff --git a/site/Site/Pages/Documentation/Controls/Overview.razor b/site/Site/Pages/Documentation/Controls/Overview.razor index e75ed4254..bd87fa9a1 100644 --- a/site/Site/Pages/Documentation/Controls/Overview.razor +++ b/site/Site/Pages/Documentation/Controls/Overview.razor @@ -79,7 +79,7 @@ Diagram.Controls.AddFor(SomeModel).Add(new BoundaryControl());

      Remove Control

      - The RemoveControl adds a deletion button positioned using Position Providers. + The RemoveControl adds a deletion button positioned using Position Providers.

      
      @@ -117,7 +117,7 @@ Diagram.Controls.AddFor(SomeModel).Add(new ArrowHeadControl(true, customLinkMark
       

      Drag New Link Control

      - The DragNewLinkControl adds a link creation button positioned using Position Providers.
      + The DragNewLinkControl adds a link creation button positioned using Position Providers.
      This is mainly for port-less links, the source anchor will be a ShapeIntersectionAnchor by default.

      diff --git a/site/Site/Pages/Documentation/Links/Anchors.razor b/site/Site/Pages/Documentation/Links/Anchors.razor index 7991bc70b..60ab64642 100644 --- a/site/Site/Pages/Documentation/Links/Anchors.razor +++ b/site/Site/Pages/Documentation/Links/Anchors.razor @@ -139,7 +139,7 @@ Diagram.Links.Add(new LinkModel(sourceAnchor, someTargetAnchor));

      DynamicAnchor chooses the closest position from the given calculated positions.
      - You can check out the list of position providers here. + You can check out the list of position providers here.

      Usage

      diff --git a/site/Site/Pages/Documentation/Misc/PositionProviders.razor b/site/Site/Pages/Documentation/Misc/PositionProviders.razor new file mode 100644 index 000000000..c11a28832 --- /dev/null +++ b/site/Site/Pages/Documentation/Misc/PositionProviders.razor @@ -0,0 +1,125 @@ +@page "/documentation/position-providers" +@layout DocumentationLayout +@inherits DocumentationPage + +Position Providers - Documentation - Blazor Diagrams + +

      Position Providers

      + +

      + Position Providers simply take some input (e.g. a model and some arguments) and return a position (Point) when asked to (by some other features in the library). +

      + +

      Bounds Based

      + +

      Options

      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameTypeDescription
      xdoubleA number between 0 and 1 that will be multiplied by the width
      ydoubleA number between 0 and 1 that will be multiplied by the height
      offsetXdoubleAny number
      offsetYdoubleAny number
      + +

      Demonstration

      + +Bounds Based Position Provider + +

      Shape Angle

      + +

      Options

      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameTypeDescription
      angledoubleIn degrees
      offsetXdoubleAny number
      offsetYdoubleAny number
      + +

      Demonstration

      + +Bounds Based Position Provider + +

      Link Path

      + +

      Options

      + + + + + + + + + + + + + + + + + + + + + + + + + + +
      NameTypeDescription
      distancedouble + If between 0 and 1, it will be multiplied by the link's length
      + If less than 0, it will start from the end of the link
      + If greater than 1, it will start from the beginning of the link +
      offsetXdoubleAny number
      offsetYdoubleAny number
      + +

      Demonstration

      + +Bounds Based Position Provider \ No newline at end of file diff --git a/site/Site/Static/Documentation.cs b/site/Site/Static/Documentation.cs index 081d52a30..9ebe46b39 100644 --- a/site/Site/Static/Documentation.cs +++ b/site/Site/Static/Documentation.cs @@ -63,6 +63,10 @@ public static class Documentation { new MenuItem("Overview", "/documentation/controls"), new MenuItem("Customization", "/documentation/controls-customization"), - }, Icon: Icons.Controls) + }, Icon: Icons.Controls), + new MenuGroup("Misc", new List + { + new MenuItem("Position Providers", "/documentation/position-providers") + }) }); } diff --git a/site/Site/wwwroot/img/BoundsBasedPositionProvider.png b/site/Site/wwwroot/img/BoundsBasedPositionProvider.png new file mode 100644 index 0000000000000000000000000000000000000000..51f6e95a944d520d7dfd747198717624b2288f66 GIT binary patch literal 13086 zcmeHtXHZjX)NTMNf)s@yf(TZWE>&q!5fu@zK|&x9M0!;zQWBbgihu_X0YnIh6bU5s z5=tln(rZEo0cnZ!o)B^a=X~GHy)*a6cW1u2_s88cnaqB(_IlS|?OA#De)rJmE-$wz zHvjIr3|8*Nc*y80Vog$w$7pzuImyQ0|lX zMK-8d2ozn_AQhBd48(*wbq4061G{{o=+e<~;wM=3&)%*f8&R#mG)#j9qMImXQC+XV zp8y`Iq$YLHr?@f{i^qf7On4gEFSeP`dt*aSyhr>b5^{Yi^WK#k(dSb|w=&S>8a=TJ z_1b%$2>pGMF`=H=f~4-Bw>0+scyVOS0Jwhn*xt|BP5S-D05LptZjSa)6LT`Iv?Vat zX97bTFZI|=3Yo)ndX+qCqiE)i;HecCDck-AMqKu=>VD$^pI_U18z8*Km@(+(56YEc zsev77ZhDNDSNcT?-WhGH-PPq2s>&mizH!3Yk21$u94iZOyR zQ2Ke(J_ebfX5)c6m+oKVC^id$%!?*@9&Pl9VI4E6M2^jj@sywZxvpnX1CYIF8#e@8LQfTm8$e z(5bA2UoUatZHU+mO|M7FxzMux$t*(lWMNhq-t=U}a6g04nbWKd4^!BBaz9gIAuBN8 zx${%3TW##XjxXa`%V1&dpPKy*HL&8>ljXymy;vh&v23w?qm6aTDYAEkV~qEv4+xPN z1$1p;LFM*=4Kfur`v#(2qFRYZA!P-{dQ0gS*S{=gF$TLp*bRD?V3MmWFJbaG!Kjv+ zhTfKA@3Z`qMJG1lOn`z*NYtgBuXSHu7NB)TJcc-FbFe2@yL^!;KI#@*#Tvxe#m$oA zHM4Y>b|29RY+Upl;1TCzRv7IrpSTaj_@tUf*1ZJZHS3k$@+8R99nhBK0Uvb(s8OrR zQHTZr=mnI%k-HufnA`O76474HH%4Mf8O;|IxVpY%uGDqcWxp(be~ElXb2Rpa4G*bfNhAMsJQ1UarzAx|Pdf?aR zXk##dKdlww44Vx=$e zqrcV{Sh%2IR8e>E<2;B{^<(ECtcTn%zxsAe#HSQxSaoNta4dn^?4=6vc8Ox`bws5! z&JC;=TpXF*+`rB}pJt-F`6YI@@~c}dbZO2Ue6_1VcFawztl(veP8(xF0Zd!SlH2un zOx8`^3G3k15cOAecFbl}0GG?qYkh%9{(C*h>t`=GR(-w|ZKUW^`i^Eu7v0~sdS$gjqBM{Nly!o4e#d!-Tn{843>m7?z_)dKSPsdFY?1MLth@-tFod%cTj z#_*TBF8TdC^@0JkR3$H0#_^#t>I`6g8Wd83OvwbtRCRGMMotf|l)V2S4G$A2=!{*7 z&A|S~&^Nxe)kZ9wt18RjjN>&(vGSyazS>(C>Rdu4Wy&`g&^PvOkS@XA1oJ^aMHPV~ zMPgcnpTTN7$jzFwR+U($&2h+@Le!n1{@zWTlcSrH72!b`S!y;@E?>n#D5~*PX{4fO zr2OSXEd4s@way2VzWv?WQ5D{A{BnD{UH8Z2>u;3l9hGxlF5RF7bo+OWwQ@{YxfhO; zmT&g+LaesetFg~g=4igizIwkjJ?@9r%|uQQWrNTL45!8HDK&f$5IXt3#v`QpIm&9T z;Y*gQ5LCA?8;lafYP6mKaPmTiuIg|nZaeJk=|5$bEz2J|bh92(I>%3N zQco(_aDNPZt;?mUvb~cp2T3yltDEHYE&95kXV}w95SVtG&?B?7xM6R?==T?IGc@<^p2HakrnOW1X4cnw`)AoXAdiuA6-}+_pLpoouY$( zR&%M|q%D3saak5*wSO)_&8;t0=CW6?o=6Pve#6-{DNl(aJKEx-#JLmsWX|06y#SL2 z_&ypvSGJfdI{fYG{Qy1U2A>B$xX4Ln(=AicJ5By>tiG7M3GN&D=VdDZw?=J0e5+eV$++SU5>)``^rx066 zwaMDR2T?OKs=Sa%w?&GqYaCbbvjlAYqv*O}!ndW7IbSa}QVmikN?f|~;n~qzi0Yr6 z5_o(&#q=ma^|j~HYUxtwMUCUcxE_rb9=V6q=(cfZ#ei+Tm$%MnG6Nuz%TL>_vAAkn z&g!XSv~l%_s+W~ri-bL?w~gTiYztwD_YIGdhlkxn=j>hladZBTR?%c(!wy#6w7n=E zeRsNOb~*yByZx6ubChJ1@%4x93oNS~$+h1Z$QBJPd6%&)Gr`(pSKHc444O1OHdM6h zkxKP!n9(l}N;j%)9iO;DYdfL=d4on!>j4x=nCNe;*z<6nz*aZH3HRsK9>anG~FY0kKC zRtB!`Fw>57^NKxzKY_kptJLtSjI3_cq~d*?Oq1P^1x9U;wD&FtA*Thd-cl&~9rHEx zQK>I14?5Y~{pnmT_{pSEnUk0QbLRh%i(yI2abq8qRq}+ruG-<+yW>#1Y^yqn3lTzE zjQsYEo1r<-#`;svT>-2%vvxg{>a9dG1! z{)yZlce3UMIj$Ct7JJxc8wqoZ_TO$@v$CHdfYm5-3uKcp zLsrxBCzbfdHE?1$0)lS-D&cG8uz(zKw}4oRPYShJ$szT_YZ z=joTrBhrp$&1U8mZ&Yq)m{v_FDJ8GeHrMB))p#@k;+v1i4^t-3z z)A{!O2k-DFB0ql^;ey0gQ&zw6y75m>P>KFLDVh&zdrx+S?N>}1E#dLTK1F;tO~$PC zto|?|%)g`lt!J*dvj)8JM25S3Qu?~rNwTyvuM(d+wSW5o@{gq= zb9YKO+DE$bQzf)($zo;Hnw2EDr&97&x@!LZsF^{H|E=u4ZHJl?eW@UH*#@1QL+PT< zQC@u=p*zs%)xT8vJGG4S%(%InWskbrUb+qsGwnid{v9$$&e`W-CY#;&IsLJcG~h!i zJN}dj;$0ZkBfLOzS2;G<1N|w0XW&{dCu~yc*w15D4>c5BAFWm)Sqwt20{^ZTUoOf} zIG>ytM@O2AJlspBx~D%Z6`Rk{m3QtBKMI+Q9@ZOs?R8mkK0J!hUKL&Q$7|NYi*${7 zKfEWsf?BM!Mfh~cFQsV(j^Y-;Zcjj!&bCGJVh#~EpNjRsnTI=oDy7#?YiA0-^O*Qn zhN^Ni-74djs>XOE1OZRmFKqG9Vs6bpXnd8jFlVAZo?2G9#$=Z^FMMUp5NdGm+OZ5rz5sDdCirlX&;h`=35$Bb?I$Yp_BtG;R}>^oO~oD}_tM zT*W`BQ4(jz2MJ3sD8y8p%s1K@OE=OzuJVh}lIQ9fy8>qfY=-;}gmO0Ysgc51@Sv#+ zqsLA=z;^Nqai#gW3Olyd>0!2p+=#0f;5~apz&m(sc9?~m5a$nc^drz+&G>O0L*_GWC=P4X909yFJJ@c)v^Lo zUp?alybodr+?e~p2M}W60R(lv69-&o6$Sv31vLQ=kH`W58dCpX(f{|+|NT5VsD-8` zLp$?IzcgIlGaNqvQ2XPhwUZ6l83tP7F4T5pogV}p}A5LmInV!t2HSLv!Br0@U$id%c|tw16d z6L|^d>(%38%E+GlQe$-<4ioU#W&r>$g)hA_nO5U1dsm&ploZt4u6XJ7V(I-YFLBG; z<*WdJNJx{Jqhh5(lLb@yzx|jm{@>8Fg3DNJX~A>CP$`@$4s)H!>iXc60Z9Ex#hzIr z`(mSCs8XB=2{J0dOUqEjyg0spsMucV zBGEE7N)lvLt^28KcPV4xXCb40wKJB`!gd#Nwf6U2q5c)rq>Gd-L9i6`rN>}{F`*hW zL0tfsYYa_^Yn}P+6opYRX)g-KNSGDYSba}$*9BAd0wkR5H)n}-3z%Ia?eF>}@u@uM z+2m{smlobD_Ewq|qH>nZ82!j9af;yx(7HGa>^xC3j1591eICaI`D*1!O!klK_BG6@M z6_uN%BXt+jJB)D~_StQA_bhQPE@3-2)JHsL(A4mA$7T(b9bJI!sh;d*$ zppB1(V%CuR>uq&Utd~y4bLwA6YV+OFJ2PYsS>LFPyz{$kQ=eqD^?m)4WUd)oy0zf< z+hgHz8E)ku+CpW_>*}2BN*KlSQOm}P`cB7BQZ|$jl(8ZT{oM+^GqI3`d*xM$8o(Kn z`M@hg{L&Le`WSM!>`ukw+G~E57bU`R&6a;1O|2_-+9z%K-D%OX+UPTufbUKVDe(O|l~FIcHkV~piF$u*&G{>beTjb zKlUu(QEbk=sM$uus>vi^6$;j6T|*||&z;q(cKIM;=Dpmh1KH&G0kvaeBGVe>PdE{I z=$Z>{92|t7UF(r7C<$fwm1{!0B7BnzrWL!OM@sHv!>tN;$DHR(+qdbln?#nCKe2P! z7k_P4>=KCf2+PMSqC?nvwmq9H8iVmEDsUimKK0D_bV#hP_g0^(QTC#nh1~Kr1N_?B zh((>M#9J`1ec&74R$I?@%Xid1eJ#VZl2a%bx3xZi%3W5^+olm*PCw3$`8~^@_l~L9 zW#HEc56N*fU_91KyX094lY4t1ru_cuH+~F9h*LxlBy3M|wp${vE`g5K5>-!ksM9#$ z2L``gM4pMV!7plI+Z5|xr$@@n`hMj_=5w=9Hc=Ld>7>(9Hs#=HRo6R)`=HW|+Vs$n ztp#JV;hFE4io&x7c>+bKBa()UQeey}S~z+?baZ-BJ4{fJ_aoRR9m*s0+$$9kbCENp zszXj&a^c(D12C@Wo8AI$|AlMYALO?n&(COY-8w&=(&r;pYWqiff`|L2R?5D?6Mc$ysPMWr_vw)0;x>?|Sh@)XL&4p|N@lYyzAsx`B z-M|4d#|+m|MD=R@K4w@!ZLKF?sSR?jd8zGeX<{dxli`kom)E=7*OtLot^UfvaH-BZ z*I=c&&pu1u^d8G@n|1=xQ$yzX`t)AA8|aROU+hxA)9bG9`*Pd+v>EoP@sF&=RX7^r zx!Amb`|Y#)a-D7~d*?Wz>j>3;nrMxw!y_1zFs3HXxDR;jn$-JDd!ati+IzQa`RN<{ z!dgCIXf=nElM3{Wy!)Ay3)dy=EGNJGvG0l@QnK*_v7Igo@$t)YG*ow;8$y#Vm-zWS-Z&4Xcm6Z z7ewp6FyWr7JARqX^qLr`=vj*Ya}?w6@HVhw_v=%LD_8O_yO7(oMmkoj!rT7bJuCGE zp&*ku=K+F^J=+cKRb-)jM-Cf{=DJ^VklApBDs`O$bs4{k;Xjf8oYSC;P!HGXS5<8d z0@kmGA!d2Xh6l?g*V?j6l#lcY8q5xndbi{FZ^^i<1jX@w@M>s4$no;oN-0q*BP6|I zMvE`87(^FV1P+uD11u9CvFBFWr+|s6ij+CC5%^GThx*_B(dqm)^QMoUEMJ37jWW4^ z({ky1Z0FZUoJhvBCM{e;em!98tMbw>ZxoVck>s(uD{`S1-!-4xr>LthIP6uWu z%tfPWvS2U!h8^l2U->c9>)KeWodtbyqVPp+5;hFjTp8KXspq^b{(8cBLuIsqESI?dd zIPFx*=Orj?$Zj}aXZM?L0D2ehnVkD%_jj4&wP6CuUMXgARzhs9Zt-S$!lWi}UAEYB zBKuWR@Fn>|vZC@^bu&iy<;g4$zE16NCby}A(uJiRaGNKe>?N~WG`VVA8ul_XV_s5%196dyya)J;1*^%Ku^A?{Zak-YJo_5;oRMFjO znfOqcM}xn^>!-}j@#3YVBxVIf|>O9tGa*e6Yy8Uy` zpVAXUruc}z+xz;bmD+NtrvvJ`)-y+^Wn6ZGfb&H;tGDomQBtUin!9a-*;j708?KxK=Nf2s{L~;^|$y`1LJC_3+9wj#G%1mX6 z$s5;C;EZayeYn97kuPUoc$tTzekdHv=TmA8$_TZehstXn*XlF|gAwL7`pZt6x6DPF zsZ&hrbE|{mGM4H*CQ4i=_sb32iM;VA-$W*7o`r{Lc3M4Df{k4B>{=oobfrlpnAhhZRhEZo-}q|I3TY8$223ah^C`n+ zjWgs+5tC}iY{Bedv_QvS9{T_-D;9%#mdpOSx?`31(k$|R8ZMteNEq--+m?lW{4+Le zET6FNVGvm5a{|vzq-S~|+*>x*UGoK_k@eac(ejDd#cZ~MEy|vpq3Ph>5!s_sO+zxH z#h^D`ODuZEQxP3&S$TO?LefC9(Q&>`W zvXFIkIPn5}(Q$#rsZIcQs;TEqt>P7R*S|f8$t3NO!nCCK$U)=2q31oX3s*vMdee`E zehQl}2V12X`jiq{LE!D}E7aAIYFYY-i|KOS=#&UUE6Nx8F)1^cdNv4H5_C*3e`8(J zZ$i(i-f8UE@~&-#qjf+8_F-?$6Hi@miRsL8XoiPK$Ri>j`jJCP4v>8d>aPF#zDqo_K~mpiaX?2`zD|(qX%8Ace`jQ!O~Nc7m-pQ zc48N;;a3xAp$qafaqgM01lN%7ukFogTEJevCsvA@UQ&g(FrdSOAL*TbsTtdmUw%xV zJ=Yy~FD;So$lmAm4|#cXTZIg^7t$)r3j*GHe_x_WpXzuv&$&FZcz+hZkX`e1V7Ixy zFcy?>q?tJ_tk=Jmkb5$nMuMEU&G^eVI>dY^<{rOb{?ae2CtbWgF9&wG$6B;Mggy3w zJlgJ2WN`+8497m<*Vl6us=Y;I(oKu@Ivl-7r};Xi*$WOROuPIvz07Q$^O^}}xXLE5 z$i|oIIpQaOG?HhLb26F~>J5yJUFthI9>0Xgg<4(fc~ma#3>3cv*PmA!Kqgmz^4~$+ zPFDOK_`uwB@D~fE4wZ7``@qd*1SwW{eI;e{x*XmZR?g((&sj{--qhVIu)@(oVbV%Q!J)q$BLrn$M2gLdF1{MtRoT(hAa0XnZxcfV>N zVyR-dtpDpriH{1y?!XnVS%k^#b-xGTKKgL0xyf1~!Q|ReSQ!4^`Qp+D0uIY3S6P?q zV!xo~N^Lt5jKa~%Z38Ea^I}9nX5KR^++tdA$9DME-0Q*&LcEb=4nE#qc2_n$m@>4O zv7OeX-(&xv4Bc~dAPyCMGlwjS{vC9YM|OdOZ(?Th!o%&Ovd+8g4{zCIaA<$(@Ox9z zcS4QNb1N^kn_@_}w?leGzoUb5*3DY| zQUhz2+SIP7vx%!B#ywZt*lciIir+m2o*0Bu$0MqHFBki*Xqqf`2V?RGyvz|1{E$bl zXGa!rgXfH0OF~H2b|Mb_XZd>Pu?pD>o~k5KF~(|Of-E_l=>GQ1DStfxV1`sqO=G_8 z5IWk-n*RL-&1d|-w9;w6R@|**UNNjxjAy2i!#VFA zGrA~XSAt#3v<&lqg6|&!lf)5G=01z^_}OqyoD4Xn zFkpABYq501&Dm$jHeSYO*f9&VR6ZRYn3R}Up`Q_`U|F>j^EuO3WjV(LV{~d(RZJ@- zP?pB9cNS~h*JK3*T~$gdUi-n##RIGEAOf*&qWRdBHpsOpFM`?cMNTH&^cN2&&eN-% z%WBOCYx##vQ+u5)jbTdvBsAT-J%|JV;G?@&^@`5F{=Nwl|3$v<41&xU;0g$V9lomd z2O2IlzGbb)Vv+E5iQF|NJq4Rxk2>IDZ}7+SMthu{{OJqb3&$Fj^>AVWl4l1pyw>la5M_fWj@riBsnWUS$X;2b%9ajKfi6Pr(V6 z=I-8SN|{be@Lca^EozfojL1*OH z-!R`dD{tgCG^Cuzs)@`-3I1!G5$8=i;Y zM64Gd>d@tQBW?k6UuEmf&yYEDlDClMfH+HyKxPllgz9p>CTC)i_Nxt#%r`X|DWa#I z9Vo(z=CjX|u$@IaY1xa^9MwJgt$)m9VXM~{1|91Fs}2hvkk8#VK|%TbQxU1VxndUD z=}S$LhkJ^Ge&;MDcX$ynGO|dn&&$-fKpaAK^dKOjDlWl20R;_R**PS&GOVB{YZf?Vh8@H%^BoV@ zUA&(!dWZzsOI1eYK~xXrE~!G{DezTYqjsi;)pT7zOlHv4>ByTRS>(@mFnivDg9sKc z2nv)Ie`A;Ou6+l~$b%f-OauEHqP)cEM@qXYkrHlA8AhV;LsOpiuoe|y2`ahWGIpiPe%;DmiGvB+R?B<0GJ+#n7{|3ZiwV}P7R4+QhBt#!5 zpA_KhyRkUh&}_aZsf_gsh&q%r)8}c=5C;AH;0%M>9ByeUATWRCk5!qc;EBWh2no8e z4QJppl0GsY2LSwjv)21zk#?(W?d1bikTuz9zjfPCqJ0D155Tv1BWWuD9tDPeIbV}H`= z{K3P9`M-{GDGP&&A3!?<{!KvB3XOjHx@4pTw%MlZfCO3G9AZUFiJlR?9U@d zc!#6KnG2m#hk210{p(#WIO$ z97isFdxG)w@;UWv*#@+Ai&J&1j`kSbmki;Lu_Gm=sw}YM9(}GIoQv6eFYy?a% z6>JUK#3&rNt@boGq;k1N6D+_7X+z1;)DG1%WuLdi8n_;-Z@qvT@peASEC)CrdoIj- zXy(3HXui_BlJ!naD3wk=?dk0kG^Oo42isnf1wbD zX;ZGtNhMVbd;9`1-$$P3V9yoz9-Kt0aj@8pQew#-KyrOBLxg#um{5M{1d_%W6giUc zLO!YjpJ`c(Zz9&$G0rhfpSl9?Xm7vGuQ+Wf+P#nSW1(1K`A+XCFwMVoM(&@?R%(J1 zF?lXaaiKuHlV<^dzGw^=Wni1ap3B3rZ^Mk8+rwIDu|tpQ;pWcgF4 z8+9j%7><=MtpE#A#iIV<&ei*jXI#q0y!W0OH9+UdH^A;B;~6d5kkql#O=h5^n1LFA z`BaZKNftkNwrbPGyo(4{Qg4Pqpu=H|P_kx)s32x4&pxajMr@CDm0jhM`Sd?WiHSu4 zLrs#o51v)HZtET7VS+A;Ga9jVz3tyspG*IlFr247Y8}-$tZh#?Gw9nOqHDu>j-f5) zw&~31_-&E}4+Hp5mL0TeX2fMB1j>{A*QqeOgF?BW#1brXG-*KeKN9{!gOg>}e}I8$ z#Yc~~v^nYjCEx9&4i{4H@%&pQVt9P#R0K-@iYtcqAhA;K%1bJCS0nST9P|u;Hp}FN z!^(tj1a>%!*2Jq!g2gTpl zZX4PFy9Y`3hm=Xa6@T7T;S3{YS=^Tgqi4wWE?9KK(1S>m54>8B_$$3eaY4V*&iI>mf#%ZX38@EdMPvRql zoo4eCXEMagi0814e)=q@$m~b+KXMX<5A|Q-Wv#Ce2L1Vf?{xi7_;8s#0F{jcP>~p( zI=ug{);GEX3PKOT=j>rik{3HoCa-)hnjjHzvdEppWqdzjphnc~05y0y9D>cBt+I6o z41C3lg$f@6UTskK47&SzO-uMcSjWck#=Me>ZRFQ=9q`4H+zZeCgReYX5#<^#L0R%1 zo0^dl?}w$U;|-JC-kd$KcRuH=IXvarYA6$Rf8SIC9L^4Rd04EiA|;~3^QxKP{U3l7 zU+FjVF*31a=d5G5DB0(T`7kq&QVpOk!PkjX5gD0Yt@-Ys4=qb?49ZWz2+ATIa?IVg zC|W{?iF}ox<))7+p=XLSyRa9Z=S1l^1Qh<00@3}~zM@W6W;OCZwk@$wa`#L*bmjB) zLDr%Yj0AM=29Vs0Ep?$8_J$l_PPvQ68>scHU& z#Itk?O_Zoan5if2e$9s9h|G@rBjxk%=b&z{dIKc?6J@asdFiK~___ zXMjps9%D^NQLQ+nApl2I<2r6~gv4JuXQCqOu44PxMCF|7`Sa(_>Dq8lKd?O~dHF-> zo?(d7$pbFtq4VtVs=9;|r6&c%O2b|VoaKnCpN-ESD0zB4+^NC2T z&gom7&wp%a$FSu8>*M(-WqmFjpGdlZl_OLxyFwItA|Dv`mx9ta11&dw`R zZ3sCm8R{a%AqD`1MbeCPO7s_==!LkMd6@XUa=VLtEGF>y_5I0d5=xVvaFO2>pF;XT zt8PH;Nsjww^}1u}@{P)qG*N5>)W@Z_9tY`qh0Pk2ta3SmEk>Emx#y9LskWih?4WK` z0M6_8ebB@l6*1}}z&IVJ_^3F)5WxZ1BlDBeN2vFEOA9nM>;ROY8RZ^M62K~wMsm5GqA)f^8O%-uk{XDgCNFYewjD#l(R|Wr zm0i>iz;1krS}(;8@0YwQ;%9Jcr@nVu{ZY_ZhdbLp+h5CXsxE5v&M}$8hK z_>I<|K9{!aZns{;QL+m@W&9~V6%=HI>l0&BgG<$*G_gd{+974<{vGFG_ad_YNK&io z$fz;a6uG`_?>E{r^X$eS9P>W`(b%MUa1&bKH&m-cx_s*cChaQwwUrgTrN@X7YaB<$ zs=t}l75Bs`cUzCT=sYQnEgo+1c&*QUcrJLk$^AE2zc*k^*xE0odwMY1-l^|+b_X5e zvP4C^wLAjQ6?`_MNTDXeH_Q`g6!fb#N49X?Oh|;AA@&;dfakQ7?;|IJ+9Hz>x-FIm zh;A@o9;dq3%Lt+3{Zio7>0yVj1)P#mg{r3ICTf|E>20#%;N%J0A|}((oLBcpMtu_p zKb?uYIj}f3(qj|M%m#QavLB+UDDe?)yul3>K1^+=wEXc90#UKeLAE!U)=8k zM{x_<3&**524k$s`ll%k%t$&a#Yjev#Sh|`xmK%qJlZlmOa`m-%vf*#Je!)W#UEEB z-uwN+ee$MMqdtNr z4j(*AF)|~T?sVhQ=OxhH~d^Fh#^ zb}h0FdB>qFogV`{J@`74AhN%o+|P$ff=YUra?|BNEj~v9&kxp5%|8D~QkNuCk7KvW zD?3mH;$FW?F-f_L;8QF~74=&3D1 z=W$hYE>4!JQ~)4De6O&!io^-(if~&!3cURx{tCvNdo|sa{P|Ue5ID^Thj|kvo@MR72P!7%TFXcQ zOUtfEF|G?j9i^YfK2)zTT4>tQULhO-01Tpp^=AEQ-wPP&_?cNC8}cL=vR@&=J5FX| z<&O&OT2zqnqs~kKK<;_Err&7VCcF+fP`=~-SC~4f&o*zJ$$Q#{havg=V2#<41?vDR zLs-k)?K(dy=R3k!7HQ?oZG}Fg(yV)t*|yx;#q=OoDC6pPot(J{$jo~}rC&cH;(zzi z$&iimHgfQwKPP2YjWGU7bZ@7rJej1KIYcCj$`~ZnI5RZHau6%?1WiY4&mwVz?lD=0 zGIbiW<0WizvXi@~&0q8E;|Z1Ddwytv3Z`867!YKmj#dkl z`K8+_hI8#lBx zIAqy9n3OxZ{0~+-8Et;GNt_{yO z6@y6cJd*xjb1uEzgxyGc)lNSBcL}#lv&V94AmM@kiz!36OkG1^6_3!@xZ21FJ zRyFJIk6a0R!+fADKI87JS=4*=*F;Gx1B=fjRUk*9&a}%Hw^{M^cX-kQJprZ_xd?Yo z{3D!EcAD_k@=0RNmCvyamB}bgZAV-=_Jr%1g2NKwS=zXt!D@Z(G4i>HZlWrb*NDg9VYlEW=aaJZ<<*XNAzwS zgam$i&oJ9`bm`q6yDm4hLO~U8Z34lbreTnvcSh@#!X{!~K1&sMpnoJAdrI?2iy%)3 z;p=GJm5!L)^f)7{pdQ2r(#}6PhJ6YZ+qACxfj4LPb>rZ}(j2Xr5shNWizgTdVA6(7 zjq(^IYU`rmbD%7?;O?o9q{GcH`w;9g58@d{Dw15q1WbJW_l#!r$7$-y6$3JssoDBV+Q7@#IHRKsKUOa93kG9iJ~35+fb8a$y^y4QX{DBo;; z0*WU;__DzQ0ED`>2$&o&H^gnixIPDW`#Ht@6NYUbidvjv9vK3<>Fl006hfN*gYbp% zUV%jgEOFXd+kQLdwxUa$nSfE!B^0OWC?m;S{9;J_U;z^ybYU{__C|Mlqj~n7f?4Wn zp~Ept0n?5w;Wj%E0x~{0Nl0=&2zHTnyudR}(`GAPjRw1vg^)}8+5VZ?qI)x7#K?Oga=1(Rj8Ose z;(4q;WSk?&SSPNltivZO_`;of8@lFRP2z~PyG~ zKK!8l^LJXcH^ik(*Swb#VZ|22A_!4`=0o4ckQOPGkJ|)6F_43cu36?1^f{;a(Fd%9 zPS_ZT!lb*wUe%f~)|9%eKy^Eu2~xS4Z?zJ=B5W7 z=05IvvI(0DhMLr0J}vn#p;Uh_EZmUu--Ocnrz>3ds855;vl|rK98!Ahmx7{sQ7}ly zqB4gx!Zp4m*@xJdU6vnMsB`X|Bs#B*Bpb0(W@zig_?yPoXgVP3kcD?K%%O+FccLe-KlgK0i4x7y<8^8c{rp;6Y5FMXHKke{n z){0FSlKRcdn#^(YHqvZI8OjRL>Y2 z{W!&1<2qMw4FrQ%9=BvVuM}*xlhq1Ms_P9yD%5KO%6g}bO;FgQo@|@1&j@*EomLN_ zo~eg9TyRTcMoL7ny#XLibF-_Ol7uhjztlT}4I_`g&x;IIZ5_k`fPrh0=kwne=Sj>Rq=j|GfNKOr+-s+4=cE?TPfgr4aL@mz` zzw?Evw;0qpro>S=Uanr!-==0%{Dc#C?^iTtOF$z2B(scBvD~`@PM_Le?fl!LvhA_f z51iMkp9lX`sng2$>|KJezl-(H6@usb4pSb?oPvVu^FV%dfjOJ_dWEE^EEP@GoG^ih z6{t7)7hE%Ci-cJNqZI6|=A{3_xwWPCH+Kp-gN)x1q6?+Jf8W(w#R*QXih#c_-n~+# z<;oiObZ*P4ApTSC^LqK&r>}g`5F@;0{&2>W-R;Gj?50~V*zlv`&L-NHWV^F;3aJK}xkd)};ix_zrIj914LR*C8}W51X|qa;AI*DJ@H&zhSgSzh8=^^CyS zJY3E5x{=~dU5+4uoPXA3+i0sU_?qO(=y(5kG4(7^tz?tu&>71OS*g%%OzYxU!b@o8 z{`|sMs33|TS#_o>%PgL|Den1^mtgHvZyvvX_Szc%*Dn@$^!LSKrohI*yJWGP}6Rm*hO%xl*ib+4*g!gyvkOXkfN~j7`{!4tb9xMiEP7 z2OeeexbbQ)>9CKZK^opa2J@MW@PTFKMs#>2el7dM$ud=$&3dDi2N}Eu<;G&u8S;ez zObu?=uz?6CSiM5k7_g!wH&@p7z+cSfu!?j5vRE6WARc249j|%HGhOe??}3m12*ODo z>5+TJRZJB9rFxj_t8uJ-0jy?$r29CWBO9*Eus6D856QLKeLtx+`=l=j^%(4AA|*2) z-n%xT!XaR(G;W*uOAyD`t64Q+lECw2?e`Bo@eFfB=SPjL^v6%@uH)*+%ekiDWUzK?+3O)Wqvvf`Qh^|x~Fy?LSN-K=)DWjyP4VJ zAq$^)DqgS(WdSgKxN|me@Y{aIP$N}4&J=Kb5(j)8R1t5I1ao-T<~=-mW~|~3X1u`K zYDT)eF!FW#Z+%wFgS?pqdpmJ3kD?iHeLIen!boGVz)sQV^0Bb%TqlsCk>&PN6Tp*0 z@m+&AH?=LKD;|EZ>&*_2dp&JStQ*2jl61zp@2R&T8vZC z0JyeR>|J3Z08>(b+-WF}+u!VsDFM3e>9+Y~mX%w6qWGyFw!w*N8c#QURoBmH&}%?j zZ6>qR73I%=YG_Elw>wQqqYZ+8>zZ^@+e&7*?!UOuUVdjg2j_0paU0Yiy-*M)1(x_N zRUm#RuV=?L>AFg>rnsZky#-f(s4OiYkk7$nvA|-f-}CgjX#I2`WZkjH!Vt89O+QB0S0j zkohFx<~WvCme-D)=u+7WbEgH3c3}u;)PeX@e;}cVr>dMlsi#z)Y@o>89hqxSc?e#b z%G_u#1M$teoaym)Puc5|H!XQoBJG8G2o`%$Sw}Vw9!LwbzkqDBM2Vma%i~RxV5v{7 zGR>6?US^{U8(@gkIH1S40-`4DRFI^RI!7VNlgRQ#-P=gS4y#vGrLXKAM^=1P?vT51 zcBgEDOTu72ur{95P7Dlg1(M{ zGESl)po$%qa3Af78}^~kkLc>Wcs;;h;>TGaC`Di+XJPEfeYeRAtAg}Bq~W0+&qypr z@wcb72mNSSPjLNX{H`9Q(wg20T5TV?o{{eJYx|{)kdg3(aIWKalGxBf_XHCte6?dJ z*JVQ(3cVqhslYxbjMEQ%jk(%A8LGlpaNyhZ;7Hvdg?Ed!zdnV;6V7!<<8#-p9|S?) z8s>*kzA!}BgsbiJgJfiU{hy2QF)B8gqo}}FPdmd}Qpmm;?kd8eNIJ23+$yvz8K<~n7gA0mHyf4klf3+t|5m*Ns zd5xzj3Z91u0i(H_%l-+^&KY*k%Q;P<+$l7g2wAoQ54^2`Ho9dEne6l=*nE>$ z@pbTzi6|U@ycU8ntmN)yKxV^@9KG`6CKg;u$Yt6KcZw^_+g4fm1^Ao@6xpvPokym) zf61?(D>sC8(NUcXY%fZqxWY;?kb1|+bZi6hBzN;Jr+f=FHo zUs?Rp^PAtT0~$)u;`b%JzN~D{i3=K`#ks;`wY8J#s;wEZVv=ZU)|Yb_`;m5=ub+SX zhn^%}KSZXWO@-rf1kpdmE;3mS>AQo79Gm}cUe==CH~w(e>3q*U{_?ghgk#6g8~sd0A$n|ifL|;Uy~fZ=TxV?oRX9GQo&+e=jWpU z!b1|{aOSzJE!3#}{Svtjz70Jrk7U!1CnJ_~d|FltEr?RGB5VegVxJk$^!f8koI3_d zPI+3aBx`kip`4KMOIFPBT*ccJ?lQu4>8Sz9D*bo6@BZw0XprMwAVYB&n|FVKsjJt8 z!f6%-Fz%r49I*f;afZo_XNj34MV$MSCa3h^@#lA~L+Cc(a&{$EXFIFA=a)4t0vex= zT0k$epv2;f9p;$W4;_CF*%uyc_4T~`RcLk{QS)5n`(Z4iuwE(e>y7&UqiDJ9VADHn}3pawJF&DY|#-o>9tekng;(^DZUA)}d{WchPATZ^a zgj<$azya6DJBM?#*fVXxdrjTzmEWnRpw%sT#UYUWM3cPG^Gt*{_K;Rd;*9|ZZa;nM zP72WCJ2!5fxwR&Q4A?hRBoawieg@9NN1F-fvq~#mwtGr~?T$L?^~B@wPoE&SYeg;ko}W`agRXT!a8Vi6iTPzFt-4J zYi|hSLtm*m6!{q32{N0*eXGFB_(t2TY_X;AUw5(GIGT6g+aJ$>VJFW0W-EK=_}E$k zb7|~jxYGQNhrP+Rwe)2*J^wOP6ZL&Ch)Id=fLa>^Qp|h5eg5!Bn%x^;FpFg2^TVve z2i=ftp9_gFYVU<4r3{nja9%_Eo~T8tgDHiYg_vcX=PtV9>%O>pa+J_f z_NT{CL2pOk0>=3r)rCyX$0bhX0d|>y6bBJkg(ok7+ck~Ng{fzT))uDqkLG!|m7@xx zb3B^!Vzb1{CGLl}Dw#4$byKnv>%!#gzaIKJyZ$^ZKHyh$&a|h+YgPsi@ZBmW7JGS2e4&;x3IYjixFZ9QG_h#$r`^1vwf#_PFvb9cpa-DfnQ_dNE!Jr1BpS=MvGd9DOAW`EHgr!#x zwpm@Hn9+@x%_4|aK7!R+yEWzx^uw)*)m5hLRHHnyv8yp;2CtdJcM_1FGt{pfOhi{3 z6nq3<8x}M_*|}6T(!8>v-duipp%pZ1nvj?y6J0I}xS0c!3HQ3(r8a;6@88 zT+79&Oh7v|g^8I;5$!|&fK!66NBrKbtRZh%la(d?Yp(vd>Xp32N4IX#=TV_0x~T=N zF8QJ*>app+1}mvJrsqIuNUpLzS;~RSk1TydvR5}HI-iH)e^jW4L}mJvYHGIKku5)@WW#6rpSF}&2`>v^1L?rl_p8D@((byWjsp9Y6$dLS z0@smU2PAiFYO(RvfOy3ZQ!vV&Tt$xUtbw8Z&Cd#8bNTjhaG__-LnRb>&$m~UPuYr? zB-mYP?~QNZ+VK!>pdG1LGhqfO`EG~L{!$Qt5ocV4&*G+Ou8jI00)(7#J`Dh9ZJRiL zXHW;}T1nr#!7X2n(yioM2N#Y5B+mafaXg2;i;R>yDdUA=Y3B;N9t(bYMPJZlc-ne} zVkP5qjnPDt$uHBF^grcTB+>71`|h`uVm#jwz-2i_npi>&dm{i`{d~y# zz}49n`_S}^XBp!+OSc>Nb$Qjk0Z-zw3%QAQzRs8819+%3)P_LB7^6%XO45;LAvb@e zz(*E-GAQQps}wUW|Dm8gUCmO;uY`8}Ym6G;T10`vSNc*su zkcm3M+!k*JXtbood{qF6`_;mB9u(UweqofwkAJA;Yua#6&^2Dx3ONRtlU7U&C<4k7 z0yA58jFPM>kSjh%ne>pQd`(OsjaGq0o3Il{0I8G0(dn(s`C|)9-Q>yPdrGn7q@@RL zF(hSB7UZDKg&FW%do8B@7D&!t(;>#PF0Lk3Nw^=r^k6M?Frwo1kSOVeOc|r5DX&;_ zam8IHf_X8MValCsij7>zK<6hH316<4RpFG%QD><_CZ+8NAdE$^Y4ojt0cT9&i!#XRt}%rSjVMA-1bt(a{RqGyC0*;;63s(1 z7ZeqLXSyiZ*6cz!`=3f21SBm=Pg#O9sJVAetm>kdD0{(puPL`xhVHWd_YN9(G-O z{MTbBPYk1ha`O)x`xo_oo4NvSkN;pi+=M`D7J)Wka(~S}HmQHAHTu-Vx4(fpjU7Pj zLI2w6+M0c#-=A*%*AA%rjpKsGk0A!_arj> zwuI4`7dxv)7zvQ{e@-C0fyeliua3?_PHzno__s_2$5$n=k;mQk$W-dZX?#3s80C7y3dw6SQdRv}|(%ecwT=}VfgVn#2s*J^hME#feM>43O+x(=Prd`VG1bvU8 zbD+LS?>wwu?Id2CC2Hi`8L>SuXsc56ARrj?Lc}`>GcbJ`YXTbZ6N5Z*J_>jPpn_9Q z*WjVP;u z_{a+Z#@TwhSGCRO_)2PjB+j2IolH^Zt_&L89K#PnpuG%!<~TGjkuO zb`kOl2JEh0kx~m>9dS%}`d3TWTGrAU^ijlM3+zOAnHLVMfja zyG4-qCjd(6xyK9kmg_RCvfA5M9YV5y<|Ge8Tr6Im=>8=y_4^6y;Jy+6Ezz5z6kdH? zjH3;led7APM3+d_uSxUS=E?Nr@oVw}y#C%_X(O^*WGW>Ia$o|IE3Zl~@^7ZPu-$*n zJNbALg-YIij{{=&e*yy=|RsUG%~N$ z?!!mzGsbyF%J0p4K(TxyqpN7P`*xy2@t2%3XFnEKxE3%bkN6(MI^zymj5nnw zndPW#31@dmpUNMIy`l_g3~*rd0nB;G50{h7C@zU%j0EXqr>?4ZeHC^l|4uQiUh-J9 z2&q6rvs)k!mwS8d~8qdoLTF>dPsjwPMSpB0X+Kh{$09YlOvrN z_;ByZD%<`gk^oV!m~2P0r^nsM9I8|G-;J?k@8=fV{4wvns&*Az#E;1I)sS&RyXLQG zd@jc=3^N7<$c*tFiTs`SYQQi}g`p{eLx}f#a?Ygaklu&4zbJ)#t`-o2c4LR>qZxyb zMXLOUumk8~P)K%*T3O?J$S5+?{Q9NWi};?btQWJ#3%b?K>f1dR1Uf$$JV{C`MyrhL zRhxG_Hq)=xtb-6u_Pu-G)PHZMtAcy-DQS+q@3+5|?o6!v%DU>nEI10+Cyd&C?&K-4 z^*H)`M9T}e$rc!iLfnpsqj0f9nZb9d^B;hkTZ0U4s> zE>`k*#YIh=J^k2i7GF5*TLzD`X-qh|)jNE1rpDXLj4E^}`Cdx3@6S z-Ry-@1#h{7MvN8>96o&tTYEsPtbe^ZzVxfwj6v%kkuB?2 literal 0 HcmV?d00001 diff --git a/site/Site/wwwroot/img/ShapeAnglePositionProvider.png b/site/Site/wwwroot/img/ShapeAnglePositionProvider.png new file mode 100644 index 0000000000000000000000000000000000000000..37d67fcf1b42de0fd726b9a8701968b7f7aa5d23 GIT binary patch literal 7525 zcmd5>cRX9~+dgTE4x{~c*mR;bYuDC6RjIvKYp-Z�px~h3c1t+Eh_{#Ewy=G_h+$ z5~MAmUqp(c1bI*VKEL<<{qes4zvuJGIC-vfJjB1yQ#&4dlqg7V;i5}Jcd5|NJ2CX;`|_QtXNT)hWE7AfD@VQ$lj6@?jv*Z{;>F~bthj2c|iLXM1C zZvJk9Z)aLq<{W{5%lTi9ZK7^y+v*|u6L9P|rb!+>tL^|96>>Ep7i=J;;%>ZkA1;O(5jsf}h_%ZKjT zul@-n{I#ya4K0(69nYOJrV{S`f{Z))FJ|Xy=o59)@>?qXi8~^TQ5R}AVpSv)ErTtz zk~Nr{AI}^GqP<%$)V_9TRo@Ct^L@E3_#85>mAA5&{bmO%z%J^76c44vYNq6*NI6fr z6J4*l0`9%e#OglwAM;|pGW@wh01#gnbS;0RcEs%Nk@AsSi6VjO!N`HX@elEmsOOwj z1BH1HYyI+S?D>{PXU{;T$O9uNXwvGEfPnr(e9376`j#iOr9RYW<%3A?=ArU@o5Tg1 zaiK4}H9gIPw^Jzj8V{pu06;E9M8vL$t-&bUK5d5~jT~1$7n&d}9ctsn^_8@y>7J}b zR;}+<8?UC9Ng40$QXI!KVA1P-)NUDa{o?a5F11&%w~%rFJSMPMkY_2L74t3N;$VBj z;09CWxAx~lz{iPjSFcAY+TP*IKWCdPk>lZxtLc(xQ3+p^>{Gp-2wg8Bh<%0z!RTV` zYgE=T0sU&H{7hbfq7O2LDLDl9sYqy9Xde94B0IJ3{hfZAt=89h?V)sHN}X^)b!F5splu?TC*XllFS{9{#Sx&!d>CsI3(87-BXm!NqkU)e#VFtM#c;7zlnUVh& z%2>Y~RI2p(Vlsg|V~^InKJ(%`PtttU34x**vMqAzM4|R>nBHpoAeU1F0CelGg{4P5 zG7?&l_E-7+3C5yc=EoAGT_VobbwydLI4)?acj=>YR90! z)pVYXP$Lz00T+Hjx2%(P(87%fdBwRe3$^ZK-{7AZ+NOBr)^fBv1aK*fkWf^^$6=+& z;v$+Mb{a5wYCp*SdN?^J7Mg} zYqS_jWP_oPNScG1e=!8$Q*h>r;ki*Zf^_KF_U5pev%a@&ykmZ^@w|=l1K(+ZLdyDe zZ!}e)Eauu`b2E%`#?NPfQ+s%2Cm?BF5;@+g9NNz)x^xr(T&XL0V_UL^rxKl%Muhqv zasO4rS!H!$Fz2hQrg~D18fVns3?+G-PIj<9wT3pS6dvd1r7E|T+<6p-o}p1op9Qw?{U;=G0+ zDO^u7MqDRul-^yofo%%-InB1IY5bB%Eoa8LH5nm>BeO^&*B9e>0z@~sK;gg#bCp9z zjG+~4Q?u)y4VtO$(e6uM41$iiZw_D%-=*0HD91)~nZfCBGZ3o%oiU5A7Q+3tNpaAu=)GiTZA&(jfRnC0LVR=~?50gb55dR^DvBPLMOAn#>#edYHIx)#F^ZgmcZe?j zabKth06?M7*pW1PEci(En#gl7g8l5M=%^u&*XF=#81qCG&J4og_4IC5=yPmQQe-!c zI-C%&<0!lwmG#EaqV=WyKj6r1FG;FDo^Z|A?wM$8H_dt@NQxKy`TyTxWNvOQXnh%7 zw6GFdXXmd%b>9wk))Xo0$w#Ry&Ktz-`VC&}-LTxKhx<8_yFbMq6`k9S1h)}b)G@e? z4?@TIy|Jhy+$9Y1{IEn!retivxJM|c?|RE3ROcW6e4%0~ORLM;mN3(CvFaRa(D=nF zr$`0w0lm&2pW{S}JNn{KYy#gRthQbTrmkM3mbv9BI>%&duK$U@8FyM!BbeVWqu$?L zb~pQc^5eAK<)bJpdJ#S7MYKopF3jt9$v~t3R?xp{*>ts*-K=wN&P_1Lqj3qqd)&W& z-<->Ag8gcPs~<_vwCq2q?LKjVX^ zsd&RWyD2An=ruZ`X?mWr)GEmcNj<8wykQY}auvLGPQX2pKO_oh@qSYs=lG^#$2KNT z*5~62JuKo4qRE4_v$aOR1Spq?3=kO1WJlG?mXBBm;GLcGRn5Mgk0lC*Ni_i9Mf7c| zfxxX6^DQ*{aZ1h6uV}8;Udxk5PfJaFk;$~)jj&&@A<5+CWM)o&0G?u}~Vmv+4S`6 z#xkTU^^SQJUTnSAx4flS;j%?4R&34RF<`_xB$Y$QI@sbTv?B^o@-jXmggfx6A0};H zNeD2RHZQiQUy&L7a2vL}<|NofdS3!EKM=)cbT)NF4JrEh>HVo@Bpo*iS@@ZQz61C) zq=eQWLc3$R?$eZY@C9Q&=r}7#lzB%~6-wEB*cYm!7EFHYma^`hl!d3Vac$cR73z);uK&@a*8r~Fdof#^ z-?Vk`0*vmNxbpNp6< zNa(0%=S_rX2^Y*oKlC8j6T8qI?WV~>t_>5zXBLwH7n|Pe5YjKs`5zKW9PyIy#;8$X z@i0!iRZtP9qWpH+ENV^tK(uZ@*PcH?GDv|SBKLZfxR*DhEIFN!Hk%Pn?5b97um$8^ zoa$(&g~jIEe5~@jhL^!ahO2la1uW;c?~0K)U`K)NP}r}4hU_MN;@KeS12fmM76q{0 zL8&+QSr}<=-%eOCK9y$;LK_o}CKjh)tr#g5PDgm*rY5^}H}-6D#2P>n6|t-&$S&1$ z@&zdyR0?;jz#68%OPrPyh8L!aQBE&?X5mKANbC^%bZZLK)yLph0Ols{693{~e2(j3 zyP69(c#YTO>3VlojqZ|EUK6xd2O=vW-Cv&Jb7G=0q;E%7`k{*T9-F=&2-1dU8*+pc zBhHI$70S}iI}f$3F8t%f5&!u5lc;cMJ8qmw&c~m{7MIB%MjVvB^|lJi;Z#Pxwgu*I zq?NOWO3Qtc;n}0rc{OR6CmhJ{hCRl(bUe}Jp@pfqBn1?@J4d_JBjTH`cC?qDRtl0I zm6TH(HO*F2QiA_JRvMz>loY`J4moE;LTIV}Tzq^y=>D&O(wqG+`4>*{ae*e}2}e@swj6xNgAHO4_R>D=t&<1&92d?+nHF1QE6(~%C#241Jocu9 zzS_vk;lgms9Z=O&MXDOTSSfhD^3<-}%BZ>o(Rp#pLjt@@{-ji&&I%3`UlB(UPtoBk z*`sG+<(a90aQZWFx}jQGq$yFpzgY*@X9hd8N2~q$UGZ$Ud8-q%MXd{IUa>s&z@Uvk zeWL(cdl#w9!ug%vA$!@@*y7dL@s~cq6{ZtXW-~0zoCY~i{aPLE2`LvD@5@|sGe?hS zJC=@d_f;LH>lv;ZT_zQAkF~+|Nb8T2su;A;hT)p0;V14#+%YzW=*d+y+ zjkK-%(p(b+8^4<1~OxQRPp=V2wNMRTw+|^J@p< z@v=i1tFWpI6t{QCr8_iV4$moW6CHgv=7w#(5W1xBvAk&yqFAkM>bxHw+mRHJ{BomO zv#!z+1y|RB5dR6FgSZDcKrchB#b{xD+y8In`6(BJHryQsDT_3({JL5?S7+&+|2`~F z9fV=?$NtF@@D7^h0`FR1ZDm#k5*8Wn4`Hhj+h3VcNjf|JTGmu5dj2LU6A=S$0OM2F z9LH`u-!pOAAq=CFi2QL5_7z*56mcSUqv?Dt9Vx{kn2eD_Hj1CsOIB+VwJaU;)SG<& z?@7o$6zpXdruRt{S;}|7z?7m>T^ti$RdadOqg7B6r!qXcxGq@BEI$#H5uFT5n16~A z#h8V%yo@d%?U;HCSNAzfmRyr2Z|(1GxUC^9FfcIgE%~5_*&2o}eRi_X(|KV>&S(l6 z>p}TZ6A)D3=q&AeooYwrb}y#}Qw7|iOwj5K6oX$48K3H&6q7sgXDKdy!qXIpTKMMV z01A&G2DS=WE1i38=xHUn$WU?kOMBRh!f4*;;Qc%)_C`gE@y-)oqJ7ZB4UAgZYK=i7 zWu;aRjVgKGdQ|)lXuJ^lZWvfRN@6+*2W99?gY4P%W_y>Sf7FD&@Y8yCk6=%3Tz2Vy zBr5NVy1JK;+GV7uY!qWeI0mA#|19-hoJ>5Hub)Le2M-kI#!i#E*=SK&%h%^B@yGv# zuBQ{V`2Gz>6zzrsv139m-aI)1^3JAv9nbr0#01!FBr@tat5y#|rQ)+Wiw-r|lQ(E- zPT%FWZixvPAAje^nke&J*H2u?-wewUtFr!3C?jo$E(qj8(**-(m!4;q9ZHW#F^ToZyOUC41O*_Ge#7AH_K;C@HO%y~y~zZ<~2Ym^v-o zx|<~OZ7K5`MY%UU$ZS-_Bw}G!t9W3!>ff-xgI^uX%{4yE@`9@uZz7?ei(W5#!5e5YI@m%rh>AWuC8%K*L)pP9~dXP2vB|a<7Pkrg*Q61{vVqXCemtSzy1dlQpTf zu(ZejFnKxwJ)6Pj;oXiE|5_h??ycYsxNHT)Ptv~cvf+=O^B-WjOtLMQj#@IvHGJd) zNA6?xm)*}=Huu8P>k`6Z5zEI}zNtX-6(wkGHnVVD%f<6kTD_-X$uYzAAuB)hOFFTd zU*}o`rEn@~-p-6pR+(|y)n4bZgUAlqlUQ@k=q9wl3^Zxep3S8H(KA0;nlG;hOV13S zL@}i7Q{I<&=ic$DBjqjZ47{kiL}v$OS&Y;(Fy0pI!V*Wf5v~wW9jPBIl((D*Z4stRAnBCYDj*yykcUuaB#3}%!k8s z;_1G8pQ4#R_oZy#V8xAZ*-vBUyHIdWlzD*}@wV{y1b*f3Q(gvb1~(a_3pgB3`fCLb zs_Q?ZPzxx^X7Cr@bRH@T#|mTwpPcPS>F~!%M2l+s#L$M3cRdSU$t==TwJvc<=Q~Z! z`@z#I*k0(h)4ljIPF7U3O=}K*Tw`9O3X0vrC98E<;{^(5fm3C>Bi77)VmmQ?O?Y-DQPz2qq>1h0ie`O|vUqCi5{y>=2eyUOXcI`+b zFQ!>ruG1;-Kzy5Bx$<4%`6g(~C>8=HUa3a(JR}u8(#p*TeSVO+VOJ}Zl&AH0xL{z- zaX~}8FfB1go z<#HP@Tk8TM3J8HNcOC|OvNFcmq<;Kq?itq6)zwv~c{*I>eo}yl4x`fppNf@+c26qo z=W*F-)iRG)a=F*XMPj5_>0bY?P}I&!;#k`Q!}&E}86)+l!JaPF>k#)*wV1HjP#i>S zxmSd^F19tNwN2L6V|)yNMn?vlEQ7vGkldJV=m(LFrsCn){_xQa6v7n*I-NLiB5|Cu zhk$%MBg4(Y@#=rjcF`DHTvs0@!qRp|uvd~JT-V-dT1Kr=IDnXR(B~_x=R7JjT{4S1 zTg$9UY5Y5a3b-o*x7r<|7LP)s?>YW;WaycvYU+*Rt1gqoliVHcy2(PF7Mwah7&I14 z)K@q^PcJH)`fz`2CAhU(UnOIOUKm142QSk%WH?L$!7}Qbf_d62GgrV4^xDS@@s;53 z@7@+b))G)H8bCL1eYeKM@GWho#bTWM8#=h&ex z4C0q4K+WRSFO>!y`^`9zZGkBWT{$7HcXpz26hFT@5l;W9FS*xt8NPvN+JR!0g^RosSfYU!oTZ`hWZxgPb`6{~hl2X8nX_ z+n`z$$a*;grn|lqIkpC8m80}kpfP9U3Dd=UB0@Da9XOV%wzpfh)^J`@0B9M)YUE#o zH)SmR{^9m%2-(^T%`LUX8?b6&FReEyejVEj1a{>0rfb^FB19*C18_Z(VNHK=}4eh^l_ui1-yO{H?))`>RVyMRT*mR_4M3d%8S? zkF?hjb!JSGG5@Cx()S)zwSG{C$*3IPpeB-7A~PngjqX^W@!I@#k(z{C+_WK}X*tlO1Jb9#>7o~EXI_Xr~<*##_G&DMXN zYGo?n^-=R-V2i|JgIIq7>{1xt#UF4H@`_%O=enHY_2yx!NPB?=O1QNb&C4Z&_!?fZ zup4PlUz5e{ScZqQS}RKec;C)Gp0XvxYJ#gvruVPvICh}`Yb!DRM+0@lAChQ%a!YSY ziepeAWbAm@UFTb`Cx~!Maf!aSobjUA9?Cs@<@G(NBt!T)$Fk(1T*pA8uPtu%e=5ld zh3kKes8C!&QO zlMY1hE$NL2r9FAX z$lAar+b%Kx?lXlKrtGE)CyWg8Bsj7I=RIEsmIMaf(*DqVmDruhFQXo9`X163gAztT z6+hUz@eYu0u7$POB{nRq4C4GFtS}tCK|BKP^X-;Y*LG zgr%@zw+w4ZXRv8RLy+k8#S(M<84VQ(Bp}gRjy{hrz~6R92O4^Dy@wfOW88DN6V6-O zH=#~`{8N)5DrpbShIeq6Rktj9Jp35X`jgBpX(nl&`;PHE4PAzDEiemxg`62=?EW8} cRJ}1K^a#A}EtYO3&w?1-G`>-B&E?5|0kdV}EC2ui literal 0 HcmV?d00001 From 648db68054e911bcc112c62e3c4b18a31d4801a4 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 14 Aug 2023 15:26:36 +0100 Subject: [PATCH 192/193] Update Versions, CHANGELOG and Workflow --- .github/workflows/main.yml | 2 +- CHANGELOG.md | 25 +++++++++++++++++++ .../Blazor.Diagrams.Algorithms.csproj | 2 +- .../Blazor.Diagrams.Core.csproj | 2 +- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 2 +- 5 files changed, 29 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3aa3f9816..6828f3461 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v2 - name: Publish app - run: cd samples/Wasm && dotnet publish -c Release + run: cd site/Site && dotnet publish -c Release - name: GitHub Pages if: success() diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a89280b8..e5358a9a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Diagrams (3.0.0) - 2023-08-14 + +Finally, the new documentation website is here! +Please don't hesitate to create issues for any problems or improvements. +PS: I suck at design. + +### Added + +- `AddLabel` method to links to easily create `LinkLabelModel` +- `AddVertex` method to links to easily create `LinkVertexModel` +- `ControlledSize` property to nodes. If `true`, the node will not be registered in the `ResizeObserver` (saves a JS call). +- `autoSize` argument to `SvgGroupModel` constructor + +### Changed + +- Renamed `Point.Substract` to `Subtract` (duh) +- Avoid rendering link selection helper on dragged link + +### Fixed + +- `SmoothPathGenerator` not working with `LinkAnchor` +- Mouse overlapping dragged link +- Useless Console Logs from `GroupModel` are now removed +- JS exception in `(un)oberve` methods when the element doesn't exist anymore + ## Diagrams (3.0.0-beta.6) - 2023-05-09 ### Added diff --git a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj index ee22699ff..8681e4d5c 100644 --- a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj +++ b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj @@ -10,7 +10,7 @@ 3.0.0 3.0.0 https://github.com/zHaytam/Blazor.Diagrams - 3.0.0-beta.7 + 3.0.0 Z.Blazor.Diagrams.Algorithms blazor diagrams diagramming svg drag algorithms layouts Z.Blazor.Diagrams.Algorithms diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index 513db9495..496607a3b 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -10,7 +10,7 @@ 3.0.0 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams - 3.0.0-beta.7 + 3.0.0 Z.Blazor.Diagrams.Core blazor diagrams diagramming svg drag Z.Blazor.Diagrams.Core diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index f62649b17..099acd468 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -9,7 +9,7 @@ 3.0.0 https://github.com/Blazor-Diagrams/Blazor.Diagrams A fully customizable and extensible all-purpose diagrams library for Blazor - 3.0.0-beta.7 + 3.0.0 true blazor diagrams diagramming svg drag Z.Blazor.Diagrams From bf24fe6b63be7f813ad9d5f414d1e8e6c8b369c1 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Mon, 14 Aug 2023 15:33:47 +0100 Subject: [PATCH 193/193] Fix failing unit tests --- .../Behaviors/DragNewLinkBehaviorTests.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index 76d27bc66..18f466eec 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -34,8 +34,8 @@ public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnP source.Should().NotBeNull(); source!.Port.Should().BeSameAs(port); var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().Be(95); - ongoingPosition.Y.Should().Be(95); + ongoingPosition.X.Should().Be(100); + ongoingPosition.Y.Should().Be(100); } [Fact] @@ -69,8 +69,8 @@ public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort() source.Should().NotBeNull(); source!.Port.Should().BeSameAs(port); var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().Be(95); - ongoingPosition.Y.Should().Be(95); + ongoingPosition.X.Should().Be(100); + ongoingPosition.Y.Should().Be(100); } [Fact] @@ -99,8 +99,8 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered() // Assert var source = link.Source as SinglePortAnchor; var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().Be(145); - ongoingPosition.Y.Should().Be(145); + ongoingPosition.X.Should().BeGreaterThan(145); + ongoingPosition.Y.Should().BeGreaterThan(145); linkRefreshed.Should().BeTrue(); } @@ -131,8 +131,8 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoom // Assert var source = link.Source as SinglePortAnchor; var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().BeApproximately(101.6, 0.1); - ongoingPosition.Y.Should().BeApproximately(101.6, 0.1); + ongoingPosition.X.Should().BeApproximately(107.7, 0.1); + ongoingPosition.Y.Should().BeApproximately(101.7, 0.1); linkRefreshed.Should().BeTrue(); }