From 1332c77b068e9df2293282ddf9b994c5fff1aea8 Mon Sep 17 00:00:00 2001 From: Matt Swain Date: Mon, 1 May 2023 11:03:41 +1000 Subject: [PATCH 001/159] update readme --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index dcd2df3ca..a067dab17 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ ![](ZBD.png) -Z.Blazor.Diagrams is a fully customizable and extensible all-purpose diagrams library for Blazor (both Server Side and WASM). It was first inspired by the popular React library [react-diagrams](https://github.com/projectstorm/react-diagrams), but then evolved into something much bigger. ZBD can be used to make advanced diagrams with a custom design. Even the behavior of the library is "hackable" and can be changed to suit your needs. +Z.Blazor.Diagrams is a fully customizable and extensible all-purpose diagrams library for Blazor (both Server Side and WASM). It was first inspired by the popular React library [react-diagrams](https://github.com/projectstorm/react-diagrams), but then evolved into something much bigger. ZBD can be used to make advanced diagrams with a custom design. Even the behavior of the library is "hackable" and can be changed to suit your needs. + +This project is a fork of the original library which will be maintained by WTG. | NuGet Package | Version | Download | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | From f2224a30af5d6c9e2b96d556edf67596bc34f86e Mon Sep 17 00:00:00 2001 From: Matt Swain Date: Tue, 2 May 2023 10:55:04 +1000 Subject: [PATCH 002/159] test githib actions --- .github/workflows/build.yml | 50 +++++++++++-------------------------- .github/workflows/main.yml | 25 ------------------- 2 files changed, 14 insertions(+), 61 deletions(-) delete mode 100644 .github/workflows/main.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8be6ac023..8aba8b971 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,45 +1,23 @@ name: Build on: push: - branches: - - master + branches: [ master ] pull_request: - types: [opened, synchronize, reopened] + branches: [ master ] + jobs: build: name: Build runs-on: ubuntu-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' - run: | - 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 }} - 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" /d:sonar.cs.opencover.reportsPaths="**\TestResults\*\*.xml" /d:sonar.exclusions="**/wwwroot/**" - 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 }}" + - uses: actions/checkout@v3 + - name: Setup dotnet + uses: actions/setup-dotnet@v3 + with: + dotnet-version: '6.0.x' + - name: Install dependencies + run: dotnet restore + - name: Build + run: dotnet build + - name: Test with the dotnet CLI + run: dotnet test diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 3aa3f9816..000000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: DeployDemoToGitHubPages -env: - PUBLISH_DIR: samples/Wasm/bin/Release/netstandard2.1/publish/wwwroot - -on: - push: - branches: [ master ] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Publish app - run: cd samples/Wasm && dotnet publish -c Release - - - name: GitHub Pages - if: success() - uses: crazy-max/ghaction-github-pages@v1.5.1 - with: - target_branch: gh-pages - build_dir: ${{ env.PUBLISH_DIR }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 4c76616605399a726d05229c76b045480994957f Mon Sep 17 00:00:00 2001 From: Matt Swain Date: Tue, 2 May 2023 10:55:25 +1000 Subject: [PATCH 003/159] revert readme change --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index a067dab17..971036bb0 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,6 @@ Z.Blazor.Diagrams is a fully customizable and extensible all-purpose diagrams library for Blazor (both Server Side and WASM). It was first inspired by the popular React library [react-diagrams](https://github.com/projectstorm/react-diagrams), but then evolved into something much bigger. ZBD can be used to make advanced diagrams with a custom design. Even the behavior of the library is "hackable" and can be changed to suit your needs. -This project is a fork of the original library which will be maintained by WTG. - | NuGet Package | Version | Download | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | | Z.Blazor.Diagrams.Core | [![NuGet](https://img.shields.io/nuget/v/Z.Blazor.Diagrams.Core.svg)](https://www.nuget.org/packages/Z.Blazor.Diagrams.Core) | [![Nuget](https://img.shields.io/nuget/dt/Z.Blazor.Diagrams.Core.svg)](https://www.nuget.org/packages/Z.Blazor.Diagrams.Core) | From f1a3e0c694f2773b4f6e0d9b4dd14851e2b12e6a Mon Sep 17 00:00:00 2001 From: Matt Swain Date: Tue, 2 May 2023 10:56:32 +1000 Subject: [PATCH 004/159] ficed readme reverted change --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 971036bb0..dcd2df3ca 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ![](ZBD.png) -Z.Blazor.Diagrams is a fully customizable and extensible all-purpose diagrams library for Blazor (both Server Side and WASM). It was first inspired by the popular React library [react-diagrams](https://github.com/projectstorm/react-diagrams), but then evolved into something much bigger. ZBD can be used to make advanced diagrams with a custom design. Even the behavior of the library is "hackable" and can be changed to suit your needs. +Z.Blazor.Diagrams is a fully customizable and extensible all-purpose diagrams library for Blazor (both Server Side and WASM). It was first inspired by the popular React library [react-diagrams](https://github.com/projectstorm/react-diagrams), but then evolved into something much bigger. ZBD can be used to make advanced diagrams with a custom design. Even the behavior of the library is "hackable" and can be changed to suit your needs. | NuGet Package | Version | Download | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | From ef9eced5be39759bd77ee26d1f88a9c09bfdbd65 Mon Sep 17 00:00:00 2001 From: Matt Swain Date: Tue, 2 May 2023 11:40:07 +1000 Subject: [PATCH 005/159] rename --- .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 8aba8b971..f35a74f8b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,4 +1,4 @@ -name: Build +name: Build and Test on: push: branches: [ master ] From f64ebd6bcb33258e4b83ca165de961d7a20760a8 Mon Sep 17 00:00:00 2001 From: Matt Swain Date: Tue, 2 May 2023 11:56:31 +1000 Subject: [PATCH 006/159] addd dotnet 3.1.x --- .github/workflows/build.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f35a74f8b..23f9abd8a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,9 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v3 with: - dotnet-version: '6.0.x' + dotnet-version: | + 6.0.x + 3.1.x - name: Install dependencies run: dotnet restore - name: Build From c787a652b60b8e9951d5d880c061821770434de0 Mon Sep 17 00:00:00 2001 From: Matt Swain Date: Tue, 2 May 2023 15:33:51 +1000 Subject: [PATCH 007/159] build debug and release --- .github/workflows/build.yml | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 23f9abd8a..ecc8e465f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,19 +7,29 @@ on: jobs: build: - name: Build + name: Build - ${{ matrix.configuration }} + + strategy: + matrix: + configuration: [ Debug, Release ] + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - name: Setup dotnet uses: actions/setup-dotnet@v3 with: dotnet-version: | 6.0.x 3.1.x + - name: Install dependencies run: dotnet restore + - name: Build - run: dotnet build - - name: Test with the dotnet CLI - run: dotnet test + run: dotnet build --configuration ${{ matrix.configuration }} + + - name: Test + if: matrix.configuration == 'Debug' + run: dotnet test --no-build From 310d9130330afbc533af27e633dcb3c39004204d Mon Sep 17 00:00:00 2001 From: Matt Swain Date: Tue, 2 May 2023 15:48:54 +1000 Subject: [PATCH 008/159] upload artifact --- .github/workflows/build.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ecc8e465f..ff735dda8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,3 +33,14 @@ jobs: - name: Test if: matrix.configuration == 'Debug' run: dotnet test --no-build + + - name: Pack + if: matrix.configuration == 'Release' + run: dotnet pack --configuration ${{ matrix.configuration }} -o packages + + - name: Upload packages + if: matrix.configuration == 'Release' + uses: actions/upload-artifact@v3 + with: + name: packages + path: packages/ From 8460752be4ca52650f4a3448f22671f098e8994e Mon Sep 17 00:00:00 2001 From: Matt Swain Date: Tue, 2 May 2023 15:52:24 +1000 Subject: [PATCH 009/159] added retention days for packages --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff735dda8..23fd31588 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,3 +44,4 @@ jobs: with: name: packages path: packages/ + retention-days: 5 From 4b67e27a8a378049c69b2dd8aedcc48957655eca Mon Sep 17 00:00:00 2001 From: Matt Swain Date: Tue, 5 Sep 2023 14:02:45 +1000 Subject: [PATCH 010/159] Update build.yml --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c471f565..98fc1e5c0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,7 +36,7 @@ jobs: - name: Pack if: matrix.configuration == 'Release' - run: dotnet pack --configuration ${{ matrix.configuration }} -o packages + run: dotnet pack --configuration ${{ matrix.configuration }} -o packages --no-build - name: Upload packages if: matrix.configuration == 'Release' @@ -44,4 +44,4 @@ jobs: with: name: packages path: packages/ - retention-days: 5 \ No newline at end of file + retention-days: 5 From a3a86d2846d4c37157c705e89a5819b82c674fc4 Mon Sep 17 00:00:00 2001 From: Heather Cox Date: Fri, 8 Dec 2023 13:25:09 +1100 Subject: [PATCH 011/159] update asp net core compoents package version --- samples/SharedDemo/SharedDemo.csproj | 4 ++-- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/SharedDemo/SharedDemo.csproj b/samples/SharedDemo/SharedDemo.csproj index 79272cab8..6fbc3a0bf 100644 --- a/samples/SharedDemo/SharedDemo.csproj +++ b/samples/SharedDemo/SharedDemo.csproj @@ -5,8 +5,8 @@ - - + + diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index 099acd468..f81d030a4 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -20,8 +20,8 @@ - - + + From c9844ed888f4f1c3bf91c767376beba5092db746 Mon Sep 17 00:00:00 2001 From: Lars Westermann Date: Tue, 6 Dec 2022 11:42:05 +0100 Subject: [PATCH 012/159] Fix link hover stroke opacity --- src/Blazor.Diagrams/Components/LinkWidget.razor.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs index 66cf6a72a..5f1d6d58a 100644 --- a/src/Blazor.Diagrams/Components/LinkWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/LinkWidget.razor.cs @@ -22,7 +22,7 @@ private RenderFragment GetSelectionHelperPath(string color, string d, int index) 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(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)); @@ -59,4 +59,4 @@ private LinkVertexModel CreateVertex(double clientX, double clientY, int index) Link.Refresh(); return vertex; } -} \ No newline at end of file +} From 87d8464b08885d5f7fe66efdb8efb4fd8fc5ddfb Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sat, 30 Sep 2023 11:12:58 +0100 Subject: [PATCH 013/159] Add Radius example to Path Generators documentation --- .../Pages/Documentation/Links/PathGenerators.razor | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/site/Site/Pages/Documentation/Links/PathGenerators.razor b/site/Site/Pages/Documentation/Links/PathGenerators.razor index 4b78263ce..4589d5052 100644 --- a/site/Site/Pages/Documentation/Links/PathGenerators.razor +++ b/site/Site/Pages/Documentation/Links/PathGenerators.razor @@ -35,7 +35,8 @@ link.PathGenerator = new SmoothPathGenerator();

Straight Path Generator

- The StraightPathGenerator generates straight lines. + The StraightPathGenerator generates straight lines.
+ You can also choose a radius for when the path contains vertices or direction changes.

Usage

@@ -78,12 +79,13 @@ link.PathGenerator = new StraightPathGenerator(); // 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 stpgBottomPort1 = stpgNode1.AddPort(PortAlignment.Bottom); 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(); - + var link = _stpgDiagram.Links.Add(new LinkModel(stpgBottomPort1, stpgBottomPort2)); + link.PathGenerator = new StraightPathGenerator(10); + link.AddVertex(new Point(200, 250)); } } \ No newline at end of file From b4f10e5c511b3e32ecad67676688e606be846e1d Mon Sep 17 00:00:00 2001 From: Suraj Date: Mon, 16 Oct 2023 12:09:20 +0530 Subject: [PATCH 014/159] fix type of diagram --- docs/Diagram-Demo/Pages/Diagrams.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/Diagram-Demo/Pages/Diagrams.razor b/docs/Diagram-Demo/Pages/Diagrams.razor index 2c56d7ef7..305809e73 100644 --- a/docs/Diagram-Demo/Pages/Diagrams.razor +++ b/docs/Diagram-Demo/Pages/Diagrams.razor @@ -28,7 +28,7 @@ or it will not be rendered. @code { - private Diagram Diagram { get; set; } + private BlazorDiagram Diagram { get; set; } protected override void OnInitialized() { From 83cb01e46c89efd3618d3445fe2b582b19e5e5dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20Z=C3=B6bl?= <41567572+K0369@users.noreply.github.com> Date: Mon, 16 Oct 2023 12:01:37 +0200 Subject: [PATCH 015/159] Adding check for target being a portModel --- site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs index 835c8fdcd..2de170860 100644 --- a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs @@ -76,7 +76,7 @@ private void OnLinKTargetChanged(BaseLinkModel link, Anchor? oldTarget, Anchor? private void OnLinkRemoved(BaseLinkModel link) { (link.Source.Model as PortModel)!.Parent.Refresh(); - if (link.Target != null) (link.Target.Model as PortModel)!.Parent.Refresh(); + if (link.Target != null && link.Target.Model is PortModel portModel) portModel.Parent.Refresh(); link.TargetChanged -= OnLinKTargetChanged; } } \ No newline at end of file From 92df6feb333f41c952aa495dc9ea3517db4dc15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20Z=C3=B6bl?= <41567572+K0369@users.noreply.github.com> Date: Mon, 16 Oct 2023 12:02:50 +0200 Subject: [PATCH 016/159] Fixing nodes not updating on change of the link target --- site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs index 2de170860..4e5717172 100644 --- a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs @@ -67,7 +67,7 @@ private void OnLinkAdded(BaseLinkModel link) private void OnLinKTargetChanged(BaseLinkModel link, Anchor? oldTarget, Anchor? newTarget) { - if (oldTarget == null && newTarget != null) // First attach + if (link.IsAttached && newTarget is not null) { (newTarget.Model as PortModel)!.Parent.Refresh(); } From 0a424a8200e3b63de92bd47d27414f548adda904 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20Z=C3=B6bl?= <41567572+K0369@users.noreply.github.com> Date: Sun, 22 Oct 2023 13:53:58 +0200 Subject: [PATCH 017/159] fixing NRE of onLinkRemoved in landing showcase diagram --- site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs index 4e5717172..c78b72851 100644 --- a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs @@ -76,7 +76,10 @@ private void OnLinKTargetChanged(BaseLinkModel link, Anchor? oldTarget, Anchor? private void OnLinkRemoved(BaseLinkModel link) { (link.Source.Model as PortModel)!.Parent.Refresh(); - if (link.Target != null && link.Target.Model is PortModel portModel) portModel.Parent.Refresh(); + if (link.Target is SinglePortAnchor anchor && anchor.Model is PortModel portModel) + { + portModel.Parent.Refresh(); + } link.TargetChanged -= OnLinKTargetChanged; } } \ No newline at end of file From f4e9b332d2697dcb4417f7982297cee8aa587a55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20Z=C3=B6bl?= <41567572+K0369@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:24:55 +0200 Subject: [PATCH 018/159] refactoring link target refreshing in landing showcase diagram --- .../Components/Landing/LandingShowcaseDiagram.razor.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs index c78b72851..17ffd29ba 100644 --- a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs @@ -67,9 +67,12 @@ private void OnLinkAdded(BaseLinkModel link) private void OnLinKTargetChanged(BaseLinkModel link, Anchor? oldTarget, Anchor? newTarget) { - if (link.IsAttached && newTarget is not null) + // only refresh on the first time the link is attached + if (oldTarget is PositionAnchor + && newTarget.Model is PortModel targetModel + && link.IsAttached) { - (newTarget.Model as PortModel)!.Parent.Refresh(); + targetModel.Parent.Refresh(); } } From 302e31e06ff7c351bca2d8dc7d01075a4d970c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20Z=C3=B6bl?= <41567572+K0369@users.noreply.github.com> Date: Sun, 22 Oct 2023 14:27:04 +0200 Subject: [PATCH 019/159] Demo-site: changing signature of onChange to reflect actual values --- site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs index 17ffd29ba..d2933a427 100644 --- a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs @@ -65,7 +65,7 @@ private void OnLinkAdded(BaseLinkModel link) link.TargetChanged += OnLinKTargetChanged; } - private void OnLinKTargetChanged(BaseLinkModel link, Anchor? oldTarget, Anchor? newTarget) + private void OnLinKTargetChanged(BaseLinkModel link, Anchor oldTarget, Anchor newTarget) { // only refresh on the first time the link is attached if (oldTarget is PositionAnchor From 92c5854d8616f8ff93ba0e60960fafc9128e1a28 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Sun, 22 Oct 2023 19:06:36 +0100 Subject: [PATCH 020/159] Add Route property to BaseLinkModel --- .../Models/Base/BaseLinkModel.cs | 3 +++ src/Blazor.Diagrams/wwwroot/script.js | 5 +++-- src/Blazor.Diagrams/wwwroot/script.min.js | 2 +- src/Blazor.Diagrams/wwwroot/script.min.js.gz | Bin 520 -> 521 bytes 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs index acba5848e..26a28dae2 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/BaseLinkModel.cs @@ -30,6 +30,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 Point[]? Route { get; private set; } public PathGeneratorResult? PathGeneratorResult { get; private set; } public bool IsAttached => Source is not PositionAnchor && Target is not PositionAnchor; public Router? Router { get; set; } @@ -129,11 +130,13 @@ private void GeneratePath() var target = Target.GetPosition(this, route); if (source != null && target != null) { + Route = route; PathGeneratorResult = pathGenerator.GetResult(Diagram, this, route, source, target); return; } } + Route = null; PathGeneratorResult = null; } diff --git a/src/Blazor.Diagrams/wwwroot/script.js b/src/Blazor.Diagrams/wwwroot/script.js index 293b8768d..be3b4728c 100644 --- a/src/Blazor.Diagrams/wwwroot/script.js +++ b/src/Blazor.Diagrams/wwwroot/script.js @@ -40,8 +40,9 @@ var s = { } }, unobserve: (element, id) => { - if (!element) return; - s.ro.unobserve(element); + if (element) { + 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 f295e538c..a4065277c 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)=>{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 +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 e444f12bdf743d6e9412c5556505e665bd16d3f3..a4db964787449a2e8b2e14e006712d8890102ecb 100644 GIT binary patch delta 503 zcmV2*)vhHMx zH2L1+!S`)#ygZv$goUH6p_%$dwV7Ue4>?@JF6GgRl?qkl6S;S|b7#|b71pr>tY zdkJ~XoNR1`S4zPcyu3Jx{>S*HZ+ae_!Fo)LQA*X;?dqv%3i)cqmcR-G%3v$$VDF#o zoyFRln|Oqh<@fMlqkFcw0M3Vji!iC5vM22@OP_^izRH02jm6BVn-KI-R>8M~iaL-w zsS$(Qc1X*Sm45@127mUJNALos-m1-d~};NtXA;0 zf)o0M{f_$lB&lGu6ycJkG@L~lyqrr6xLBgAo{$S11jv$>8o#)Rk`{s5L1ejcdKYy2 zZgM_P%U_-WwI|OD5aSeawxzguf&)<%9W1HNL`R;dN^oLoXgb>LOc*!k+=&KmhGBgm zBm6;ryKVHd51%I9QN`DSHs( z(&Xdgd%u7F+M1BXa;OY$O@vqsdqu%i4`5Tc0lM}*T8EpDtplVxsHnj567^q8LaNRe z2)pd2CqvHT?K(o(LJ&fhmqYD?a5i(8Mg0OPhstB5jLK8)-hbJZawX~3#0e%UD9R;CdANu=czq}dlxmJ%4vbvTUZJk|sPT)7r1=nJ97d5_XEwof z?V1_;|a3U#`=0665XKbNU6STO_u03Qzg>;M1& From bf3dbac01117a21f2c5bab42eadeb7d92fb0b3c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20Z=C3=B6bl?= <41567572+K0369@users.noreply.github.com> Date: Mon, 23 Oct 2023 16:46:12 +0200 Subject: [PATCH 021/159] Adding check for "ShouldDelete"-constraint to remove control --- .../Controls/Default/RemoveControl.cs | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs index 1e1704256..b9dacfc8a 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs @@ -23,18 +23,25 @@ public RemoveControl(IPositionProvider positionProvider) public override Point? GetPosition(Model model) => _positionProvider.GetPosition(model); - public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs _) + public override async ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs _) { switch (model) { + case NodeModel node: - diagram.Nodes.Remove(node); + if (await diagram.Options.Constraints.ShouldDeleteNode.Invoke(node)) + { + diagram.Nodes.Remove(node); + } break; case BaseLinkModel link: - diagram.Links.Remove(link); + if (await diagram.Options.Constraints.ShouldDeleteLink.Invoke(link)) + { + diagram.Links.Remove(link); + } break; - } - return ValueTask.CompletedTask; + + } } } \ No newline at end of file From a90b859895d522de50423fa0aa7a05083e69d965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20Z=C3=B6bl?= <41567572+K0369@users.noreply.github.com> Date: Mon, 23 Oct 2023 16:47:30 +0200 Subject: [PATCH 022/159] Adding unit tests --- .../Controls/RemoveControlTests.cs | 179 ++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs diff --git a/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs b/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs new file mode 100644 index 000000000..e24fce0ba --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs @@ -0,0 +1,179 @@ +using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using Moq; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Controls +{ + public class RemoveControlTests + { + public PointerEventArgs PointerEventArgs + => new(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); + + [Fact] + public async Task OnPointerDown_NoConstraints_RemovesNode() + { + // Arrange + RemoveControl removeControl = new(0, 0); + Diagram diagram = new TestDiagram(); + var nodeMock = new Mock(Point.Zero); + var node = diagram.Nodes.Add(nodeMock.Object); + + // Act + await removeControl.OnPointerDown(diagram, node, PointerEventArgs); + + // Assert + Assert.Empty(diagram.Nodes); + } + + [Fact] + public async Task OnPointerDown_ShouldDeleteNodeTrue_RemovesNode() + { + // Arrange + RemoveControl removeControl = new(0, 0); + Diagram diagram = new TestDiagram( + new Options.DiagramOptions + { + Constraints = + { + ShouldDeleteNode = (node) => ValueTask.FromResult(true) + } + }); + var nodeMock = new Mock(Point.Zero); + var node = diagram.Nodes.Add(nodeMock.Object); + + // Act + await removeControl.OnPointerDown(diagram, node, PointerEventArgs); + + // Assert + Assert.Empty(diagram.Nodes); + } + + [Fact] + public async Task OnPointerDown_ShouldDeleteNodeFalse_KeepsNode() + { + // Arrange + RemoveControl removeControl = new(0, 0); + Diagram diagram = new TestDiagram( + new Options.DiagramOptions + { + Constraints = + { + ShouldDeleteNode = (node) => ValueTask.FromResult(false) + } + }); + var nodeMock = new Mock(Point.Zero); + var node = diagram.Nodes.Add(nodeMock.Object); + + // Act + await removeControl.OnPointerDown(diagram, node, PointerEventArgs); + + // Assert + Assert.Contains(node, diagram.Nodes); + } + + [Fact] + public async Task OnPointerDown_NoConstraints_RemovesLink() + { + // Arrange + RemoveControl removeControl = new(0, 0); + Diagram diagram = new TestDiagram(); + + var node1 = new NodeModel(new Point(50, 50)); + var node2 = new NodeModel(new Point(300, 300)); + diagram.Nodes.Add(new[] { node1, node2 }); + node1.AddPort(PortAlignment.Right); + node2.AddPort(PortAlignment.Left); + + var link = new LinkModel( + node1.GetPort(PortAlignment.Right)!, + node2.GetPort(PortAlignment.Left)! + ); + + diagram.Links.Add(link); + + // Act + await removeControl.OnPointerDown(diagram, link, PointerEventArgs); + + // Assert + Assert.Empty(diagram.Links); + } + + [Fact] + public async Task OnPointerDown_ShouldDeleteLinkTrue_RemovesLink() + { + // Arrange + RemoveControl removeControl = new(0, 0); + Diagram diagram = new TestDiagram( + new Options.DiagramOptions + { + Constraints = + { + ShouldDeleteLink = (node) => ValueTask.FromResult(true) + } + }); + + var node1 = new NodeModel(new Point(50, 50)); + var node2 = new NodeModel(new Point(300, 300)); + diagram.Nodes.Add(new[] { node1, node2 }); + node1.AddPort(PortAlignment.Right); + node2.AddPort(PortAlignment.Left); + + var link = new LinkModel( + node1.GetPort(PortAlignment.Right)!, + node2.GetPort(PortAlignment.Left)! + ); + + diagram.Links.Add(link); + + // Act + await removeControl.OnPointerDown(diagram, link, PointerEventArgs); + + // Assert + Assert.Empty(diagram.Links); + } + + [Fact] + public async Task OnPointerDown_ShouldDeleteLinkFalse_KeepsLink() + { + // Arrange + RemoveControl removeControl = new(0, 0); + Diagram diagram = new TestDiagram( + new Options.DiagramOptions + { + Constraints = + { + ShouldDeleteLink = (node) => ValueTask.FromResult(false) + } + }); + + var node1 = new NodeModel(new Point(50, 50)); + var node2 = new NodeModel(new Point(300, 300)); + diagram.Nodes.Add(new[] { node1, node2 }); + node1.AddPort(PortAlignment.Right); + node2.AddPort(PortAlignment.Left); + + var link = new LinkModel( + node1.GetPort(PortAlignment.Right)!, + node2.GetPort(PortAlignment.Left)! + ); + + diagram.Links.Add(link); + + // Act + await removeControl.OnPointerDown(diagram, link, PointerEventArgs); + + // Assert + Assert.Contains(link, diagram.Links); + } + + } +} From 45747a77926f3d5d4434fe12859835b93b5efe32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20Z=C3=B6bl?= <41567572+K0369@users.noreply.github.com> Date: Mon, 23 Oct 2023 17:28:43 +0200 Subject: [PATCH 023/159] Fixing #369 by adding invariant culture conversion --- .../TestComponents/CustomVertexWidget.razor | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Blazor.Diagrams.Tests/TestComponents/CustomVertexWidget.razor b/tests/Blazor.Diagrams.Tests/TestComponents/CustomVertexWidget.razor index 21bc519c4..73d2b4d8a 100644 --- a/tests/Blazor.Diagrams.Tests/TestComponents/CustomVertexWidget.razor +++ b/tests/Blazor.Diagrams.Tests/TestComponents/CustomVertexWidget.razor @@ -1,6 +1,6 @@ @using Blazor.Diagrams.Core.Models; - + @code { [Parameter] public LinkVertexModel Vertex { get; set; } = null!; From 1ba36f767ac6ec033ac5c64c6caac425dd4181d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Merlin=20Z=C3=B6bl?= Date: Thu, 26 Oct 2023 10:03:32 +0200 Subject: [PATCH 024/159] Adding functionality for GroupModel Removal to RemoveControl --- .../Controls/Default/RemoveControl.cs | 43 ++++++--- .../Controls/RemoveControlTests.cs | 93 +++++++++++++++++++ 2 files changed, 124 insertions(+), 12 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs index b9dacfc8a..9227fcf98 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs @@ -25,23 +25,42 @@ public RemoveControl(IPositionProvider positionProvider) public override async ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs _) { - switch (model) + if (await ShouldDeleteModel(diagram, model)) { + DeleteModel(diagram, model); + } + } + private static void DeleteModel(Diagram diagram, Model model) + { + switch (model) + { + case GroupModel group: + diagram.Groups.Delete(group); + return; case NodeModel node: - if (await diagram.Options.Constraints.ShouldDeleteNode.Invoke(node)) - { - diagram.Nodes.Remove(node); - } - break; + diagram.Nodes.Remove(node); + return; + case BaseLinkModel link: - if (await diagram.Options.Constraints.ShouldDeleteLink.Invoke(link)) - { - diagram.Links.Remove(link); - } - break; + diagram.Links.Remove(link); + return; + } + } - + private static async ValueTask ShouldDeleteModel(Diagram diagram, Model model) + { + if (model.Locked) + { + return false; } + + return model switch + { + GroupModel group => await diagram.Options.Constraints.ShouldDeleteGroup.Invoke(group), + NodeModel node => await diagram.Options.Constraints.ShouldDeleteNode.Invoke(node), + BaseLinkModel link => await diagram.Options.Constraints.ShouldDeleteLink.Invoke(link), + _ => false, + }; } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs b/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs index e24fce0ba..1b2201ef5 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs @@ -175,5 +175,98 @@ public async Task OnPointerDown_ShouldDeleteLinkFalse_KeepsLink() Assert.Contains(link, diagram.Links); } + [Fact] + public async Task OnPointerDown_NoConstraints_RemovesGroup() + { + // Arrange + RemoveControl removeControl = new(0, 0); + Diagram diagram = new TestDiagram(); + + var node1 = new NodeModel(new Point(50, 50)); + var node2 = new NodeModel(new Point(300, 300)); + diagram.Nodes.Add(new[] { node1, node2 }); + node1.AddPort(PortAlignment.Right); + node2.AddPort(PortAlignment.Left); + + var group = new GroupModel(new[] { node1, node2 }); + + + diagram.Groups.Add(group); + + // Act + await removeControl.OnPointerDown(diagram, group, PointerEventArgs); + + // Assert + Assert.Empty(diagram.Groups); + Assert.Empty(diagram.Nodes); + } + + [Fact] + public async Task OnPointerDown_ShouldDeleteGroupTrue_RemovesGroup() + { + // Arrange + RemoveControl removeControl = new(0, 0); + Diagram diagram = new TestDiagram( + new Options.DiagramOptions + { + Constraints = + { + ShouldDeleteGroup = (node) => ValueTask.FromResult(true) + } + }); + + var node1 = new NodeModel(new Point(50, 50)); + var node2 = new NodeModel(new Point(300, 300)); + diagram.Nodes.Add(new[] { node1, node2 }); + node1.AddPort(PortAlignment.Right); + node2.AddPort(PortAlignment.Left); + + var group = new GroupModel(new[] { node1, node2 }); + + + diagram.Groups.Add(group); + + // Act + await removeControl.OnPointerDown(diagram, group, PointerEventArgs); + + // Assert + Assert.Empty(diagram.Groups); + Assert.Empty(diagram.Nodes); + } + + [Fact] + public async Task OnPointerDown_ShouldDeleteGroupFalse_KeepsGroup() + { + // Arrange + RemoveControl removeControl = new(0, 0); + Diagram diagram = new TestDiagram( + new Options.DiagramOptions + { + Constraints = + { + ShouldDeleteGroup = (node) => ValueTask.FromResult(false) + } + }); + + var node1 = new NodeModel(new Point(50, 50)); + var node2 = new NodeModel(new Point(300, 300)); + diagram.Nodes.Add(new[] { node1, node2 }); + node1.AddPort(PortAlignment.Right); + node2.AddPort(PortAlignment.Left); + + var group = new GroupModel(new[] { node1, node2 }); + + + diagram.Groups.Add(group); + + // Act + await removeControl.OnPointerDown(diagram, group, PointerEventArgs); + + // Assert + Assert.Contains(group, diagram.Groups); + Assert.Contains(node1, diagram.Nodes); + Assert.Contains(node2, diagram.Nodes); + } + } } From f9a0ae12bde965a8f8a6ff7fb3ca97885c350540 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 27 Oct 2023 11:30:10 +0100 Subject: [PATCH 025/159] Update Versions and CHANGELOG --- CHANGELOG.md | 13 +++++++++++++ .../Blazor.Diagrams.Algorithms.csproj | 6 +++--- .../Blazor.Diagrams.Core.csproj | 6 +++--- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 6 +++--- 4 files changed, 22 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5358a9a8..bd66b19c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ 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.1) - 2023-10-27 + +### Added + +- `Route` property to `BaseLinkModel` to hold the result of the executed router + +### Fixed + +- Constraints not checked when using `RemoveControl` (thanks to @[K0369](https://github.com/K0369)) +- NRE Exception on the landing page demo when dragging a new link and not linking it to something (thanks to @[K0369](https://github.com/K0369)) +- `LinkVertexWidgetTests` failing on cultures that are not using a dot as decimal separator (thanks to @[K0369](https://github.com/K0369)) +- NRE exception in the Diagram Demo project (thanks to @[Suraj0500](https://github.com/Suraj0500)) + ## Diagrams (3.0.0) - 2023-08-14 Finally, the new documentation website is here! diff --git a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj index 8681e4d5c..e137e0a10 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 - 3.0.0 - 3.0.0 + 3.0.1 + 3.0.1 https://github.com/zHaytam/Blazor.Diagrams - 3.0.0 + 3.0.1 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 496607a3b..cb81f6203 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 - 3.0.0 - 3.0.0 + 3.0.1 + 3.0.1 https://github.com/Blazor-Diagrams/Blazor.Diagrams - 3.0.0 + 3.0.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 f81d030a4..3e806eb7f 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -5,11 +5,11 @@ enable zHaytam MIT - 3.0.0 - 3.0.0 + 3.0.1 + 3.0.1 https://github.com/Blazor-Diagrams/Blazor.Diagrams A fully customizable and extensible all-purpose diagrams library for Blazor - 3.0.0 + 3.0.1 true blazor diagrams diagramming svg drag Z.Blazor.Diagrams From 2b69a4eaedb9c3705ae4812c3838942063ddb6e0 Mon Sep 17 00:00:00 2001 From: Haytam Zanid Date: Fri, 27 Oct 2023 11:30:23 +0100 Subject: [PATCH 026/159] Add new workflow to push to nuget --- .github/workflows/push.yml | 30 ++++++++++++++++++++++++++++++ Blazor.Diagrams.sln | 1 + 2 files changed, 31 insertions(+) create mode 100644 .github/workflows/push.yml diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 000000000..73a9ff327 --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,30 @@ +name: PushToNuget +env: + NUGET_DIR: '${{ github.workspace }}/nuget' + +on: + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Pack Blazor.Diagrams.Core + working-directory: src/Blazor.Diagrams.Core + run: 'dotnet pack --configuration Release --output ${{ env.NUGET_DIR }}' + + - name: Pack Blazor.Diagrams + working-directory: src/Blazor.Diagrams + run: 'dotnet pack --configuration Release --output ${{ env.NUGET_DIR }}' + + - name: Pack Blazor.Diagrams.Algorithms + working-directory: src/Blazor.Diagrams.Algorithms + run: 'dotnet pack --configuration Release --output ${{ env.NUGET_DIR }}' + + - name: Push + run: | + foreach($file in (Get-ChildItem "${{ env.NUGET_DIR }}" -Recurse -Include *.nupkg)) { + dotnet nuget push $file --api-key "${{ secrets.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate + } diff --git a/Blazor.Diagrams.sln b/Blazor.Diagrams.sln index 5f73d1027..862fc051e 100644 --- a/Blazor.Diagrams.sln +++ b/Blazor.Diagrams.sln @@ -24,6 +24,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .github\workflows\build.yml = .github\workflows\build.yml CHANGELOG.md = CHANGELOG.md .github\workflows\main.yml = .github\workflows\main.yml + .github\workflows\push.yml = .github\workflows\push.yml README.md = README.md EndProjectSection EndProject From 3b28ad0cf7bbeb1b7b862475004ae650bc052982 Mon Sep 17 00:00:00 2001 From: Haytam Zanid <34218324+zHaytam@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:17:55 +0100 Subject: [PATCH 027/159] Update push.yml --- .github/workflows/push.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index 73a9ff327..e2b92944a 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -13,15 +13,15 @@ jobs: - name: Pack Blazor.Diagrams.Core working-directory: src/Blazor.Diagrams.Core - run: 'dotnet pack --configuration Release --output ${{ env.NUGET_DIR }}' + run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}' - name: Pack Blazor.Diagrams working-directory: src/Blazor.Diagrams - run: 'dotnet pack --configuration Release --output ${{ env.NUGET_DIR }}' + run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}' - name: Pack Blazor.Diagrams.Algorithms working-directory: src/Blazor.Diagrams.Algorithms - run: 'dotnet pack --configuration Release --output ${{ env.NUGET_DIR }}' + run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}' - name: Push run: | From 7d722839ee5a1332b6ca9ffa9315cb49b5ffcaf2 Mon Sep 17 00:00:00 2001 From: Haytam Zanid <34218324+zHaytam@users.noreply.github.com> Date: Fri, 27 Oct 2023 12:40:02 +0100 Subject: [PATCH 028/159] Update push.yml --- .github/workflows/push.yml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index e2b92944a..d15fccc35 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -23,8 +23,11 @@ jobs: working-directory: src/Blazor.Diagrams.Algorithms run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}' - - name: Push - run: | - foreach($file in (Get-ChildItem "${{ env.NUGET_DIR }}" -Recurse -Include *.nupkg)) { - dotnet nuget push $file --api-key "${{ secrets.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate - } + - name: Push Blazor.Diagrams.Core + run: 'dotnet nuget push ${{ env.NUGET_DIR }}/Z.Blazor.Diagrams.Core.3.0.1.nupkg --api-key "${{ secrets.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate' + + - name: Push Blazor.Diagrams + run: 'dotnet nuget push ${{ env.NUGET_DIR }}/Z.Blazor.Diagrams.3.0.1.nupkg --api-key "${{ secrets.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate' + + - name: Push Blazor.Diagrams.Algorithms + run: 'dotnet nuget push ${{ env.NUGET_DIR }}/Z.Blazor.Diagrams.Algorithms.3.0.1.nupkg --api-key "${{ secrets.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate' From 3beabc4eec334b483df583588cd3f1debee39ecf Mon Sep 17 00:00:00 2001 From: Heather <30523814+Heathermcx@users.noreply.github.com> Date: Tue, 12 Dec 2023 10:37:27 +1100 Subject: [PATCH 029/159] Use Controls to implement node resizing (#10) --- .../Documentation/Controls/Overview.razor | 35 ++++++++ .../Controls/Default/ResizeControl.cs | 38 ++++++++ src/Blazor.Diagrams.Core/Models/GroupModel.cs | 26 ++---- src/Blazor.Diagrams.Core/Models/NodeModel.cs | 23 ++++- .../Resizing/BottomLeftResizerProvider.cs | 75 ++++++++++++++++ .../Resizing/BottomRightResizerProvider.cs | 66 ++++++++++++++ .../Positions/Resizing/IResizerProvider.cs | 14 +++ .../Resizing/TopLeftResizerProvider.cs | 75 ++++++++++++++++ .../Resizing/TopRightResizerProvider.cs | 74 ++++++++++++++++ src/Blazor.Diagrams/BlazorDiagram.cs | 3 +- .../Controls/ResizeControlWidget.razor | 10 +++ .../Components/NodeWidget.razor | 10 ++- .../wwwroot/default.styles.css | 24 +++++ .../wwwroot/default.styles.min.css | 2 +- .../wwwroot/default.styles.min.css.gz | Bin 683 -> 735 bytes src/Blazor.Diagrams/wwwroot/style.min.css.gz | Bin 409 -> 409 bytes .../Controls/ResizeControlTests.cs | 75 ++++++++++++++++ .../BottomLeftResizerProviderTests.cs | 82 ++++++++++++++++++ .../BottomRightResizerProviderTests.cs | 80 +++++++++++++++++ .../Resizing/TopLeftResizerProviderTests.cs | 82 ++++++++++++++++++ .../Resizing/TopRightResizerProviderTests.cs | 82 ++++++++++++++++++ .../Controls/ResizeControlWidgetTests.cs | 25 ++++++ 22 files changed, 876 insertions(+), 25 deletions(-) create mode 100644 src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs create mode 100644 src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs create mode 100644 src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs create mode 100644 src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs create mode 100644 src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs create mode 100644 src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs create mode 100644 src/Blazor.Diagrams/Components/Controls/ResizeControlWidget.razor create mode 100644 tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs create mode 100644 tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs create mode 100644 tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs create mode 100644 tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs create mode 100644 tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs create mode 100644 tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs diff --git a/site/Site/Pages/Documentation/Controls/Overview.razor b/site/Site/Pages/Documentation/Controls/Overview.razor index bd87fa9a1..335838ae5 100644 --- a/site/Site/Pages/Documentation/Controls/Overview.razor +++ b/site/Site/Pages/Documentation/Controls/Overview.razor @@ -3,6 +3,7 @@ @using Blazor.Diagrams.Core.Controls; @using Blazor.Diagrams.Core.PathGenerators; @using Blazor.Diagrams.Core.Positions; +@using Blazor.Diagrams.Core.Positions.Resizing; @using Blazor.Diagrams.Core.Routers; @layout DocumentationLayout @inherits DocumentationPage @@ -135,6 +136,26 @@ Diagram.Controls.AddFor(SomeModel) +

Resize Control

+ +

+ The ResizeControl adds a resizer which is a box that when dragged, can resize the node. The resizer position and movement of the node is controlled using a Resizer Provider.
+ There are four ResizerProviders, one for each corner. Custom resizing behavior can be created by implementing IResizerProvider. +

+ +

+Diagram.Controls.AddFor(SomeModel).Add(new ResizeControl(new BottomRightResizerProvider()));
+Diagram.Controls.AddFor(SomeModel).Add(new ResizeControl(new BottomLeftResizerProvider()));
+Diagram.Controls.AddFor(SomeModel).Add(new ResizeControl(new TopRightResizerProvider()));
+Diagram.Controls.AddFor(SomeModel).Add(new ResizeControl(new TopLeftResizerProvider()));
+
+ +
+ + + +
+ @@ -143,6 +164,7 @@ Diagram.Controls.AddFor(SomeModel) private BlazorDiagram _rDiagram = new(); private BlazorDiagram _ahDiagram = new(); private BlazorDiagram _dnlDiagram = new(); + private BlazorDiagram _resizerDiagram = new(); protected override void OnInitialized() { @@ -198,5 +220,18 @@ Diagram.Controls.AddFor(SomeModel) _dnlDiagram.Controls.AddFor(dnlNode2).Add(new DragNewLinkControl(0, 0.5, offsetX: -20)); _dnlDiagram.SelectModel(dnlNode1, false); _dnlDiagram.SelectModel(dnlNode2, false); + + // Resize Control + var resizeNode1 = _resizerDiagram.Nodes.Add(new NodeModel(new Point(100, 100))); + var resizeNode2 = _resizerDiagram.Nodes.Add(new NodeModel(new Point(500, 150))); + resizeNode1.Title = "Title"; + resizeNode2.Title = "Title"; + _resizerDiagram.Controls.AddFor(resizeNode1, ControlsType.OnSelection).Add(new ResizeControl(new BottomRightResizerProvider())); + _resizerDiagram.Controls.AddFor(resizeNode1, ControlsType.OnSelection).Add(new ResizeControl(new BottomLeftResizerProvider())); + _resizerDiagram.Controls.AddFor(resizeNode1, ControlsType.OnSelection).Add(new ResizeControl(new TopRightResizerProvider())); + _resizerDiagram.Controls.AddFor(resizeNode1, ControlsType.OnSelection).Add(new ResizeControl(new TopLeftResizerProvider())); + _resizerDiagram.Controls.AddFor(resizeNode2, ControlsType.OnSelection).Add(new ResizeControl(new BottomRightResizerProvider())); + _resizerDiagram.SelectModel(resizeNode1, false); + _resizerDiagram.SelectModel(resizeNode2, false); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs new file mode 100644 index 000000000..4517f673d --- /dev/null +++ b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs @@ -0,0 +1,38 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Positions.Resizing; +using System.Threading.Tasks; + +namespace Blazor.Diagrams.Core.Controls.Default +{ + public class ResizeControl : ExecutableControl + { + private readonly IResizerProvider _resizeProvider; + + public ResizeControl(IResizerProvider resizeProvider) + { + _resizeProvider = resizeProvider; + } + + public override Point? GetPosition(Model model) => _resizeProvider.GetPosition(model); + + public string? Class => _resizeProvider.Class; + + public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) + { + _resizeProvider.OnResizeStart(diagram, model, e); + diagram.PointerMove += _resizeProvider.OnPointerMove; + diagram.PointerUp += _resizeProvider.OnResizeEnd; + diagram.PointerUp += (_, _) => OnResizeEnd(diagram); + + return ValueTask.CompletedTask; + } + + void OnResizeEnd(Diagram diagram) + { + diagram.PointerMove -= _resizeProvider.OnPointerMove; + diagram.PointerUp -= _resizeProvider.OnResizeEnd; + } + } +} diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index 041d872ec..4a450be1b 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -27,13 +27,10 @@ public void AddChild(NodeModel child) { _children.Add(child); child.Group = this; - child.SizeChanged += OnNodeChanged; + child.SizeChanging += OnNodeChanged; child.Moving += OnNodeChanged; - if (UpdateDimensions()) - { - Refresh(); - } + UpdateDimensions(); } public void RemoveChild(NodeModel child) @@ -42,14 +39,10 @@ public void RemoveChild(NodeModel child) return; child.Group = null; - child.SizeChanged -= OnNodeChanged; + child.SizeChanging -= OnNodeChanged; child.Moving -= OnNodeChanged; - if (UpdateDimensions()) - { - Refresh(); - RefreshLinks(); - } + UpdateDimensions(); } public override void SetPosition(double x, double y) @@ -83,7 +76,7 @@ public void Ungroup() foreach (var child in Children) { child.Group = null; - child.SizeChanged -= OnNodeChanged; + child.SizeChanging -= OnNodeChanged; child.Moving -= OnNodeChanged; } @@ -96,7 +89,7 @@ private void Initialize(IEnumerable children) { _children.Add(child); child.Group = this; - child.SizeChanged += OnNodeChanged; + child.SizeChanging += OnNodeChanged; child.Moving += OnNodeChanged; } @@ -105,10 +98,7 @@ private void Initialize(IEnumerable children) private void OnNodeChanged(NodeModel node) { - if (UpdateDimensions()) - { - Refresh(); - } + UpdateDimensions(); } private bool UpdateDimensions() @@ -128,7 +118,7 @@ private bool UpdateDimensions() TriggerMoving(); } - Size = new Size(bounds.Width + Padding * 2, bounds.Height + Padding * 2); + SetSize(bounds.Width + Padding * 2, bounds.Height + Padding * 2); return true; } } diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs index 053c5b3d8..610a5aac3 100644 --- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs +++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs @@ -11,7 +11,9 @@ public class NodeModel : MovableModel, IHasBounds, IHasShape, ILinkable private readonly List _ports = new(); private readonly List _links = new(); private Size? _size; + public Size MinimumDimensions { get; set; } = new Size(0, 0); + public event Action? SizeChanging; public event Action? SizeChanged; public event Action? Moving; @@ -28,11 +30,7 @@ public Size? Size get => _size; set { - if (value?.Equals(_size) == true) - return; - _size = value; - SizeChanged?.Invoke(this); } } public bool ControlledSize { get; init; } @@ -103,6 +101,23 @@ public override void SetPosition(double x, double y) Moving?.Invoke(this); } + public void SetSize(double width, double height) + { + var newSize = new Size(width, height); + if (newSize.Equals(_size) == true) + return; + + Size = newSize; + Refresh(); + RefreshLinks(); + SizeChanging?.Invoke(this); + } + + public void TriggerSizeChanged() + { + SizeChanged?.Invoke(this); + } + public virtual void UpdatePositionSilently(double deltaX, double deltaY) { base.SetPosition(Position.X + deltaX, Position.Y + deltaY); diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs new file mode 100644 index 000000000..c4e576c92 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -0,0 +1,75 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions.Resizing +{ + public class BottomLeftResizerProvider : IResizerProvider + { + public string? Class => "bottomleft"; + + private Size _originalSize = null!; + private Point _originalPosition = null!; + private Point _originalMousePosition = null!; + private NodeModel _nodeModel = null!; + + public Point? GetPosition(Model model) + { + if (model is NodeModel nodeModel && nodeModel.Size is not null) + { + return new Point(nodeModel.Position.X - 5, nodeModel.Position.Y + nodeModel.Size.Height + 5); + } + return null; + } + + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs) + { + if (model is NodeModel nodeModel) + { + _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); + _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); + _originalSize = nodeModel.Size!; + _nodeModel = nodeModel; + } + } + + public void OnPointerMove(Model? model, PointerEventArgs args) + { + if (_nodeModel is null) + { + return; + } + + var height = _originalSize.Height + (args.ClientY - _originalMousePosition.Y); + var width = _originalSize.Width - (args.ClientX - _originalMousePosition.X); + + var positionX = _originalPosition.X + (args.ClientX - _originalMousePosition.X); + var positionY = _originalPosition.Y; + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + positionX = _nodeModel.Position.X; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + positionY = _nodeModel.Position.Y; + } + + _nodeModel.SetPosition(positionX, positionY); + _nodeModel.SetSize(width, height); + } + + public void OnResizeEnd(Model? model, PointerEventArgs args) + { + _nodeModel?.TriggerSizeChanged(); + _originalSize = null!; + _originalPosition = null!; + _originalMousePosition = null!; + _nodeModel = null!; + } + + } +} diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs new file mode 100644 index 000000000..f55e26d71 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -0,0 +1,66 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions.Resizing +{ + public class BottomRightResizerProvider : IResizerProvider + { + public string? Class => "bottomright"; + + private Size _originalSize = null!; + private Point _originalMousePosition = null!; + private NodeModel _nodeModel = null!; + + public Point? GetPosition(Model model) + { + if (model is NodeModel nodeModel && nodeModel.Size is not null) + { + return new Point(nodeModel.Position.X + nodeModel.Size.Width + 5, nodeModel.Position.Y + nodeModel.Size.Height + 5); + } + return null; + } + + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs) + { + if (model is NodeModel nodeModel) + { + _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); + _originalSize = nodeModel.Size!; + _nodeModel = nodeModel; + } + } + + public void OnPointerMove(Model? model, PointerEventArgs args) + { + if (_nodeModel is null) + { + return; + } + + var height = _originalSize.Height + (args.ClientY - _originalMousePosition.Y); + var width = _originalSize.Width + (args.ClientX - _originalMousePosition.X); + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + } + + _nodeModel.SetSize(width, height); + } + + public void OnResizeEnd(Model? model, PointerEventArgs args) + { + _nodeModel?.TriggerSizeChanged(); + _originalSize = null!; + _originalMousePosition = null!; + _nodeModel = null!; + } + + } +} diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs new file mode 100644 index 000000000..65a2ecdde --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs @@ -0,0 +1,14 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions.Resizing +{ + public interface IResizerProvider : IPositionProvider + { + public string? Class { get; } + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); + public void OnPointerMove(Model? model, PointerEventArgs args); + public void OnResizeEnd(Model? model, PointerEventArgs args); + } +} diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs new file mode 100644 index 000000000..7c3afe5ce --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -0,0 +1,75 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions.Resizing +{ + public class TopLeftResizerProvider : IResizerProvider + { + public string? Class => "topleft"; + + private Size _originalSize = null!; + private Point _originalPosition = null!; + private Point _originalMousePosition = null!; + private NodeModel _nodeModel = null!; + + public Point? GetPosition(Model model) + { + if (model is NodeModel nodeModel && nodeModel.Size is not null) + { + return new Point(nodeModel.Position.X - 5, nodeModel.Position.Y - 5); + } + return null; + } + + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs) + { + if (model is NodeModel nodeModel) + { + _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); + _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); + _originalSize = nodeModel.Size!; + _nodeModel = nodeModel; + } + } + + public void OnPointerMove(Model? model, PointerEventArgs args) + { + if (_nodeModel is null) + { + return; + } + + var height = _originalSize.Height - (args.ClientY - _originalMousePosition.Y); + var width = _originalSize.Width - (args.ClientX - _originalMousePosition.X); + + var positionX = _originalPosition.X + (args.ClientX - _originalMousePosition.X); + var positionY = _originalPosition.Y + (args.ClientY - _originalMousePosition.Y); + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + positionX = _nodeModel.Position.X; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + positionY = _nodeModel.Position.Y; + } + + _nodeModel.SetPosition(positionX, positionY); + _nodeModel.SetSize(width, height); + } + + public void OnResizeEnd(Model? model, PointerEventArgs args) + { + _nodeModel?.TriggerSizeChanged(); + _originalSize = null!; + _originalPosition = null!; + _originalMousePosition = null!; + _nodeModel = null!; + } + + } +} diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs new file mode 100644 index 000000000..e9ea4e14a --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -0,0 +1,74 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions.Resizing +{ + public class TopRightResizerProvider : IResizerProvider + { + public string? Class => "topright"; + + private Size _originalSize = null!; + private Point _originalPosition = null!; + private Point _originalMousePosition = null!; + private NodeModel _nodeModel = null!; + + public Point? GetPosition(Model model) + { + if (model is NodeModel nodeModel && nodeModel.Size is not null) + { + return new Point(nodeModel.Position.X + nodeModel.Size.Width + 5, nodeModel.Position.Y - 5); + } + return null; + } + + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs) + { + if (model is NodeModel nodeModel) + { + _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); + _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); + _originalSize = nodeModel.Size!; + _nodeModel = nodeModel; + } + } + + public void OnPointerMove(Model? model, PointerEventArgs args) + { + if (_nodeModel is null) + { + return; + } + var height = _originalSize.Height - (args.ClientY - _originalMousePosition.Y); + var width = _originalSize.Width + (args.ClientX - _originalMousePosition.X); + + var positionX = _originalPosition.X; + var positionY = _originalPosition.Y + (args.ClientY - _originalMousePosition.Y); + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + positionX = _nodeModel.Position.X; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + positionY = _nodeModel.Position.Y; + } + + _nodeModel.SetPosition(positionX, positionY); + _nodeModel.SetSize(width, height); + } + + public void OnResizeEnd(Model? model, PointerEventArgs args) + { + _nodeModel?.TriggerSizeChanged(); + _originalSize = null!; + _originalPosition = null!; + _originalMousePosition = null!; + _nodeModel = null!; + } + + } +} diff --git a/src/Blazor.Diagrams/BlazorDiagram.cs b/src/Blazor.Diagrams/BlazorDiagram.cs index ab61d98a8..b18eab9dc 100644 --- a/src/Blazor.Diagrams/BlazorDiagram.cs +++ b/src/Blazor.Diagrams/BlazorDiagram.cs @@ -19,7 +19,8 @@ public BlazorDiagram(BlazorDiagramOptions? options = null) [typeof(RemoveControl)] = typeof(RemoveControlWidget), [typeof(BoundaryControl)] = typeof(BoundaryControlWidget), [typeof(DragNewLinkControl)] = typeof(DragNewLinkControlWidget), - [typeof(ArrowHeadControl)] = typeof(ArrowHeadControlWidget) + [typeof(ArrowHeadControl)] = typeof(ArrowHeadControlWidget), + [typeof(ResizeControl)] = typeof(ResizeControlWidget) }; Options = options ?? new BlazorDiagramOptions(); diff --git a/src/Blazor.Diagrams/Components/Controls/ResizeControlWidget.razor b/src/Blazor.Diagrams/Components/Controls/ResizeControlWidget.razor new file mode 100644 index 000000000..cfd15bd7b --- /dev/null +++ b/src/Blazor.Diagrams/Components/Controls/ResizeControlWidget.razor @@ -0,0 +1,10 @@ +
+ +@code +{ + [Parameter] + public ResizeControl Control { get; set; } = null!; + + [Parameter] + public Model Model { get; set; } = null!; +} diff --git a/src/Blazor.Diagrams/Components/NodeWidget.razor b/src/Blazor.Diagrams/Components/NodeWidget.razor index 37ff3da8d..726e51d2c 100644 --- a/src/Blazor.Diagrams/Components/NodeWidget.razor +++ b/src/Blazor.Diagrams/Components/NodeWidget.razor @@ -1,4 +1,4 @@ -
+
@(Node.Title ?? "Title") @foreach (var port in Node.Ports) { @@ -11,4 +11,12 @@ [Parameter] public NodeModel Node { get; set; } = null!; + private string GenerateStyle() + { + if (Node.Size is not null) + { + return $"width: {Node.Size.Width}px; height: {Node.Size.Height}px"; + } + return string.Empty; + } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.css b/src/Blazor.Diagrams/wwwroot/default.styles.css index b095bbe4e..2b355029a 100644 --- a/src/Blazor.Diagrams/wwwroot/default.styles.css +++ b/src/Blazor.Diagrams/wwwroot/default.styles.css @@ -127,4 +127,28 @@ g.diagram-group.default.selected > rect { transform: translate(-50%, -50%); } +.default-node-resizer { + width: 5px; + height: 5px; + background-color: #f5f5f5; + border: 1px solid #6e9fd4; + position: absolute; +} + +.default-node-resizer.bottomright { + cursor: nwse-resize; +} + +.default-node-resizer.topright { + cursor: nesw-resize; +} + +.default-node-resizer.bottomleft { + cursor: nesw-resize; +} + +.default-node-resizer.topleft { + cursor: nwse-resize; +} + /*# sourceMappingURL=wwwroot\default.styles.css.map */ diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css b/src/Blazor.Diagrams/wwwroot/default.styles.min.css index c44adedc3..e794e823e 100644 --- a/src/Blazor.Diagrams/wwwroot/default.styles.min.css +++ b/src/Blazor.Diagrams/wwwroot/default.styles.min.css @@ -1 +1 @@ -.default-node{width:100px;height:80px;border-radius:10px;background-color:#f5f5f5;border:1px solid #e8e8e8;-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.default-node.selected{border:1px solid #6e9fd4;}.default-node.selected .diagram-port{border:1px solid #6e9fd4;}.default-node .diagram-port,.default.diagram-group .diagram-port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .diagram-port:hover,.default-node .diagram-port.has-links,.default.diagram-group .diagram-port.has-links{background-color:#000;}.default-node .diagram-port.bottom,.default.diagram-group .diagram-port.bottom{bottom:0;left:50%;}.default-node .diagram-port.bottomleft,.default.diagram-group .diagram-port.bottomleft{bottom:0;left:0;}.default-node .diagram-port.bottomright,.default.diagram-group .diagram-port.bottomright{bottom:0;right:0;}.default-node .diagram-port.top,.default.diagram-group .diagram-port.top{top:0;left:50%;}.default-node .diagram-port.topleft,.default.diagram-group .diagram-port.topleft{top:0;left:0;}.default-node .diagram-port.topright,.default.diagram-group .diagram-port.topright{top:0;right:0;}.default-node .diagram-port.left,.default.diagram-group .diagram-port.left{left:0;top:50%;}.default-node .diagram-port.right,.default.diagram-group .diagram-port.right{right:0;top:50%;}.diagram-navigator.default{position:absolute;bottom:10px;right:10px;border:3px solid #9ba8b0;border-radius:15px;padding:20px;background-color:#fff;}div.diagram-group.default{outline:2px solid #000;background:#c6c6c6;}div.diagram-group.default.selected{outline:2px solid #6e9fd4;}g.diagram-group.default rect{outline:2px solid #000;fill:#c6c632;}g.diagram-group.default.selected>rect{outline:2px solid #008000;}.diagram-link div.default-link-label{display:inline-block;color:#fff;background-color:#6e9fd4;border-radius:.25rem;padding:.25rem;text-align:center;font-size:.875rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:3rem;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);} \ No newline at end of file +.default-node{width:100px;height:80px;border-radius:10px;background-color:#f5f5f5;border:1px solid #e8e8e8;-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.default-node.selected{border:1px solid #6e9fd4;}.default-node.selected .diagram-port{border:1px solid #6e9fd4;}.default-node .diagram-port,.default.diagram-group .diagram-port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .diagram-port:hover,.default-node .diagram-port.has-links,.default.diagram-group .diagram-port.has-links{background-color:#000;}.default-node .diagram-port.bottom,.default.diagram-group .diagram-port.bottom{bottom:0;left:50%;}.default-node .diagram-port.bottomleft,.default.diagram-group .diagram-port.bottomleft{bottom:0;left:0;}.default-node .diagram-port.bottomright,.default.diagram-group .diagram-port.bottomright{bottom:0;right:0;}.default-node .diagram-port.top,.default.diagram-group .diagram-port.top{top:0;left:50%;}.default-node .diagram-port.topleft,.default.diagram-group .diagram-port.topleft{top:0;left:0;}.default-node .diagram-port.topright,.default.diagram-group .diagram-port.topright{top:0;right:0;}.default-node .diagram-port.left,.default.diagram-group .diagram-port.left{left:0;top:50%;}.default-node .diagram-port.right,.default.diagram-group .diagram-port.right{right:0;top:50%;}.diagram-navigator.default{position:absolute;bottom:10px;right:10px;border:3px solid #9ba8b0;border-radius:15px;padding:20px;background-color:#fff;}div.diagram-group.default{outline:2px solid #000;background:#c6c6c6;}div.diagram-group.default.selected{outline:2px solid #6e9fd4;}g.diagram-group.default rect{outline:2px solid #000;fill:#c6c632;}g.diagram-group.default.selected>rect{outline:2px solid #008000;}.diagram-link div.default-link-label{display:inline-block;color:#fff;background-color:#6e9fd4;border-radius:.25rem;padding:.25rem;text-align:center;font-size:.875rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:3rem;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);}.default-node-resizer{width:5px;height:5px;background-color:#f5f5f5;border:1px solid #6e9fd4;position:absolute;}.default-node-resizer.bottomright{cursor:nwse-resize;}.default-node-resizer.topright{cursor:nesw-resize;}.default-node-resizer.bottomleft{cursor:nesw-resize;}.default-node-resizer.topleft{cursor:nwse-resize;} \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz b/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz index 905f20cd3084e4807fd8feb323b3f30ca017d835..3c43a8a10a61907cb9619cdbdc3efc83f8d13ceb 100644 GIT binary patch literal 735 zcmV<50wDb#iwFP!000003bj^kj@mE~y$aH*(yoM~5SEs8q~68Bli=dmk?jDxXiwH3 zJyNewJ8`}?K*Ep80^;$Tc{Ah541a(B2`HqbzCFD6=x)nq(%dk3epKx%rdTq|u>AFgzUXfPM zIZ+uC7_G(?y9vKNQn2*Zt$0@|ZE=NURifHkCyB+C2JVJ}_wop8c?S{fiAJ1>gPs+3 z2fvI%3B&NNH$ftek@-BL8f$OR{opX>keVumGZ-B95f+F4!Za%qwzn9R)sp4V(+*oWuC}kq6yU>+3lcw<}WyfJCtxK{r z_N@jSbyV(c?(E>F-FLU{80ot96>Cl3&93V%l-0cDManp@`Pb1+3oZN4+ora@LwSAI z#}-vMz7U)MagqSPG&7P3X9gu)?hbJ;m*Wik2!~e_L>mQpdpUk<;B1CtHMQdqY4!m) z*#4+6%~UP5wN}T5g%n`Ns{FVn)nnmEkTZem!L{yl7)69mrOI*jWdBaUGqP2@bYCx( z?Fq=l@$g+k3S6kl&(KXbLfzv7BhsgQY9=;(gC^|fsyON9{FYYRni>_*rwNtbSedWX RwAgE&{R6yf)WanU007NuYeE13 literal 683 zcmV;c0#yAUiwFP!000003bj^iZlf>|zDlHOrACSoAx)baY42k2K)f+FvQ3gU>dE%U z9%-+zwt)a85AtJWCBfrw=9?Ge{QdnWppcP@8zdx!(}7X5#mg`(kMR~*zBPE`o)f7k zD5MBwmA2$aA=GD~RTc(4V+ZI5=9qv}ddFSv0pE{AYqvYG#-eSOF)7?IEU2mz;dy zUW}cr&?w_@?91GKkPyOIE^rFMfQnmGf?O=uxFvP1sNIW3x8GGu~E#^u?_>t(3O7QZk>YbJa;=ag~9aq2R6DgIe4`1Y4pJXJV%(h26nV z<50pdyy;DlNMmF%ji|=j3c4R0#vC%^Q#gUaVIN^}_-{;;B2ljHj}Uo&KDQ5jiI zqp-k}{Y=i4#ZFID1MU;t_xpkTwCILlQf8)m?_F=4b_92G^ML2I+pWVr>kUYdJdgW0F5LN-bG{VH5rY)L3(BCn^=g)x(5@fT(LVJK&pWM%AI z4LItk+}qsQ!B6R{TQ`h!Q~QFocJAhvb!Wa00|h0{qn8kxV!V1EF1|6CQyBGt@<2B5ur1w3hbZk-wAj|wu=|;Ywm5| Rf!sO%`Ul{II$Grk008_RO;G>< diff --git a/src/Blazor.Diagrams/wwwroot/style.min.css.gz b/src/Blazor.Diagrams/wwwroot/style.min.css.gz index e72cb7d8af23d7af571c773a260d0f0ccfe835f3..4a312feec6aef85e5620eea68d1cd461ca9d351d 100644 GIT binary patch delta 381 zcmV-@0fPRS1DOMmC4Wh48BgYeNAe1;l%j3Z86SNj{7L?N|FY!s{UbN1Td#Z7Xcu)D zcWAgPABy6>>Hyo0kK-)`WF%L5u$nPKMFU%OP&#t*62Y%6?aB^~0arDH59CX=gL;Fk z6C6J%gNzw0SU2HR=mQhi8IB*h!$TIvxoU~RwS{bX3>OT_UX z1D}fT$UuVg#54qf4Pc?+8MdcGN)6ZIl`!{6FF2Ql(AHi+<(8Tam~q}Tum813`g1d0 z*v$E!^|xxOn|6Dhj@W7@KOPi11AD1cYC6ls!i%+v2)jg`YVrnr`MBL@qM*s{isH9Z z>uK2jyZTO4lC48)Bwd29$0&$UI4J=p}{ bTMm0@2G-(}Sbi1$vK77n8oH^c9R&aY=R@iQw0k#`U0GyBrTUPbNQyVgwbUbGz}oKP{bX3>OT_6P z1D}fT$UuVg#54qf4Pc?+8Mddllp3zZD`D=DUT`i8p{>1u$}Ke;Fw?wgUjJ*6^yg-} zu$l8c>%D5Kn|6Dhj@W7@KfNh*26j@X)O41Mg%@iT5ynKFYVrnr`MBL>qM*s{isH9Z z>uK2jyZW|!D#_NNHIgpDchxbe#y$TQnKYyHoO`NYgy?Kaeuo(); + var control = new ResizeControl(resizeProvider.Object); + var model = new Mock(); + + control.GetPosition(model.Object); + + resizeProvider.Verify(m => m.GetPosition(model.Object), Times.Once); + } + + [Fact] + public void OnPointerDown_ShouldInvokeResizeStart() + { + var resizeProvider = new Mock(); + var control = new ResizeControl(resizeProvider.Object); + var diagram = Mock.Of(); + var model = Mock.Of(); + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); + + control.OnPointerDown(diagram, model, eventArgs); + + resizeProvider.Verify(m => m.OnResizeStart(diagram, model, eventArgs), Times.Once); + } + + [Fact] + public void OnPointerDown_ShouldAddEventHandlers() + { + var resizeProvider = new Mock(); + var control = new ResizeControl(resizeProvider.Object); + var diagram = new TestDiagram(); + var model = Mock.Of(); + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); + + control.OnPointerDown(diagram, model, eventArgs); + + diagram.TriggerPointerMove(model, eventArgs); + resizeProvider.Verify(m => m.OnPointerMove(model, eventArgs), Times.Once); + + diagram.TriggerPointerUp(model, eventArgs); + resizeProvider.Verify(m => m.OnResizeEnd(model, eventArgs), Times.Once); + } + + [Fact] + public void OnPointerUp_ShouldRemoveEventHandlers() + { + var resizeProvider = new Mock(); + var control = new ResizeControl(resizeProvider.Object); + var diagram = new TestDiagram(); + var model = Mock.Of(); + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); + + control.OnPointerDown(diagram, model, eventArgs); + diagram.TriggerPointerUp(model, eventArgs); + + diagram.TriggerPointerMove(model, eventArgs); + resizeProvider.Verify(m => m.OnPointerMove(model, eventArgs), Times.Never); + + diagram.TriggerPointerUp(model, eventArgs); + resizeProvider.Verify(m => m.OnResizeEnd(model, eventArgs), Times.Once); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs new file mode 100644 index 000000000..79ef6c17b --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs @@ -0,0 +1,82 @@ +using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Positions.Resizing; +using FluentAssertions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Positions.Resizing +{ + public class BottomLeftResizerProviderTests + { + [Fact] + public void DragResizer_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(215); + } + + [Fact] + public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(99, -199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + eventArgs = new PointerEventArgs(300, -300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(99); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(0); + node.Size.Height.Should().Be(0); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs new file mode 100644 index 000000000..20ab27770 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs @@ -0,0 +1,80 @@ +using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Positions.Resizing; +using FluentAssertions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Positions.Resizing +{ + public class BottomRightResizerProviderTests + { + [Fact] + public void DragResizer_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(215); + } + + [Fact] + public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(-300, -300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(0); + node.Size.Height.Should().Be(0); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs new file mode 100644 index 000000000..1cc715d59 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs @@ -0,0 +1,82 @@ +using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Positions.Resizing; +using FluentAssertions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Positions.Resizing +{ + public class TopLeftResizerProviderTests + { + [Fact] + public void DragResizer_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(15); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(185); + } + + [Fact] + public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(99, 199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + eventArgs = new PointerEventArgs(300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(99); + node.Position.Y.Should().Be(199); + node.Size.Width.Should().Be(0); + node.Size.Height.Should().Be(0); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs new file mode 100644 index 000000000..a138d524f --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs @@ -0,0 +1,82 @@ +using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Positions.Resizing; +using FluentAssertions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Positions.Resizing +{ + public class TopRightResizerProviderTests + { + [Fact] + public void DragResizer_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(15); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(185); + } + + [Fact] + public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(-99, 199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + eventArgs = new PointerEventArgs(-300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(199); + node.Size.Width.Should().Be(0); + node.Size.Height.Should().Be(0); + } + } +} diff --git a/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs new file mode 100644 index 000000000..bf51e541c --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs @@ -0,0 +1,25 @@ +using Blazor.Diagrams.Components.Controls; +using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Positions.Resizing; +using Bunit; +using Moq; +using Xunit; + +namespace Blazor.Diagrams.Tests.Components.Controls +{ + public class ResizeControlWidgetTests + { + [Fact] + public void ShouldRenderDiv() + { + using var ctx = new TestContext(); + var providerMock = Mock.Of(); + + var cut = ctx.RenderComponent(parameters => + parameters.Add(w => w.Control, new ResizeControl(providerMock)) + ); + + cut.MarkupMatches("
"); + } + } +} From 822a1fbc8b16953b9964c53ef9eab5af6832bd13 Mon Sep 17 00:00:00 2001 From: Matt Swain Date: Thu, 25 May 2023 09:51:18 +1000 Subject: [PATCH 030/159] Add configuration of mouse actions (#3) * Added unit tests * Fixed spelling * Added functionality * fixed code review comments * Fixed scroll behaviour * Fixed selection box behaviour * refactored to not use enum * refactored to not use types * refactor * fixed build issue --- .../{ => Behaviors/Base}/Behavior.cs | 2 +- .../Behaviors/Base/DragBehavior.cs | 49 +++++++ .../Behaviors/Base/WheelBehavior.cs | 42 ++++++ .../Behaviors/DebugEventsBehavior.cs | 3 +- .../Behaviors/DragMovablesBehavior.cs | 1 + .../Behaviors/DragNewLinkBehavior.cs | 1 + .../Behaviors/EventsBehavior.cs | 1 + .../Behaviors/KeyboardShortcutsBehavior.cs | 3 +- .../Behaviors/PanBehavior.cs | 46 ++---- .../Behaviors/ScrollBehavior.cs | 25 ++++ .../Behaviors/SelectionBehavior.cs | 1 + .../Behaviors/SelectionBoxBehavior.cs | 79 +++++++++++ .../Behaviors/VirtualizationBehavior.cs | 1 + .../Behaviors/ZoomBehavior.cs | 61 ++++---- .../Controls/ControlsBehavior.cs | 1 + src/Blazor.Diagrams.Core/Diagram.cs | 47 ++++--- .../Options/DiagramBehaviorOptions.cs | 26 ++++ .../Options/DiagramOptions.cs | 2 + src/Blazor.Diagrams/BlazorDiagram.cs | 1 + .../Widgets/SelectionBoxWidget.razor | 2 +- .../Widgets/SelectionBoxWidget.razor.cs | 76 +++------- .../Behaviors/PanBehaviorTests.cs | 54 ++++++++ .../Behaviors/ScrollBehaviorTests.cs | 43 ++++++ .../Behaviors/SelectionBoxBehaviorTests.cs | 131 ++++++++++++++++++ .../Behaviors/ZoomBehaviorTests.cs | 40 ++++++ .../Options/DiagramBehaviorOptionsTests.cs | 127 +++++++++++++++++ .../Widgets/SelectionBoxWidgetTests.cs | 41 ++++++ 27 files changed, 755 insertions(+), 151 deletions(-) rename src/Blazor.Diagrams.Core/{ => Behaviors/Base}/Behavior.cs (82%) create mode 100644 src/Blazor.Diagrams.Core/Behaviors/Base/DragBehavior.cs create mode 100644 src/Blazor.Diagrams.Core/Behaviors/Base/WheelBehavior.cs create mode 100644 src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs create mode 100644 src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs create mode 100644 src/Blazor.Diagrams.Core/Options/DiagramBehaviorOptions.cs create mode 100644 tests/Blazor.Diagrams.Core.Tests/Behaviors/PanBehaviorTests.cs create mode 100644 tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs create mode 100644 tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs create mode 100644 tests/Blazor.Diagrams.Core.Tests/Behaviors/ZoomBehaviorTests.cs create mode 100644 tests/Blazor.Diagrams.Core.Tests/Options/DiagramBehaviorOptionsTests.cs create mode 100644 tests/Blazor.Diagrams.Tests/Components/Widgets/SelectionBoxWidgetTests.cs diff --git a/src/Blazor.Diagrams.Core/Behavior.cs b/src/Blazor.Diagrams.Core/Behaviors/Base/Behavior.cs similarity index 82% rename from src/Blazor.Diagrams.Core/Behavior.cs rename to src/Blazor.Diagrams.Core/Behaviors/Base/Behavior.cs index 78e020602..84cfabb83 100644 --- a/src/Blazor.Diagrams.Core/Behavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/Base/Behavior.cs @@ -1,6 +1,6 @@ using System; -namespace Blazor.Diagrams.Core; +namespace Blazor.Diagrams.Core.Behaviors.Base; public abstract class Behavior : IDisposable { diff --git a/src/Blazor.Diagrams.Core/Behaviors/Base/DragBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/Base/DragBehavior.cs new file mode 100644 index 000000000..3985dd241 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Behaviors/Base/DragBehavior.cs @@ -0,0 +1,49 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Behaviors.Base +{ + public abstract class DragBehavior : Behavior + { + public DragBehavior(Diagram diagram) + : base(diagram) + { + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + } + + protected abstract void OnPointerDown(Model? model, PointerEventArgs e); + + protected abstract void OnPointerMove(Model? model, PointerEventArgs e); + + protected abstract void OnPointerUp(Model? model, PointerEventArgs e); + + public virtual bool IsBehaviorEnabled(PointerEventArgs e) + { + if (e.AltKey && !e.CtrlKey && !e.ShiftKey + && Diagram.BehaviorOptions.DiagramAltDragBehavior is not null) + { + return this == Diagram.BehaviorOptions.DiagramAltDragBehavior; + } + else if (!e.AltKey && e.CtrlKey && !e.ShiftKey + && Diagram.BehaviorOptions.DiagramCtrlDragBehavior is not null) + { + return this == Diagram.BehaviorOptions.DiagramCtrlDragBehavior; + } + else if (!e.AltKey && !e.CtrlKey && e.ShiftKey + && Diagram.BehaviorOptions.DiagramShiftDragBehavior is not null) + { + return this == Diagram.BehaviorOptions.DiagramShiftDragBehavior; + } + return this == Diagram.BehaviorOptions.DiagramDragBehavior; + } + + public override void Dispose() + { + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + } + } +} diff --git a/src/Blazor.Diagrams.Core/Behaviors/Base/WheelBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/Base/WheelBehavior.cs new file mode 100644 index 000000000..01792efc5 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Behaviors/Base/WheelBehavior.cs @@ -0,0 +1,42 @@ +using Blazor.Diagrams.Core.Events; + +namespace Blazor.Diagrams.Core.Behaviors.Base +{ + public abstract class WheelBehavior : Behavior + { + protected WheelBehavior(Diagram diagram) + : base(diagram) + { + + Diagram.Wheel += OnDiagramWheel; + } + + protected abstract void OnDiagramWheel(WheelEventArgs e); + + public virtual bool IsBehaviorEnabled(WheelEventArgs e) + { + if (e.AltKey && !e.CtrlKey && !e.ShiftKey + && Diagram.BehaviorOptions.DiagramAltWheelBehavior is not null) + { + return this == Diagram.BehaviorOptions.DiagramAltWheelBehavior; + } + else if (!e.AltKey && e.CtrlKey && !e.ShiftKey + && Diagram.BehaviorOptions.DiagramCtrlWheelBehavior is not null) + { + return this == Diagram.BehaviorOptions.DiagramCtrlWheelBehavior; + } + else if (!e.AltKey && !e.CtrlKey && e.ShiftKey + && Diagram.BehaviorOptions.DiagramShiftWheelBehavior is not null) + { + return this == Diagram.BehaviorOptions.DiagramShiftWheelBehavior; + } + + return this == Diagram.BehaviorOptions.DiagramWheelBehavior; + } + + public override void Dispose() + { + Diagram.Wheel -= OnDiagramWheel; + } + } +} diff --git a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs index f3fe081d1..c5f71078d 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Behaviors.Base; +using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; using System; diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index db5aa2c0d..face95c33 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Behaviors.Base; namespace Blazor.Diagrams.Core.Behaviors; diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 31d0c5ede..7efa83dc1 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.Events; using System.Linq; using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Behaviors.Base; using Blazor.Diagrams.Core.Geometry; namespace Blazor.Diagrams.Core.Behaviors; diff --git a/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs index 6bb8bc0c7..705b1d650 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs @@ -1,6 +1,7 @@ using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Events; using System.Diagnostics; +using Blazor.Diagrams.Core.Behaviors.Base; namespace Blazor.Diagrams.Core.Behaviors; diff --git a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs index 5243bed09..6e136e0b5 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/KeyboardShortcutsBehavior.cs @@ -1,4 +1,5 @@ -using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Behaviors.Base; +using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Utils; using System; using System.Collections.Generic; diff --git a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs index bb4d974b7..7856bc26a 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs @@ -1,10 +1,11 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Behaviors.Base; namespace Blazor.Diagrams.Core.Behaviors; -public class PanBehavior : Behavior +public class PanBehavior : DragBehavior { private Point? _initialPan; private double _lastClientX; @@ -12,55 +13,30 @@ public class PanBehavior : Behavior public PanBehavior(Diagram diagram) : base(diagram) { - Diagram.PointerDown += OnPointerDown; - Diagram.PointerMove += OnPointerMove; - Diagram.PointerUp += OnPointerUp; } - private void OnPointerDown(Model? model, PointerEventArgs e) + protected override void OnPointerDown(Model? model, PointerEventArgs e) { - if (e.Button != (int)MouseEventButton.Left) - return; - - Start(model, e.ClientX, e.ClientY, e.ShiftKey); - } - - 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, bool shiftKey) - { - if (!Diagram.Options.AllowPanning || model != null || shiftKey) + if (e.Button != (int)MouseEventButton.Left || model != null || !Diagram.Options.AllowPanning || !IsBehaviorEnabled(e)) return; _initialPan = Diagram.Pan; - _lastClientX = clientX; - _lastClientY = clientY; + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; } - private void Move(double clientX, double clientY) + protected override void OnPointerMove(Model? model, PointerEventArgs e) { - if (!Diagram.Options.AllowPanning || _initialPan == null) + if (_initialPan == null) return; - var deltaX = clientX - _lastClientX - (Diagram.Pan.X - _initialPan.X); - var deltaY = clientY - _lastClientY - (Diagram.Pan.Y - _initialPan.Y); + var deltaX = e.ClientX - _lastClientX - (Diagram.Pan.X - _initialPan.X); + var deltaY = e.ClientY - _lastClientY - (Diagram.Pan.Y - _initialPan.Y); Diagram.UpdatePan(deltaX, deltaY); } - private void End() + protected override void OnPointerUp(Model? model, PointerEventArgs e) { - if (!Diagram.Options.AllowPanning) - return; - _initialPan = null; } - - public override void Dispose() - { - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs new file mode 100644 index 000000000..fa3ef63e1 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs @@ -0,0 +1,25 @@ +using Blazor.Diagrams.Core.Behaviors.Base; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Options; + +namespace Blazor.Diagrams.Core.Behaviors +{ + public class ScrollBehavior : WheelBehavior + { + public ScrollBehavior(Diagram diagram) + : base(diagram) + { + } + + protected override void OnDiagramWheel(WheelEventArgs e) + { + if (Diagram.Container == null || !IsBehaviorEnabled(e)) + return; + + var x = Diagram.Pan.X - (e.DeltaX / Diagram.Options.Zoom.ScaleFactor); + var y = Diagram.Pan.Y - (e.DeltaY / Diagram.Options.Zoom.ScaleFactor); + + Diagram.SetPan(x, y); + } + } +} diff --git a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs index 8c3d75ab8..3c1b6c3ae 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/SelectionBehavior.cs @@ -1,5 +1,6 @@ using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Behaviors.Base; namespace Blazor.Diagrams.Core.Behaviors; diff --git a/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs new file mode 100644 index 000000000..3364becd3 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs @@ -0,0 +1,79 @@ +using Blazor.Diagrams.Core.Behaviors.Base; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; +using System; +using System.Linq; + +namespace Blazor.Diagrams.Core.Behaviors +{ + public class SelectionBoxBehavior : DragBehavior + { + private Point? _initialClientPoint; + + public event EventHandler? SelectionBoundsChanged; + + public SelectionBoxBehavior(Diagram diagram) + : base(diagram) + { + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + } + + public override void Dispose() + { + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + } + + protected override void OnPointerDown(Model? model, PointerEventArgs e) + { + if (SelectionBoundsChanged is null || model != null || !IsBehaviorEnabled(e)) + return; + + _initialClientPoint = new Point(e.ClientX, e.ClientY); + } + + protected override void OnPointerMove(Model? model, PointerEventArgs e) + { + if (_initialClientPoint == null) + return; + + UpdateSelectionBox(e); + + var start = Diagram.GetRelativeMousePoint(_initialClientPoint.X, _initialClientPoint.Y); + var end = Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY); + var (sX, sY) = (Math.Min(start.X, end.X), Math.Min(start.Y, end.Y)); + var (eX, eY) = (Math.Max(start.X, end.X), Math.Max(start.Y, end.Y)); + var bounds = new Rectangle(sX, sY, eX, eY); + + foreach (var node in Diagram.Nodes) + { + var nodeBounds = node.GetBounds(); + if (nodeBounds == null) + continue; + + if (bounds.Overlap(nodeBounds)) + Diagram.SelectModel(node, false); + else if (node.Selected) Diagram.UnselectModel(node); + } + } + + void UpdateSelectionBox(MouseEventArgs e) + { + var start = Diagram.GetRelativePoint(_initialClientPoint!.X, _initialClientPoint.Y); + var end = Diagram.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)); + SelectionBoundsChanged?.Invoke(this, new Rectangle(sX, sY, eX, eY)); + } + + protected override void OnPointerUp(Model? model, PointerEventArgs e) + { + _initialClientPoint = null; + SelectionBoundsChanged?.Invoke(this, null); + } + } +} diff --git a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs index acd4dd7b0..7d0573b06 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs @@ -1,3 +1,4 @@ +using Blazor.Diagrams.Core.Behaviors.Base; using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Core.Behaviors; diff --git a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs index 3da507b75..1c2a4f5c4 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs @@ -1,45 +1,41 @@ -using Blazor.Diagrams.Core.Events; - +using Blazor.Diagrams.Core.Behaviors.Base; +using Blazor.Diagrams.Core.Events; using System; namespace Blazor.Diagrams.Core.Behaviors; -public class ZoomBehavior : Behavior +public class ZoomBehavior : WheelBehavior { public ZoomBehavior(Diagram diagram) : base(diagram) { - Diagram.Wheel += Diagram_Wheel; } - private void Diagram_Wheel(WheelEventArgs e) + protected override void OnDiagramWheel(WheelEventArgs e) { - if (Diagram.Container == null || e.DeltaY == 0) - return; - - if (!Diagram.Options.Zoom.Enabled) + if (Diagram.Container == null || e.DeltaY == 0 || !Diagram.Options.Zoom.Enabled || !IsBehaviorEnabled(e)) 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; + 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(() => { @@ -47,9 +43,4 @@ private void Diagram_Wheel(WheelEventArgs e) Diagram.SetZoom(newZoom); }); } - - public override void Dispose() - { - Diagram.Wheel -= Diagram_Wheel; - } } diff --git a/src/Blazor.Diagrams.Core/Controls/ControlsBehavior.cs b/src/Blazor.Diagrams.Core/Controls/ControlsBehavior.cs index 05148b5e6..82f53a592 100644 --- a/src/Blazor.Diagrams.Core/Controls/ControlsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Controls/ControlsBehavior.cs @@ -1,3 +1,4 @@ +using Blazor.Diagrams.Core.Behaviors.Base; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Models.Base; diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index a2fffeacb..3f1023865 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -10,6 +10,7 @@ using System.Runtime.CompilerServices; using Blazor.Diagrams.Core.Options; using Blazor.Diagrams.Core.Controls; +using Blazor.Diagrams.Core.Behaviors.Base; [assembly: InternalsVisibleTo("Blazor.Diagrams")] [assembly: InternalsVisibleTo("Blazor.Diagrams.Tests")] @@ -47,6 +48,7 @@ protected Diagram() Links = new LinkLayer(this); Groups = new GroupLayer(this); Controls = new ControlsLayer(); + BehaviorOptions = new DiagramBehaviorOptions(); Nodes.Added += OnSelectableAdded; Links.Added += OnSelectableAdded; @@ -56,18 +58,15 @@ protected Diagram() 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)); + RegisterDefaultBehaviors(); + + BehaviorOptions.DiagramDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramShiftDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramWheelBehavior ??= GetBehavior(); } public abstract DiagramOptions Options { get; } + public DiagramBehaviorOptions BehaviorOptions { get; } public NodeLayer Nodes { get; } public LinkLayer Links { get; } public GroupLayer Groups { get; } @@ -169,17 +168,31 @@ public void UnselectAll() #endregion #region Behaviors - - public void RegisterBehavior(Behavior behavior) + void RegisterDefaultBehaviors() { - var type = behavior.GetType(); - if (_behaviors.ContainsKey(type)) - throw new Exception($"Behavior '{type.Name}' already registered"); - - _behaviors.Add(type, behavior); + 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)); + RegisterBehavior(new ScrollBehavior(this)); + RegisterBehavior(new SelectionBoxBehavior(this)); } - public T? GetBehavior() where T : Behavior + 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); + } + + public T? GetBehavior() where T : Behavior { var type = typeof(T); return (T?)(_behaviors.ContainsKey(type) ? _behaviors[type] : null); diff --git a/src/Blazor.Diagrams.Core/Options/DiagramBehaviorOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramBehaviorOptions.cs new file mode 100644 index 000000000..a0b7b0aa7 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Options/DiagramBehaviorOptions.cs @@ -0,0 +1,26 @@ +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Behaviors.Base; +using System; +using System.Runtime.CompilerServices; + +namespace Blazor.Diagrams.Core.Options +{ + public class DiagramBehaviorOptions + { + public WheelBehavior? DiagramWheelBehavior { get; set; } + + public WheelBehavior? DiagramAltWheelBehavior { get; set; } + + public WheelBehavior? DiagramCtrlWheelBehavior { get; set; } + + public WheelBehavior? DiagramShiftWheelBehavior { get; set; } + + public DragBehavior? DiagramDragBehavior { get; set; } + + public DragBehavior? DiagramAltDragBehavior { get; set; } + + public DragBehavior? DiagramCtrlDragBehavior { get; set; } + + public DragBehavior? DiagramShiftDragBehavior { get; set; } + } +} diff --git a/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs b/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs index 4175eda0e..9fcb6ed0e 100644 --- a/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs +++ b/src/Blazor.Diagrams.Core/Options/DiagramOptions.cs @@ -1,3 +1,5 @@ +using Blazor.Diagrams.Core.Behaviors; + namespace Blazor.Diagrams.Core.Options; public class DiagramOptions diff --git a/src/Blazor.Diagrams/BlazorDiagram.cs b/src/Blazor.Diagrams/BlazorDiagram.cs index b18eab9dc..1f34c5e67 100644 --- a/src/Blazor.Diagrams/BlazorDiagram.cs +++ b/src/Blazor.Diagrams/BlazorDiagram.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Blazor.Diagrams.Components.Controls; using Blazor.Diagrams.Core; +using Blazor.Diagrams.Core.Behaviors; using Blazor.Diagrams.Core.Controls.Default; using Blazor.Diagrams.Core.Models.Base; using Blazor.Diagrams.Options; diff --git a/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor b/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor index 4ad4e761e..5cfa9417e 100644 --- a/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor +++ b/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor @@ -1,4 +1,4 @@ -@if (_selectionBoxTopLeft != null && _selectionBoxSize != null) +@if (_selectionBounds is not null) {
} \ 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 738fabf1a..988166755 100644 --- a/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/SelectionBoxWidget.razor.cs @@ -1,4 +1,5 @@ using System; +using Blazor.Diagrams.Core.Behaviors; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models.Base; @@ -8,9 +9,8 @@ namespace Blazor.Diagrams.Components.Widgets; public partial class SelectionBoxWidget : IDisposable { - private Point? _initialClientPoint; - private Size? _selectionBoxSize; // Todo: remove unneeded instantiations - private Point? _selectionBoxTopLeft; // Todo: remove unneeded instantiations + private Rectangle? _selectionBounds; + private SelectionBoxBehavior? _selectionBoxBehavior; [CascadingParameter] public BlazorDiagram BlazorDiagram { get; set; } = null!; @@ -18,74 +18,30 @@ public partial class SelectionBoxWidget : IDisposable public void Dispose() { - BlazorDiagram.PointerDown -= OnPointerDown; - BlazorDiagram.PointerMove -= OnPointerMove; - BlazorDiagram.PointerUp -= OnPointerUp; + if (_selectionBoxBehavior is not null) + { + _selectionBoxBehavior.SelectionBoundsChanged -= SelectionBoundsChanged; + } } protected override void OnInitialized() { - BlazorDiagram.PointerDown += OnPointerDown; - BlazorDiagram.PointerMove += OnPointerMove; - BlazorDiagram.PointerUp += OnPointerUp; - } - - 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;"); - } - - private void OnPointerDown(Model? model, MouseEventArgs e) - { - if (model != null || !e.ShiftKey) - return; - - _initialClientPoint = new Point(e.ClientX, e.ClientY); - } - - 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) + _selectionBoxBehavior = BlazorDiagram.GetBehavior(); + if (_selectionBoxBehavior is not null) { - var nodeBounds = node.GetBounds(); - if (nodeBounds == null) - continue; - - if (bounds.Overlap(nodeBounds)) - BlazorDiagram.SelectModel(node, false); - else if (node.Selected) BlazorDiagram.UnselectModel(node); + _selectionBoxBehavior.SelectionBoundsChanged += SelectionBoundsChanged; } - - InvokeAsync(StateHasChanged); } - private void SetSelectionBoxInformation(MouseEventArgs e) + void SelectionBoundsChanged(object? sender, Rectangle? bounds) { - 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); + _selectionBounds = bounds; + InvokeAsync(StateHasChanged); } - private void OnPointerUp(Model? model, MouseEventArgs e) + private string GenerateStyle() { - _initialClientPoint = null; - _selectionBoxTopLeft = null; - _selectionBoxSize = null; - InvokeAsync(StateHasChanged); + return FormattableString.Invariant( + $"position: absolute; background: {Background}; top: {_selectionBounds!.Top}px; left: {_selectionBounds.Left}px; width: {_selectionBounds.Width}px; height: {_selectionBounds.Height}px;"); } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/PanBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/PanBehaviorTests.cs new file mode 100644 index 000000000..3feee5090 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/PanBehaviorTests.cs @@ -0,0 +1,54 @@ +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Behaviors +{ + public class PanBehaviorTests + { + [Fact] + public void Behavior_WhenBehaviorEnabled_ShouldPan() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + + Assert.Equal(0, diagram.Pan.X); + Assert.Equal(0, diagram.Pan.Y); + + // Act + diagram.TriggerPointerDown(null, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(200, 200, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + Assert.Equal(100, diagram.Pan.X); + Assert.Equal(100, diagram.Pan.Y); + } + + [Fact] + public void Behavior_WhenBehaviorDisabled_ShouldNotPan() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = null; + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + + Assert.Equal(0, diagram.Pan.X); + Assert.Equal(0, diagram.Pan.Y); + + // Act + diagram.TriggerPointerDown(null, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(200, 200, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + Assert.Equal(0, diagram.Pan.X); + Assert.Equal(0, diagram.Pan.Y); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs new file mode 100644 index 000000000..acd78bbdc --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs @@ -0,0 +1,43 @@ +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Behaviors +{ + public class ScrollBehaviorTests + { + [Fact] + public void Behavior_WhenBehaviorEnabled_ShouldScroll() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + diagram.Options.Zoom.ScaleFactor = 1.05; + + // Act + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + + // Assert + Assert.Equal(-95, diagram.Pan.X, 0); + Assert.Equal(-190, diagram.Pan.Y, 0); + } + + [Fact] + public void Behavior_WhenBehaviorDisabled_ShouldNotScroll() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = null; + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + + // Act + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + + // Assert + Assert.Equal(0, diagram.Pan.X); + Assert.Equal(0, diagram.Pan.Y); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs new file mode 100644 index 000000000..ba0d94d6d --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs @@ -0,0 +1,131 @@ +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Behaviors +{ + public class SelectionBoxBehaviorTests + { + [Fact] + public void Behavior_WhenBehaviorEnabled_ShouldUpdateSelectionBounds() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + + var selectionBoxBehavior = diagram.GetBehavior()!; + bool boundsChangedEventInvoked = false; + Rectangle? lastBounds = null; + selectionBoxBehavior.SelectionBoundsChanged += (_, newBounds) => + { + boundsChangedEventInvoked = true; + lastBounds = newBounds; + }; + + // Act + diagram.TriggerPointerDown(null, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(200, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + Assert.True(boundsChangedEventInvoked); + Assert.Equal(100, lastBounds!.Width); + Assert.Equal(50, lastBounds.Height); + Assert.Equal(100, lastBounds.Top); + Assert.Equal(100, lastBounds.Left); + } + + [Fact] + public void Behavior_WhenBehaviorDisabled_ShouldNotUpdateSelectionBounds() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = null; + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + + var selectionBoxBehavior = diagram.GetBehavior()!; + bool boundsChangedEventInvoked = false; + Rectangle? lastBounds = null; + selectionBoxBehavior.SelectionBoundsChanged += (_, newBounds) => + { + boundsChangedEventInvoked = true; + lastBounds = newBounds; + }; + + // Act + diagram.TriggerPointerDown(null, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(200, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + Assert.False(boundsChangedEventInvoked); + Assert.Null(lastBounds); + } + + [Fact] + public void Behavior_WithBoundsChangedDelegate_ShouldSelectNodesInsideArea() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + + var selectionBoxBehavior = diagram.GetBehavior()!; + selectionBoxBehavior.SelectionBoundsChanged += (_, _) => { }; + + var node = new NodeModel() + { + Size = new Size(100, 100), + Position = new Point(150, 150) + }; + diagram.Nodes.Add(node); + + // Act + diagram.TriggerPointerDown(null, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(300, 300, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + Assert.True(node.Selected); + + diagram.TriggerPointerMove(null, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + Assert.False(node.Selected); + } + + [Fact] + public void Behavior_WithoutBoundsChangedDelegate_ShouldNotSelectNodesInsideArea() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + + var node = new NodeModel() + { + Size = new Size(100, 100), + Position = new Point(150, 150) + }; + diagram.Nodes.Add(node); + + // Act + diagram.TriggerPointerDown(null, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(300, 300, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + Assert.False(node.Selected); + + diagram.TriggerPointerMove(null, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + Assert.False(node.Selected); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ZoomBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ZoomBehaviorTests.cs new file mode 100644 index 000000000..526c9b6c9 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ZoomBehaviorTests.cs @@ -0,0 +1,40 @@ +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Behaviors +{ + public class ZoomBehaviorTests + { + [Fact] + public void Behavior_WhenBehaviorEnabled_ShouldZoom() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(0, 0, 100, 100)); + + // Act + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 0, 100, 0, 0)); + + // Assert + Assert.Equal(1.05, diagram.Zoom); + } + + [Fact] + public void Behavior_WhenBehaviorDisabled_ShouldNotZoom() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = null; + diagram.SetContainer(new Rectangle(0, 0, 100, 100)); + + // Act + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 0, 100, 0, 0)); + + // Assert + Assert.Equal(1, diagram.Zoom); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Options/DiagramBehaviorOptionsTests.cs b/tests/Blazor.Diagrams.Core.Tests/Options/DiagramBehaviorOptionsTests.cs new file mode 100644 index 000000000..62e73a38f --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Options/DiagramBehaviorOptionsTests.cs @@ -0,0 +1,127 @@ +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Options; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Options +{ + public class DiagramBehaviorOptionsTests + { + [Fact] + public void DiagramBehaviorOptions_DragBehavior_IsBehaviorEnabled() + { + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = null; + Assert.False(diagram.GetBehavior()!.IsBehaviorEnabled(new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true))); + + diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior(); + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true))); + } + + [Fact] + public void DiagramBehaviorOptions_AltDragBehavior_IsBehaviorEnabled() + { + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior(); + diagram.BehaviorOptions.DiagramAltDragBehavior = null; + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new PointerEventArgs(0, 0, 0, 0, false, false, true, 0, 0, 0, 0, 0, 0, string.Empty, true))); + + diagram.BehaviorOptions.DiagramDragBehavior = null; + Assert.False(diagram.GetBehavior()!.IsBehaviorEnabled(new PointerEventArgs(0, 0, 0, 0, false, false, true, 0, 0, 0, 0, 0, 0, string.Empty, true))); + + diagram.BehaviorOptions.DiagramAltDragBehavior = diagram.GetBehavior(); + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new PointerEventArgs(0, 0, 0, 0, false, false, true, 0, 0, 0, 0, 0, 0, string.Empty, true))); + } + + [Fact] + public void DiagramBehaviorOptions_CtrlDragBehavior_IsBehaviorEnabled() + { + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior(); + diagram.BehaviorOptions.DiagramCtrlDragBehavior = null; + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new PointerEventArgs(0, 0, 0, 0, true, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true))); + + diagram.BehaviorOptions.DiagramDragBehavior = null; + Assert.False(diagram.GetBehavior()!.IsBehaviorEnabled(new PointerEventArgs(0, 0, 0, 0, true, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true))); + + diagram.BehaviorOptions.DiagramCtrlDragBehavior = diagram.GetBehavior(); + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new PointerEventArgs(0, 0, 0, 0, true, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true))); + } + + [Fact] + public void DiagramBehaviorOptions_ShiftDragBehavior_IsBehaviorEnabled() + { + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior(); + diagram.BehaviorOptions.DiagramShiftDragBehavior = null; + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new PointerEventArgs(0, 0, 0, 0, false, true, false, 0, 0, 0, 0, 0, 0, string.Empty, true))); + + diagram.BehaviorOptions.DiagramDragBehavior = null; + Assert.False(diagram.GetBehavior()!.IsBehaviorEnabled(new PointerEventArgs(0, 0, 0, 0, false, true, false, 0, 0, 0, 0, 0, 0, string.Empty, true))); + + diagram.BehaviorOptions.DiagramShiftDragBehavior = diagram.GetBehavior(); + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new PointerEventArgs(0, 0, 0, 0, false, true, false, 0, 0, 0, 0, 0, 0, string.Empty, true))); + } + + [Fact] + public void DiagramBehaviorOptions_DefaultScrollBehavior_IsBehaviorEnabled() + { + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = null; + Assert.False(diagram.GetBehavior()!.IsBehaviorEnabled(new WheelEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0))); + + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new WheelEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0))); + } + + [Fact] + public void DiagramBehaviorOptions_AltScrollBehavior_IsBehaviorEnabled() + { + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.BehaviorOptions.DiagramAltWheelBehavior = null; + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new WheelEventArgs(0, 0, 0, 0, false, false, true, 0, 0, 0, 0))); + + diagram.BehaviorOptions.DiagramWheelBehavior = null; + Assert.False(diagram.GetBehavior()!.IsBehaviorEnabled(new WheelEventArgs(0, 0, 0, 0, false, false, true, 0, 0, 0, 0))); + + diagram.BehaviorOptions.DiagramAltWheelBehavior = diagram.GetBehavior(); + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new WheelEventArgs(0, 0, 0, 0, false, false, true, 0, 0, 0, 0))); + } + + [Fact] + public void DiagramBehaviorOptions_CtrlScrollBehavior_IsBehaviorEnabled() + { + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.BehaviorOptions.DiagramCtrlWheelBehavior = null; + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new WheelEventArgs(0, 0, 0, 0, true, false, false, 0, 0, 0, 0))); + + diagram.BehaviorOptions.DiagramWheelBehavior = null; + Assert.False(diagram.GetBehavior()!.IsBehaviorEnabled(new WheelEventArgs(0, 0, 0, 0, true, false, false, 0, 0, 0, 0))); + + diagram.BehaviorOptions.DiagramCtrlWheelBehavior = diagram.GetBehavior(); + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new WheelEventArgs(0, 0, 0, 0, true, false, false, 0, 0, 0, 0))); + } + + [Fact] + public void DiagramBehaviorOptions_ShiftScrollBehavior_IsBehaviorEnabled() + { + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.BehaviorOptions.DiagramShiftWheelBehavior = null; + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new WheelEventArgs(0, 0, 0, 0, false, true, false, 0, 0, 0, 0))); + + diagram.BehaviorOptions.DiagramWheelBehavior = null; + Assert.False(diagram.GetBehavior()!.IsBehaviorEnabled(new WheelEventArgs(0, 0, 0, 0, false, true, false, 0, 0, 0, 0))); + + diagram.BehaviorOptions.DiagramShiftWheelBehavior = diagram.GetBehavior(); + Assert.True(diagram.GetBehavior()!.IsBehaviorEnabled(new WheelEventArgs(0, 0, 0, 0, false, true, false, 0, 0, 0, 0))); + } + } +} diff --git a/tests/Blazor.Diagrams.Tests/Components/Widgets/SelectionBoxWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/Widgets/SelectionBoxWidgetTests.cs new file mode 100644 index 000000000..11d913539 --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/Components/Widgets/SelectionBoxWidgetTests.cs @@ -0,0 +1,41 @@ +using Bunit; +using Xunit; +using Blazor.Diagrams.Components.Widgets; +using AngleSharp.Css.Dom; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Behaviors; + +namespace Blazor.Diagrams.Tests.Components.Widgets +{ + public class SelectionBoxWidgetTests + { + [Fact] + public void SelectionBoxWidget_SelectionBoundsChanged_RendersSelectionBoxWidgetInCorrectLocation() + { + // Arrange + using var ctx = new TestContext(); + var diagram = new BlazorDiagram(); + diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior(); + diagram.SetPan(-75, -100); + diagram.SetContainer(new Rectangle(new Point(0, 0), new Size(500, 500))); + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.BlazorDiagram, diagram)); + Assert.Throws(() => cut.Find("div")); + diagram.TriggerPointerDown(null, + new PointerEventArgs(100, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(200, 250, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + + // Assert + var widget = cut.Find("div"); + Assert.Equal("100px", widget.GetStyle().GetWidth()); + Assert.Equal("100px", widget.GetStyle().GetHeight()); + Assert.Equal("150px", widget.GetStyle().GetTop()); + Assert.Equal("100px", widget.GetStyle().GetLeft()); + } + } +} From b6e8f9f6744a0b194501e3cd496cb044dd2aa969 Mon Sep 17 00:00:00 2001 From: Marcos Cousens-Schulz <983341+MarcosCosmos@users.noreply.github.com> Date: Fri, 2 Jun 2023 11:36:48 +1000 Subject: [PATCH 031/159] Make Diagram.SetContainer accept null gracefully Authored-by: Marcos C-S --- src/Blazor.Diagrams.Core/Diagram.cs | 4 ++-- tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 3f1023865..61b8b09f8 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -264,9 +264,9 @@ public void SetZoom(double newZoom) Refresh(); } - public void SetContainer(Rectangle newRect) + public void SetContainer(Rectangle? newRect) { - if (newRect.Equals(Container)) + if (Equals(newRect, Container)) return; Container = newRect; diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs index 85a812dfc..b596c9ac7 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs @@ -122,4 +122,17 @@ public void ZoomOptions_ThrowExceptionWhenLessThan0(double zoomValue) var diagram = new TestDiagram(); Assert.Throws(() => diagram.Options.Zoom.Minimum = zoomValue); } + + [Fact] + public void SetContainer_ShouldAcceptNullGracefully() + { + // Arrange + var diagram = new TestDiagram(); + + //Act + var exception = Record.Exception(() => diagram.SetContainer(null)); + + // Assert + exception.Should().BeNull(); + } } From cf435c19cd2d404ad0e23badcafc0ee9f97f9f5f Mon Sep 17 00:00:00 2001 From: Marcos Cousens-Schulz <983341+MarcosCosmos@users.noreply.github.com> Date: Tue, 20 Jun 2023 11:16:32 +1000 Subject: [PATCH 032/159] Marcos/diagram set container null V2 (#7) --- .../Components/Renderers/PortRenderer.cs | 13 ++++++++--- .../Extensions/JSRuntimeExtensions.cs | 2 +- .../Components/Renderers/PortRendererTest.cs | 22 +++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) create mode 100644 tests/Blazor.Diagrams.Tests/Components/Renderers/PortRendererTest.cs diff --git a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs index bbd4786f1..275d4108b 100644 --- a/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs +++ b/src/Blazor.Diagrams/Components/Renderers/PortRenderer.cs @@ -124,10 +124,17 @@ private async Task UpdateDimensions() var zoom = BlazorDiagram.Zoom; var pan = BlazorDiagram.Pan; var rect = await JSRuntime.GetBoundingClientRect(_element); + + if (rect is not null) + { + Port.Size = new Size(rect.Width / zoom, rect.Height / zoom); - 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); + if (BlazorDiagram.Container is not null) + { + 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/Extensions/JSRuntimeExtensions.cs b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs index 459456863..42488844f 100644 --- a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs +++ b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs @@ -8,7 +8,7 @@ namespace Blazor.Diagrams.Extensions; 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); } diff --git a/tests/Blazor.Diagrams.Tests/Components/Renderers/PortRendererTest.cs b/tests/Blazor.Diagrams.Tests/Components/Renderers/PortRendererTest.cs new file mode 100644 index 000000000..912f2a309 --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/Components/Renderers/PortRendererTest.cs @@ -0,0 +1,22 @@ +using Blazor.Diagrams.Components.Renderers; +using Blazor.Diagrams.Core.Models; +using Bunit; +using Xunit; + +namespace Blazor.Diagrams.Tests.Components.Renderers; + +public class PortRendererTest +{ + [Fact] + void UpdateDimensionsDoesNotCrashWithNullContainer() + { + using var ctx = new TestContext(); + ctx.JSInterop.Mode = JSRuntimeMode.Loose; + var node = new NodeModel(); + var portModel = new PortModel(node, PortAlignment.Bottom); + + var component = ctx.RenderComponent(parameters => parameters + .Add(n => n.Port, portModel) + .Add(n => n.BlazorDiagram, new BlazorDiagram())); + } +} \ No newline at end of file From 8561ff2f39d31615cd90970daab868e77792ed85 Mon Sep 17 00:00:00 2001 From: Heather Cox Date: Wed, 13 Dec 2023 15:55:22 +1100 Subject: [PATCH 033/159] resolve build errors --- .../Controls/RemoveControlTests.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs b/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs index 1b2201ef5..22bd86d95 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs @@ -3,6 +3,7 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Options; using Moq; using System; using System.Collections.Generic; @@ -40,7 +41,7 @@ public async Task OnPointerDown_ShouldDeleteNodeTrue_RemovesNode() // Arrange RemoveControl removeControl = new(0, 0); Diagram diagram = new TestDiagram( - new Options.DiagramOptions + new DiagramOptions { Constraints = { @@ -63,7 +64,7 @@ public async Task OnPointerDown_ShouldDeleteNodeFalse_KeepsNode() // Arrange RemoveControl removeControl = new(0, 0); Diagram diagram = new TestDiagram( - new Options.DiagramOptions + new DiagramOptions { Constraints = { @@ -113,7 +114,7 @@ public async Task OnPointerDown_ShouldDeleteLinkTrue_RemovesLink() // Arrange RemoveControl removeControl = new(0, 0); Diagram diagram = new TestDiagram( - new Options.DiagramOptions + new DiagramOptions { Constraints = { @@ -147,7 +148,7 @@ public async Task OnPointerDown_ShouldDeleteLinkFalse_KeepsLink() // Arrange RemoveControl removeControl = new(0, 0); Diagram diagram = new TestDiagram( - new Options.DiagramOptions + new DiagramOptions { Constraints = { @@ -207,7 +208,7 @@ public async Task OnPointerDown_ShouldDeleteGroupTrue_RemovesGroup() // Arrange RemoveControl removeControl = new(0, 0); Diagram diagram = new TestDiagram( - new Options.DiagramOptions + new DiagramOptions { Constraints = { @@ -240,7 +241,7 @@ public async Task OnPointerDown_ShouldDeleteGroupFalse_KeepsGroup() // Arrange RemoveControl removeControl = new(0, 0); Diagram diagram = new TestDiagram( - new Options.DiagramOptions + new DiagramOptions { Constraints = { From 56642ff7d354e6847bb872c2e38b0b8be20662fe Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Tue, 12 Dec 2023 09:58:07 +1100 Subject: [PATCH 034/159] Added Tests --- .../Behaviors/ResizeBehaviorTest.cs | 233 ++++++++++++++++++ .../Behaviors/ScrollBehaviorTests.cs | 96 +++++--- 2 files changed, 294 insertions(+), 35 deletions(-) create mode 100644 tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs new file mode 100644 index 000000000..22c5c2d5a --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs @@ -0,0 +1,233 @@ +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using FluentAssertions; +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Behaviors +{ + public class ResizeBehaviorTest + { + [Fact] + public void ShouldRecalculateSizeAndPosition_TopLeft() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.TopLeft); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(15); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(185); + } + + [Fact] + public void ShouldRecalculateSizeAndPosition_TopRight() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.TopRight); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(15); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(185); + } + + [Fact] + public void ShouldRecalculateSizeAndPosition_BottomLeft() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.BottomLeft); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(215); + } + + [Fact] + public void ShouldRecalculateSizeAndPosition_BottomRight() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.BottomRight); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(215); + } + + [Fact] + public void ShouldStopResizingSmallerThanMinimumSize() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.TopLeft); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(node.MinimumDimensions.Width); + node.Size.Height.Should().Be(node.MinimumDimensions.Height); + } + + [Fact] + public void ShouldStopResizeOnPointerUp() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.TopLeft); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(15); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(185); + + diagram.TriggerPointerUp(null, eventArgs); + + // move pointer after pointer up + eventArgs = new PointerEventArgs(30, 50, 1, 1, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // should be no change + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(15); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(185); + } + + [Fact] + public void NodeUpdatesWhenScrollingWhileResizing() + { + // Arrange + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var resizer = node.AddResizer(ResizerPosition.TopLeft); + + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // Act + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerDown(resizer, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.Options.Zoom.ScaleFactor = 1.05; + node.Size = new Size(100, 200); + diagram.Nodes.Add(node); + + diagram.SelectModel(node, false); + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + + // Assert + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(-175); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(185); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs index acd78bbdc..61265c0df 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs @@ -1,43 +1,69 @@ using Blazor.Diagrams.Core.Behaviors; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; using Xunit; namespace Blazor.Diagrams.Core.Tests.Behaviors { - public class ScrollBehaviorTests - { - [Fact] - public void Behavior_WhenBehaviorEnabled_ShouldScroll() - { - // Arrange - var diagram = new TestDiagram(); - diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); - diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); - diagram.Options.Zoom.ScaleFactor = 1.05; - - // Act - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); - - // Assert - Assert.Equal(-95, diagram.Pan.X, 0); - Assert.Equal(-190, diagram.Pan.Y, 0); - } - - [Fact] - public void Behavior_WhenBehaviorDisabled_ShouldNotScroll() - { - // Arrange - var diagram = new TestDiagram(); - diagram.BehaviorOptions.DiagramWheelBehavior = null; - diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); - - // Act - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); - - // Assert - Assert.Equal(0, diagram.Pan.X); - Assert.Equal(0, diagram.Pan.Y); - } - } + public class ScrollBehaviorTests + { + [Fact] + public void Behavior_WhenBehaviorEnabled_ShouldScroll() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + diagram.Options.Zoom.ScaleFactor = 1.05; + + // Act + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + + // Assert + Assert.Equal(-95, diagram.Pan.X, 0); + Assert.Equal(-190, diagram.Pan.Y, 0); + } + + [Fact] + public void Behavior_WhenBehaviorDisabled_ShouldNotScroll() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = null; + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + + // Act + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + + // Assert + Assert.Equal(0, diagram.Pan.X); + Assert.Equal(0, diagram.Pan.Y); + } + + [Fact] + public void NodeUpdatesWhenScrollingWhileDragging() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + diagram.Options.Zoom.ScaleFactor = 1.05; + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + diagram.Nodes.Add(node); + + // Act + diagram.SelectModel(node, false); + diagram.TriggerPointerDown(node, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + + // Assert + Assert.Equal(-95, diagram.Pan.X, 0); + Assert.Equal(-190, diagram.Pan.Y, 0); + Assert.Equal(-190, node.Position.Y); + } + + } } From cb3c5dbaa41cfbfe385e152d1b152adcf271f320 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Wed, 13 Dec 2023 13:35:52 +1100 Subject: [PATCH 035/159] First commit --- .../Behaviors/DragMovablesBehavior.cs | 154 ++++++- .../Behaviors/ScrollBehavior.cs | 30 +- src/Blazor.Diagrams.Core/Diagram.cs | 412 +++++++++++++++++- 3 files changed, 573 insertions(+), 23 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index face95c33..7e778c097 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -1,15 +1,16 @@ -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Behaviors.Base; using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; using System; using System.Collections.Generic; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Behaviors.Base; namespace Blazor.Diagrams.Core.Behaviors; public class DragMovablesBehavior : Behavior { +<<<<<<< HEAD private readonly Dictionary _initialPositions; private double? _lastClientX; private double? _lastClientY; @@ -112,4 +113,149 @@ public override void Dispose() Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; } +======= + public class DragMovablesBehavior : Behavior + { + private readonly Dictionary _initialPositions; + private double? _lastClientX; + private double? _lastClientY; + private bool _moved; + private double _totalScrollX = 0; + private double _totalScrollY = 0; + + public DragMovablesBehavior(Diagram diagram) : base(diagram) + { + _initialPositions = new Dictionary(); + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + Diagram.Wheel += OnPointerMove; + } + + private void OnPointerDown(Model? model, PointerEventArgs e) + { + if (model is not MovableModel) + return; + + _initialPositions.Clear(); + foreach (var sm in Diagram.GetSelectedModels()) + { + if (sm is not MovableModel movable || movable.Locked) + continue; + + // Special case: groups without auto size on + if (sm is NodeModel node && node.Group != null && !node.Group.AutoSize) + continue; + + var position = movable.Position; + if (Diagram.Options.GridSnapToCenter && movable is NodeModel n) + { + position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2, + movable.Position.Y + (n.Size?.Height ?? 0) / 2); + } + + _initialPositions.Add(movable, position); + } + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + _moved = false; + } + + public 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; + + moveNodes(model, deltaX, deltaY); + } + + public void OnPointerMove(WheelEventArgs e) + { + if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) + return; + + _moved = true; + + _totalScrollX += e.DeltaX; + _totalScrollY += e.DeltaY; + + // Use _totalScrollX and _totalScrollY for moving nodes + moveNodes(null, _totalScrollX, _totalScrollY); + + Console.WriteLine($"TotalScrollX: {_totalScrollX}, TotalScrollY: {_totalScrollY}"); + + _lastClientX -= e.DeltaX; + _lastClientY -= e.DeltaY; + + Console.WriteLine($"Updated LastClientX: {_lastClientX}, Updated LastClientY: {_lastClientY}"); + } + + private void moveNodes(Model? model, double deltaX, double deltaY) + { + Console.WriteLine($"DeltaX: {deltaX}, DeltaY: {deltaY}"); + 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) + { + 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; + + if (_moved) + { + foreach (var (movable, _) in _initialPositions) + { + movable.TriggerMoved(); + } + } + + _initialPositions.Clear(); + _totalScrollX = 0; + _totalScrollY = 0; + _lastClientX = null; + _lastClientY = null; + } + + private double ApplyGridSize(double n) + { + if (Diagram.Options.GridSize == null) + 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); + } + + public override void Dispose() + { + _initialPositions.Clear(); + + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + Diagram.Wheel -= OnPointerMove; + } + } +>>>>>>> 7bf2db0 (First commit) } diff --git a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs index fa3ef63e1..9b563428e 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs @@ -4,22 +4,22 @@ namespace Blazor.Diagrams.Core.Behaviors { - public class ScrollBehavior : WheelBehavior - { - public ScrollBehavior(Diagram diagram) - : base(diagram) - { - } + public class ScrollBehavior : WheelBehavior + { + public ScrollBehavior(Diagram diagram) + : base(diagram) + { + } - protected override void OnDiagramWheel(WheelEventArgs e) - { - if (Diagram.Container == null || !IsBehaviorEnabled(e)) - return; + protected override void OnDiagramWheel(WheelEventArgs e) + { + if (Diagram.Container == null || !IsBehaviorEnabled(e)) + return; - var x = Diagram.Pan.X - (e.DeltaX / Diagram.Options.Zoom.ScaleFactor); - var y = Diagram.Pan.Y - (e.DeltaY / Diagram.Options.Zoom.ScaleFactor); + var x = Diagram.Pan.X - (e.DeltaX / Diagram.Options.Zoom.ScaleFactor); + var y = Diagram.Pan.Y - (e.DeltaY / Diagram.Options.Zoom.ScaleFactor); - Diagram.SetPan(x, y); - } - } + Diagram.SetPan(x, y); + } + } } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 61b8b09f8..edc44fd76 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -1,16 +1,16 @@ using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Behaviors.Base; +using Blazor.Diagrams.Core.Controls; +using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Extensions; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Layers; using Blazor.Diagrams.Core.Models.Base; -using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Options; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; -using Blazor.Diagrams.Core.Options; -using Blazor.Diagrams.Core.Controls; -using Blazor.Diagrams.Core.Behaviors.Base; [assembly: InternalsVisibleTo("Blazor.Diagrams")] [assembly: InternalsVisibleTo("Blazor.Diagrams.Tests")] @@ -20,6 +20,7 @@ namespace Blazor.Diagrams.Core; public abstract class Diagram { +<<<<<<< HEAD private readonly Dictionary _behaviors; private readonly List _orderedSelectables; @@ -416,4 +417,407 @@ private void OnModelOrderChanged(Model model) public void TriggerPointerDoubleClick(Model? model, PointerEventArgs e) => PointerDoubleClick?.Invoke(model, e); #endregion +======= + 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() + { + _behaviors = new Dictionary(); + _orderedSelectables = new List(); + + Nodes = new NodeLayer(this); + Links = new LinkLayer(this); + Groups = new GroupLayer(this); + Controls = new ControlsLayer(); + BehaviorOptions = new DiagramBehaviorOptions(); + + Nodes.Added += OnSelectableAdded; + Links.Added += OnSelectableAdded; + Groups.Added += OnSelectableAdded; + + Nodes.Removed += OnSelectableRemoved; + Links.Removed += OnSelectableRemoved; + Groups.Removed += OnSelectableRemoved; + + RegisterDefaultBehaviors(); + + BehaviorOptions.DiagramDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramShiftDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramWheelBehavior ??= GetBehavior(); + } + + public abstract DiagramOptions Options { get; } + public DiagramBehaviorOptions BehaviorOptions { 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(); + } + + public void Batch(Action action) + { + 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; + action(); + SuspendRefresh = false; + Refresh(); + } + + #region Selection + + public IEnumerable GetSelectedModels() + { + foreach (var node in Nodes) + { + 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 group in Groups) + { + if (group.Selected) + yield return group; + } + } + + public void SelectModel(SelectableModel model, bool unselectOthers) + { + if (model.Selected) + return; + + if (unselectOthers) + UnselectAll(); + + 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); + } + } + + #endregion + + #region Behaviors + + void RegisterDefaultBehaviors() + { + 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)); + RegisterBehavior(new ScrollBehavior(this)); + RegisterBehavior(new SelectionBoxBehavior(this)); + RegisterBehavior(new ResizeBehavior(this)); + } + + 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); + } + + 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; + + _behaviors[type].Dispose(); + _behaviors.Remove(type); + } + + #endregion + + 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; + + SuspendRefresh = true; + + 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); + + SuspendRefresh = false; + 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 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(); + } + + public void SetContainer(Rectangle? newRect) + { + if (Equals(newRect, Container)) + return; + + 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"); + + 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"); + + 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"); + + 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; + } + + /// + /// 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); + + if (model.Order == 0) + { + 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; + + RefreshOrders(); + } + + #endregion + + #region Events + + 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); + + public void TriggerPointerEnter(Model? model, PointerEventArgs e) => PointerEnter?.Invoke(model, e); + + public void TriggerPointerLeave(Model? model, PointerEventArgs e) => PointerLeave?.Invoke(model, e); + + public void TriggerKeyDown(KeyboardEventArgs e) => KeyDown?.Invoke(e); + + public void TriggerWheel(WheelEventArgs e) => Wheel?.Invoke(e); + + public void TriggerPointerClick(Model? model, PointerEventArgs e) => PointerClick?.Invoke(model, e); + + public void TriggerPointerDoubleClick(Model? model, PointerEventArgs e) => PointerDoubleClick?.Invoke(model, e); + + #endregion + } +>>>>>>> 7bf2db0 (First commit) } \ No newline at end of file From b817d57e68ed16ad6617a93dd0d18d9f6e1b1a15 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Wed, 13 Dec 2023 13:41:57 +1100 Subject: [PATCH 036/159] Updated test --- .../Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs index 61265c0df..b82c98d8a 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs @@ -62,7 +62,7 @@ public void NodeUpdatesWhenScrollingWhileDragging() // Assert Assert.Equal(-95, diagram.Pan.X, 0); Assert.Equal(-190, diagram.Pan.Y, 0); - Assert.Equal(-190, node.Position.Y); + Assert.Equal(200, node.Position.Y); } } From f4dfbfb4d6177c1d65d63260fc98e942f94db588 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Wed, 13 Dec 2023 14:49:12 +1100 Subject: [PATCH 037/159] Drag and scroll fixed --- .../Behaviors/DragMovablesBehavior.cs | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 7e778c097..fa0a9203b 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -120,8 +120,8 @@ public class DragMovablesBehavior : Behavior private double? _lastClientX; private double? _lastClientY; private bool _moved; - private double _totalScrollX = 0; - private double _totalScrollY = 0; + private double _totalMovedX = 0; + private double _totalMovedY = 0; public DragMovablesBehavior(Diagram diagram) : base(diagram) { @@ -172,7 +172,14 @@ public void OnPointerMove(Model? model, PointerEventArgs e) var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; - moveNodes(model, deltaX, deltaY); + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + moveNodes(model, _totalMovedX, _totalMovedY); + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + } public void OnPointerMove(WheelEventArgs e) @@ -182,27 +189,19 @@ public void OnPointerMove(WheelEventArgs e) _moved = true; - _totalScrollX += e.DeltaX; - _totalScrollY += e.DeltaY; + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; - // Use _totalScrollX and _totalScrollY for moving nodes - moveNodes(null, _totalScrollX, _totalScrollY); - - Console.WriteLine($"TotalScrollX: {_totalScrollX}, TotalScrollY: {_totalScrollY}"); - - _lastClientX -= e.DeltaX; - _lastClientY -= e.DeltaY; - - Console.WriteLine($"Updated LastClientX: {_lastClientX}, Updated LastClientY: {_lastClientY}"); + moveNodes(null, _totalMovedX, _totalMovedY); } private void moveNodes(Model? model, double deltaX, double deltaY) { - Console.WriteLine($"DeltaX: {deltaX}, DeltaY: {deltaY}"); 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) { node.SetPosition(ndx - (node.Size?.Width ?? 0) / 2, ndy - (node.Size?.Height ?? 0) / 2); @@ -228,8 +227,8 @@ private void OnPointerUp(Model? model, PointerEventArgs e) } _initialPositions.Clear(); - _totalScrollX = 0; - _totalScrollY = 0; + _totalMovedX = 0; + _totalMovedY = 0; _lastClientX = null; _lastClientY = null; } From 4d710a4a1ab16698cd22342db5e841fc0aaac45f Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 14 Dec 2023 10:20:46 +1100 Subject: [PATCH 038/159] fixed cherry pick errors --- .../Behaviors/DragMovablesBehavior.cs | 309 ++++++------------ .../Behaviors/ResizeBehaviorTest.cs | 233 ------------- 2 files changed, 99 insertions(+), 443 deletions(-) delete mode 100644 tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index fa0a9203b..a69d40017 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -10,251 +10,140 @@ namespace Blazor.Diagrams.Core.Behaviors; public class DragMovablesBehavior : Behavior { -<<<<<<< HEAD - private readonly Dictionary _initialPositions; - private double? _lastClientX; - private double? _lastClientY; - private bool _moved; - - 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; - - _initialPositions.Clear(); - foreach (var sm in Diagram.GetSelectedModels()) - { - if (sm is not MovableModel movable || movable.Locked) - continue; - - // Special case: groups without auto size on - if (sm is NodeModel node && node.Group != null && !node.Group.AutoSize) - continue; - - var position = movable.Position; - if (Diagram.Options.GridSnapToCenter && movable is NodeModel n) - { - position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2, - movable.Position.Y + (n.Size?.Height ?? 0) / 2); - } - - _initialPositions.Add(movable, position); - } - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _moved = false; - } - - 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) - { - 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); - } - } - } - - private void OnPointerUp(Model? model, PointerEventArgs e) - { - if (_initialPositions.Count == 0) - return; - - if (_moved) - { - foreach (var (movable, _) in _initialPositions) - { - movable.TriggerMoved(); - } - } - - _initialPositions.Clear(); - _lastClientX = null; - _lastClientY = null; - } - - private double ApplyGridSize(double n) - { - if (Diagram.Options.GridSize == null) - return n; - - var gridSize = Diagram.Options.GridSize.Value; - return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); - } + private readonly Dictionary _initialPositions; + private double? _lastClientX; + private double? _lastClientY; + private bool _moved; + private double _totalMovedX = 0; + private double _totalMovedY = 0; + + public DragMovablesBehavior(Diagram diagram) : base(diagram) + { + _initialPositions = new Dictionary(); + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + Diagram.Wheel += OnPointerMove; + } - public override void Dispose() - { - _initialPositions.Clear(); - - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - } -======= - public class DragMovablesBehavior : Behavior + private void OnPointerDown(Model? model, PointerEventArgs e) { - private readonly Dictionary _initialPositions; - private double? _lastClientX; - private double? _lastClientY; - private bool _moved; - private double _totalMovedX = 0; - private double _totalMovedY = 0; + if (model is not MovableModel) + return; - public DragMovablesBehavior(Diagram diagram) : base(diagram) + _initialPositions.Clear(); + foreach (var sm in Diagram.GetSelectedModels()) { - _initialPositions = new Dictionary(); - Diagram.PointerDown += OnPointerDown; - Diagram.PointerMove += OnPointerMove; - Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnPointerMove; - } + if (sm is not MovableModel movable || movable.Locked) + continue; - private void OnPointerDown(Model? model, PointerEventArgs e) - { - if (model is not MovableModel) - return; + // Special case: groups without auto size on + if (sm is NodeModel node && node.Group != null && !node.Group.AutoSize) + continue; - _initialPositions.Clear(); - foreach (var sm in Diagram.GetSelectedModels()) + var position = movable.Position; + if (Diagram.Options.GridSnapToCenter && movable is NodeModel n) { - if (sm is not MovableModel movable || movable.Locked) - continue; - - // Special case: groups without auto size on - if (sm is NodeModel node && node.Group != null && !node.Group.AutoSize) - continue; - - var position = movable.Position; - if (Diagram.Options.GridSnapToCenter && movable is NodeModel n) - { - position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2, - movable.Position.Y + (n.Size?.Height ?? 0) / 2); - } - - _initialPositions.Add(movable, position); + 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); } - public 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; + public void OnPointerMove(Model? model, PointerEventArgs e) + { + if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) + return; - var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; - var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; + _moved = true; - _totalMovedX += deltaX; - _totalMovedY += deltaY; + var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; + var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; - moveNodes(model, _totalMovedX, _totalMovedY); + _totalMovedX += deltaX; + _totalMovedY += deltaY; - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; + moveNodes(model, _totalMovedX, _totalMovedY); - } + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; - public void OnPointerMove(WheelEventArgs e) - { - if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) - return; + } - _moved = true; + public void OnPointerMove(WheelEventArgs e) + { + if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) + return; - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; + _moved = true; - moveNodes(null, _totalMovedX, _totalMovedY); - } + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; - private void moveNodes(Model? model, double deltaX, double deltaY) + moveNodes(null, _totalMovedX, _totalMovedY); + } + + private void moveNodes(Model? model, double deltaX, double deltaY) + { + foreach (var (movable, initialPosition) in _initialPositions) { - foreach (var (movable, initialPosition) in _initialPositions) - { - var ndx = ApplyGridSize(deltaX + initialPosition.X); - var ndy = ApplyGridSize(deltaY + initialPosition.Y); + 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); - } + 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); } } + } - 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(); - _totalMovedX = 0; - _totalMovedY = 0; - _lastClientX = null; - _lastClientY = null; } - private double ApplyGridSize(double n) - { - if (Diagram.Options.GridSize == null) - return n; + _initialPositions.Clear(); + _totalMovedX = 0; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; + } + + private double ApplyGridSize(double n) + { + if (Diagram.Options.GridSize == null) + return n; - var gridSize = Diagram.Options.GridSize.Value; + 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); - } + return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); + } - public override void Dispose() - { - _initialPositions.Clear(); + public override void Dispose() + { + _initialPositions.Clear(); - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnPointerMove; - } + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + Diagram.Wheel -= OnPointerMove; } ->>>>>>> 7bf2db0 (First commit) } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs deleted file mode 100644 index 22c5c2d5a..000000000 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ResizeBehaviorTest.cs +++ /dev/null @@ -1,233 +0,0 @@ -using Blazor.Diagrams.Core.Behaviors; -using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; -using FluentAssertions; -using System; -using System.Threading.Tasks; -using Xunit; - -namespace Blazor.Diagrams.Core.Tests.Behaviors -{ - public class ResizeBehaviorTest - { - [Fact] - public void ShouldRecalculateSizeAndPosition_TopLeft() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.TopLeft); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - - // after resize - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(15); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(185); - } - - [Fact] - public void ShouldRecalculateSizeAndPosition_TopRight() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.TopRight); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(15); - node.Size.Width.Should().Be(110); - node.Size.Height.Should().Be(185); - } - - [Fact] - public void ShouldRecalculateSizeAndPosition_BottomLeft() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.BottomLeft); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(215); - } - - [Fact] - public void ShouldRecalculateSizeAndPosition_BottomRight() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.BottomRight); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(110); - node.Size.Height.Should().Be(215); - } - - [Fact] - public void ShouldStopResizingSmallerThanMinimumSize() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.TopLeft); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(node.MinimumDimensions.Width); - node.Size.Height.Should().Be(node.MinimumDimensions.Height); - } - - [Fact] - public void ShouldStopResizeOnPointerUp() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.TopLeft); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(15); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(185); - - diagram.TriggerPointerUp(null, eventArgs); - - // move pointer after pointer up - eventArgs = new PointerEventArgs(30, 50, 1, 1, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // should be no change - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(15); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(185); - } - - [Fact] - public void NodeUpdatesWhenScrollingWhileResizing() - { - // Arrange - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var resizer = node.AddResizer(ResizerPosition.TopLeft); - - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // Act - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerDown(resizer, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); - diagram.Options.Zoom.ScaleFactor = 1.05; - node.Size = new Size(100, 200); - diagram.Nodes.Add(node); - - diagram.SelectModel(node, false); - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); - - // Assert - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(-175); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(185); - } - } -} From 191dd7ca5bd6731ffb3dd698cb66a3cbd7e6fece Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 14 Dec 2023 13:32:47 +1100 Subject: [PATCH 039/159] Arrows now update when scrolling --- .../Behaviors/DragNewLinkBehavior.cs | 344 +++--- src/Blazor.Diagrams.Core/Diagram.cs | 1004 +++++------------ .../Behaviors/DragNewLinkBehaviorTests.cs | 894 ++++++++------- 3 files changed, 943 insertions(+), 1299 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 7efa83dc1..2f0d92ac1 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -1,172 +1,188 @@ -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; -using Blazor.Diagrams.Core.Events; -using System.Linq; -using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Anchors; using Blazor.Diagrams.Core.Behaviors.Base; +using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using System; +using System.Linq; namespace Blazor.Diagrams.Core.Behaviors; public class DragNewLinkBehavior : Behavior { - private PositionAnchor? _targetPositionAnchor; - - public BaseLinkModel? OngoingLink { get; private set; } - - 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(CalculateTargetPosition(clientX, clientY)); - OngoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor); - if (OngoingLink == null) - return; - - Diagram.Links.Add(OngoingLink); - } - - public void StartFrom(BaseLinkModel link, double clientX, double clientY) - { - if (OngoingLink != null) - return; - - _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); - 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 (port.Locked) - return; - - _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); - } - } - - private void OnPointerMove(Model? model, MouseEventArgs e) - { - if (OngoingLink == null || model != null) - return; - - _targetPositionAnchor!.SetPosition(CalculateTargetPosition(e.ClientX, e.ClientY)); - - 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)); - } - } - - OngoingLink.Refresh(); - OngoingLink.RefreshLinks(); - } - - private void OnPointerUp(Model? model, MouseEventArgs e) - { - if (OngoingLink == null) - return; - - 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(); - } - else if (Diagram.Options.Links.RequireTarget) - { - 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; - } - - 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) - 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)) - { - var distance = position.DistanceTo(port.Position); - - if (distance <= Diagram.Options.Links.SnappingRadius && (OngoingLink.Source.Model?.CanAttachTo(port) != false)) - { - if (distance < nearestSnapPortDistance) - { - nearestSnapPortDistance = distance; - nearestSnapPort = port; - } - } - } - - return nearestSnapPort; - } - - public override void Dispose() - { - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - } + private PositionAnchor? _targetPositionAnchor; + + public BaseLinkModel? OngoingLink { get; private set; } + + public DragNewLinkBehavior(Diagram diagram) : base(diagram) + { + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + Diagram.Wheel += OnPointerMove; + } + + public void StartFrom(ILinkable source, double clientX, double clientY) + { + if (OngoingLink != null) + return; + + _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); + OngoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor); + if (OngoingLink == null) + return; + + Diagram.Links.Add(OngoingLink); + } + + public void StartFrom(BaseLinkModel link, double clientX, double clientY) + { + if (OngoingLink != null) + return; + + _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); + 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 (port.Locked) + return; + + _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); + } + } + + private void OnPointerMove(Model? model, MouseEventArgs e) + { + if (OngoingLink == null || model != null) + return; + + UpdateLinkPosition(e.ClientX, e.ClientY); + } + + private void OnPointerMove(WheelEventArgs e) + { + if (OngoingLink == null) + return; + + UpdateLinkPosition(e.ClientX + e.DeltaX, e.ClientY + e.DeltaY); + } + + private void UpdateLinkPosition(double clientX, double clientY) + { + _targetPositionAnchor!.SetPosition(CalculateTargetPosition(clientX, clientY)); + + 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)); + } + } + + OngoingLink!.Refresh(); + OngoingLink!.RefreshLinks(); + } + + private void OnPointerUp(Model? model, MouseEventArgs e) + { + if (OngoingLink == null) + return; + + 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(); + } + else if (Diagram.Options.Links.RequireTarget) + { + 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; + } + + 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) + 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)) + { + var distance = position.DistanceTo(port.Position); + + if (distance <= Diagram.Options.Links.SnappingRadius && (OngoingLink.Source.Model?.CanAttachTo(port) != false)) + { + if (distance < nearestSnapPortDistance) + { + nearestSnapPortDistance = distance; + nearestSnapPort = port; + } + } + } + + return nearestSnapPort; + } + + public override void Dispose() + { + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + Diagram.Wheel -= OnPointerMove; + } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index edc44fd76..fb5ab2edb 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -20,804 +20,400 @@ namespace Blazor.Diagrams.Core; public abstract class Diagram { -<<<<<<< HEAD - 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(); - BehaviorOptions = new DiagramBehaviorOptions(); - - Nodes.Added += OnSelectableAdded; - Links.Added += OnSelectableAdded; - Groups.Added += OnSelectableAdded; - - Nodes.Removed += OnSelectableRemoved; - Links.Removed += OnSelectableRemoved; - Groups.Removed += OnSelectableRemoved; - - RegisterDefaultBehaviors(); - - BehaviorOptions.DiagramDragBehavior ??= GetBehavior(); - BehaviorOptions.DiagramShiftDragBehavior ??= GetBehavior(); - BehaviorOptions.DiagramWheelBehavior ??= GetBehavior(); - } - - public abstract DiagramOptions Options { get; } - public DiagramBehaviorOptions BehaviorOptions { 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(); - } - - public void Batch(Action action) - { - 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; - action(); - SuspendRefresh = false; - Refresh(); - } - - #region Selection - - public IEnumerable GetSelectedModels() - { - foreach (var node in Nodes) - { - 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 group in Groups) - { - if (group.Selected) - yield return group; - } - } - - public void SelectModel(SelectableModel model, bool unselectOthers) - { - if (model.Selected) - return; - - if (unselectOthers) - UnselectAll(); - - 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); - } - } - - #endregion - - #region Behaviors - void RegisterDefaultBehaviors() - { - 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)); - RegisterBehavior(new ScrollBehavior(this)); - RegisterBehavior(new SelectionBoxBehavior(this)); - } - - public void RegisterBehavior(Behavior behavior) + 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() { - var type = behavior.GetType(); - if (_behaviors.ContainsKey(type)) - throw new Exception($"Behavior '{type.Name}' already registered"); - - _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); - if (!_behaviors.ContainsKey(type)) - return; - - _behaviors[type].Dispose(); - _behaviors.Remove(type); - } - - #endregion - - 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; - - SuspendRefresh = true; - - 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); - - SuspendRefresh = false; - 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 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(); - } - - public void SetContainer(Rectangle? newRect) - { - if (Equals(newRect, Container)) - return; - - 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"); - - 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"); - - 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"); - - 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; - } - - /// - /// 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); - - if (model.Order == 0) - { - model.Order = maxOrder + 1; - } - - model.OrderChanged += OnModelOrderChanged; - } + _behaviors = new Dictionary(); + _orderedSelectables = new List(); - private void OnSelectableRemoved(SelectableModel model) - { - model.OrderChanged -= OnModelOrderChanged; - _orderedSelectables.Remove(model); - } + Nodes = new NodeLayer(this); + Links = new LinkLayer(this); + Groups = new GroupLayer(this); + Controls = new ControlsLayer(); + BehaviorOptions = new DiagramBehaviorOptions(); - private void OnModelOrderChanged(Model model) - { - if (SuspendSorting) - return; + Nodes.Added += OnSelectableAdded; + Links.Added += OnSelectableAdded; + Groups.Added += OnSelectableAdded; - RefreshOrders(); - } + Nodes.Removed += OnSelectableRemoved; + Links.Removed += OnSelectableRemoved; + Groups.Removed += OnSelectableRemoved; - #endregion + RegisterDefaultBehaviors(); - #region Events - - 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); - - public void TriggerPointerEnter(Model? model, PointerEventArgs e) => PointerEnter?.Invoke(model, e); - - public void TriggerPointerLeave(Model? model, PointerEventArgs e) => PointerLeave?.Invoke(model, e); - - public void TriggerKeyDown(KeyboardEventArgs e) => KeyDown?.Invoke(e); + BehaviorOptions.DiagramDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramShiftDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramWheelBehavior ??= GetBehavior(); + } - public void TriggerWheel(WheelEventArgs e) => Wheel?.Invoke(e); + public abstract DiagramOptions Options { get; } + public DiagramBehaviorOptions BehaviorOptions { 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 void TriggerPointerClick(Model? model, PointerEventArgs e) => PointerClick?.Invoke(model, e); + Changed?.Invoke(); + } - public void TriggerPointerDoubleClick(Model? model, PointerEventArgs e) => PointerDoubleClick?.Invoke(model, e); - - #endregion -======= - public abstract class Diagram + public void Batch(Action action) { - 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() + if (SuspendRefresh) { - _behaviors = new Dictionary(); - _orderedSelectables = new List(); - - Nodes = new NodeLayer(this); - Links = new LinkLayer(this); - Groups = new GroupLayer(this); - Controls = new ControlsLayer(); - BehaviorOptions = new DiagramBehaviorOptions(); - - Nodes.Added += OnSelectableAdded; - Links.Added += OnSelectableAdded; - Groups.Added += OnSelectableAdded; - - Nodes.Removed += OnSelectableRemoved; - Links.Removed += OnSelectableRemoved; - Groups.Removed += OnSelectableRemoved; + // If it's already suspended, just execute the action and leave it suspended + // It's probably handled by an outer batch + action(); + return; + } - RegisterDefaultBehaviors(); + SuspendRefresh = true; + action(); + SuspendRefresh = false; + Refresh(); + } - BehaviorOptions.DiagramDragBehavior ??= GetBehavior(); - BehaviorOptions.DiagramShiftDragBehavior ??= GetBehavior(); - BehaviorOptions.DiagramWheelBehavior ??= GetBehavior(); - } + #region Selection - public abstract DiagramOptions Options { get; } - public DiagramBehaviorOptions BehaviorOptions { 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() + public IEnumerable GetSelectedModels() + { + foreach (var node in Nodes) { - if (SuspendRefresh) - return; - - Changed?.Invoke(); + if (node.Selected) + yield return node; } - public void Batch(Action action) + foreach (var link in Links) { - if (SuspendRefresh) + if (link.Selected) + yield return link; + + foreach (var vertex in link.Vertices) { - // If it's already suspended, just execute the action and leave it suspended - // It's probably handled by an outer batch - action(); - return; + if (vertex.Selected) + yield return vertex; } - - SuspendRefresh = true; - action(); - SuspendRefresh = false; - Refresh(); } - #region Selection - - public IEnumerable GetSelectedModels() + foreach (var group in Groups) { - foreach (var node in Nodes) - { - if (node.Selected) - yield return node; - } + if (group.Selected) + yield return group; + } + } - foreach (var link in Links) - { - if (link.Selected) - yield return link; - - foreach (var vertex in link.Vertices) - { - if (vertex.Selected) - yield return vertex; - } - } + public void SelectModel(SelectableModel model, bool unselectOthers) + { + if (model.Selected) + return; - foreach (var group in Groups) - { - 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); - } + model.Selected = false; + model.Refresh(); + SelectionChanged?.Invoke(model); + } - public void UnselectModel(SelectableModel model) + public void UnselectAll() + { + foreach (var model in GetSelectedModels()) { - if (!model.Selected) - return; - 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 + void RegisterDefaultBehaviors() + { + 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)); + RegisterBehavior(new ScrollBehavior(this)); + RegisterBehavior(new SelectionBoxBehavior(this)); + } - #region Behaviors + public void RegisterBehavior(Behavior behavior) + { + var type = behavior.GetType(); + if (_behaviors.ContainsKey(type)) + throw new Exception($"Behavior '{type.Name}' already registered"); - void RegisterDefaultBehaviors() - { - 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)); - RegisterBehavior(new ScrollBehavior(this)); - RegisterBehavior(new SelectionBoxBehavior(this)); - RegisterBehavior(new ResizeBehavior(this)); - } + _behaviors.Add(type, behavior); + } - public void RegisterBehavior(Behavior behavior) - { - var type = behavior.GetType(); - if (_behaviors.ContainsKey(type)) - throw new Exception($"Behavior '{type.Name}' already registered"); + public T? GetBehavior() where T : Behavior + { + var type = typeof(T); + return (T?)(_behaviors.ContainsKey(type) ? _behaviors[type] : null); + } - _behaviors.Add(type, behavior); - } + public void UnregisterBehavior() where T : Behavior + { + var type = typeof(T); + if (!_behaviors.ContainsKey(type)) + return; - public T? GetBehavior() where T : Behavior - { - var type = typeof(T); - return (T?)(_behaviors.ContainsKey(type) ? _behaviors[type] : null); - } + _behaviors[type].Dispose(); + _behaviors.Remove(type); + } - public void UnregisterBehavior() where T : Behavior - { - var type = typeof(T); - if (!_behaviors.ContainsKey(type)) - return; + #endregion - _behaviors[type].Dispose(); - _behaviors.Remove(type); - } + public void ZoomToFit(double margin = 10) + { + if (Container == null || Nodes.Count == 0) + return; - #endregion + 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; - public void ZoomToFit(double margin = 10) - { - if (Container == null || Nodes.Count == 0) - return; + SuspendRefresh = true; - 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 xf = Container.Width / width; + var yf = Container.Height / height; + SetZoom(Math.Min(xf, yf)); - SuspendRefresh = true; + var nx = Container.Left + Pan.X + minX * Zoom; + var ny = Container.Top + Pan.Y + minY * Zoom; + UpdatePan(Container.Left - nx, Container.Top - ny); - var xf = Container.Width / width; - var yf = Container.Height / height; - SetZoom(Math.Min(xf, yf)); + SuspendRefresh = false; + Refresh(); + } - var nx = Container.Left + Pan.X + minX * Zoom; - var ny = Container.Top + Pan.Y + minY * Zoom; - UpdatePan(Container.Left - nx, Container.Top - ny); + public void SetPan(double x, double y) + { + Pan = new Point(x, y); + PanChanged?.Invoke(); + Refresh(); + } - SuspendRefresh = false; - Refresh(); - } + public void UpdatePan(double deltaX, double deltaY) + { + Pan = Pan.Add(deltaX, deltaY); + PanChanged?.Invoke(); + Refresh(); + } - public void SetPan(double x, double y) - { - Pan = new Point(x, y); - 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 UpdatePan(double deltaX, double deltaY) - { - Pan = Pan.Add(deltaX, deltaY); - PanChanged?.Invoke(); - Refresh(); - } + if (newZoom < Options.Zoom.Minimum) + newZoom = Options.Zoom.Minimum; - public void SetZoom(double newZoom) - { - if (newZoom <= 0) - throw new ArgumentException($"{nameof(newZoom)} cannot be equal or lower than 0"); + Zoom = newZoom; + ZoomChanged?.Invoke(); + Refresh(); + } - if (newZoom < Options.Zoom.Minimum) - newZoom = Options.Zoom.Minimum; + public void SetContainer(Rectangle? newRect) + { + if (Equals(newRect, Container)) + return; - Zoom = newZoom; - ZoomChanged?.Invoke(); - Refresh(); - } + Container = newRect; + ContainerChanged?.Invoke(); + Refresh(); + } - public void SetContainer(Rectangle? newRect) - { - if (Equals(newRect, Container)) - return; + 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"); - Container = newRect; - ContainerChanged?.Invoke(); - Refresh(); - } + return new Point((clientX - Container.Left - Pan.X) / Zoom, (clientY - Container.Top - Pan.Y) / Zoom); + } - 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 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 - Pan.X) / Zoom, (clientY - Container.Top - Pan.Y) / Zoom); - } + return new Point(clientX - Container.Left, clientY - Container.Top); + } - 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 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(clientX - Container.Left, clientY - Container.Top); - } + return new Point(Zoom * clientX + Container.Left + Pan.X, Zoom * clientY + Container.Top + Pan.Y); + } - 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"); + #region Ordering - return new Point(Zoom * clientX + Container.Left + Pan.X, Zoom * clientY + Container.Top + Pan.Y); - } + public void SendToBack(SelectableModel model) + { + var minOrder = GetMinOrder(); + if (model.Order == minOrder) + return; - #region Ordering + if (!_orderedSelectables.Remove(model)) + return; - public void SendToBack(SelectableModel model) + _orderedSelectables.Insert(0, model); + + // Todo: can optimize this by only updating the order of items before model + Batch(() => { - var minOrder = GetMinOrder(); - if (model.Order == minOrder) - return; + SuspendSorting = true; + for (var i = 0; i < _orderedSelectables.Count; i++) + { + _orderedSelectables[i].Order = i + 1; + } + SuspendSorting = false; + }); + } - if (!_orderedSelectables.Remove(model)) - return; + public void SendToFront(SelectableModel model) + { + var maxOrder = GetMaxOrder(); + if (model.Order == maxOrder) + return; - _orderedSelectables.Insert(0, model); + if (!_orderedSelectables.Remove(model)) + return; - // 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; - }); - } + _orderedSelectables.Add(model); - public void SendToFront(SelectableModel model) - { - var maxOrder = GetMaxOrder(); - if (model.Order == maxOrder) - return; + SuspendSorting = true; + model.Order = maxOrder + 1; + SuspendSorting = false; + Refresh(); + } - if (!_orderedSelectables.Remove(model)) - return; + public int GetMinOrder() + { + return _orderedSelectables.Count > 0 ? _orderedSelectables[0].Order : 0; + } - _orderedSelectables.Add(model); + public int GetMaxOrder() + { + return _orderedSelectables.Count > 0 ? _orderedSelectables[^1].Order : 0; + } - SuspendSorting = true; - model.Order = maxOrder + 1; - SuspendSorting = false; - Refresh(); - } + /// + /// Sorts the list of selectables based on their order + /// + public void RefreshOrders(bool refresh = true) + { + _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); - public int GetMinOrder() + if (refresh) { - return _orderedSelectables.Count > 0 ? _orderedSelectables[0].Order : 0; + Refresh(); } + } - public int GetMaxOrder() - { - return _orderedSelectables.Count > 0 ? _orderedSelectables[^1].Order : 0; - } + private void OnSelectableAdded(SelectableModel model) + { + var maxOrder = GetMaxOrder(); + _orderedSelectables.Add(model); - /// - /// Sorts the list of selectables based on their order - /// - public void RefreshOrders(bool refresh = true) + if (model.Order == 0) { - _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); - - if (refresh) - { - Refresh(); - } + model.Order = maxOrder + 1; } - private void OnSelectableAdded(SelectableModel model) - { - var maxOrder = GetMaxOrder(); - _orderedSelectables.Add(model); - - if (model.Order == 0) - { - model.Order = maxOrder + 1; - } - - model.OrderChanged += OnModelOrderChanged; - } + model.OrderChanged += OnModelOrderChanged; + } - private void OnSelectableRemoved(SelectableModel model) - { - model.OrderChanged -= OnModelOrderChanged; - _orderedSelectables.Remove(model); - } + private void OnSelectableRemoved(SelectableModel model) + { + model.OrderChanged -= OnModelOrderChanged; + _orderedSelectables.Remove(model); + } - private void OnModelOrderChanged(Model model) - { - if (SuspendSorting) - return; + private void OnModelOrderChanged(Model model) + { + if (SuspendSorting) + return; - RefreshOrders(); - } + 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 - } ->>>>>>> 7bf2db0 (First commit) + #endregion } \ 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 18f466eec..73fa046ea 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -10,435 +10,467 @@ namespace Blazor.Diagrams.Core.Tests.Behaviors; public class DragNewLinkBehaviorTests { - [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) - { - 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(100); - ongoingPosition.Y.Should().Be(100); - } - - [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) => - { - 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(100); - ongoingPosition.Y.Should().Be(100); - } - - [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) - { - 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().BeGreaterThan(145); - ongoingPosition.Y.Should().BeGreaterThan(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) - { - 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(107.7, 0.1); - ongoingPosition.Y.Should().BeApproximately(101.7, 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) - { - 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() - { - // 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() - { - // 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() - { - // 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() - { - // 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() - { - // 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); - } + [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) + { + 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(100); + ongoingPosition.Y.Should().Be(100); + } + + [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) => + { + 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(100); + ongoingPosition.Y.Should().Be(100); + } + + [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) + { + 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().BeGreaterThan(145); + ongoingPosition.Y.Should().BeGreaterThan(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) + { + 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(107.7, 0.1); + ongoingPosition.Y.Should().BeApproximately(101.7, 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) + { + 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() + { + // 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() + { + // 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() + { + // 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() + { + // 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() + { + // 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); + } + + [Fact] + public void Behavior_ShouldUpdateOngoingPosition_WhenWheelIsTriggered() + { + // 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)); + diagram.TriggerWheel(new WheelEventArgs(150, 150, 0, 0, false, false, false, 100, 100, 0, 0)); + + // Assert + var source = link.Source as SinglePortAnchor; + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().BeGreaterThan(245); + ongoingPosition.Y.Should().BeGreaterThan(245); + linkRefreshed.Should().BeTrue(); + } } \ No newline at end of file From 5dbd0193571a7bc1279a3e67b539efc014483a09 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 14 Dec 2023 13:46:32 +1100 Subject: [PATCH 040/159] Moved scroll test --- .../Behaviors/DragMovablesBehaviorTests.cs | 278 ++++++++++-------- .../Behaviors/ScrollBehaviorTests.cs | 25 -- 2 files changed, 148 insertions(+), 155 deletions(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs index ba73788b3..657b08b28 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -10,134 +10,152 @@ 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); - } - - [Theory] - [InlineData(false, 0, 0, 45, 45)] - [InlineData(true, 0, 0, 35, 35)] - [InlineData(false, 3, 3, 45, 45)] - [InlineData(true, 3, 3, 50, 50)] - public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, double initialX, double initialY, double deltaX, double deltaY) - { - // Arrange - var diagram = new TestDiagram(new DiagramOptions - { - GridSize = 15, - GridSnapToCenter = gridSnapToCenter - }); - var nodeMock = new Mock(Point.Zero); - var node = diagram.Nodes.Add(nodeMock.Object); - node.Size = new Size(20, 20); - node.Position = new Point(initialX, initialY); - diagram.SelectModel(node, false); - - // Act - //Move 40px in X and Y - diagram.TriggerPointerDown(node, - new PointerEventArgs(20, 20, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(60, 60, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - nodeMock.Verify(n => n.SetPosition(deltaX, deltaY), Times.Once); - } - - [Fact] - public void Behavior_ShouldTriggerMoved() - { - // 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(); - } - - [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(); - } - - [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); - } + [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); + } + + [Theory] + [InlineData(false, 0, 0, 45, 45)] + [InlineData(true, 0, 0, 35, 35)] + [InlineData(false, 3, 3, 45, 45)] + [InlineData(true, 3, 3, 50, 50)] + public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, double initialX, double initialY, double deltaX, double deltaY) + { + // Arrange + var diagram = new TestDiagram(new DiagramOptions + { + GridSize = 15, + GridSnapToCenter = gridSnapToCenter + }); + var nodeMock = new Mock(Point.Zero); + var node = diagram.Nodes.Add(nodeMock.Object); + node.Size = new Size(20, 20); + node.Position = new Point(initialX, initialY); + diagram.SelectModel(node, false); + + // Act + //Move 40px in X and Y + diagram.TriggerPointerDown(node, + new PointerEventArgs(20, 20, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(60, 60, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + nodeMock.Verify(n => n.SetPosition(deltaX, deltaY), Times.Once); + } + + [Fact] + public void Behavior_ShouldTriggerMoved() + { + // 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(); + } + + [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(); + } + + [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); + } + + [Fact] + public void Behavior_ShouldCallSetPosition_WhenWheelIsTriggered() + { + // 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.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 100, 0, 0)); + + // Assert + nodeMock.Verify(n => n.SetPosition(100, 100), Times.Once); + } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs index b82c98d8a..a1f09a899 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs @@ -40,30 +40,5 @@ public void Behavior_WhenBehaviorDisabled_ShouldNotScroll() Assert.Equal(0, diagram.Pan.X); Assert.Equal(0, diagram.Pan.Y); } - - [Fact] - public void NodeUpdatesWhenScrollingWhileDragging() - { - // Arrange - var diagram = new TestDiagram(); - diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); - diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); - diagram.Options.Zoom.ScaleFactor = 1.05; - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - diagram.Nodes.Add(node); - - // Act - diagram.SelectModel(node, false); - diagram.TriggerPointerDown(node, - new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); - - // Assert - Assert.Equal(-95, diagram.Pan.X, 0); - Assert.Equal(-190, diagram.Pan.Y, 0); - Assert.Equal(200, node.Position.Y); - } - } } From 56f36fffa00b96befeec4d019fe00e22fcda7d7d Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Fri, 15 Dec 2023 15:24:33 +1100 Subject: [PATCH 041/159] Top Right and Bottom Right implemented --- .../Controls/Default/ResizeControl.cs | 48 ++++++------- .../Resizing/BottomRightResizerProvider.cs | 58 +++++++++++++--- .../Positions/Resizing/IResizerProvider.cs | 15 ++-- .../Resizing/TopRightResizerProvider.cs | 68 ++++++++++++++++--- 4 files changed, 141 insertions(+), 48 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs index 4517f673d..fa02cac27 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs @@ -6,33 +6,35 @@ namespace Blazor.Diagrams.Core.Controls.Default { - public class ResizeControl : ExecutableControl - { - private readonly IResizerProvider _resizeProvider; + public class ResizeControl : ExecutableControl + { + private readonly IResizerProvider _resizeProvider; - public ResizeControl(IResizerProvider resizeProvider) - { - _resizeProvider = resizeProvider; - } + public ResizeControl(IResizerProvider resizeProvider) + { + _resizeProvider = resizeProvider; + } - public override Point? GetPosition(Model model) => _resizeProvider.GetPosition(model); + public override Point? GetPosition(Model model) => _resizeProvider.GetPosition(model); - public string? Class => _resizeProvider.Class; + public string? Class => _resizeProvider.Class; - public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) - { - _resizeProvider.OnResizeStart(diagram, model, e); - diagram.PointerMove += _resizeProvider.OnPointerMove; - diagram.PointerUp += _resizeProvider.OnResizeEnd; - diagram.PointerUp += (_, _) => OnResizeEnd(diagram); + public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) + { + _resizeProvider.OnResizeStart(diagram, model, e); + diagram.PointerMove += _resizeProvider.OnPointerMove; + diagram.Wheel += _resizeProvider.OnPointerMove; + diagram.PointerUp += _resizeProvider.OnResizeEnd; + diagram.PointerUp += (_, _) => OnResizeEnd(diagram); - return ValueTask.CompletedTask; - } + return ValueTask.CompletedTask; + } - void OnResizeEnd(Diagram diagram) - { - diagram.PointerMove -= _resizeProvider.OnPointerMove; - diagram.PointerUp -= _resizeProvider.OnResizeEnd; - } - } + void OnResizeEnd(Diagram diagram) + { + diagram.PointerMove -= _resizeProvider.OnPointerMove; + diagram.Wheel -= _resizeProvider.OnPointerMove; + diagram.PointerUp -= _resizeProvider.OnResizeEnd; + } + } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs index f55e26d71..843b1741f 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -10,10 +10,13 @@ public class BottomRightResizerProvider : IResizerProvider public string? Class => "bottomright"; private Size _originalSize = null!; - private Point _originalMousePosition = null!; + private double? _lastClientX; + private double? _lastClientY; private NodeModel _nodeModel = null!; + private double _totalMovedX = 0; + private double _totalMovedY = 0; - public Point? GetPosition(Model model) + public Point? GetPosition(Model model) { if (model is NodeModel nodeModel && nodeModel.Size is not null) { @@ -22,25 +25,61 @@ public class BottomRightResizerProvider : IResizerProvider return null; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs) + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) { if (model is NodeModel nodeModel) { - _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; _originalSize = nodeModel.Size!; _nodeModel = nodeModel; } } - public void OnPointerMove(Model? model, PointerEventArgs args) + public void OnPointerMove(Model? model, PointerEventArgs e) + { + if (_nodeModel is null || _lastClientX == null || _lastClientY == null) + { + return; + } + + + var deltaX = (e.ClientX - _lastClientX.Value); + var deltaY = (e.ClientY - _lastClientY.Value); + + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + var height = _originalSize.Height + _totalMovedY; + var width = _originalSize.Width + _totalMovedX; + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + } + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + _nodeModel.SetSize(width, height); + } + + public void OnPointerMove(WheelEventArgs e) { if (_nodeModel is null) { return; } - var height = _originalSize.Height + (args.ClientY - _originalMousePosition.Y); - var width = _originalSize.Width + (args.ClientX - _originalMousePosition.X); + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; + + var height = _originalSize.Height + _totalMovedY; + var width = _originalSize.Width + _totalMovedX; if (width < _nodeModel.MinimumDimensions.Width) { @@ -58,8 +97,11 @@ public void OnResizeEnd(Model? model, PointerEventArgs args) { _nodeModel?.TriggerSizeChanged(); _originalSize = null!; - _originalMousePosition = null!; _nodeModel = null!; + _totalMovedX = 0; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs index 65a2ecdde..c15331e4e 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs @@ -4,11 +4,12 @@ namespace Blazor.Diagrams.Core.Positions.Resizing { - public interface IResizerProvider : IPositionProvider - { - public string? Class { get; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); - public void OnPointerMove(Model? model, PointerEventArgs args); - public void OnResizeEnd(Model? model, PointerEventArgs args); - } + public interface IResizerProvider : IPositionProvider + { + public string? Class { get; } + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); + public void OnPointerMove(Model? model, PointerEventArgs args); + public void OnPointerMove(WheelEventArgs args); + public void OnResizeEnd(Model? model, PointerEventArgs args); + } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs index e9ea4e14a..987b149b7 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -7,12 +7,15 @@ namespace Blazor.Diagrams.Core.Positions.Resizing { public class TopRightResizerProvider : IResizerProvider { - public string? Class => "topright"; + public string? Class => "topright"; - private Size _originalSize = null!; + private Size _originalSize = null!; private Point _originalPosition = null!; - private Point _originalMousePosition = null!; private NodeModel _nodeModel = null!; + private double? _lastClientX; + private double? _lastClientY; + private double _totalMovedX = 0; + private double _totalMovedY = 0; public Point? GetPosition(Model model) { @@ -23,28 +26,70 @@ public class TopRightResizerProvider : IResizerProvider return null; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs) + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) { if (model is NodeModel nodeModel) { + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); _originalSize = nodeModel.Size!; _nodeModel = nodeModel; } } - public void OnPointerMove(Model? model, PointerEventArgs args) + public void OnPointerMove(Model? model, PointerEventArgs e) + { + if (_nodeModel is null || _lastClientX == null || _lastClientY == null) + { + return; + } + + var deltaX = (e.ClientX - _lastClientX.Value); + var deltaY = (e.ClientY - _lastClientY.Value); + + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + var height = _originalSize.Height - _totalMovedY; + var width = _originalSize.Width + _totalMovedX; + + var positionX = _originalPosition.X; + var positionY = _originalPosition.Y + _totalMovedY; + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + positionX = _nodeModel.Position.X; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + positionY = _nodeModel.Position.Y; + } + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + _nodeModel.SetPosition(positionX, positionY); + _nodeModel.SetSize(width, height); + } + + public void OnPointerMove(WheelEventArgs e) { if (_nodeModel is null) { return; } - var height = _originalSize.Height - (args.ClientY - _originalMousePosition.Y); - var width = _originalSize.Width + (args.ClientX - _originalMousePosition.X); + + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; + + var height = _originalSize.Height - _totalMovedY; + var width = _originalSize.Width + _totalMovedX; var positionX = _originalPosition.X; - var positionY = _originalPosition.Y + (args.ClientY - _originalMousePosition.Y); + var positionY = _originalPosition.Y + _totalMovedY; if (width < _nodeModel.MinimumDimensions.Width) { @@ -66,7 +111,10 @@ public void OnResizeEnd(Model? model, PointerEventArgs args) _nodeModel?.TriggerSizeChanged(); _originalSize = null!; _originalPosition = null!; - _originalMousePosition = null!; + _totalMovedX = 0; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; _nodeModel = null!; } From c0518e92b2ffd7bc11eb7368cf66bc0a767f09f7 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Fri, 15 Dec 2023 16:45:17 +1100 Subject: [PATCH 042/159] Bottom and top left implemented --- .../Resizing/BottomLeftResizerProvider.cs | 67 ++++++++++++++--- .../Resizing/TopLeftResizerProvider.cs | 72 +++++++++++++++---- 2 files changed, 117 insertions(+), 22 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs index c4e576c92..66f34fe7f 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -7,12 +7,15 @@ namespace Blazor.Diagrams.Core.Positions.Resizing { public class BottomLeftResizerProvider : IResizerProvider { - public string? Class => "bottomleft"; + public string? Class => "bottomleft"; - private Size _originalSize = null!; + private Size _originalSize = null!; private Point _originalPosition = null!; - private Point _originalMousePosition = null!; private NodeModel _nodeModel = null!; + private double? _lastClientX; + private double? _lastClientY; + private double _totalMovedX = 0; + private double _totalMovedY = 0; public Point? GetPosition(Model model) { @@ -23,28 +26,70 @@ public class BottomLeftResizerProvider : IResizerProvider return null; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs) + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) { if (model is NodeModel nodeModel) { + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); _originalSize = nodeModel.Size!; _nodeModel = nodeModel; } } - public void OnPointerMove(Model? model, PointerEventArgs args) + public void OnPointerMove(Model? model, PointerEventArgs e) + { + if (_nodeModel is null || _lastClientX == null || _lastClientY == null) + { + return; + } + + var deltaX = (e.ClientX - _lastClientX.Value); + var deltaY = (e.ClientY - _lastClientY.Value); + + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + var height = _originalSize.Height + _totalMovedY; + var width = _originalSize.Width - _totalMovedX; + + var positionX = _originalPosition.X + _totalMovedX; + + var positionY = _originalPosition.Y; + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + positionX = _nodeModel.Position.X; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + positionY = _nodeModel.Position.Y; + } + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + _nodeModel.SetPosition(positionX, positionY); + _nodeModel.SetSize(width, height); + } + + public void OnPointerMove(WheelEventArgs e) { if (_nodeModel is null) { return; } - var height = _originalSize.Height + (args.ClientY - _originalMousePosition.Y); - var width = _originalSize.Width - (args.ClientX - _originalMousePosition.X); + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; + + var height = _originalSize.Height + _totalMovedY; + var width = _originalSize.Width - _totalMovedX; - var positionX = _originalPosition.X + (args.ClientX - _originalMousePosition.X); + var positionX = _originalPosition.X + _totalMovedX; var positionY = _originalPosition.Y; if (width < _nodeModel.MinimumDimensions.Width) @@ -67,7 +112,9 @@ public void OnResizeEnd(Model? model, PointerEventArgs args) _nodeModel?.TriggerSizeChanged(); _originalSize = null!; _originalPosition = null!; - _originalMousePosition = null!; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; _nodeModel = null!; } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs index 7c3afe5ce..6a0525c80 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -7,12 +7,15 @@ namespace Blazor.Diagrams.Core.Positions.Resizing { public class TopLeftResizerProvider : IResizerProvider { - public string? Class => "topleft"; + public string? Class => "topleft"; - private Size _originalSize = null!; + private Size _originalSize = null!; private Point _originalPosition = null!; - private Point _originalMousePosition = null!; private NodeModel _nodeModel = null!; + private double? _lastClientX; + private double? _lastClientY; + private double _totalMovedX = 0; + private double _totalMovedY = 0; public Point? GetPosition(Model model) { @@ -23,29 +26,71 @@ public class TopLeftResizerProvider : IResizerProvider return null; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs) + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) { if (model is NodeModel nodeModel) { + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); _originalSize = nodeModel.Size!; _nodeModel = nodeModel; } } - public void OnPointerMove(Model? model, PointerEventArgs args) + public void OnPointerMove(Model? model, PointerEventArgs e) { - if (_nodeModel is null) + if (_nodeModel is null || _lastClientX == null || _lastClientY == null) { return; } - var height = _originalSize.Height - (args.ClientY - _originalMousePosition.Y); - var width = _originalSize.Width - (args.ClientX - _originalMousePosition.X); + var deltaX = (e.ClientX - _lastClientX.Value); + var deltaY = (e.ClientY - _lastClientY.Value); - var positionX = _originalPosition.X + (args.ClientX - _originalMousePosition.X); - var positionY = _originalPosition.Y + (args.ClientY - _originalMousePosition.Y); + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + + var height = _originalSize.Height - _totalMovedY; + var width = _originalSize.Width - _totalMovedX; + + var positionX = _originalPosition.X + _totalMovedX; + var positionY = _originalPosition.Y + _totalMovedY; + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + positionX = _nodeModel.Position.X; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + positionY = _nodeModel.Position.Y; + } + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + _nodeModel.SetPosition(positionX, positionY); + _nodeModel.SetSize(width, height); + } + + public void OnPointerMove(WheelEventArgs e) + { + if (_nodeModel is null || _lastClientX == null || _lastClientY == null) + { + return; + } + + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; + + var height = _originalSize.Height - _totalMovedY; + var width = _originalSize.Width - _totalMovedX; + + var positionX = _originalPosition.X + _totalMovedX; + var positionY = _originalPosition.Y + _totalMovedY; if (width < _nodeModel.MinimumDimensions.Width) { @@ -67,7 +112,10 @@ public void OnResizeEnd(Model? model, PointerEventArgs args) _nodeModel?.TriggerSizeChanged(); _originalSize = null!; _originalPosition = null!; - _originalMousePosition = null!; + _totalMovedX = 0; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; _nodeModel = null!; } From d2690172a1eede0d6c0ed0c40b427230ac82be8c Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Mon, 18 Dec 2023 14:02:46 +1100 Subject: [PATCH 043/159] Updated file to use spaces --- .../Behaviors/DragMovablesBehavior.cs | 272 +++++++++--------- 1 file changed, 136 insertions(+), 136 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index a69d40017..8ffeff845 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -10,140 +10,140 @@ namespace Blazor.Diagrams.Core.Behaviors; public class DragMovablesBehavior : Behavior { - private readonly Dictionary _initialPositions; - private double? _lastClientX; - private double? _lastClientY; - private bool _moved; - private double _totalMovedX = 0; - private double _totalMovedY = 0; - - public DragMovablesBehavior(Diagram diagram) : base(diagram) - { - _initialPositions = new Dictionary(); - Diagram.PointerDown += OnPointerDown; - Diagram.PointerMove += OnPointerMove; - Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnPointerMove; - } - - private void OnPointerDown(Model? model, PointerEventArgs e) - { - if (model is not MovableModel) - return; - - _initialPositions.Clear(); - foreach (var sm in Diagram.GetSelectedModels()) - { - if (sm is not MovableModel movable || movable.Locked) - continue; - - // Special case: groups without auto size on - if (sm is NodeModel node && node.Group != null && !node.Group.AutoSize) - continue; - - var position = movable.Position; - if (Diagram.Options.GridSnapToCenter && movable is NodeModel n) - { - position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2, - movable.Position.Y + (n.Size?.Height ?? 0) / 2); - } - - _initialPositions.Add(movable, position); - } - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _moved = false; - } - - public 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; - - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - moveNodes(model, _totalMovedX, _totalMovedY); - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - - } - - public void OnPointerMove(WheelEventArgs e) - { - if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) - return; - - _moved = true; - - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; - - moveNodes(null, _totalMovedX, _totalMovedY); - } - - private void moveNodes(Model? model, double deltaX, double deltaY) - { - 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) - { - 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; - - if (_moved) - { - foreach (var (movable, _) in _initialPositions) - { - movable.TriggerMoved(); - } - } - - _initialPositions.Clear(); - _totalMovedX = 0; - _totalMovedY = 0; - _lastClientX = null; - _lastClientY = null; - } - - private double ApplyGridSize(double n) - { - if (Diagram.Options.GridSize == null) - return n; - - var gridSize = Diagram.Options.GridSize.Value; - - return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); - } - - public override void Dispose() - { - _initialPositions.Clear(); - - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnPointerMove; - } + private readonly Dictionary _initialPositions; + private double? _lastClientX; + private double? _lastClientY; + private bool _moved; + private double _totalMovedX = 0; + private double _totalMovedY = 0; + + public DragMovablesBehavior(Diagram diagram) : base(diagram) + { + _initialPositions = new Dictionary(); + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + Diagram.Wheel += OnPointerMove; + } + + private void OnPointerDown(Model? model, PointerEventArgs e) + { + if (model is not MovableModel) + return; + + _initialPositions.Clear(); + foreach (var sm in Diagram.GetSelectedModels()) + { + if (sm is not MovableModel movable || movable.Locked) + continue; + + // Special case: groups without auto size on + if (sm is NodeModel node && node.Group != null && !node.Group.AutoSize) + continue; + + var position = movable.Position; + if (Diagram.Options.GridSnapToCenter && movable is NodeModel n) + { + position = new Point(movable.Position.X + (n.Size?.Width ?? 0) / 2, + movable.Position.Y + (n.Size?.Height ?? 0) / 2); + } + + _initialPositions.Add(movable, position); + } + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + _moved = false; + } + + public 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; + + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + moveNodes(model, _totalMovedX, _totalMovedY); + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + } + + public void OnPointerMove(WheelEventArgs e) + { + if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) + return; + + _moved = true; + + _totalMovedX += e.DeltaX; + _totalMovedY += e.DeltaY; + + moveNodes(null, _totalMovedX, _totalMovedY); + } + + private void moveNodes(Model? model, double deltaX, double deltaY) + { + 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) + { + 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; + + if (_moved) + { + foreach (var (movable, _) in _initialPositions) + { + movable.TriggerMoved(); + } + } + + _initialPositions.Clear(); + _totalMovedX = 0; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; + } + + private double ApplyGridSize(double n) + { + if (Diagram.Options.GridSize == null) + return n; + + var gridSize = Diagram.Options.GridSize.Value; + + return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); + } + + public override void Dispose() + { + _initialPositions.Clear(); + + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + Diagram.Wheel -= OnPointerMove; + } } From 346fbf4ec7ae66234d39c7dac64deed2293dcb2d Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Mon, 18 Dec 2023 14:08:40 +1100 Subject: [PATCH 044/159] Fixed all spacing --- .../Behaviors/DragNewLinkBehavior.cs | 348 +++---- .../Behaviors/ScrollBehavior.cs | 30 +- .../Controls/Default/ResizeControl.cs | 50 +- src/Blazor.Diagrams.Core/Diagram.cs | 748 +++++++------- .../Positions/Resizing/IResizerProvider.cs | 16 +- .../Behaviors/DragMovablesBehaviorTests.cs | 296 +++--- .../Behaviors/DragNewLinkBehaviorTests.cs | 926 +++++++++--------- .../Behaviors/ScrollBehaviorTests.cs | 60 +- 8 files changed, 1237 insertions(+), 1237 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 2f0d92ac1..cca9fd980 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -11,178 +11,178 @@ namespace Blazor.Diagrams.Core.Behaviors; public class DragNewLinkBehavior : Behavior { - private PositionAnchor? _targetPositionAnchor; - - public BaseLinkModel? OngoingLink { get; private set; } - - public DragNewLinkBehavior(Diagram diagram) : base(diagram) - { - Diagram.PointerDown += OnPointerDown; - Diagram.PointerMove += OnPointerMove; - Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnPointerMove; - } - - public void StartFrom(ILinkable source, double clientX, double clientY) - { - if (OngoingLink != null) - return; - - _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); - OngoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor); - if (OngoingLink == null) - return; - - Diagram.Links.Add(OngoingLink); - } - - public void StartFrom(BaseLinkModel link, double clientX, double clientY) - { - if (OngoingLink != null) - return; - - _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); - 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 (port.Locked) - return; - - _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); - } - } - - private void OnPointerMove(Model? model, MouseEventArgs e) - { - if (OngoingLink == null || model != null) - return; - - UpdateLinkPosition(e.ClientX, e.ClientY); - } - - private void OnPointerMove(WheelEventArgs e) - { - if (OngoingLink == null) - return; - - UpdateLinkPosition(e.ClientX + e.DeltaX, e.ClientY + e.DeltaY); - } - - private void UpdateLinkPosition(double clientX, double clientY) - { - _targetPositionAnchor!.SetPosition(CalculateTargetPosition(clientX, clientY)); - - 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)); - } - } - - OngoingLink!.Refresh(); - OngoingLink!.RefreshLinks(); - } - - private void OnPointerUp(Model? model, MouseEventArgs e) - { - if (OngoingLink == null) - return; - - 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(); - } - else if (Diagram.Options.Links.RequireTarget) - { - 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; - } - - 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) - 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)) - { - var distance = position.DistanceTo(port.Position); - - if (distance <= Diagram.Options.Links.SnappingRadius && (OngoingLink.Source.Model?.CanAttachTo(port) != false)) - { - if (distance < nearestSnapPortDistance) - { - nearestSnapPortDistance = distance; - nearestSnapPort = port; - } - } - } - - return nearestSnapPort; - } - - public override void Dispose() - { - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnPointerMove; - } + private PositionAnchor? _targetPositionAnchor; + + public BaseLinkModel? OngoingLink { get; private set; } + + public DragNewLinkBehavior(Diagram diagram) : base(diagram) + { + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + Diagram.Wheel += OnPointerMove; + } + + public void StartFrom(ILinkable source, double clientX, double clientY) + { + if (OngoingLink != null) + return; + + _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); + OngoingLink = Diagram.Options.Links.Factory(Diagram, source, _targetPositionAnchor); + if (OngoingLink == null) + return; + + Diagram.Links.Add(OngoingLink); + } + + public void StartFrom(BaseLinkModel link, double clientX, double clientY) + { + if (OngoingLink != null) + return; + + _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(clientX, clientY)); + 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 (port.Locked) + return; + + _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); + } + } + + private void OnPointerMove(Model? model, MouseEventArgs e) + { + if (OngoingLink == null || model != null) + return; + + UpdateLinkPosition(e.ClientX, e.ClientY); + } + + private void OnPointerMove(WheelEventArgs e) + { + if (OngoingLink == null) + return; + + UpdateLinkPosition(e.ClientX + e.DeltaX, e.ClientY + e.DeltaY); + } + + private void UpdateLinkPosition(double clientX, double clientY) + { + _targetPositionAnchor!.SetPosition(CalculateTargetPosition(clientX, clientY)); + + 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)); + } + } + + OngoingLink!.Refresh(); + OngoingLink!.RefreshLinks(); + } + + private void OnPointerUp(Model? model, MouseEventArgs e) + { + if (OngoingLink == null) + return; + + 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(); + } + else if (Diagram.Options.Links.RequireTarget) + { + 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; + } + + 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) + 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)) + { + var distance = position.DistanceTo(port.Position); + + if (distance <= Diagram.Options.Links.SnappingRadius && (OngoingLink.Source.Model?.CanAttachTo(port) != false)) + { + if (distance < nearestSnapPortDistance) + { + nearestSnapPortDistance = distance; + nearestSnapPort = port; + } + } + } + + return nearestSnapPort; + } + + public override void Dispose() + { + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + Diagram.Wheel -= OnPointerMove; + } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs index 9b563428e..fa3ef63e1 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs @@ -4,22 +4,22 @@ namespace Blazor.Diagrams.Core.Behaviors { - public class ScrollBehavior : WheelBehavior - { - public ScrollBehavior(Diagram diagram) - : base(diagram) - { - } + public class ScrollBehavior : WheelBehavior + { + public ScrollBehavior(Diagram diagram) + : base(diagram) + { + } - protected override void OnDiagramWheel(WheelEventArgs e) - { - if (Diagram.Container == null || !IsBehaviorEnabled(e)) - return; + protected override void OnDiagramWheel(WheelEventArgs e) + { + if (Diagram.Container == null || !IsBehaviorEnabled(e)) + return; - var x = Diagram.Pan.X - (e.DeltaX / Diagram.Options.Zoom.ScaleFactor); - var y = Diagram.Pan.Y - (e.DeltaY / Diagram.Options.Zoom.ScaleFactor); + var x = Diagram.Pan.X - (e.DeltaX / Diagram.Options.Zoom.ScaleFactor); + var y = Diagram.Pan.Y - (e.DeltaY / Diagram.Options.Zoom.ScaleFactor); - Diagram.SetPan(x, y); - } - } + Diagram.SetPan(x, y); + } + } } diff --git a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs index fa02cac27..3457605f6 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs @@ -6,35 +6,35 @@ namespace Blazor.Diagrams.Core.Controls.Default { - public class ResizeControl : ExecutableControl - { - private readonly IResizerProvider _resizeProvider; + public class ResizeControl : ExecutableControl + { + private readonly IResizerProvider _resizeProvider; - public ResizeControl(IResizerProvider resizeProvider) - { - _resizeProvider = resizeProvider; - } + public ResizeControl(IResizerProvider resizeProvider) + { + _resizeProvider = resizeProvider; + } - public override Point? GetPosition(Model model) => _resizeProvider.GetPosition(model); + public override Point? GetPosition(Model model) => _resizeProvider.GetPosition(model); - public string? Class => _resizeProvider.Class; + public string? Class => _resizeProvider.Class; - public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) - { - _resizeProvider.OnResizeStart(diagram, model, e); - diagram.PointerMove += _resizeProvider.OnPointerMove; - diagram.Wheel += _resizeProvider.OnPointerMove; - diagram.PointerUp += _resizeProvider.OnResizeEnd; - diagram.PointerUp += (_, _) => OnResizeEnd(diagram); + public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) + { + _resizeProvider.OnResizeStart(diagram, model, e); + diagram.PointerMove += _resizeProvider.OnPointerMove; + diagram.Wheel += _resizeProvider.OnPointerMove; + diagram.PointerUp += _resizeProvider.OnResizeEnd; + diagram.PointerUp += (_, _) => OnResizeEnd(diagram); - return ValueTask.CompletedTask; - } + return ValueTask.CompletedTask; + } - void OnResizeEnd(Diagram diagram) - { - diagram.PointerMove -= _resizeProvider.OnPointerMove; - diagram.Wheel -= _resizeProvider.OnPointerMove; - diagram.PointerUp -= _resizeProvider.OnResizeEnd; - } - } + void OnResizeEnd(Diagram diagram) + { + diagram.PointerMove -= _resizeProvider.OnPointerMove; + diagram.Wheel -= _resizeProvider.OnPointerMove; + diagram.PointerUp -= _resizeProvider.OnResizeEnd; + } + } } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index fb5ab2edb..c80f64d2c 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -20,400 +20,400 @@ namespace Blazor.Diagrams.Core; 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() - { - _behaviors = new Dictionary(); - _orderedSelectables = new List(); - - Nodes = new NodeLayer(this); - Links = new LinkLayer(this); - Groups = new GroupLayer(this); - Controls = new ControlsLayer(); - BehaviorOptions = new DiagramBehaviorOptions(); - - Nodes.Added += OnSelectableAdded; - Links.Added += OnSelectableAdded; - Groups.Added += OnSelectableAdded; - - Nodes.Removed += OnSelectableRemoved; - Links.Removed += OnSelectableRemoved; - Groups.Removed += OnSelectableRemoved; - - RegisterDefaultBehaviors(); - - BehaviorOptions.DiagramDragBehavior ??= GetBehavior(); - BehaviorOptions.DiagramShiftDragBehavior ??= GetBehavior(); - BehaviorOptions.DiagramWheelBehavior ??= GetBehavior(); - } - - public abstract DiagramOptions Options { get; } - public DiagramBehaviorOptions BehaviorOptions { 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(); - } - - public void Batch(Action action) - { - 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; - action(); - SuspendRefresh = false; - Refresh(); - } - - #region Selection - - public IEnumerable GetSelectedModels() - { - foreach (var node in Nodes) - { - 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 group in Groups) - { - if (group.Selected) - yield return group; - } - } - - public void SelectModel(SelectableModel model, bool unselectOthers) - { - if (model.Selected) - return; - - if (unselectOthers) - UnselectAll(); - - 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); - } - } - - #endregion - - #region Behaviors - void RegisterDefaultBehaviors() - { - 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)); - RegisterBehavior(new ScrollBehavior(this)); - RegisterBehavior(new SelectionBoxBehavior(this)); - } - - 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); - } - - 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; - - _behaviors[type].Dispose(); - _behaviors.Remove(type); - } - - #endregion - - 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; - - SuspendRefresh = true; - - 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); - - SuspendRefresh = false; - 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 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(); - } - - public void SetContainer(Rectangle? newRect) - { - if (Equals(newRect, Container)) - return; - - 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"); - - 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"); - - 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"); - - 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(); - } + 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(); + BehaviorOptions = new DiagramBehaviorOptions(); + + Nodes.Added += OnSelectableAdded; + Links.Added += OnSelectableAdded; + Groups.Added += OnSelectableAdded; + + Nodes.Removed += OnSelectableRemoved; + Links.Removed += OnSelectableRemoved; + Groups.Removed += OnSelectableRemoved; + + RegisterDefaultBehaviors(); + + BehaviorOptions.DiagramDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramShiftDragBehavior ??= GetBehavior(); + BehaviorOptions.DiagramWheelBehavior ??= GetBehavior(); + } + + public abstract DiagramOptions Options { get; } + public DiagramBehaviorOptions BehaviorOptions { 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(); + } + + public void Batch(Action action) + { + 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; + action(); + SuspendRefresh = false; + Refresh(); + } + + #region Selection + + public IEnumerable GetSelectedModels() + { + foreach (var node in Nodes) + { + 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 group in Groups) + { + if (group.Selected) + yield return group; + } + } + + public void SelectModel(SelectableModel model, bool unselectOthers) + { + if (model.Selected) + return; + + if (unselectOthers) + UnselectAll(); + + 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); + } + } + + #endregion + + #region Behaviors + void RegisterDefaultBehaviors() + { + 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)); + RegisterBehavior(new ScrollBehavior(this)); + RegisterBehavior(new SelectionBoxBehavior(this)); + } + + 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); + } + + 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; + + _behaviors[type].Dispose(); + _behaviors.Remove(type); + } + + #endregion + + 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; + + SuspendRefresh = true; + + 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); + + SuspendRefresh = false; + 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 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(); + } + + public void SetContainer(Rectangle? newRect) + { + if (Equals(newRect, Container)) + return; + + 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"); + + 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"); + + 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"); + + 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 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) - { - _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); + /// + /// 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(); - } - } + if (refresh) + { + Refresh(); + } + } - private void OnSelectableAdded(SelectableModel model) - { - var maxOrder = GetMaxOrder(); - _orderedSelectables.Add(model); + private void OnSelectableAdded(SelectableModel model) + { + var maxOrder = GetMaxOrder(); + _orderedSelectables.Add(model); - if (model.Order == 0) - { - model.Order = maxOrder + 1; - } + if (model.Order == 0) + { + model.Order = maxOrder + 1; + } - model.OrderChanged += OnModelOrderChanged; - } + model.OrderChanged += OnModelOrderChanged; + } - private void OnSelectableRemoved(SelectableModel model) - { - model.OrderChanged -= OnModelOrderChanged; - _orderedSelectables.Remove(model); - } + private void OnSelectableRemoved(SelectableModel model) + { + model.OrderChanged -= OnModelOrderChanged; + _orderedSelectables.Remove(model); + } - private void OnModelOrderChanged(Model model) - { - if (SuspendSorting) - return; + private void OnModelOrderChanged(Model model) + { + if (SuspendSorting) + return; - RefreshOrders(); - } + 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/Positions/Resizing/IResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs index c15331e4e..91e7d8ce4 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs @@ -4,12 +4,12 @@ namespace Blazor.Diagrams.Core.Positions.Resizing { - public interface IResizerProvider : IPositionProvider - { - public string? Class { get; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); - public void OnPointerMove(Model? model, PointerEventArgs args); - public void OnPointerMove(WheelEventArgs args); - public void OnResizeEnd(Model? model, PointerEventArgs args); - } + public interface IResizerProvider : IPositionProvider + { + public string? Class { get; } + public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); + public void OnPointerMove(Model? model, PointerEventArgs args); + public void OnPointerMove(WheelEventArgs args); + public void OnResizeEnd(Model? model, PointerEventArgs args); + } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs index 657b08b28..d4fd868b3 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -10,152 +10,152 @@ 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); - } - - [Theory] - [InlineData(false, 0, 0, 45, 45)] - [InlineData(true, 0, 0, 35, 35)] - [InlineData(false, 3, 3, 45, 45)] - [InlineData(true, 3, 3, 50, 50)] - public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, double initialX, double initialY, double deltaX, double deltaY) - { - // Arrange - var diagram = new TestDiagram(new DiagramOptions - { - GridSize = 15, - GridSnapToCenter = gridSnapToCenter - }); - var nodeMock = new Mock(Point.Zero); - var node = diagram.Nodes.Add(nodeMock.Object); - node.Size = new Size(20, 20); - node.Position = new Point(initialX, initialY); - diagram.SelectModel(node, false); - - // Act - //Move 40px in X and Y - diagram.TriggerPointerDown(node, - new PointerEventArgs(20, 20, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - diagram.TriggerPointerMove(null, - new PointerEventArgs(60, 60, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); - - // Assert - nodeMock.Verify(n => n.SetPosition(deltaX, deltaY), Times.Once); - } - - [Fact] - public void Behavior_ShouldTriggerMoved() - { - // 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(); - } - - [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(); - } - - [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); - } - - [Fact] - public void Behavior_ShouldCallSetPosition_WhenWheelIsTriggered() - { - // 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.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 100, 0, 0)); - - // Assert - nodeMock.Verify(n => n.SetPosition(100, 100), Times.Once); - } + [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); + } + + [Theory] + [InlineData(false, 0, 0, 45, 45)] + [InlineData(true, 0, 0, 35, 35)] + [InlineData(false, 3, 3, 45, 45)] + [InlineData(true, 3, 3, 50, 50)] + public void Behavior_SnapToGrid_ShouldCallSetPosition(bool gridSnapToCenter, double initialX, double initialY, double deltaX, double deltaY) + { + // Arrange + var diagram = new TestDiagram(new DiagramOptions + { + GridSize = 15, + GridSnapToCenter = gridSnapToCenter + }); + var nodeMock = new Mock(Point.Zero); + var node = diagram.Nodes.Add(nodeMock.Object); + node.Size = new Size(20, 20); + node.Position = new Point(initialX, initialY); + diagram.SelectModel(node, false); + + // Act + //Move 40px in X and Y + diagram.TriggerPointerDown(node, + new PointerEventArgs(20, 20, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + diagram.TriggerPointerMove(null, + new PointerEventArgs(60, 60, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + nodeMock.Verify(n => n.SetPosition(deltaX, deltaY), Times.Once); + } + + [Fact] + public void Behavior_ShouldTriggerMoved() + { + // 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(); + } + + [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(); + } + + [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); + } + + [Fact] + public void Behavior_ShouldCallSetPosition_WhenWheelIsTriggered() + { + // 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.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 100, 0, 0)); + + // Assert + nodeMock.Verify(n => n.SetPosition(100, 100), Times.Once); + } } \ 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 73fa046ea..85a988b6f 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -10,467 +10,467 @@ namespace Blazor.Diagrams.Core.Tests.Behaviors; public class DragNewLinkBehaviorTests { - [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) - { - 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(100); - ongoingPosition.Y.Should().Be(100); - } - - [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) => - { - 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(100); - ongoingPosition.Y.Should().Be(100); - } - - [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) - { - 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().BeGreaterThan(145); - ongoingPosition.Y.Should().BeGreaterThan(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) - { - 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(107.7, 0.1); - ongoingPosition.Y.Should().BeApproximately(101.7, 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) - { - 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() - { - // 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() - { - // 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() - { - // 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() - { - // 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() - { - // 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); - } - - [Fact] - public void Behavior_ShouldUpdateOngoingPosition_WhenWheelIsTriggered() - { - // 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)); - diagram.TriggerWheel(new WheelEventArgs(150, 150, 0, 0, false, false, false, 100, 100, 0, 0)); - - // Assert - var source = link.Source as SinglePortAnchor; - var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().BeGreaterThan(245); - ongoingPosition.Y.Should().BeGreaterThan(245); - linkRefreshed.Should().BeTrue(); - } + [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) + { + 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(100); + ongoingPosition.Y.Should().Be(100); + } + + [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) => + { + 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(100); + ongoingPosition.Y.Should().Be(100); + } + + [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) + { + 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().BeGreaterThan(145); + ongoingPosition.Y.Should().BeGreaterThan(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) + { + 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(107.7, 0.1); + ongoingPosition.Y.Should().BeApproximately(101.7, 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) + { + 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() + { + // 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() + { + // 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() + { + // 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() + { + // 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() + { + // 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); + } + + [Fact] + public void Behavior_ShouldUpdateOngoingPosition_WhenWheelIsTriggered() + { + // 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)); + diagram.TriggerWheel(new WheelEventArgs(150, 150, 0, 0, false, false, false, 100, 100, 0, 0)); + + // Assert + var source = link.Source as SinglePortAnchor; + var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; + ongoingPosition.X.Should().BeGreaterThan(245); + ongoingPosition.Y.Should().BeGreaterThan(245); + linkRefreshed.Should().BeTrue(); + } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs index a1f09a899..4181ce3d2 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs @@ -6,39 +6,39 @@ namespace Blazor.Diagrams.Core.Tests.Behaviors { - public class ScrollBehaviorTests - { - [Fact] - public void Behavior_WhenBehaviorEnabled_ShouldScroll() - { - // Arrange - var diagram = new TestDiagram(); - diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); - diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); - diagram.Options.Zoom.ScaleFactor = 1.05; + public class ScrollBehaviorTests + { + [Fact] + public void Behavior_WhenBehaviorEnabled_ShouldScroll() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + diagram.Options.Zoom.ScaleFactor = 1.05; - // Act - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + // Act + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); - // Assert - Assert.Equal(-95, diagram.Pan.X, 0); - Assert.Equal(-190, diagram.Pan.Y, 0); - } + // Assert + Assert.Equal(-95, diagram.Pan.X, 0); + Assert.Equal(-190, diagram.Pan.Y, 0); + } - [Fact] - public void Behavior_WhenBehaviorDisabled_ShouldNotScroll() - { - // Arrange - var diagram = new TestDiagram(); - diagram.BehaviorOptions.DiagramWheelBehavior = null; - diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); + [Fact] + public void Behavior_WhenBehaviorDisabled_ShouldNotScroll() + { + // Arrange + var diagram = new TestDiagram(); + diagram.BehaviorOptions.DiagramWheelBehavior = null; + diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100))); - // Act - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); + // Act + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); - // Assert - Assert.Equal(0, diagram.Pan.X); - Assert.Equal(0, diagram.Pan.Y); - } - } + // Assert + Assert.Equal(0, diagram.Pan.X); + Assert.Equal(0, diagram.Pan.Y); + } + } } From ca6b4099a5ffeee969e7c130950716089a4a49ce Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Mon, 18 Dec 2023 14:14:30 +1100 Subject: [PATCH 045/159] Removed some blank lines --- src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 8ffeff845..38bf7fdf9 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -56,13 +56,12 @@ private void OnPointerDown(Model? model, PointerEventArgs e) _moved = false; } - public void OnPointerMove(Model? model, PointerEventArgs e) + 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; @@ -95,7 +94,6 @@ private void moveNodes(Model? model, double deltaX, double deltaY) { 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); @@ -119,7 +117,6 @@ private void OnPointerUp(Model? model, PointerEventArgs e) movable.TriggerMoved(); } } - _initialPositions.Clear(); _totalMovedX = 0; _totalMovedY = 0; @@ -133,14 +130,12 @@ private double ApplyGridSize(double n) return n; var gridSize = Diagram.Options.GridSize.Value; - return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); } public override void Dispose() { _initialPositions.Clear(); - Diagram.PointerDown -= OnPointerDown; Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; From f4a3d3e859742da72a33efccd18fecb5ee511d0e Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Tue, 19 Dec 2023 11:54:36 +1100 Subject: [PATCH 046/159] Addressed PR comments --- .../Behaviors/DragMovablesBehavior.cs | 12 ++--- .../Behaviors/DragNewLinkBehavior.cs | 8 +-- .../Controls/Default/ResizeControl.cs | 4 +- .../Resizing/BottomLeftResizerProvider.cs | 49 +++++-------------- .../Resizing/BottomRightResizerProvider.cs | 43 +++++----------- .../Positions/Resizing/IResizerProvider.cs | 2 +- .../Resizing/TopLeftResizerProvider.cs | 49 +++++-------------- .../Resizing/TopRightResizerProvider.cs | 48 +++++------------- 8 files changed, 65 insertions(+), 150 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 38bf7fdf9..729d531f3 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -23,7 +23,7 @@ public DragMovablesBehavior(Diagram diagram) : base(diagram) Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnPointerMove; + Diagram.Wheel += OnWheel; } private void OnPointerDown(Model? model, PointerEventArgs e) @@ -68,14 +68,14 @@ private void OnPointerMove(Model? model, PointerEventArgs e) _totalMovedX += deltaX; _totalMovedY += deltaY; - moveNodes(model, _totalMovedX, _totalMovedY); + MoveNodes(model, _totalMovedX, _totalMovedY); _lastClientX = e.ClientX; _lastClientY = e.ClientY; } - public void OnPointerMove(WheelEventArgs e) + public void OnWheel(WheelEventArgs e) { if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) return; @@ -85,10 +85,10 @@ public void OnPointerMove(WheelEventArgs e) _totalMovedX += e.DeltaX; _totalMovedY += e.DeltaY; - moveNodes(null, _totalMovedX, _totalMovedY); + MoveNodes(null, _totalMovedX, _totalMovedY); } - private void moveNodes(Model? model, double deltaX, double deltaY) + private void MoveNodes(Model? model, double deltaX, double deltaY) { foreach (var (movable, initialPosition) in _initialPositions) { @@ -139,6 +139,6 @@ public override void Dispose() Diagram.PointerDown -= OnPointerDown; Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnPointerMove; + Diagram.Wheel -= OnWheel; } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index cca9fd980..bddca19e6 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -20,7 +20,7 @@ public DragNewLinkBehavior(Diagram diagram) : base(diagram) Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnPointerMove; + Diagram.Wheel += OnWheel; } public void StartFrom(ILinkable source, double clientX, double clientY) @@ -79,7 +79,7 @@ private void OnPointerMove(Model? model, MouseEventArgs e) UpdateLinkPosition(e.ClientX, e.ClientY); } - private void OnPointerMove(WheelEventArgs e) + private void OnWheel(WheelEventArgs e) { if (OngoingLink == null) return; @@ -101,7 +101,7 @@ private void UpdateLinkPosition(double clientX, double clientY) } OngoingLink!.Refresh(); - OngoingLink!.RefreshLinks(); + OngoingLink.RefreshLinks(); } private void OnPointerUp(Model? model, MouseEventArgs e) @@ -183,6 +183,6 @@ public override void Dispose() Diagram.PointerDown -= OnPointerDown; Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnPointerMove; + Diagram.Wheel -= OnWheel; } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs index 3457605f6..1c4da4504 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs @@ -23,7 +23,7 @@ public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEve { _resizeProvider.OnResizeStart(diagram, model, e); diagram.PointerMove += _resizeProvider.OnPointerMove; - diagram.Wheel += _resizeProvider.OnPointerMove; + diagram.Wheel += _resizeProvider.OnWheel; diagram.PointerUp += _resizeProvider.OnResizeEnd; diagram.PointerUp += (_, _) => OnResizeEnd(diagram); @@ -33,7 +33,7 @@ public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEve void OnResizeEnd(Diagram diagram) { diagram.PointerMove -= _resizeProvider.OnPointerMove; - diagram.Wheel -= _resizeProvider.OnPointerMove; + diagram.Wheel -= _resizeProvider.OnWheel; diagram.PointerUp -= _resizeProvider.OnResizeEnd; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs index 66f34fe7f..9969b0479 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -40,51 +40,28 @@ public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) public void OnPointerMove(Model? model, PointerEventArgs e) { - if (_nodeModel is null || _lastClientX == null || _lastClientY == null) - { - return; - } - - var deltaX = (e.ClientX - _lastClientX.Value); - var deltaY = (e.ClientY - _lastClientY.Value); - - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - var height = _originalSize.Height + _totalMovedY; - var width = _originalSize.Width - _totalMovedX; - - var positionX = _originalPosition.X + _totalMovedX; - - var positionY = _originalPosition.Y; + if (_nodeModel is null) return; - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - positionX = _nodeModel.Position.X; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - positionY = _nodeModel.Position.Y; - } + var deltaX = (e.ClientX - _lastClientX!.Value); + var deltaY = (e.ClientY - _lastClientY!.Value); _lastClientX = e.ClientX; _lastClientY = e.ClientY; - _nodeModel.SetPosition(positionX, positionY); - _nodeModel.SetSize(width, height); + ResizeNode(deltaX, deltaY); } - public void OnPointerMove(WheelEventArgs e) + public void OnWheel(WheelEventArgs e) { - if (_nodeModel is null) - { - return; - } + if (_nodeModel is null) return; + + ResizeNode(e.DeltaX, e.DeltaY); + } - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; + public void ResizeNode(double deltaX, double deltaY) + { + _totalMovedX += deltaX; + _totalMovedY += deltaY; var height = _originalSize.Height + _totalMovedY; var width = _originalSize.Width - _totalMovedX; diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs index 843b1741f..69ae3ed48 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -38,45 +38,28 @@ public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) public void OnPointerMove(Model? model, PointerEventArgs e) { - if (_nodeModel is null || _lastClientX == null || _lastClientY == null) - { - return; - } - - - var deltaX = (e.ClientX - _lastClientX.Value); - var deltaY = (e.ClientY - _lastClientY.Value); - - _totalMovedX += deltaX; - _totalMovedY += deltaY; + if (_nodeModel is null) return; - var height = _originalSize.Height + _totalMovedY; - var width = _originalSize.Width + _totalMovedX; - - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - } + var deltaX = (e.ClientX - _lastClientX!.Value); + var deltaY = (e.ClientY - _lastClientY!.Value); _lastClientX = e.ClientX; _lastClientY = e.ClientY; - _nodeModel.SetSize(width, height); + ResizeNode(deltaX, deltaY); } - public void OnPointerMove(WheelEventArgs e) + public void OnWheel(WheelEventArgs e) { - if (_nodeModel is null) - { - return; - } + if (_nodeModel is null) return; + + ResizeNode(e.DeltaX, e.DeltaY); + } - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; + public void ResizeNode(double deltaX, double deltaY) + { + _totalMovedX += deltaX; + _totalMovedY += deltaY; var height = _originalSize.Height + _totalMovedY; var width = _originalSize.Width + _totalMovedX; diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs index 91e7d8ce4..e36f91190 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs @@ -9,7 +9,7 @@ public interface IResizerProvider : IPositionProvider public string? Class { get; } public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); public void OnPointerMove(Model? model, PointerEventArgs args); - public void OnPointerMove(WheelEventArgs args); + public void OnWheel(WheelEventArgs args); public void OnResizeEnd(Model? model, PointerEventArgs args); } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs index 6a0525c80..4184473f8 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -40,51 +40,28 @@ public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) public void OnPointerMove(Model? model, PointerEventArgs e) { - if (_nodeModel is null || _lastClientX == null || _lastClientY == null) - { - return; - } - - var deltaX = (e.ClientX - _lastClientX.Value); - var deltaY = (e.ClientY - _lastClientY.Value); - - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - - var height = _originalSize.Height - _totalMovedY; - var width = _originalSize.Width - _totalMovedX; - - var positionX = _originalPosition.X + _totalMovedX; - var positionY = _originalPosition.Y + _totalMovedY; + if (_nodeModel is null) return; - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - positionX = _nodeModel.Position.X; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - positionY = _nodeModel.Position.Y; - } + var deltaX = (e.ClientX - _lastClientX!.Value); + var deltaY = (e.ClientY - _lastClientY!.Value); _lastClientX = e.ClientX; _lastClientY = e.ClientY; - _nodeModel.SetPosition(positionX, positionY); - _nodeModel.SetSize(width, height); + ResizeNode(deltaX, deltaY); } - public void OnPointerMove(WheelEventArgs e) + public void OnWheel(WheelEventArgs e) { - if (_nodeModel is null || _lastClientX == null || _lastClientY == null) - { - return; - } + if (_nodeModel is null) return; + + ResizeNode(e.DeltaX, e.DeltaY); + } - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; + public void ResizeNode(double deltaX, double deltaY) + { + _totalMovedX += deltaX; + _totalMovedY += deltaY; var height = _originalSize.Height - _totalMovedY; var width = _originalSize.Width - _totalMovedX; diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs index 987b149b7..d0c7d1db3 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -40,50 +40,28 @@ public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) public void OnPointerMove(Model? model, PointerEventArgs e) { - if (_nodeModel is null || _lastClientX == null || _lastClientY == null) - { - return; - } - - var deltaX = (e.ClientX - _lastClientX.Value); - var deltaY = (e.ClientY - _lastClientY.Value); - - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - var height = _originalSize.Height - _totalMovedY; - var width = _originalSize.Width + _totalMovedX; + if (_nodeModel is null) return; - var positionX = _originalPosition.X; - var positionY = _originalPosition.Y + _totalMovedY; - - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - positionX = _nodeModel.Position.X; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - positionY = _nodeModel.Position.Y; - } + var deltaX = (e.ClientX - _lastClientX!.Value); + var deltaY = (e.ClientY - _lastClientY!.Value); _lastClientX = e.ClientX; _lastClientY = e.ClientY; - _nodeModel.SetPosition(positionX, positionY); - _nodeModel.SetSize(width, height); + ResizeNode(deltaX, deltaY); } - public void OnPointerMove(WheelEventArgs e) + public void OnWheel(WheelEventArgs e) { - if (_nodeModel is null) - { - return; - } + if (_nodeModel is null) return; + + ResizeNode(e.DeltaX, e.DeltaY); + } - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; + public void ResizeNode(double deltaX, double deltaY) + { + _totalMovedX += deltaX; + _totalMovedY += deltaY; var height = _originalSize.Height - _totalMovedY; var width = _originalSize.Width + _totalMovedX; From d204bb92319c60aa5e454917c69ac8c5d415e5cb Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Tue, 19 Dec 2023 14:23:32 +1100 Subject: [PATCH 047/159] Added tests --- .../BottomLeftResizerProviderTests.cs | 31 +++++++++++++++++++ .../BottomRightResizerProviderTests.cs | 31 +++++++++++++++++++ .../Resizing/TopLeftResizerProviderTests.cs | 31 +++++++++++++++++++ .../Resizing/TopRightResizerProviderTests.cs | 30 ++++++++++++++++++ 4 files changed, 123 insertions(+) diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs index 79ef6c17b..0a57183b3 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs @@ -46,6 +46,37 @@ public void DragResizer_ShouldResizeNode() node.Size.Height.Should().Be(215); } + [Fact] + public void ScrollingWheel_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(300); + } + [Fact] public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() { diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs index 20ab27770..684761362 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs @@ -46,6 +46,37 @@ public void DragResizer_ShouldResizeNode() node.Size.Height.Should().Be(215); } + [Fact] + public void ScrollingWheel_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(300); + } + [Fact] public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() { diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs index 1cc715d59..9e239f135 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs @@ -46,6 +46,37 @@ public void DragResizer_ShouldResizeNode() node.Size.Height.Should().Be(185); } + [Fact] + public void ScrollingWheel_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(100); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(100); + } + [Fact] public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() { diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs index a138d524f..bd4712a7c 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs @@ -46,6 +46,36 @@ public void DragResizer_ShouldResizeNode() node.Size.Height.Should().Be(185); } + [Fact] + public void ScrollingWheel_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(100); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(100); + } + [Fact] public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() { From 80178747178588f0f007b323a3590076208006b0 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 4 Jan 2024 11:02:47 +1100 Subject: [PATCH 048/159] Fixed implementation for DragMovablesBehavior --- .../Behaviors/DebugEventsBehavior.cs | 2 +- .../Behaviors/DragMovablesBehavior.cs | 11 +++-- .../Behaviors/ScrollBehavior.cs | 9 ++++- .../Behaviors/VirtualizationBehavior.cs | 11 +++-- .../Behaviors/ZoomBehavior.cs | 40 +++++++++---------- src/Blazor.Diagrams.Core/Diagram.cs | 8 ++-- .../Components/Widgets/GridWidget.razor.cs | 13 ++++-- .../DiagramTests.cs | 4 +- 8 files changed, 57 insertions(+), 41 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs index c5f71078d..cd8018477 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs @@ -52,7 +52,7 @@ private void Nodes_Added(NodeModel obj) Console.WriteLine($"Nodes.Added, Nodes=[{obj}]"); } - private void Diagram_PanChanged() + private void Diagram_PanChanged(double deltaX, double deltaY) { Console.WriteLine($"PanChanged, Pan={Diagram.Pan}"); } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 729d531f3..3a01eed76 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -23,7 +23,7 @@ public DragMovablesBehavior(Diagram diagram) : base(diagram) Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnWheel; + Diagram.PanChanged += OnPanChanged; } private void OnPointerDown(Model? model, PointerEventArgs e) @@ -74,16 +74,15 @@ private void OnPointerMove(Model? model, PointerEventArgs e) _lastClientY = e.ClientY; } - - public void OnWheel(WheelEventArgs e) + public void OnPanChanged(double deltaX, double deltaY) { if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) return; _moved = true; - _totalMovedX += e.DeltaX; - _totalMovedY += e.DeltaY; + _totalMovedX += deltaX; + _totalMovedY += deltaY; MoveNodes(null, _totalMovedX, _totalMovedY); } @@ -139,6 +138,6 @@ public override void Dispose() Diagram.PointerDown -= OnPointerDown; Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnWheel; + Diagram.PanChanged -= OnPanChanged; } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs index fa3ef63e1..cde088e7a 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs @@ -18,8 +18,15 @@ protected override void OnDiagramWheel(WheelEventArgs e) var x = Diagram.Pan.X - (e.DeltaX / Diagram.Options.Zoom.ScaleFactor); var y = Diagram.Pan.Y - (e.DeltaY / Diagram.Options.Zoom.ScaleFactor); + Diagram.GetScreenPoint(x, y); - Diagram.SetPan(x, y); + var _lastClientX = e.ClientX - e.DeltaX; + var _lastClientY = e.ClientY - e.DeltaY; + + var deltaX = e.ClientX - _lastClientX + (Diagram.Pan.X - x); + var deltaY = e.ClientY - _lastClientY + (Diagram.Pan.Y - y); + + Diagram.SetPan(x, y, deltaX, deltaY); } } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs index 7d0573b06..c9d03bfba 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs @@ -12,11 +12,16 @@ public VirtualizationBehavior(Diagram diagram) : base(diagram) Diagram.ContainerChanged += CheckVisibility; } + private void CheckVisibility(double deltaX, double deltaY) + { + CheckVisibility(); + } + private void CheckVisibility() { if (!Diagram.Options.Virtualization.Enabled) return; - + if (Diagram.Container == null) return; @@ -49,11 +54,11 @@ 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; diff --git a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs index 1c2a4f5c4..ebf44e585 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs @@ -15,31 +15,31 @@ protected override void OnDiagramWheel(WheelEventArgs e) if (Diagram.Container == null || e.DeltaY == 0 || !Diagram.Options.Zoom.Enabled || !IsBehaviorEnabled(e)) 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); + 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; + 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; + // 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.SetPan(newPanX, newPanY, 0, 0); Diagram.SetZoom(newZoom); }); } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index c80f64d2c..7f1bc7404 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -34,7 +34,7 @@ public abstract class Diagram public event Action? PointerDoubleClick; public event Action? SelectionChanged; - public event Action? PanChanged; + public event Action? PanChanged; public event Action? ZoomChanged; public event Action? ContainerChanged; public event Action? Changed; @@ -237,17 +237,17 @@ public void ZoomToFit(double margin = 10) Refresh(); } - public void SetPan(double x, double y) + public void SetPan(double x, double y, double deltaX, double deltaY) { Pan = new Point(x, y); - PanChanged?.Invoke(); + PanChanged?.Invoke(deltaX, deltaY); Refresh(); } public void UpdatePan(double deltaX, double deltaY) { Pan = Pan.Add(deltaX, deltaY); - PanChanged?.Invoke(); + PanChanged?.Invoke(deltaX, deltaY); Refresh(); } diff --git a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs index 596f985ff..ba3ac9547 100644 --- a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs @@ -1,7 +1,7 @@ -using System; -using System.Text; using Blazor.Diagrams.Core.Extensions; using Microsoft.AspNetCore.Components; +using System; +using System.Text; namespace Blazor.Diagrams.Components.Widgets; @@ -11,7 +11,7 @@ public partial class GridWidget : IDisposable 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; @@ -38,6 +38,11 @@ protected override void OnParametersSet() _visible = BlazorDiagram.Zoom > ZoomThreshold; } + private void RefreshPosition(double deltaX, double deltaY) + { + RefreshPosition(); + } + private void RefreshPosition() { _posX = BlazorDiagram.Pan.X; @@ -67,7 +72,7 @@ private string GenerateStyle() default: throw new ArgumentOutOfRangeException(); } - + return sb.ToString(); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs index b596c9ac7..34654be45 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs @@ -84,7 +84,7 @@ public void ZoomToFit_ShouldTriggerAppropriateEvents() // Act diagram.Changed += () => refreshes++; diagram.ZoomChanged += () => zoomChanges++; - diagram.PanChanged += () => panChanges++; + diagram.PanChanged += (x, y) => panChanges++; diagram.ZoomToFit(10); // Assert @@ -122,7 +122,7 @@ public void ZoomOptions_ThrowExceptionWhenLessThan0(double zoomValue) var diagram = new TestDiagram(); Assert.Throws(() => diagram.Options.Zoom.Minimum = zoomValue); } - + [Fact] public void SetContainer_ShouldAcceptNullGracefully() { From b6ac41be26230ab078c4bd582f8ed92b303146bb Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 4 Jan 2024 11:16:56 +1100 Subject: [PATCH 049/159] Started using pan changed for resize controls --- src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs | 4 ++-- .../Positions/Resizing/BottomLeftResizerProvider.cs | 4 ++-- .../Positions/Resizing/BottomRightResizerProvider.cs | 4 ++-- .../Positions/Resizing/IResizerProvider.cs | 2 +- .../Positions/Resizing/TopLeftResizerProvider.cs | 4 ++-- .../Positions/Resizing/TopRightResizerProvider.cs | 4 ++-- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs index 1c4da4504..9a01aafc1 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs @@ -23,7 +23,7 @@ public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEve { _resizeProvider.OnResizeStart(diagram, model, e); diagram.PointerMove += _resizeProvider.OnPointerMove; - diagram.Wheel += _resizeProvider.OnWheel; + diagram.PanChanged += _resizeProvider.OnPanChanged; diagram.PointerUp += _resizeProvider.OnResizeEnd; diagram.PointerUp += (_, _) => OnResizeEnd(diagram); @@ -33,7 +33,7 @@ public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEve void OnResizeEnd(Diagram diagram) { diagram.PointerMove -= _resizeProvider.OnPointerMove; - diagram.Wheel -= _resizeProvider.OnWheel; + diagram.PanChanged -= _resizeProvider.OnPanChanged; diagram.PointerUp -= _resizeProvider.OnResizeEnd; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs index 9969b0479..4725fc5ab 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -51,11 +51,11 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnWheel(WheelEventArgs e) + public void OnPanChanged(double deltaX, double deltaY) { if (_nodeModel is null) return; - ResizeNode(e.DeltaX, e.DeltaY); + ResizeNode(deltaX, deltaY); } public void ResizeNode(double deltaX, double deltaY) diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs index 69ae3ed48..5730a8343 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -49,11 +49,11 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnWheel(WheelEventArgs e) + public void OnPanChanged(double deltaX, double deltaY) { if (_nodeModel is null) return; - ResizeNode(e.DeltaX, e.DeltaY); + ResizeNode(deltaX, deltaY); } public void ResizeNode(double deltaX, double deltaY) diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs index e36f91190..96e16e189 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs @@ -9,7 +9,7 @@ public interface IResizerProvider : IPositionProvider public string? Class { get; } public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); public void OnPointerMove(Model? model, PointerEventArgs args); - public void OnWheel(WheelEventArgs args); + public void OnPanChanged(double deltaX, double deltaY); public void OnResizeEnd(Model? model, PointerEventArgs args); } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs index 4184473f8..cbe7d3d3d 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -51,11 +51,11 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnWheel(WheelEventArgs e) + public void OnPanChanged(double deltaX, double deltaY) { if (_nodeModel is null) return; - ResizeNode(e.DeltaX, e.DeltaY); + ResizeNode(deltaX, deltaY); } public void ResizeNode(double deltaX, double deltaY) diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs index d0c7d1db3..0babc8fc0 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -51,11 +51,11 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnWheel(WheelEventArgs e) + public void OnPanChanged(double deltaX, double deltaY) { if (_nodeModel is null) return; - ResizeNode(e.DeltaX, e.DeltaY); + ResizeNode(deltaX, deltaY); } public void ResizeNode(double deltaX, double deltaY) From 0e8e39aa0a2935760170a1b643b0c2681ed252e6 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 4 Jan 2024 12:06:59 +1100 Subject: [PATCH 050/159] Started using Pan Changed for DragNewLinkBehavior --- src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs | 2 +- .../Behaviors/DragMovablesBehavior.cs | 2 +- src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs | 8 ++++---- src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs | 2 +- .../Behaviors/VirtualizationBehavior.cs | 2 +- src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs | 2 +- src/Blazor.Diagrams.Core/Diagram.cs | 8 ++++---- .../Positions/Resizing/BottomLeftResizerProvider.cs | 2 +- .../Positions/Resizing/BottomRightResizerProvider.cs | 2 +- .../Positions/Resizing/IResizerProvider.cs | 2 +- .../Positions/Resizing/TopLeftResizerProvider.cs | 2 +- .../Positions/Resizing/TopRightResizerProvider.cs | 2 +- .../Components/Widgets/GridWidget.razor.cs | 2 +- 13 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs index cd8018477..afd1f0c78 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs @@ -52,7 +52,7 @@ private void Nodes_Added(NodeModel obj) Console.WriteLine($"Nodes.Added, Nodes=[{obj}]"); } - private void Diagram_PanChanged(double deltaX, double deltaY) + private void Diagram_PanChanged(double deltaX, double deltaY, double clientX, double clientY) { Console.WriteLine($"PanChanged, Pan={Diagram.Pan}"); } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 3a01eed76..0ea3dc66b 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -74,7 +74,7 @@ private void OnPointerMove(Model? model, PointerEventArgs e) _lastClientY = e.ClientY; } - public void OnPanChanged(double deltaX, double deltaY) + public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) { if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) return; diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index bddca19e6..a0b074046 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -20,7 +20,7 @@ public DragNewLinkBehavior(Diagram diagram) : base(diagram) Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; Diagram.PointerUp += OnPointerUp; - Diagram.Wheel += OnWheel; + Diagram.PanChanged += OnPanChanged; } public void StartFrom(ILinkable source, double clientX, double clientY) @@ -79,12 +79,12 @@ private void OnPointerMove(Model? model, MouseEventArgs e) UpdateLinkPosition(e.ClientX, e.ClientY); } - private void OnWheel(WheelEventArgs e) + private void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) { if (OngoingLink == null) return; - UpdateLinkPosition(e.ClientX + e.DeltaX, e.ClientY + e.DeltaY); + UpdateLinkPosition(clientX + deltaX, clientY + deltaY); } private void UpdateLinkPosition(double clientX, double clientY) @@ -183,6 +183,6 @@ public override void Dispose() Diagram.PointerDown -= OnPointerDown; Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; - Diagram.Wheel -= OnWheel; + Diagram.PanChanged -= OnPanChanged; } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs index cde088e7a..bb745489a 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs @@ -26,7 +26,7 @@ protected override void OnDiagramWheel(WheelEventArgs e) var deltaX = e.ClientX - _lastClientX + (Diagram.Pan.X - x); var deltaY = e.ClientY - _lastClientY + (Diagram.Pan.Y - y); - Diagram.SetPan(x, y, deltaX, deltaY); + Diagram.SetPan(x, y, deltaX, deltaY, _lastClientX, _lastClientY); } } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs index c9d03bfba..8674366d5 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs @@ -12,7 +12,7 @@ public VirtualizationBehavior(Diagram diagram) : base(diagram) Diagram.ContainerChanged += CheckVisibility; } - private void CheckVisibility(double deltaX, double deltaY) + private void CheckVisibility(double deltaX, double deltaY, double clientX, double clientY) { CheckVisibility(); } diff --git a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs index ebf44e585..3489abe73 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs @@ -39,7 +39,7 @@ protected override void OnDiagramWheel(WheelEventArgs e) Diagram.Batch(() => { - Diagram.SetPan(newPanX, newPanY, 0, 0); + Diagram.SetPan(newPanX, newPanY, 0, 0, 0, 0); Diagram.SetZoom(newZoom); }); } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 7f1bc7404..6160ff2b2 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -34,7 +34,7 @@ public abstract class Diagram public event Action? PointerDoubleClick; public event Action? SelectionChanged; - public event Action? PanChanged; + public event Action? PanChanged; public event Action? ZoomChanged; public event Action? ContainerChanged; public event Action? Changed; @@ -237,17 +237,17 @@ public void ZoomToFit(double margin = 10) Refresh(); } - public void SetPan(double x, double y, double deltaX, double deltaY) + public void SetPan(double x, double y, double deltaX, double deltaY, double clientX, double clientY) { Pan = new Point(x, y); - PanChanged?.Invoke(deltaX, deltaY); + PanChanged?.Invoke(deltaX, deltaY, clientX, clientY); Refresh(); } public void UpdatePan(double deltaX, double deltaY) { Pan = Pan.Add(deltaX, deltaY); - PanChanged?.Invoke(deltaX, deltaY); + PanChanged?.Invoke(deltaX, deltaY, 0, 0); Refresh(); } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs index 4725fc5ab..80fb7f003 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -51,7 +51,7 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnPanChanged(double deltaX, double deltaY) + public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) { if (_nodeModel is null) return; diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs index 5730a8343..56e6fe9cd 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -49,7 +49,7 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnPanChanged(double deltaX, double deltaY) + public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) { if (_nodeModel is null) return; diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs index 96e16e189..a271faf1e 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs @@ -9,7 +9,7 @@ public interface IResizerProvider : IPositionProvider public string? Class { get; } public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); public void OnPointerMove(Model? model, PointerEventArgs args); - public void OnPanChanged(double deltaX, double deltaY); + public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY); public void OnResizeEnd(Model? model, PointerEventArgs args); } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs index cbe7d3d3d..779107f5e 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -51,7 +51,7 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnPanChanged(double deltaX, double deltaY) + public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) { if (_nodeModel is null) return; diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs index 0babc8fc0..6c8220c91 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -51,7 +51,7 @@ public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - public void OnPanChanged(double deltaX, double deltaY) + public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) { if (_nodeModel is null) return; diff --git a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs index ba3ac9547..ec1914de8 100644 --- a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs @@ -38,7 +38,7 @@ protected override void OnParametersSet() _visible = BlazorDiagram.Zoom > ZoomThreshold; } - private void RefreshPosition(double deltaX, double deltaY) + private void RefreshPosition(double deltaX, double deltaY, double clientX, double clientY) { RefreshPosition(); } From 735834227e48ea4dcdd5a59d8d880fcfc098e3f2 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 4 Jan 2024 13:51:10 +1100 Subject: [PATCH 051/159] Fixed error in test --- tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs index 34654be45..43bf9a843 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs @@ -84,7 +84,7 @@ public void ZoomToFit_ShouldTriggerAppropriateEvents() // Act diagram.Changed += () => refreshes++; diagram.ZoomChanged += () => zoomChanges++; - diagram.PanChanged += (x, y) => panChanges++; + diagram.PanChanged += (deltaX, deltaY, clientX, clientY) => panChanges++; diagram.ZoomToFit(10); // Assert From d0deab77874f93f1fa59a0b592ca068a11140f82 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Thu, 4 Jan 2024 16:29:30 +1100 Subject: [PATCH 052/159] Refactored ResizeProviders for better reusability --- .../Controls/Default/ResizeControl.cs | 4 +- .../Resizing/BottomLeftResizerProvider.cs | 106 ++--------------- .../Resizing/BottomRightResizerProvider.cs | 97 ++-------------- .../Positions/Resizing/IResizerProvider.cs | 15 --- .../Positions/Resizing/ResizerProvider.cs | 108 ++++++++++++++++++ .../Resizing/TopLeftResizerProvider.cs | 107 ++--------------- .../Resizing/TopRightResizerProvider.cs | 106 ++--------------- .../Controls/ResizeControlTests.cs | 10 +- .../Controls/ResizeControlWidgetTests.cs | 2 +- 9 files changed, 162 insertions(+), 393 deletions(-) delete mode 100644 src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs create mode 100644 src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs diff --git a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs index 9a01aafc1..1818f996e 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs @@ -8,9 +8,9 @@ namespace Blazor.Diagrams.Core.Controls.Default { public class ResizeControl : ExecutableControl { - private readonly IResizerProvider _resizeProvider; + private readonly ResizerProvider _resizeProvider; - public ResizeControl(IResizerProvider resizeProvider) + public ResizeControl(ResizerProvider resizeProvider) { _resizeProvider = resizeProvider; } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs index 80fb7f003..e5d4f48ae 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -1,99 +1,17 @@ -using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; - -namespace Blazor.Diagrams.Core.Positions.Resizing +namespace Blazor.Diagrams.Core.Positions.Resizing { - public class BottomLeftResizerProvider : IResizerProvider + public class BottomLeftResizerProvider : ResizerProvider { - public string? Class => "bottomleft"; - - private Size _originalSize = null!; - private Point _originalPosition = null!; - private NodeModel _nodeModel = null!; - private double? _lastClientX; - private double? _lastClientY; - private double _totalMovedX = 0; - private double _totalMovedY = 0; - - public Point? GetPosition(Model model) - { - if (model is NodeModel nodeModel && nodeModel.Size is not null) - { - return new Point(nodeModel.Position.X - 5, nodeModel.Position.Y + nodeModel.Size.Height + 5); - } - return null; - } - - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) - { - if (model is NodeModel nodeModel) - { - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalSize = nodeModel.Size!; - _nodeModel = nodeModel; - } - } - - public void OnPointerMove(Model? model, PointerEventArgs e) - { - if (_nodeModel is null) return; - - var deltaX = (e.ClientX - _lastClientX!.Value); - var deltaY = (e.ClientY - _lastClientY!.Value); - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - - ResizeNode(deltaX, deltaY); - } - - public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) - { - if (_nodeModel is null) return; - - ResizeNode(deltaX, deltaY); - } - - public void ResizeNode(double deltaX, double deltaY) - { - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - var height = _originalSize.Height + _totalMovedY; - var width = _originalSize.Width - _totalMovedX; - - var positionX = _originalPosition.X + _totalMovedX; - var positionY = _originalPosition.Y; - - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - positionX = _nodeModel.Position.X; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - positionY = _nodeModel.Position.Y; - } - - _nodeModel.SetPosition(positionX, positionY); - _nodeModel.SetSize(width, height); - } - - public void OnResizeEnd(Model? model, PointerEventArgs args) - { - _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _originalPosition = null!; - _totalMovedY = 0; - _lastClientX = null; - _lastClientY = null; - _nodeModel = null!; - } + override public string? Class => "bottomleft"; + + override public double HeightOffset => 5; + override public double WidthOffset => -5; + override public bool ShouldUseWidth => false; + override public bool ShouldUseHeight => true; + override public bool ShouldChangeXPositionOnResize => true; + override public bool ShouldChangeYPositionOnResize => false; + override public bool ShouldAddTotalMovedX => false; + override public bool ShouldAddTotalMovedY => true; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs index 56e6fe9cd..05a985621 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -1,91 +1,16 @@ -using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; - -namespace Blazor.Diagrams.Core.Positions.Resizing +namespace Blazor.Diagrams.Core.Positions.Resizing { - public class BottomRightResizerProvider : IResizerProvider + public class BottomRightResizerProvider : ResizerProvider { - public string? Class => "bottomright"; - - private Size _originalSize = null!; - private double? _lastClientX; - private double? _lastClientY; - private NodeModel _nodeModel = null!; - private double _totalMovedX = 0; - private double _totalMovedY = 0; - - public Point? GetPosition(Model model) - { - if (model is NodeModel nodeModel && nodeModel.Size is not null) - { - return new Point(nodeModel.Position.X + nodeModel.Size.Width + 5, nodeModel.Position.Y + nodeModel.Size.Height + 5); - } - return null; - } - - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) - { - if (model is NodeModel nodeModel) - { - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _originalSize = nodeModel.Size!; - _nodeModel = nodeModel; - } - } - - public void OnPointerMove(Model? model, PointerEventArgs e) - { - if (_nodeModel is null) return; - - var deltaX = (e.ClientX - _lastClientX!.Value); - var deltaY = (e.ClientY - _lastClientY!.Value); - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - - ResizeNode(deltaX, deltaY); - } - - public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) - { - if (_nodeModel is null) return; - - ResizeNode(deltaX, deltaY); - } - - public void ResizeNode(double deltaX, double deltaY) - { - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - var height = _originalSize.Height + _totalMovedY; - var width = _originalSize.Width + _totalMovedX; - - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - } - - _nodeModel.SetSize(width, height); - } - - public void OnResizeEnd(Model? model, PointerEventArgs args) - { - _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _nodeModel = null!; - _totalMovedX = 0; - _totalMovedY = 0; - _lastClientX = null; - _lastClientY = null; - } + override public string? Class => "bottomright"; + override public double HeightOffset => 5; + override public double WidthOffset => 5; + override public bool ShouldUseWidth => true; + override public bool ShouldUseHeight => true; + override public bool ShouldChangeXPositionOnResize => false; + override public bool ShouldChangeYPositionOnResize => false; + override public bool ShouldAddTotalMovedX => true; + override public bool ShouldAddTotalMovedY => true; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs deleted file mode 100644 index a271faf1e..000000000 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/IResizerProvider.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; - -namespace Blazor.Diagrams.Core.Positions.Resizing -{ - public interface IResizerProvider : IPositionProvider - { - public string? Class { get; } - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventArgs); - public void OnPointerMove(Model? model, PointerEventArgs args); - public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY); - public void OnResizeEnd(Model? model, PointerEventArgs args); - } -} diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs new file mode 100644 index 000000000..6de309e49 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs @@ -0,0 +1,108 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions.Resizing +{ + public abstract class ResizerProvider : IPositionProvider + { + abstract public string? Class { get; } + + private Size _originalSize = null!; + private Point _originalPosition = null!; + private double? _lastClientX; + private double? _lastClientY; + private NodeModel _nodeModel = null!; + private double _totalMovedX = 0; + private double _totalMovedY = 0; + + abstract public double WidthOffset { get; } + abstract public double HeightOffset { get; } + abstract public bool ShouldUseWidth { get; } + abstract public bool ShouldUseHeight { get; } + abstract public bool ShouldChangeXPositionOnResize { get; } + abstract public bool ShouldChangeYPositionOnResize { get; } + abstract public bool ShouldAddTotalMovedX { get; } + abstract public bool ShouldAddTotalMovedY { get; } + + virtual public Point? GetPosition(Model model) + { + if (model is NodeModel nodeModel && nodeModel.Size is not null) + { + return new Point(nodeModel.Position.X + (ShouldUseWidth ? nodeModel.Size.Width : 0) + WidthOffset, nodeModel.Position.Y + (ShouldUseHeight ? nodeModel.Size.Height : 0) + HeightOffset); + } + return null; + } + + virtual public void ResizeNode(double deltaX, double deltaY) + { + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + var width = _originalSize.Width + (ShouldAddTotalMovedX ? _totalMovedX : -_totalMovedX); + var height = _originalSize.Height + (ShouldAddTotalMovedY ? _totalMovedY : -_totalMovedY); + + var positionX = _originalPosition.X + (ShouldChangeXPositionOnResize ? _totalMovedX : 0); + var positionY = _originalPosition.Y + (ShouldChangeYPositionOnResize ? _totalMovedY : 0); + + if (width < _nodeModel.MinimumDimensions.Width) + { + width = _nodeModel.MinimumDimensions.Width; + positionX = _nodeModel.Position.X; + } + if (height < _nodeModel.MinimumDimensions.Height) + { + height = _nodeModel.MinimumDimensions.Height; + positionY = _nodeModel.Position.Y; + } + + _nodeModel.SetPosition(positionX, positionY); + _nodeModel.SetSize(width, height); + } + + virtual public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) + { + if (model is NodeModel nodeModel) + { + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); + _originalSize = nodeModel.Size!; + _nodeModel = nodeModel; + } + } + + virtual public void OnPointerMove(Model? model, PointerEventArgs e) + { + if (_nodeModel is null) return; + + var deltaX = (e.ClientX - _lastClientX!.Value); + var deltaY = (e.ClientY - _lastClientY!.Value); + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + ResizeNode(deltaX, deltaY); + } + + virtual public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) + { + if (_nodeModel is null) return; + + ResizeNode(deltaX, deltaY); + } + + virtual public void OnResizeEnd(Model? model, PointerEventArgs args) + { + _nodeModel?.TriggerSizeChanged(); + _originalSize = null!; + _originalPosition = null!; + _nodeModel = null!; + _totalMovedX = 0; + _totalMovedY = 0; + _lastClientX = null; + _lastClientY = null; + } + } +} diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs index 779107f5e..eb787029f 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -1,100 +1,17 @@ -using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; - -namespace Blazor.Diagrams.Core.Positions.Resizing +namespace Blazor.Diagrams.Core.Positions.Resizing { - public class TopLeftResizerProvider : IResizerProvider + public class TopLeftResizerProvider : ResizerProvider { - public string? Class => "topleft"; - - private Size _originalSize = null!; - private Point _originalPosition = null!; - private NodeModel _nodeModel = null!; - private double? _lastClientX; - private double? _lastClientY; - private double _totalMovedX = 0; - private double _totalMovedY = 0; - - public Point? GetPosition(Model model) - { - if (model is NodeModel nodeModel && nodeModel.Size is not null) - { - return new Point(nodeModel.Position.X - 5, nodeModel.Position.Y - 5); - } - return null; - } - - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) - { - if (model is NodeModel nodeModel) - { - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalSize = nodeModel.Size!; - _nodeModel = nodeModel; - } - } - - public void OnPointerMove(Model? model, PointerEventArgs e) - { - if (_nodeModel is null) return; - - var deltaX = (e.ClientX - _lastClientX!.Value); - var deltaY = (e.ClientY - _lastClientY!.Value); - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - - ResizeNode(deltaX, deltaY); - } - - public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) - { - if (_nodeModel is null) return; - - ResizeNode(deltaX, deltaY); - } - - public void ResizeNode(double deltaX, double deltaY) - { - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - var height = _originalSize.Height - _totalMovedY; - var width = _originalSize.Width - _totalMovedX; - - var positionX = _originalPosition.X + _totalMovedX; - var positionY = _originalPosition.Y + _totalMovedY; - - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - positionX = _nodeModel.Position.X; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - positionY = _nodeModel.Position.Y; - } - - _nodeModel.SetPosition(positionX, positionY); - _nodeModel.SetSize(width, height); - } - - public void OnResizeEnd(Model? model, PointerEventArgs args) - { - _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _originalPosition = null!; - _totalMovedX = 0; - _totalMovedY = 0; - _lastClientX = null; - _lastClientY = null; - _nodeModel = null!; - } + override public string? Class => "topleft"; + + override public double HeightOffset => -5; + override public double WidthOffset => -5; + override public bool ShouldUseWidth => false; + override public bool ShouldUseHeight => false; + override public bool ShouldChangeXPositionOnResize => true; + override public bool ShouldChangeYPositionOnResize => true; + override public bool ShouldAddTotalMovedX => false; + override public bool ShouldAddTotalMovedY => false; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs index 6c8220c91..175b4af4d 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -1,100 +1,16 @@ -using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; - -namespace Blazor.Diagrams.Core.Positions.Resizing +namespace Blazor.Diagrams.Core.Positions.Resizing { - public class TopRightResizerProvider : IResizerProvider + public class TopRightResizerProvider : ResizerProvider { - public string? Class => "topright"; - - private Size _originalSize = null!; - private Point _originalPosition = null!; - private NodeModel _nodeModel = null!; - private double? _lastClientX; - private double? _lastClientY; - private double _totalMovedX = 0; - private double _totalMovedY = 0; - - public Point? GetPosition(Model model) - { - if (model is NodeModel nodeModel && nodeModel.Size is not null) - { - return new Point(nodeModel.Position.X + nodeModel.Size.Width + 5, nodeModel.Position.Y - 5); - } - return null; - } - - public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e) - { - if (model is NodeModel nodeModel) - { - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalSize = nodeModel.Size!; - _nodeModel = nodeModel; - } - } - - public void OnPointerMove(Model? model, PointerEventArgs e) - { - if (_nodeModel is null) return; - - var deltaX = (e.ClientX - _lastClientX!.Value); - var deltaY = (e.ClientY - _lastClientY!.Value); - - _lastClientX = e.ClientX; - _lastClientY = e.ClientY; - - ResizeNode(deltaX, deltaY); - } - - public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) - { - if (_nodeModel is null) return; - - ResizeNode(deltaX, deltaY); - } - - public void ResizeNode(double deltaX, double deltaY) - { - _totalMovedX += deltaX; - _totalMovedY += deltaY; - - var height = _originalSize.Height - _totalMovedY; - var width = _originalSize.Width + _totalMovedX; - - var positionX = _originalPosition.X; - var positionY = _originalPosition.Y + _totalMovedY; - - if (width < _nodeModel.MinimumDimensions.Width) - { - width = _nodeModel.MinimumDimensions.Width; - positionX = _nodeModel.Position.X; - } - if (height < _nodeModel.MinimumDimensions.Height) - { - height = _nodeModel.MinimumDimensions.Height; - positionY = _nodeModel.Position.Y; - } - - _nodeModel.SetPosition(positionX, positionY); - _nodeModel.SetSize(width, height); - } - - public void OnResizeEnd(Model? model, PointerEventArgs args) - { - _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _originalPosition = null!; - _totalMovedX = 0; - _totalMovedY = 0; - _lastClientX = null; - _lastClientY = null; - _nodeModel = null!; - } + override public string? Class => "topright"; + override public double HeightOffset => -5; + override public double WidthOffset => 5; + override public bool ShouldUseWidth => true; + override public bool ShouldUseHeight => false; + override public bool ShouldChangeXPositionOnResize => false; + override public bool ShouldChangeYPositionOnResize => true; + override public bool ShouldAddTotalMovedX => true; + override public bool ShouldAddTotalMovedY => false; } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs index dcd9bab14..83df648a3 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs @@ -12,7 +12,7 @@ public class ResizeControlTests [Fact] public void GetPosition_ShouldUseResizeProviderGetPosition() { - var resizeProvider = new Mock(); + var resizeProvider = new Mock(); var control = new ResizeControl(resizeProvider.Object); var model = new Mock(); @@ -24,7 +24,7 @@ public void GetPosition_ShouldUseResizeProviderGetPosition() [Fact] public void OnPointerDown_ShouldInvokeResizeStart() { - var resizeProvider = new Mock(); + var resizeProvider = new Mock(); var control = new ResizeControl(resizeProvider.Object); var diagram = Mock.Of(); var model = Mock.Of(); @@ -38,14 +38,14 @@ public void OnPointerDown_ShouldInvokeResizeStart() [Fact] public void OnPointerDown_ShouldAddEventHandlers() { - var resizeProvider = new Mock(); + var resizeProvider = new Mock(); var control = new ResizeControl(resizeProvider.Object); var diagram = new TestDiagram(); var model = Mock.Of(); var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); control.OnPointerDown(diagram, model, eventArgs); - + diagram.TriggerPointerMove(model, eventArgs); resizeProvider.Verify(m => m.OnPointerMove(model, eventArgs), Times.Once); @@ -56,7 +56,7 @@ public void OnPointerDown_ShouldAddEventHandlers() [Fact] public void OnPointerUp_ShouldRemoveEventHandlers() { - var resizeProvider = new Mock(); + var resizeProvider = new Mock(); var control = new ResizeControl(resizeProvider.Object); var diagram = new TestDiagram(); var model = Mock.Of(); diff --git a/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs index bf51e541c..7e7b34199 100644 --- a/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs +++ b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs @@ -13,7 +13,7 @@ public class ResizeControlWidgetTests public void ShouldRenderDiv() { using var ctx = new TestContext(); - var providerMock = Mock.Of(); + var providerMock = Mock.Of(); var cut = ctx.RenderComponent(parameters => parameters.Add(w => w.Control, new ResizeControl(providerMock)) From 9f43a85a38a3b29a32d65b228f3a2f327e403077 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Fri, 5 Jan 2024 16:59:17 +1100 Subject: [PATCH 053/159] Finished fixing tests --- .../Behaviors/ZoomBehavior.cs | 2 +- src/Blazor.Diagrams.Core/Diagram.cs | 2 +- .../Behaviors/DragMovablesBehaviorTests.cs | 7 ++++-- .../Behaviors/DragNewLinkBehaviorTests.cs | 8 ++++--- .../BottomLeftResizerProviderTests.cs | 17 ++++++-------- .../BottomRightResizerProviderTests.cs | 16 ++++++-------- .../Resizing/TopLeftResizerProviderTests.cs | 22 +++++++++---------- .../Resizing/TopRightResizerProviderTests.cs | 19 +++++++--------- 8 files changed, 44 insertions(+), 49 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs index 3489abe73..830b6d695 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs @@ -39,7 +39,7 @@ protected override void OnDiagramWheel(WheelEventArgs e) Diagram.Batch(() => { - Diagram.SetPan(newPanX, newPanY, 0, 0, 0, 0); + Diagram.SetPan(newPanX, newPanY); Diagram.SetZoom(newZoom); }); } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 6160ff2b2..401492d47 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -237,7 +237,7 @@ public void ZoomToFit(double margin = 10) Refresh(); } - public void SetPan(double x, double y, double deltaX, double deltaY, double clientX, double clientY) + public void SetPan(double x, double y, double deltaX = 0, double deltaY = 0, double clientX = 0, double clientY = 0) { Pan = new Point(x, y); PanChanged?.Invoke(deltaX, deltaY, clientX, clientY); diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs index d4fd868b3..bcd467a13 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -1,3 +1,4 @@ +using Blazor.Diagrams.Core.Behaviors; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; @@ -142,13 +143,15 @@ public void Behavior_ShouldCallSetPosition_WhenGroupHasAutoSize() } [Fact] - public void Behavior_ShouldCallSetPosition_WhenWheelIsTriggered() + public void Behavior_ShouldCallSetPosition_WhenPanChanges() { // Arrange var diagram = new TestDiagram(); var nodeMock = new Mock(Point.Zero); var node = diagram.Nodes.Add(nodeMock.Object); diagram.SelectModel(node, false); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + diagram.SetContainer(new Rectangle(0, 0, 100, 100)); // Act diagram.TriggerPointerDown(node, @@ -156,6 +159,6 @@ public void Behavior_ShouldCallSetPosition_WhenWheelIsTriggered() diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 100, 0, 0)); // Assert - nodeMock.Verify(n => n.SetPosition(100, 100), Times.Once); + nodeMock.Verify(n => n.SetPosition(It.IsInRange(194, 196, Range.Exclusive), It.IsInRange(194, 196, Range.Exclusive)), Times.Once); } } \ 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 85a988b6f..afe93f6d9 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -1,4 +1,5 @@ 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; @@ -443,7 +444,7 @@ public void Behavior_ShouldTriggerLinkTargetAttached_WhenLinkSnappedToPortAndMou } [Fact] - public void Behavior_ShouldUpdateOngoingPosition_WhenWheelIsTriggered() + public void Behavior_ShouldUpdateOngoingPosition_WhenPanChanges() { // Arrange var diagram = new TestDiagram(); @@ -456,6 +457,7 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenWheelIsTriggered() Position = new Point(110, 60), Size = new Size(10, 20) }); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); // Act diagram.TriggerPointerDown(port, @@ -469,8 +471,8 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenWheelIsTriggered() // Assert var source = link.Source as SinglePortAnchor; var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().BeGreaterThan(245); - ongoingPosition.Y.Should().BeGreaterThan(245); + ongoingPosition.X.Should().BeApproximately(337, 1); + ongoingPosition.Y.Should().BeApproximately(337, 1); linkRefreshed.Should().BeTrue(); } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs index 0a57183b3..155441257 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs @@ -1,14 +1,10 @@ -using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Controls.Default; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Positions.Resizing; using FluentAssertions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Blazor.Diagrams.Core.Tests.Positions.Resizing @@ -47,7 +43,7 @@ public void DragResizer_ShouldResizeNode() } [Fact] - public void ScrollingWheel_ShouldResizeNode() + public void PanChanged_ShouldResizeNode() { // setup var diagram = new TestDiagram(); @@ -57,6 +53,7 @@ public void ScrollingWheel_ShouldResizeNode() var control = new ResizeControl(new BottomLeftResizerProvider()); diagram.Controls.AddFor(node).Add(control); diagram.SelectModel(node, false); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); // before resize node.Position.X.Should().Be(0); @@ -71,10 +68,10 @@ public void ScrollingWheel_ShouldResizeNode() // after resize - node.Position.X.Should().Be(10); + node.Position.X.Should().BeApproximately(19, 1); node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(300); + node.Size.Width.Should().BeApproximately(80, 1); + node.Size.Height.Should().BeApproximately(395, 1); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs index 684761362..4954bacc8 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs @@ -1,14 +1,10 @@ -using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Controls.Default; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Positions.Resizing; using FluentAssertions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Blazor.Diagrams.Core.Tests.Positions.Resizing @@ -47,7 +43,7 @@ public void DragResizer_ShouldResizeNode() } [Fact] - public void ScrollingWheel_ShouldResizeNode() + public void PanChanged_ShouldResizeNode() { // setup var diagram = new TestDiagram(); @@ -57,6 +53,8 @@ public void ScrollingWheel_ShouldResizeNode() var control = new ResizeControl(new BottomRightResizerProvider()); diagram.Controls.AddFor(node).Add(control); diagram.SelectModel(node, false); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + // before resize node.Position.X.Should().Be(0); @@ -73,8 +71,8 @@ public void ScrollingWheel_ShouldResizeNode() // after resize node.Position.X.Should().Be(0); node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(110); - node.Size.Height.Should().Be(300); + node.Size.Width.Should().BeApproximately(119, 1); + node.Size.Height.Should().BeApproximately(395, 1); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs index 9e239f135..fb263cbb1 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs @@ -1,14 +1,10 @@ -using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Controls.Default; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Positions.Resizing; using FluentAssertions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Blazor.Diagrams.Core.Tests.Positions.Resizing @@ -47,7 +43,7 @@ public void DragResizer_ShouldResizeNode() } [Fact] - public void ScrollingWheel_ShouldResizeNode() + public void PanChanged_ShouldResizeNode() { // setup var diagram = new TestDiagram(); @@ -57,6 +53,8 @@ public void ScrollingWheel_ShouldResizeNode() var control = new ResizeControl(new TopLeftResizerProvider()); diagram.Controls.AddFor(node).Add(control); diagram.SelectModel(node, false); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + // before resize node.Position.X.Should().Be(0); @@ -67,14 +65,14 @@ public void ScrollingWheel_ShouldResizeNode() // resize var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); control.OnPointerDown(diagram, node, eventArgs); - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, -100, 0, 0)); // after resize - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(100); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(100); + node.Position.X.Should().BeApproximately(19, 1); + node.Position.Y.Should().BeApproximately(-195, 1); + node.Size.Width.Should().BeApproximately(80, 1); + node.Size.Height.Should().BeApproximately(395, 1); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs index bd4712a7c..96ef9e7f1 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs @@ -1,14 +1,10 @@ -using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Behaviors; +using Blazor.Diagrams.Core.Controls.Default; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Positions.Resizing; using FluentAssertions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Blazor.Diagrams.Core.Tests.Positions.Resizing @@ -47,7 +43,7 @@ public void DragResizer_ShouldResizeNode() } [Fact] - public void ScrollingWheel_ShouldResizeNode() + public void PanChanged_ShouldResizeNode() { // setup var diagram = new TestDiagram(); @@ -57,6 +53,7 @@ public void ScrollingWheel_ShouldResizeNode() var control = new ResizeControl(new TopRightResizerProvider()); diagram.Controls.AddFor(node).Add(control); diagram.SelectModel(node, false); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); // before resize node.Position.X.Should().Be(0); @@ -67,13 +64,13 @@ public void ScrollingWheel_ShouldResizeNode() // resize var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); control.OnPointerDown(diagram, node, eventArgs); - diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, -100, 0, 0)); // after resize node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(100); - node.Size.Width.Should().Be(110); - node.Size.Height.Should().Be(100); + node.Position.Y.Should().BeApproximately(-195, 1); + node.Size.Width.Should().BeApproximately(119, 1); + node.Size.Height.Should().BeApproximately(395, 1); } [Fact] From f6e831bbd862e8ca3fb699760964840988f1bfd3 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Tue, 9 Jan 2024 14:31:35 +1100 Subject: [PATCH 054/159] Addressed PR comments --- .../Behaviors/DebugEventsBehavior.cs | 2 +- .../Behaviors/DragMovablesBehavior.cs | 2 +- .../Behaviors/DragNewLinkBehavior.cs | 18 +++++++++++++----- .../Behaviors/PanBehavior.cs | 6 +++--- .../Behaviors/ScrollBehavior.cs | 13 +------------ .../Behaviors/VirtualizationBehavior.cs | 2 +- src/Blazor.Diagrams.Core/Diagram.cs | 8 ++++---- .../Positions/Resizing/ResizerProvider.cs | 2 +- .../Components/Widgets/GridWidget.razor.cs | 2 +- .../Behaviors/DragMovablesBehaviorTests.cs | 2 +- .../Behaviors/DragNewLinkBehaviorTests.cs | 6 +++--- .../Behaviors/ScrollBehaviorTests.cs | 5 ++--- .../Blazor.Diagrams.Core.Tests/DiagramTests.cs | 2 +- .../Resizing/BottomLeftResizerProviderTests.cs | 6 +++--- .../BottomRightResizerProviderTests.cs | 4 ++-- .../Resizing/TopLeftResizerProviderTests.cs | 8 ++++---- .../Resizing/TopRightResizerProviderTests.cs | 6 +++--- 17 files changed, 45 insertions(+), 49 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs index afd1f0c78..cd8018477 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DebugEventsBehavior.cs @@ -52,7 +52,7 @@ private void Nodes_Added(NodeModel obj) Console.WriteLine($"Nodes.Added, Nodes=[{obj}]"); } - private void Diagram_PanChanged(double deltaX, double deltaY, double clientX, double clientY) + private void Diagram_PanChanged(double deltaX, double deltaY) { Console.WriteLine($"PanChanged, Pan={Diagram.Pan}"); } diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 0ea3dc66b..3a01eed76 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -74,7 +74,7 @@ private void OnPointerMove(Model? model, PointerEventArgs e) _lastClientY = e.ClientY; } - public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) + public void OnPanChanged(double deltaX, double deltaY) { if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) return; diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index a0b074046..262a692e9 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -4,7 +4,6 @@ using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Models.Base; -using System; using System.Linq; namespace Blazor.Diagrams.Core.Behaviors; @@ -14,6 +13,8 @@ public class DragNewLinkBehavior : Behavior private PositionAnchor? _targetPositionAnchor; public BaseLinkModel? OngoingLink { get; private set; } + private double? _lastClientX; + private double? _lastClientY; public DragNewLinkBehavior(Diagram diagram) : base(diagram) { @@ -69,22 +70,27 @@ private void OnPointerDown(Model? model, MouseEventArgs e) OngoingLink.SetTarget(_targetPositionAnchor); Diagram.Links.Add(OngoingLink); } + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; } private void OnPointerMove(Model? model, MouseEventArgs e) { - if (OngoingLink == null || model != null) + if (OngoingLink == null || model != null || _lastClientX == null || _lastClientY == null) return; - UpdateLinkPosition(e.ClientX, e.ClientY); + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + UpdateLinkPosition((double)_lastClientX, (double)_lastClientY); } - private void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) + private void OnPanChanged(double deltaX, double deltaY) { if (OngoingLink == null) return; - UpdateLinkPosition(clientX + deltaX, clientY + deltaY); + UpdateLinkPosition((double)_lastClientX!, (double)_lastClientY!); } private void UpdateLinkPosition(double clientX, double clientY) @@ -134,6 +140,8 @@ private void OnPointerUp(Model? model, MouseEventArgs e) } OngoingLink = null; + _lastClientX = null; + _lastClientY = null; } private Point CalculateTargetPosition(double clientX, double clientY) diff --git a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs index 7856bc26a..4dab58e9a 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/PanBehavior.cs @@ -1,7 +1,7 @@ -using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Behaviors.Base; using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Behaviors.Base; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; namespace Blazor.Diagrams.Core.Behaviors; diff --git a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs index bb745489a..36e5e6d52 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs @@ -1,6 +1,5 @@ using Blazor.Diagrams.Core.Behaviors.Base; using Blazor.Diagrams.Core.Events; -using Blazor.Diagrams.Core.Options; namespace Blazor.Diagrams.Core.Behaviors { @@ -16,17 +15,7 @@ protected override void OnDiagramWheel(WheelEventArgs e) if (Diagram.Container == null || !IsBehaviorEnabled(e)) return; - var x = Diagram.Pan.X - (e.DeltaX / Diagram.Options.Zoom.ScaleFactor); - var y = Diagram.Pan.Y - (e.DeltaY / Diagram.Options.Zoom.ScaleFactor); - Diagram.GetScreenPoint(x, y); - - var _lastClientX = e.ClientX - e.DeltaX; - var _lastClientY = e.ClientY - e.DeltaY; - - var deltaX = e.ClientX - _lastClientX + (Diagram.Pan.X - x); - var deltaY = e.ClientY - _lastClientY + (Diagram.Pan.Y - y); - - Diagram.SetPan(x, y, deltaX, deltaY, _lastClientX, _lastClientY); + Diagram.UpdatePan(-e.DeltaX / Diagram.Zoom, -e.DeltaY / Diagram.Zoom); } } } diff --git a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs index 8674366d5..c9d03bfba 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs @@ -12,7 +12,7 @@ public VirtualizationBehavior(Diagram diagram) : base(diagram) Diagram.ContainerChanged += CheckVisibility; } - private void CheckVisibility(double deltaX, double deltaY, double clientX, double clientY) + private void CheckVisibility(double deltaX, double deltaY) { CheckVisibility(); } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 401492d47..b1cc4eeca 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -34,7 +34,7 @@ public abstract class Diagram public event Action? PointerDoubleClick; public event Action? SelectionChanged; - public event Action? PanChanged; + public event Action? PanChanged; public event Action? ZoomChanged; public event Action? ContainerChanged; public event Action? Changed; @@ -237,17 +237,17 @@ public void ZoomToFit(double margin = 10) Refresh(); } - public void SetPan(double x, double y, double deltaX = 0, double deltaY = 0, double clientX = 0, double clientY = 0) + public void SetPan(double x, double y) { Pan = new Point(x, y); - PanChanged?.Invoke(deltaX, deltaY, clientX, clientY); + PanChanged?.Invoke(x, y); Refresh(); } public void UpdatePan(double deltaX, double deltaY) { Pan = Pan.Add(deltaX, deltaY); - PanChanged?.Invoke(deltaX, deltaY, 0, 0); + PanChanged?.Invoke(-deltaX, -deltaY); Refresh(); } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs index 6de309e49..76259ceee 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs @@ -86,7 +86,7 @@ virtual public void OnPointerMove(Model? model, PointerEventArgs e) ResizeNode(deltaX, deltaY); } - virtual public void OnPanChanged(double deltaX, double deltaY, double clientX, double clientY) + virtual public void OnPanChanged(double deltaX, double deltaY) { if (_nodeModel is null) return; diff --git a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs index ec1914de8..ba3ac9547 100644 --- a/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs +++ b/src/Blazor.Diagrams/Components/Widgets/GridWidget.razor.cs @@ -38,7 +38,7 @@ protected override void OnParametersSet() _visible = BlazorDiagram.Zoom > ZoomThreshold; } - private void RefreshPosition(double deltaX, double deltaY, double clientX, double clientY) + private void RefreshPosition(double deltaX, double deltaY) { RefreshPosition(); } diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs index bcd467a13..813322dcc 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -159,6 +159,6 @@ public void Behavior_ShouldCallSetPosition_WhenPanChanges() diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 100, 0, 0)); // Assert - nodeMock.Verify(n => n.SetPosition(It.IsInRange(194, 196, Range.Exclusive), It.IsInRange(194, 196, Range.Exclusive)), Times.Once); + nodeMock.Verify(n => n.SetPosition(100, 100), Times.Once); } } \ 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 afe93f6d9..ec6f28462 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -454,7 +454,7 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenPanChanges() var port = node.AddPort(new PortModel(node) { Initialized = true, - Position = new Point(110, 60), + Position = new Point(100, 50), Size = new Size(10, 20) }); diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); @@ -471,8 +471,8 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenPanChanges() // Assert var source = link.Source as SinglePortAnchor; var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!; - ongoingPosition.X.Should().BeApproximately(337, 1); - ongoingPosition.Y.Should().BeApproximately(337, 1); + ongoingPosition.X.Should().BeApproximately(expectedValue: 246, 1); + ongoingPosition.Y.Should().BeApproximately(expectedValue: 246, 1); linkRefreshed.Should().BeTrue(); } } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs index 4181ce3d2..d18be5ed3 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/ScrollBehaviorTests.cs @@ -1,7 +1,6 @@ using Blazor.Diagrams.Core.Behaviors; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; -using Blazor.Diagrams.Core.Models; using Xunit; namespace Blazor.Diagrams.Core.Tests.Behaviors @@ -21,8 +20,8 @@ public void Behavior_WhenBehaviorEnabled_ShouldScroll() diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 100, 200, 0, 0)); // Assert - Assert.Equal(-95, diagram.Pan.X, 0); - Assert.Equal(-190, diagram.Pan.Y, 0); + Assert.Equal(-100, diagram.Pan.X); + Assert.Equal(-200, diagram.Pan.Y); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs index 43bf9a843..c129c8bde 100644 --- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs @@ -84,7 +84,7 @@ public void ZoomToFit_ShouldTriggerAppropriateEvents() // Act diagram.Changed += () => refreshes++; diagram.ZoomChanged += () => zoomChanges++; - diagram.PanChanged += (deltaX, deltaY, clientX, clientY) => panChanges++; + diagram.PanChanged += (deltaX, deltaY) => panChanges++; diagram.ZoomToFit(10); // Assert diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs index 155441257..596b856e4 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs @@ -68,10 +68,10 @@ public void PanChanged_ShouldResizeNode() // after resize - node.Position.X.Should().BeApproximately(19, 1); + node.Position.X.Should().Be(10); node.Position.Y.Should().Be(0); - node.Size.Width.Should().BeApproximately(80, 1); - node.Size.Height.Should().BeApproximately(395, 1); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(300); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs index 4954bacc8..c375036b3 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs @@ -71,8 +71,8 @@ public void PanChanged_ShouldResizeNode() // after resize node.Position.X.Should().Be(0); node.Position.Y.Should().Be(0); - node.Size.Width.Should().BeApproximately(119, 1); - node.Size.Height.Should().BeApproximately(395, 1); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(300); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs index fb263cbb1..ecca1e652 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs @@ -69,10 +69,10 @@ public void PanChanged_ShouldResizeNode() // after resize - node.Position.X.Should().BeApproximately(19, 1); - node.Position.Y.Should().BeApproximately(-195, 1); - node.Size.Width.Should().BeApproximately(80, 1); - node.Size.Height.Should().BeApproximately(395, 1); + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(-100); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(300); } [Fact] diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs index 96ef9e7f1..920518fc4 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs @@ -68,9 +68,9 @@ public void PanChanged_ShouldResizeNode() // after resize node.Position.X.Should().Be(0); - node.Position.Y.Should().BeApproximately(-195, 1); - node.Size.Width.Should().BeApproximately(119, 1); - node.Size.Height.Should().BeApproximately(395, 1); + node.Position.Y.Should().Be(-100); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(300); } [Fact] From 5f6626cd0133aa087e2ef97766c5ba57d6ad784a Mon Sep 17 00:00:00 2001 From: Heather <30523814+Heathermcx@users.noreply.github.com> Date: Tue, 9 Jan 2024 17:10:13 +1100 Subject: [PATCH 055/159] Account for diagram zoom when dragging resizers (#16) * add test * add fix * enable zoom for testing * finish writing tests * add fix for remaining resizers * Revert "enable zoom for testing" This reverts commit 69fa0aa301bedf49ddbfbb91e31c21d2f4016902. * fix formatting * make fields nullable * fix nullables * use file scoped namespaces --- .../Resizing/BottomLeftResizerProvider.cs | 32 +-- .../Resizing/BottomRightResizerProvider.cs | 25 ++- .../Resizing/TopLeftResizerProvider.cs | 33 +-- .../Resizing/TopRightResizerProvider.cs | 34 +-- .../Controls/ResizeControlTests.cs | 103 +++++----- .../BottomLeftResizerProviderTests.cs | 193 ++++++++++++------ .../BottomRightResizerProviderTests.cs | 189 +++++++++++------ .../Resizing/TopLeftResizerProviderTests.cs | 193 ++++++++++++------ .../Resizing/TopRightResizerProviderTests.cs | 193 ++++++++++++------ .../Controls/ResizeControlWidgetTests.cs | 25 ++- 10 files changed, 642 insertions(+), 378 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs index c4e576c92..f3e8b10d4 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -7,12 +7,14 @@ namespace Blazor.Diagrams.Core.Positions.Resizing { public class BottomLeftResizerProvider : IResizerProvider { - public string? Class => "bottomleft"; + public string? Class => "bottomleft"; + + private Size? _originalSize; + private Point? _originalPosition; + private Point? _originalMousePosition; + private NodeModel? _nodeModel; + private Diagram? _diagram; - private Size _originalSize = null!; - private Point _originalPosition = null!; - private Point _originalMousePosition = null!; - private NodeModel _nodeModel = null!; public Point? GetPosition(Model model) { @@ -29,22 +31,23 @@ public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventAr { _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); - _originalSize = nodeModel.Size!; + _originalSize = nodeModel.Size; _nodeModel = nodeModel; + _diagram = diagram; } } public void OnPointerMove(Model? model, PointerEventArgs args) { - if (_nodeModel is null) + if (_originalSize is null || _originalPosition is null || _originalMousePosition is null || _nodeModel is null || _diagram is null) { return; } - var height = _originalSize.Height + (args.ClientY - _originalMousePosition.Y); - var width = _originalSize.Width - (args.ClientX - _originalMousePosition.X); + var height = _originalSize.Height + (args.ClientY - _originalMousePosition.Y) / _diagram.Zoom; + var width = _originalSize.Width - (args.ClientX - _originalMousePosition.X) / _diagram.Zoom; - var positionX = _originalPosition.X + (args.ClientX - _originalMousePosition.X); + var positionX = _originalPosition.X + (args.ClientX - _originalMousePosition.X) / _diagram.Zoom; var positionY = _originalPosition.Y; if (width < _nodeModel.MinimumDimensions.Width) @@ -65,10 +68,11 @@ public void OnPointerMove(Model? model, PointerEventArgs args) public void OnResizeEnd(Model? model, PointerEventArgs args) { _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _originalPosition = null!; - _originalMousePosition = null!; - _nodeModel = null!; + _originalSize = null; + _originalPosition = null; + _originalMousePosition = null; + _nodeModel = null; + _diagram = null; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs index f55e26d71..d5cad9e89 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -9,11 +9,12 @@ public class BottomRightResizerProvider : IResizerProvider { public string? Class => "bottomright"; - private Size _originalSize = null!; - private Point _originalMousePosition = null!; - private NodeModel _nodeModel = null!; + private Size? _originalSize; + private Point? _originalMousePosition; + private NodeModel? _nodeModel; + private Diagram? _diagram; - public Point? GetPosition(Model model) + public Point? GetPosition(Model model) { if (model is NodeModel nodeModel && nodeModel.Size is not null) { @@ -27,20 +28,21 @@ public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventAr if (model is NodeModel nodeModel) { _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); - _originalSize = nodeModel.Size!; + _originalSize = nodeModel.Size; _nodeModel = nodeModel; + _diagram = diagram; } } public void OnPointerMove(Model? model, PointerEventArgs args) { - if (_nodeModel is null) + if (_originalSize is null || _originalMousePosition is null || _nodeModel is null || _diagram is null) { return; } - var height = _originalSize.Height + (args.ClientY - _originalMousePosition.Y); - var width = _originalSize.Width + (args.ClientX - _originalMousePosition.X); + var height = _originalSize.Height + (args.ClientY - _originalMousePosition.Y) / _diagram.Zoom; + var width = _originalSize.Width + (args.ClientX - _originalMousePosition.X) / _diagram.Zoom; if (width < _nodeModel.MinimumDimensions.Width) { @@ -57,9 +59,10 @@ public void OnPointerMove(Model? model, PointerEventArgs args) public void OnResizeEnd(Model? model, PointerEventArgs args) { _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _originalMousePosition = null!; - _nodeModel = null!; + _originalSize = null; + _originalMousePosition = null; + _nodeModel = null; + _diagram = null; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs index 7c3afe5ce..c9d7fbe6b 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -7,12 +7,13 @@ namespace Blazor.Diagrams.Core.Positions.Resizing { public class TopLeftResizerProvider : IResizerProvider { - public string? Class => "topleft"; + public string? Class => "topleft"; - private Size _originalSize = null!; - private Point _originalPosition = null!; - private Point _originalMousePosition = null!; - private NodeModel _nodeModel = null!; + private Size? _originalSize; + private Point? _originalPosition; + private Point? _originalMousePosition; + private NodeModel? _nodeModel; + private Diagram? _diagram; public Point? GetPosition(Model model) { @@ -29,23 +30,24 @@ public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventAr { _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); - _originalSize = nodeModel.Size!; + _originalSize = nodeModel.Size; _nodeModel = nodeModel; + _diagram = diagram; } } public void OnPointerMove(Model? model, PointerEventArgs args) { - if (_nodeModel is null) + if (_originalSize is null || _originalPosition is null || _originalMousePosition is null || _nodeModel is null || _diagram is null) { return; } - var height = _originalSize.Height - (args.ClientY - _originalMousePosition.Y); - var width = _originalSize.Width - (args.ClientX - _originalMousePosition.X); + var height = _originalSize.Height - (args.ClientY - _originalMousePosition.Y) / _diagram.Zoom; + var width = _originalSize.Width - (args.ClientX - _originalMousePosition.X) / _diagram.Zoom; - var positionX = _originalPosition.X + (args.ClientX - _originalMousePosition.X); - var positionY = _originalPosition.Y + (args.ClientY - _originalMousePosition.Y); + var positionX = _originalPosition.X + (args.ClientX - _originalMousePosition.X) / _diagram.Zoom; + var positionY = _originalPosition.Y + (args.ClientY - _originalMousePosition.Y) / _diagram.Zoom; if (width < _nodeModel.MinimumDimensions.Width) { @@ -65,10 +67,11 @@ public void OnPointerMove(Model? model, PointerEventArgs args) public void OnResizeEnd(Model? model, PointerEventArgs args) { _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _originalPosition = null!; - _originalMousePosition = null!; - _nodeModel = null!; + _originalSize = null; + _originalPosition = null; + _originalMousePosition = null; + _nodeModel = null; + _diagram = null; } } diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs index e9ea4e14a..8f19bc727 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -7,12 +7,13 @@ namespace Blazor.Diagrams.Core.Positions.Resizing { public class TopRightResizerProvider : IResizerProvider { - public string? Class => "topright"; + public string? Class => "topright"; - private Size _originalSize = null!; - private Point _originalPosition = null!; - private Point _originalMousePosition = null!; - private NodeModel _nodeModel = null!; + private Size? _originalSize; + private Point? _originalPosition; + private Point? _originalMousePosition; + private NodeModel? _nodeModel; + private Diagram? _diagram; public Point? GetPosition(Model model) { @@ -29,22 +30,24 @@ public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs eventAr { _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); _originalMousePosition = new Point(eventArgs.ClientX, eventArgs.ClientY); - _originalSize = nodeModel.Size!; + _originalSize = nodeModel.Size; _nodeModel = nodeModel; + _diagram = diagram; } } public void OnPointerMove(Model? model, PointerEventArgs args) { - if (_nodeModel is null) + if (_originalSize is null || _originalPosition is null || _originalMousePosition is null || _nodeModel is null || _diagram is null) { return; } - var height = _originalSize.Height - (args.ClientY - _originalMousePosition.Y); - var width = _originalSize.Width + (args.ClientX - _originalMousePosition.X); + + var height = _originalSize.Height - (args.ClientY - _originalMousePosition.Y) / _diagram.Zoom; + var width = _originalSize.Width + (args.ClientX - _originalMousePosition.X) / _diagram.Zoom; var positionX = _originalPosition.X; - var positionY = _originalPosition.Y + (args.ClientY - _originalMousePosition.Y); + var positionY = _originalPosition.Y + (args.ClientY - _originalMousePosition.Y) / _diagram.Zoom; if (width < _nodeModel.MinimumDimensions.Width) { @@ -64,11 +67,12 @@ public void OnPointerMove(Model? model, PointerEventArgs args) public void OnResizeEnd(Model? model, PointerEventArgs args) { _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _originalPosition = null!; - _originalMousePosition = null!; - _nodeModel = null!; + _originalSize = null; + _originalPosition = null; + _originalMousePosition = null; + _nodeModel = null; + _diagram = null; } } -} +} \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs index dcd9bab14..5192be721 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs @@ -5,71 +5,70 @@ using Moq; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Controls +namespace Blazor.Diagrams.Core.Tests.Controls; + +public class ResizeControlTests { - public class ResizeControlTests + [Fact] + public void GetPosition_ShouldUseResizeProviderGetPosition() { - [Fact] - public void GetPosition_ShouldUseResizeProviderGetPosition() - { - var resizeProvider = new Mock(); - var control = new ResizeControl(resizeProvider.Object); - var model = new Mock(); + var resizeProvider = new Mock(); + var control = new ResizeControl(resizeProvider.Object); + var model = new Mock(); - control.GetPosition(model.Object); + control.GetPosition(model.Object); - resizeProvider.Verify(m => m.GetPosition(model.Object), Times.Once); - } + resizeProvider.Verify(m => m.GetPosition(model.Object), Times.Once); + } - [Fact] - public void OnPointerDown_ShouldInvokeResizeStart() - { - var resizeProvider = new Mock(); - var control = new ResizeControl(resizeProvider.Object); - var diagram = Mock.Of(); - var model = Mock.Of(); - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); + [Fact] + public void OnPointerDown_ShouldInvokeResizeStart() + { + var resizeProvider = new Mock(); + var control = new ResizeControl(resizeProvider.Object); + var diagram = Mock.Of(); + var model = Mock.Of(); + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); - control.OnPointerDown(diagram, model, eventArgs); + control.OnPointerDown(diagram, model, eventArgs); - resizeProvider.Verify(m => m.OnResizeStart(diagram, model, eventArgs), Times.Once); - } + resizeProvider.Verify(m => m.OnResizeStart(diagram, model, eventArgs), Times.Once); + } - [Fact] - public void OnPointerDown_ShouldAddEventHandlers() - { - var resizeProvider = new Mock(); - var control = new ResizeControl(resizeProvider.Object); - var diagram = new TestDiagram(); - var model = Mock.Of(); - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); + [Fact] + public void OnPointerDown_ShouldAddEventHandlers() + { + var resizeProvider = new Mock(); + var control = new ResizeControl(resizeProvider.Object); + var diagram = new TestDiagram(); + var model = Mock.Of(); + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); - control.OnPointerDown(diagram, model, eventArgs); - - diagram.TriggerPointerMove(model, eventArgs); - resizeProvider.Verify(m => m.OnPointerMove(model, eventArgs), Times.Once); + control.OnPointerDown(diagram, model, eventArgs); + + diagram.TriggerPointerMove(model, eventArgs); + resizeProvider.Verify(m => m.OnPointerMove(model, eventArgs), Times.Once); - diagram.TriggerPointerUp(model, eventArgs); - resizeProvider.Verify(m => m.OnResizeEnd(model, eventArgs), Times.Once); - } + diagram.TriggerPointerUp(model, eventArgs); + resizeProvider.Verify(m => m.OnResizeEnd(model, eventArgs), Times.Once); + } - [Fact] - public void OnPointerUp_ShouldRemoveEventHandlers() - { - var resizeProvider = new Mock(); - var control = new ResizeControl(resizeProvider.Object); - var diagram = new TestDiagram(); - var model = Mock.Of(); - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); + [Fact] + public void OnPointerUp_ShouldRemoveEventHandlers() + { + var resizeProvider = new Mock(); + var control = new ResizeControl(resizeProvider.Object); + var diagram = new TestDiagram(); + var model = Mock.Of(); + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); - control.OnPointerDown(diagram, model, eventArgs); - diagram.TriggerPointerUp(model, eventArgs); + control.OnPointerDown(diagram, model, eventArgs); + diagram.TriggerPointerUp(model, eventArgs); - diagram.TriggerPointerMove(model, eventArgs); - resizeProvider.Verify(m => m.OnPointerMove(model, eventArgs), Times.Never); + diagram.TriggerPointerMove(model, eventArgs); + resizeProvider.Verify(m => m.OnPointerMove(model, eventArgs), Times.Never); - diagram.TriggerPointerUp(model, eventArgs); - resizeProvider.Verify(m => m.OnResizeEnd(model, eventArgs), Times.Once); - } + diagram.TriggerPointerUp(model, eventArgs); + resizeProvider.Verify(m => m.OnResizeEnd(model, eventArgs), Times.Once); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs index 79ef6c17b..1605cb14f 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs @@ -11,72 +11,135 @@ using System.Threading.Tasks; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Positions.Resizing +namespace Blazor.Diagrams.Core.Tests.Positions.Resizing; + +public class BottomLeftResizerProviderTests { - public class BottomLeftResizerProviderTests + [Fact] + public void DragResizer_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(215); + } + + [Fact] + public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(99, -199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + eventArgs = new PointerEventArgs(300, -300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(99); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(0); + node.Size.Height.Should().Be(0); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut() { - [Fact] - public void DragResizer_ShouldResizeNode() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var control = new ResizeControl(new BottomLeftResizerProvider()); - diagram.Controls.AddFor(node).Add(control); - diagram.SelectModel(node, false); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - control.OnPointerDown(diagram, node, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(215); - } - - [Fact] - public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var control = new ResizeControl(new BottomLeftResizerProvider()); - diagram.Controls.AddFor(node).Add(control); - diagram.SelectModel(node, false); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - control.OnPointerDown(diagram, node, eventArgs); - eventArgs = new PointerEventArgs(99, -199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - eventArgs = new PointerEventArgs(300, -300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(99); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(0); - node.Size.Height.Should().Be(0); - } + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + diagram.SetZoom(0.5); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(20); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(80); + node.Size.Height.Should().Be(230); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + diagram.SetZoom(2); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(5); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(95); + node.Size.Height.Should().Be(207.5); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs index 20ab27770..959724e6d 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs @@ -11,70 +11,133 @@ using System.Threading.Tasks; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Positions.Resizing +namespace Blazor.Diagrams.Core.Tests.Positions.Resizing; + +public class BottomRightResizerProviderTests { - public class BottomRightResizerProviderTests + [Fact] + public void DragResizer_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(215); + } + + [Fact] + public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(-300, -300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(0); + node.Size.Height.Should().Be(0); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut() { - [Fact] - public void DragResizer_ShouldResizeNode() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var control = new ResizeControl(new BottomRightResizerProvider()); - diagram.Controls.AddFor(node).Add(control); - diagram.SelectModel(node, false); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - control.OnPointerDown(diagram, node, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(110); - node.Size.Height.Should().Be(215); - } - - [Fact] - public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var control = new ResizeControl(new BottomRightResizerProvider()); - diagram.Controls.AddFor(node).Add(control); - diagram.SelectModel(node, false); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - control.OnPointerDown(diagram, node, eventArgs); - eventArgs = new PointerEventArgs(-300, -300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(0); - node.Size.Height.Should().Be(0); - } + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + diagram.SetZoom(0.5); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(120); + node.Size.Height.Should().Be(230); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + diagram.SetZoom(2); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(105); + node.Size.Height.Should().Be(207.5); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs index 1cc715d59..13d241d08 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs @@ -11,72 +11,135 @@ using System.Threading.Tasks; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Positions.Resizing +namespace Blazor.Diagrams.Core.Tests.Positions.Resizing; + +public class TopLeftResizerProviderTests { - public class TopLeftResizerProviderTests + [Fact] + public void DragResizer_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(10); + node.Position.Y.Should().Be(15); + node.Size.Width.Should().Be(90); + node.Size.Height.Should().Be(185); + } + + [Fact] + public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(99, 199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + eventArgs = new PointerEventArgs(300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(99); + node.Position.Y.Should().Be(199); + node.Size.Width.Should().Be(0); + node.Size.Height.Should().Be(0); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut() { - [Fact] - public void DragResizer_ShouldResizeNode() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var control = new ResizeControl(new TopLeftResizerProvider()); - diagram.Controls.AddFor(node).Add(control); - diagram.SelectModel(node, false); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - control.OnPointerDown(diagram, node, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(10); - node.Position.Y.Should().Be(15); - node.Size.Width.Should().Be(90); - node.Size.Height.Should().Be(185); - } - - [Fact] - public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var control = new ResizeControl(new TopLeftResizerProvider()); - diagram.Controls.AddFor(node).Add(control); - diagram.SelectModel(node, false); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - control.OnPointerDown(diagram, node, eventArgs); - eventArgs = new PointerEventArgs(99, 199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - eventArgs = new PointerEventArgs(300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(99); - node.Position.Y.Should().Be(199); - node.Size.Width.Should().Be(0); - node.Size.Height.Should().Be(0); - } + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + diagram.SetZoom(0.5); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(20); + node.Position.Y.Should().Be(30); + node.Size.Width.Should().Be(80); + node.Size.Height.Should().Be(170); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + diagram.SetZoom(2); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(5); + node.Position.Y.Should().Be(7.5); + node.Size.Width.Should().Be(95); + node.Size.Height.Should().Be(192.5); } } diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs index a138d524f..5fcd3b821 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs @@ -11,72 +11,135 @@ using System.Threading.Tasks; using Xunit; -namespace Blazor.Diagrams.Core.Tests.Positions.Resizing +namespace Blazor.Diagrams.Core.Tests.Positions.Resizing; + +public class TopRightResizerProviderTests { - public class TopRightResizerProviderTests + [Fact] + public void DragResizer_ShouldResizeNode() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(15); + node.Size.Width.Should().Be(110); + node.Size.Height.Should().Be(185); + } + + [Fact] + public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(-99, 199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + eventArgs = new PointerEventArgs(-300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(199); + node.Size.Width.Should().Be(0); + node.Size.Height.Should().Be(0); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut() { - [Fact] - public void DragResizer_ShouldResizeNode() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var control = new ResizeControl(new TopRightResizerProvider()); - diagram.Controls.AddFor(node).Add(control); - diagram.SelectModel(node, false); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - control.OnPointerDown(diagram, node, eventArgs); - eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(15); - node.Size.Width.Should().Be(110); - node.Size.Height.Should().Be(185); - } - - [Fact] - public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize() - { - // setup - var diagram = new TestDiagram(); - diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); - var node = new NodeModel(position: new Point(0, 0)); - node.Size = new Size(100, 200); - var control = new ResizeControl(new TopRightResizerProvider()); - diagram.Controls.AddFor(node).Add(control); - diagram.SelectModel(node, false); - - // before resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(0); - node.Size.Width.Should().Be(100); - node.Size.Height.Should().Be(200); - - // resize - var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - control.OnPointerDown(diagram, node, eventArgs); - eventArgs = new PointerEventArgs(-99, 199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - eventArgs = new PointerEventArgs(-300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); - diagram.TriggerPointerMove(null, eventArgs); - - // after resize - node.Position.X.Should().Be(0); - node.Position.Y.Should().Be(199); - node.Size.Width.Should().Be(0); - node.Size.Height.Should().Be(0); - } + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + diagram.SetZoom(0.5); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(30); + node.Size.Width.Should().Be(120); + node.Size.Height.Should().Be(170); + } + + [Fact] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + diagram.SetZoom(2); + + // before resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(0); + node.Size.Width.Should().Be(100); + node.Size.Height.Should().Be(200); + + // resize + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + control.OnPointerDown(diagram, node, eventArgs); + eventArgs = new PointerEventArgs(10, 15, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + node.Position.X.Should().Be(0); + node.Position.Y.Should().Be(7.5); + node.Size.Width.Should().Be(105); + node.Size.Height.Should().Be(192.5); } } diff --git a/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs index bf51e541c..ebc758c47 100644 --- a/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs +++ b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs @@ -5,21 +5,20 @@ using Moq; using Xunit; -namespace Blazor.Diagrams.Tests.Components.Controls -{ +namespace Blazor.Diagrams.Tests.Components.Controls; + public class ResizeControlWidgetTests +{ + [Fact] + public void ShouldRenderDiv() { - [Fact] - public void ShouldRenderDiv() - { - using var ctx = new TestContext(); - var providerMock = Mock.Of(); - - var cut = ctx.RenderComponent(parameters => - parameters.Add(w => w.Control, new ResizeControl(providerMock)) - ); + using var ctx = new TestContext(); + var providerMock = Mock.Of(); + + var cut = ctx.RenderComponent(parameters => + parameters.Add(w => w.Control, new ResizeControl(providerMock)) + ); - cut.MarkupMatches("
"); - } + cut.MarkupMatches("
"); } } From ff004387d85898f1992259077b63d9d49174459f Mon Sep 17 00:00:00 2001 From: Aarthi Narayanan Date: Thu, 11 Jan 2024 22:45:13 +0530 Subject: [PATCH 056/159] Introducing DisableDragNewLink property to disable draging a new link --- .../Behaviors/DragNewLinkBehavior.cs | 2 +- src/Blazor.Diagrams.Core/Models/Base/Model.cs | 1 + .../Behaviors/DragNewLinkBehaviorTests.cs | 26 ++++++++++++++++++- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 7efa83dc1..250dbde3e 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -56,7 +56,7 @@ private void OnPointerDown(Model? model, MouseEventArgs e) if (model is PortModel port) { - if (port.Locked) + if (port.Locked || port.DisableDragNewLink) return; _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(e.ClientX, e.ClientY)); diff --git a/src/Blazor.Diagrams.Core/Models/Base/Model.cs b/src/Blazor.Diagrams.Core/Models/Base/Model.cs index 738b45091..1b67c3f0c 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/Model.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/Model.cs @@ -18,6 +18,7 @@ protected Model(string id) public string Id { get; } public bool Locked { get; set; } + public bool DisableDragNewLink { get; set; } public bool Visible { get => _visible; diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index 18f466eec..ea29c183e 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -21,7 +21,8 @@ public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnP { Initialized = true, Position = new Point(110, 60), - Size = new Size(10, 20) + Size = new Size(10, 20), + DisableDragNewLink = false, }); // Act @@ -441,4 +442,27 @@ public void Behavior_ShouldTriggerLinkTargetAttached_WhenLinkSnappedToPortAndMou // Assert targetAttachedTriggers.Should().Be(1); } + + [Fact] + public void Behavior_ShouldNotCreateLinkWithSinglePortAnchorSource_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) + { + Initialized = true, + Position = new Point(110, 60), + Size = new Size(10, 20), + DisableDragNewLink = true, + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + diagram.Links.Count.Should().Be(0); + } } \ No newline at end of file From bc943bc9f669008a781fb1c258a503b6e9957b41 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Mon, 15 Jan 2024 16:41:31 +1100 Subject: [PATCH 057/159] Addressed new PR comments --- .../Behaviors/DragNewLinkBehavior.cs | 13 ++++++++----- src/Blazor.Diagrams.Core/Diagram.cs | 5 ++++- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 262a692e9..6d742c5ef 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -87,26 +87,29 @@ private void OnPointerMove(Model? model, MouseEventArgs e) private void OnPanChanged(double deltaX, double deltaY) { - if (OngoingLink == null) + if (OngoingLink == null || _lastClientX == null || _lastClientY == null) return; - UpdateLinkPosition((double)_lastClientX!, (double)_lastClientY!); + UpdateLinkPosition((double)_lastClientX, (double)_lastClientY); } private void UpdateLinkPosition(double clientX, double clientY) { + if (OngoingLink == null) + return; + _targetPositionAnchor!.SetPosition(CalculateTargetPosition(clientX, 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.Refresh(); OngoingLink.RefreshLinks(); } diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index b1cc4eeca..28cca0a5d 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -239,8 +239,11 @@ public void ZoomToFit(double margin = 10) public void SetPan(double x, double y) { + var oldPanX = Pan.X; + var oldPanY = Pan.Y; + Pan = new Point(x, y); - PanChanged?.Invoke(x, y); + PanChanged?.Invoke(oldPanX - Pan.X, oldPanY - Pan.Y); Refresh(); } From 81435c060e065a2a43b4e0910f5c71430d5b9730 Mon Sep 17 00:00:00 2001 From: Aarthi Narayanan Date: Tue, 16 Jan 2024 00:32:53 +0530 Subject: [PATCH 058/159] Renamed Property to Enabled --- src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs | 2 +- src/Blazor.Diagrams.Core/Models/Base/Model.cs | 2 +- .../Behaviors/DragNewLinkBehaviorTests.cs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 250dbde3e..76e3cd734 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -56,7 +56,7 @@ private void OnPointerDown(Model? model, MouseEventArgs e) if (model is PortModel port) { - if (port.Locked || port.DisableDragNewLink) + if (port.Locked || port.Enabled) return; _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(e.ClientX, e.ClientY)); diff --git a/src/Blazor.Diagrams.Core/Models/Base/Model.cs b/src/Blazor.Diagrams.Core/Models/Base/Model.cs index 1b67c3f0c..ae7506860 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/Model.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/Model.cs @@ -18,7 +18,7 @@ protected Model(string id) public string Id { get; } public bool Locked { get; set; } - public bool DisableDragNewLink { get; set; } + public bool Enabled { get; set; } public bool Visible { get => _visible; diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index ea29c183e..8a99cd869 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -22,7 +22,7 @@ public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnP Initialized = true, Position = new Point(110, 60), Size = new Size(10, 20), - DisableDragNewLink = false, + Enabled = false, }); // Act @@ -455,7 +455,7 @@ public void Behavior_ShouldNotCreateLinkWithSinglePortAnchorSource_WhenMouseDown Initialized = true, Position = new Point(110, 60), Size = new Size(10, 20), - DisableDragNewLink = true, + Enabled = true, }); // Act From d9a3acefebebcc40bb7d2f7e5135f3e26ccb9cfe Mon Sep 17 00:00:00 2001 From: Aarthi Narayanan Date: Tue, 16 Jan 2024 11:30:25 +0530 Subject: [PATCH 059/159] Changed implmentation as per the name change --- src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs | 2 +- src/Blazor.Diagrams.Core/Models/Base/Model.cs | 2 +- .../Behaviors/DragNewLinkBehaviorTests.cs | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 76e3cd734..5e77b6299 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -56,7 +56,7 @@ private void OnPointerDown(Model? model, MouseEventArgs e) if (model is PortModel port) { - if (port.Locked || port.Enabled) + if (port.Locked || !port.Enabled) return; _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(e.ClientX, e.ClientY)); diff --git a/src/Blazor.Diagrams.Core/Models/Base/Model.cs b/src/Blazor.Diagrams.Core/Models/Base/Model.cs index ae7506860..0c40525df 100644 --- a/src/Blazor.Diagrams.Core/Models/Base/Model.cs +++ b/src/Blazor.Diagrams.Core/Models/Base/Model.cs @@ -18,7 +18,7 @@ protected Model(string id) public string Id { get; } public bool Locked { get; set; } - public bool Enabled { get; set; } + public bool Enabled { get; set; } = true; public bool Visible { get => _visible; diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index 8a99cd869..25b58f889 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -22,7 +22,6 @@ public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnP Initialized = true, Position = new Point(110, 60), Size = new Size(10, 20), - Enabled = false, }); // Act @@ -455,7 +454,7 @@ public void Behavior_ShouldNotCreateLinkWithSinglePortAnchorSource_WhenMouseDown Initialized = true, Position = new Point(110, 60), Size = new Size(10, 20), - Enabled = true, + Enabled = false, }); // Act From 0d65a4317346da9758c632608eaa131575ab48f1 Mon Sep 17 00:00:00 2001 From: Shams Azam Date: Tue, 16 Jan 2024 12:29:00 +0530 Subject: [PATCH 060/159] Introduced cursor change based on allow panning. --- .../Components/DiagramCanvas.razor | 1 + .../Components/DiagramCanvas.razor.cs | 2 + src/Blazor.Diagrams/wwwroot/style.css | 2 - .../Components/DiagramCursorTests.cs | 57 +++++++++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor b/src/Blazor.Diagrams/Components/DiagramCanvas.razor index 769058441..a5ad2f36c 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor @@ -1,5 +1,6 @@ 
? _reference; private bool _shouldRender; + private string? Style; protected ElementReference elementReference; @@ -52,6 +53,7 @@ protected override void OnInitialized() _reference = DotNetObjectReference.Create(this); BlazorDiagram.Changed += OnDiagramChanged; + Style = BlazorDiagram.Options.AllowPanning ? "cursor: grab;" : "cursor: default;"; } protected override async Task OnAfterRenderAsync(bool firstRender) diff --git a/src/Blazor.Diagrams/wwwroot/style.css b/src/Blazor.Diagrams/wwwroot/style.css index 35c0aaa30..6079a2d2f 100644 --- a/src/Blazor.Diagrams/wwwroot/style.css +++ b/src/Blazor.Diagrams/wwwroot/style.css @@ -4,8 +4,6 @@ position: relative; outline: none; overflow: hidden; - cursor: -webkit-grab; - cursor: grab; touch-action: none; } diff --git a/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs b/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs new file mode 100644 index 000000000..774645e88 --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs @@ -0,0 +1,57 @@ +using AngleSharp.Css.Dom; +using AngleSharp.Dom; +using Blazor.Diagrams.Components; +using Blazor.Diagrams.Core; +using Blazor.Diagrams.Core.Geometry; +using Bunit; +using FluentAssertions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Behaviors +{ + public class DiagramCursorTests + { + [Fact] + public void Behavior_WhenPanningOptionIsAllowed_CursorShouldBeGrab() + { + // Arrange + using var ctx = new TestContext(); + var diagram = new BlazorDiagram(); + diagram.Options.AllowPanning = true; + ctx.JSInterop.Setup("ZBlazorDiagrams.getBoundingClientRect", _ => true); + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.BlazorDiagram, diagram)); + var diagramCanvas = cut.Find(".diagram-canvas"); + var canvasStyle = diagramCanvas.GetStyle().CssText; + + // Assert + canvasStyle.Should().Contain("cursor: grab"); + } + + [Fact] + public void Behavior_WhenPanningOptionIsNotAllowed_CursorShouldBeDefault() + { + // Arrange + using var ctx = new TestContext(); + var diagram = new BlazorDiagram(); + diagram.Options.AllowPanning = false; + ctx.JSInterop.Setup("ZBlazorDiagrams.getBoundingClientRect", _ => true); + + // Act + var cut = ctx.RenderComponent(parameters => parameters + .Add(n => n.BlazorDiagram, diagram)); + var diagramCanvas = cut.Find(".diagram-canvas"); + var canvasStyle = diagramCanvas.GetStyle().CssText; + + // Assert + canvasStyle.Should().Contain("cursor: default"); + } + } +} From 70eda6cf54ac7efdb360037e3137dbfbe1297949 Mon Sep 17 00:00:00 2001 From: Shams Azam Date: Wed, 17 Jan 2024 11:04:56 +0530 Subject: [PATCH 061/159] removed unnecessary usings from test class. --- .../Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs b/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs index 774645e88..09219cfa4 100644 --- a/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs +++ b/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs @@ -1,15 +1,8 @@ using AngleSharp.Css.Dom; -using AngleSharp.Dom; using Blazor.Diagrams.Components; -using Blazor.Diagrams.Core; using Blazor.Diagrams.Core.Geometry; using Bunit; using FluentAssertions; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Xunit; namespace Blazor.Diagrams.Core.Tests.Behaviors From 4f97ed2ac73b23e5b878428d98681b80c92f70da Mon Sep 17 00:00:00 2001 From: Shams Azam Date: Wed, 17 Jan 2024 18:08:26 +0530 Subject: [PATCH 062/159] added cursor: -webkit-grab. --- src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs | 2 +- tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index 6d084a5eb..a8eb0bb6b 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -53,7 +53,7 @@ protected override void OnInitialized() _reference = DotNetObjectReference.Create(this); BlazorDiagram.Changed += OnDiagramChanged; - Style = BlazorDiagram.Options.AllowPanning ? "cursor: grab;" : "cursor: default;"; + Style = BlazorDiagram.Options.AllowPanning ? "cursor: grab; cursor: -webkit-grab;" : "cursor: default;"; } protected override async Task OnAfterRenderAsync(bool firstRender) diff --git a/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs b/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs index 09219cfa4..ed6ad41e7 100644 --- a/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs +++ b/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs @@ -22,10 +22,9 @@ public void Behavior_WhenPanningOptionIsAllowed_CursorShouldBeGrab() var cut = ctx.RenderComponent(parameters => parameters .Add(n => n.BlazorDiagram, diagram)); var diagramCanvas = cut.Find(".diagram-canvas"); - var canvasStyle = diagramCanvas.GetStyle().CssText; // Assert - canvasStyle.Should().Contain("cursor: grab"); + diagramCanvas.ToMarkup().Should().Contain("cursor: grab; cursor: -webkit-grab;"); } [Fact] From 4e993c08793d4a306557dc42a1bbddd741a374ec Mon Sep 17 00:00:00 2001 From: Aarthi Narayanan Date: Wed, 17 Jan 2024 18:33:49 +0530 Subject: [PATCH 063/159] Removed commas --- .../Behaviors/DragNewLinkBehaviorTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs index 25b58f889..e5c746835 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -21,7 +21,7 @@ public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnP { Initialized = true, Position = new Point(110, 60), - Size = new Size(10, 20), + Size = new Size(10, 20) }); // Act @@ -454,7 +454,7 @@ public void Behavior_ShouldNotCreateLinkWithSinglePortAnchorSource_WhenMouseDown Initialized = true, Position = new Point(110, 60), Size = new Size(10, 20), - Enabled = false, + Enabled = false }); // Act From f1c37b48a1749482852585a1927e0643b6ef73bd Mon Sep 17 00:00:00 2001 From: Heather <30523814+Heathermcx@users.noreply.github.com> Date: Mon, 22 Jan 2024 17:17:47 +1100 Subject: [PATCH 064/159] Add GitHub action to create release (#18) Add a GitHub action that creates a release when a PR is merged to master. The release can then be downloaded and pushed to proget by one of our team members. --- .github/workflows/build.yml | 8 +-- .github/workflows/push.yml | 33 ---------- .github/workflows/release.yml | 61 ++++++++++++++++++ Directory.Build.props | 3 + PushNuget.ps1 | 26 ++++++++ WTGPublishPackageGuide.md | 12 ++++ build/Version.props | 5 ++ nuget.config | 6 ++ .../Blazor.Diagrams.Core.csproj | 16 ++--- src/Blazor.Diagrams.Core/Diagram.cs | 7 +- src/Blazor.Diagrams/Blazor.Diagrams.csproj | 27 +++++--- src/Blazor.Diagrams/sgKey.snk | Bin 0 -> 596 bytes .../Blazor.Diagrams.Core.Tests.csproj | 2 + .../Blazor.Diagrams.Tests.csproj | 5 +- 14 files changed, 151 insertions(+), 60 deletions(-) delete mode 100644 .github/workflows/push.yml create mode 100644 .github/workflows/release.yml create mode 100644 Directory.Build.props create mode 100644 PushNuget.ps1 create mode 100644 WTGPublishPackageGuide.md create mode 100644 build/Version.props create mode 100644 nuget.config create mode 100644 src/Blazor.Diagrams/sgKey.snk diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 98fc1e5c0..1accd03d5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,14 +34,10 @@ jobs: if: matrix.configuration == 'Debug' run: dotnet test --no-build - - name: Pack - if: matrix.configuration == 'Release' - run: dotnet pack --configuration ${{ matrix.configuration }} -o packages --no-build - - name: Upload packages if: matrix.configuration == 'Release' uses: actions/upload-artifact@v3 with: - name: packages - path: packages/ + name: package + path: /home/runner/work/Blazor.Diagrams/Blazor.Diagrams/src/Blazor.Diagrams/bin/Release/*.nupkg retention-days: 5 diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml deleted file mode 100644 index d15fccc35..000000000 --- a/.github/workflows/push.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: PushToNuget -env: - NUGET_DIR: '${{ github.workspace }}/nuget' - -on: - workflow_dispatch: - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - - name: Pack Blazor.Diagrams.Core - working-directory: src/Blazor.Diagrams.Core - run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}' - - - name: Pack Blazor.Diagrams - working-directory: src/Blazor.Diagrams - run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}' - - - name: Pack Blazor.Diagrams.Algorithms - working-directory: src/Blazor.Diagrams.Algorithms - run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}' - - - name: Push Blazor.Diagrams.Core - run: 'dotnet nuget push ${{ env.NUGET_DIR }}/Z.Blazor.Diagrams.Core.3.0.1.nupkg --api-key "${{ secrets.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate' - - - name: Push Blazor.Diagrams - run: 'dotnet nuget push ${{ env.NUGET_DIR }}/Z.Blazor.Diagrams.3.0.1.nupkg --api-key "${{ secrets.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate' - - - name: Push Blazor.Diagrams.Algorithms - run: 'dotnet nuget push ${{ env.NUGET_DIR }}/Z.Blazor.Diagrams.Algorithms.3.0.1.nupkg --api-key "${{ secrets.NUGET_API_KEY }}" --source https://api.nuget.org/v3/index.json --skip-duplicate' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..6ce136ca1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,61 @@ +name: Create release +# On push to master branch. i.e. when we merge a PR. +on: + push: + branches: [ master ] + workflow_dispatch: + +env: + PACKAGE_PATH: /home/runner/work/Blazor.Diagrams/Blazor.Diagrams/src/Blazor.Diagrams/bin/Release/*.nupkg + +jobs: + build: + name: Build - Release + + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - name: Setup dotnet + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 6.0.x + 3.1.x + + # Finds the latest release and increases the version + - name: Get next version + uses: reecetech/version-increment@2023.9.3 + id: version + with: + scheme: semver + increment: patch + + - name: Install version tool + run: dotnet tool install dotnetCampus.TagToVersion -g --version 1.0.11 + + # Writes the new version number to build/Version.props + - name: Set version + run: dotnet TagToVersion -t ${{ steps.version.outputs.version }} + + - name: Install dependencies + run: dotnet restore + + # Pacakge is created on build + - name: Build + run: dotnet build --configuration Release + + # Upload package as an atrifact to the GitHub action + - name: Upload packages + uses: actions/upload-artifact@v3 + with: + name: package + path: ${{ env.PACKAGE_PATH }} + retention-days: 5 + + # Create a new release and upload the package to the release + - name: Release + uses: softprops/action-gh-release@v1 + with: + files: ${{ env.PACKAGE_PATH }} + tag_name: ${{ steps.version.outputs.version }} diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 000000000..2c52bf207 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/PushNuget.ps1 b/PushNuget.ps1 new file mode 100644 index 000000000..eb16a480c --- /dev/null +++ b/PushNuget.ps1 @@ -0,0 +1,26 @@ +pushd $PSScriptRoot + +$feedSource = "http://proget.wtg.zone/nuget/WTG-Internal/" +$apiKey = "" +$packagePath = "*.nupkg" + +function Write-Log { + Write-Host "$(get-date -f "yyyy-MM-dd HH:mm:ss.fff")`t$args" +} + +try { + $scriptSw = [System.Diagnostics.Stopwatch]::StartNew() + & nuget.exe push $packagePath -ApiKey $apiKey -Source $feedSource -Verbosity detailed + if (-not $?) + { + Write-Log "FAILED to deploy: $packagePath)" + } + else + { + Write-Log "Deployed $packagePath )" + } +} +finally { + popd + Write-Log "Finished (took: $($scriptSw.Elapsed))" +} \ No newline at end of file diff --git a/WTGPublishPackageGuide.md b/WTGPublishPackageGuide.md new file mode 100644 index 000000000..462fc9c85 --- /dev/null +++ b/WTGPublishPackageGuide.md @@ -0,0 +1,12 @@ +# Publishing WTG.Z.Blazor.Diagrams for WTG usage +This doc explains how the package we use at WTG is updated. + +When a PR is merged to master, the GitHub Action 'Create release' should run. It can also be run manually if needed. This action creates a GitHub Release and uploads the package to the release. The version number is also handled by the action. The package is created on build in the action and should contain Blazor.Diagrams.dll, Blazor.Diagrams.Core.dll and SvgPathProperties.dll + +## To push package to proget +1. **Find the release**. Most likely the most recent release is the one to use. To find all releases, go to the repo main page then click 'Releases'. +2. **Download the package**. Expand the 'Assets' section of the release and download the *.nupkg file. +3. **Push to proget**. Modify the PushNuget.ps1 script so that it had the correct path to the downloaded *.nupkg file and the API key. Ask another team member if not sure. Once the script has all required information, run the script to push the package. +4. **Update package version in WTG**. In Directory.Packages.Props, update the version of WTG.Z.Blazor.Diagrams to match the package that was just uploaded to proget. + + diff --git a/build/Version.props b/build/Version.props new file mode 100644 index 000000000..2420175e6 --- /dev/null +++ b/build/Version.props @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/nuget.config b/nuget.config new file mode 100644 index 000000000..8d0531206 --- /dev/null +++ b/nuget.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index cb81f6203..163567b7d 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -3,20 +3,19 @@ net6.0 enable - true + false MIT - zHaytam + zHaytam, WiseTech Global A fully customizable and extensible all-purpose diagrams library for Blazor - 3.0.1 - 3.0.1 - https://github.com/Blazor-Diagrams/Blazor.Diagrams - 3.0.1 - Z.Blazor.Diagrams.Core + https://github.com/WiseTechGlobal/Blazor.Diagrams + WTG.Z.Blazor.Diagrams.Core blazor diagrams diagramming svg drag - Z.Blazor.Diagrams.Core + WTG.Z.Blazor.Diagrams.Core ZBD.png https://blazor-diagrams.zhaytam.com/ README.md + True + ..\Blazor.Diagrams\sgKey.snk @@ -32,6 +31,7 @@ + diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index 61b8b09f8..99e43bcdb 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -12,10 +12,9 @@ using Blazor.Diagrams.Core.Controls; using Blazor.Diagrams.Core.Behaviors.Base; -[assembly: InternalsVisibleTo("Blazor.Diagrams")] -[assembly: InternalsVisibleTo("Blazor.Diagrams.Tests")] -[assembly: InternalsVisibleTo("Blazor.Diagrams.Core.Tests")] - +[assembly: InternalsVisibleTo("Blazor.Diagrams, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b19ccf452d560c78a01faeff3ea2dd095ebc2b24abb6ce02394e44ecc5fad730037d475c0678cbfc201a727462866c8148fe30e0171816b7569e0d0e74f01d741cd84dfde651f0d817a74e1121566b66759566601eceaf504566c83a9c1fd9b574c48652f0e183919f951e5dd39085964a6bb4bb1edf3c15226acab7d73bf7cf")] +[assembly: InternalsVisibleTo("Blazor.Diagrams.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b19ccf452d560c78a01faeff3ea2dd095ebc2b24abb6ce02394e44ecc5fad730037d475c0678cbfc201a727462866c8148fe30e0171816b7569e0d0e74f01d741cd84dfde651f0d817a74e1121566b66759566601eceaf504566c83a9c1fd9b574c48652f0e183919f951e5dd39085964a6bb4bb1edf3c15226acab7d73bf7cf")] +[assembly: InternalsVisibleTo("Blazor.Diagrams.Core.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b19ccf452d560c78a01faeff3ea2dd095ebc2b24abb6ce02394e44ecc5fad730037d475c0678cbfc201a727462866c8148fe30e0171816b7569e0d0e74f01d741cd84dfde651f0d817a74e1121566b66759566601eceaf504566c83a9c1fd9b574c48652f0e183919f951e5dd39085964a6bb4bb1edf3c15226acab7d73bf7cf")] namespace Blazor.Diagrams.Core; public abstract class Diagram diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj index 3e806eb7f..c674d4a8a 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -3,29 +3,29 @@ net6.0 enable - zHaytam + zHaytam, WiseTech Global MIT - 3.0.1 - 3.0.1 - https://github.com/Blazor-Diagrams/Blazor.Diagrams + https://github.com/WiseTechGlobal/Blazor.Diagrams A fully customizable and extensible all-purpose diagrams library for Blazor - 3.0.1 true blazor diagrams diagramming svg drag - Z.Blazor.Diagrams + WTG.Z.Blazor.Diagrams https://blazor-diagrams.zhaytam.com/ - Z.Blazor.Diagrams + WTG.Z.Blazor.Diagrams ZBD.png README.md + True + sgKey.snk + - + @@ -50,4 +50,15 @@ + + $(TargetsForTfmSpecificBuildOutput);GetBinariesForPackage + + + + + + + + + diff --git a/src/Blazor.Diagrams/sgKey.snk b/src/Blazor.Diagrams/sgKey.snk new file mode 100644 index 0000000000000000000000000000000000000000..4ff3c2f7af87363f0b25898c10cc329400e87b78 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa500988oXQ;m1bZb&aY5KX2?36AKA5Z#D-Gv;e(N%l^$KwkcE~?YqYx_-#ir}YRb3QJNM7W zaJ9SUK??)9%#nnEKtmyn z1-du!oIu-It4P7jlG;D#C)XS4W6dIe^SPf%cz**qtU zKv%@yGJVNIzfO3VJ5)Bb)}foP%7W#=lW3E>H&xtNLqo00`-9Y8pQyy`O|8ifEd;lH z6|YD(us{fm_G>6z i@4=d*Tm9Mkw5g0pb1xCoGqhw~Od4{t{sVa5_I1G(6cEn< literal 0 HcmV?d00001 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 0e1f0d2ff..11c8ea2cb 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj +++ b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj @@ -4,6 +4,8 @@ net6.0 enable false + True + ..\..\src\Blazor.Diagrams\sgKey.snk diff --git a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj index dead3f03e..a2895c117 100644 --- a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj +++ b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj @@ -5,6 +5,8 @@ enable false true + True + ..\..\src\Blazor.Diagrams\sgKey.snk @@ -12,7 +14,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -25,6 +27,7 @@ + From b6d3af7fcc0d8a92921b3e1694724dfccf52ada0 Mon Sep 17 00:00:00 2001 From: Suraj Desai <143598462+suraj-desai-wtg@users.noreply.github.com> Date: Tue, 23 Jan 2024 03:50:46 +0530 Subject: [PATCH 065/159] add ControlsType.AlwaysOn for controls that should always be visible (#21) --- .../Documentation/Controls/Overview.razor | 1 + .../Controls/ControlsContainer.cs | 4 +++ .../Controls/ControlsType.cs | 3 +- .../Controls/ControlsContainerTests.cs | 28 +++++++++++++++++++ 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 tests/Blazor.Diagrams.Core.Tests/Controls/ControlsContainerTests.cs diff --git a/site/Site/Pages/Documentation/Controls/Overview.razor b/site/Site/Pages/Documentation/Controls/Overview.razor index 335838ae5..034a26374 100644 --- a/site/Site/Pages/Documentation/Controls/Overview.razor +++ b/site/Site/Pages/Documentation/Controls/Overview.razor @@ -40,6 +40,7 @@ // Initialize var node1Controls = Diagram.Controls.AddFor(Node1); // OnSelection default var node2Controls = Diagram.Controls.AddFor(Node2, ControlsType.OnHover); +var node3Controls = Diagram.Controls.AddFor(Node2, ControlsType.AlwaysOn); // Control always visible // Add node1Controls.Add(new SomeControl()); diff --git a/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs b/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs index 5ebc16b46..1a71d1d0d 100644 --- a/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs +++ b/src/Blazor.Diagrams.Core/Controls/ControlsContainer.cs @@ -16,6 +16,10 @@ public ControlsContainer(Model model, ControlsType type = ControlsType.OnSelecti { Model = model; Type = type; + if (type == ControlsType.AlwaysOn) + { + Visible = true; + } } public Model Model { get; } diff --git a/src/Blazor.Diagrams.Core/Controls/ControlsType.cs b/src/Blazor.Diagrams.Core/Controls/ControlsType.cs index 5997e72c9..641a79fdd 100644 --- a/src/Blazor.Diagrams.Core/Controls/ControlsType.cs +++ b/src/Blazor.Diagrams.Core/Controls/ControlsType.cs @@ -3,5 +3,6 @@ namespace Blazor.Diagrams.Core.Controls; public enum ControlsType { OnHover, - OnSelection + OnSelection, + AlwaysOn } \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Controls/ControlsContainerTests.cs b/tests/Blazor.Diagrams.Core.Tests/Controls/ControlsContainerTests.cs new file mode 100644 index 000000000..33291ca77 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Controls/ControlsContainerTests.cs @@ -0,0 +1,28 @@ +using Blazor.Diagrams.Core.Controls; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Models; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Controls +{ + public class ControlsContainerTests + { + [Fact] + public void AlwaysOnControlType_AlwaysVisible() + { + // Arrange + var diagram = new TestDiagram(); + var node = diagram.Nodes.Add(new NodeModel()); + var controls = diagram.Controls.AddFor(node, ControlsType.AlwaysOn); + + // Assert + Assert.True(controls.Visible); + + node.Selected = true; + Assert.True(controls.Visible); + + diagram.TriggerPointerEnter(node, new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + Assert.True(controls.Visible); + } + } +} From c2a1cb2c9cd0764e8a837409bf7a04514cd06737 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Tue, 23 Jan 2024 10:45:44 +1100 Subject: [PATCH 066/159] Fixed null --- .../Positions/Resizing/ResizerProvider.cs | 32 +++++++++++-------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs index 76259ceee..b63588b17 100644 --- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs @@ -9,13 +9,14 @@ public abstract class ResizerProvider : IPositionProvider { abstract public string? Class { get; } - private Size _originalSize = null!; - private Point _originalPosition = null!; + private Size? _originalSize = null; + private Point? _originalPosition = null; private double? _lastClientX; private double? _lastClientY; - private NodeModel _nodeModel = null!; + private NodeModel? _nodeModel = null; private double _totalMovedX = 0; private double _totalMovedY = 0; + private Diagram? _diagram; abstract public double WidthOffset { get; } abstract public double HeightOffset { get; } @@ -40,13 +41,13 @@ virtual public void ResizeNode(double deltaX, double deltaY) _totalMovedX += deltaX; _totalMovedY += deltaY; - var width = _originalSize.Width + (ShouldAddTotalMovedX ? _totalMovedX : -_totalMovedX); - var height = _originalSize.Height + (ShouldAddTotalMovedY ? _totalMovedY : -_totalMovedY); + var width = _originalSize!.Width + (ShouldAddTotalMovedX ? _totalMovedX : -_totalMovedX) / _diagram!.Zoom; + var height = _originalSize.Height + (ShouldAddTotalMovedY ? _totalMovedY : -_totalMovedY) / _diagram!.Zoom; - var positionX = _originalPosition.X + (ShouldChangeXPositionOnResize ? _totalMovedX : 0); - var positionY = _originalPosition.Y + (ShouldChangeYPositionOnResize ? _totalMovedY : 0); + var positionX = _originalPosition!.X + (ShouldChangeXPositionOnResize ? _totalMovedX : 0) / _diagram!.Zoom; + var positionY = _originalPosition.Y + (ShouldChangeYPositionOnResize ? _totalMovedY : 0) / _diagram!.Zoom; - if (width < _nodeModel.MinimumDimensions.Width) + if (width < _nodeModel!.MinimumDimensions.Width) { width = _nodeModel.MinimumDimensions.Width; positionX = _nodeModel.Position.X; @@ -68,14 +69,18 @@ virtual public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs _lastClientX = e.ClientX; _lastClientY = e.ClientY; _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); - _originalSize = nodeModel.Size!; + _originalSize = nodeModel.Size; _nodeModel = nodeModel; + _diagram = diagram; } } virtual public void OnPointerMove(Model? model, PointerEventArgs e) { - if (_nodeModel is null) return; + if (_originalSize is null || _originalPosition is null || _nodeModel is null || _diagram is null) + { + return; + } var deltaX = (e.ClientX - _lastClientX!.Value); var deltaY = (e.ClientY - _lastClientY!.Value); @@ -96,13 +101,14 @@ virtual public void OnPanChanged(double deltaX, double deltaY) virtual public void OnResizeEnd(Model? model, PointerEventArgs args) { _nodeModel?.TriggerSizeChanged(); - _originalSize = null!; - _originalPosition = null!; - _nodeModel = null!; + _originalSize = null; + _originalPosition = null; + _nodeModel = null; _totalMovedX = 0; _totalMovedY = 0; _lastClientX = null; _lastClientY = null; + _diagram = null; } } } From 0086686531cd6dc8ee47433acb932c9027618b29 Mon Sep 17 00:00:00 2001 From: Renan Alvarenga Date: Tue, 23 Jan 2024 13:41:42 +1100 Subject: [PATCH 067/159] Addressed PR comments --- site/Site/Pages/Documentation/Controls/Overview.razor | 2 +- site/Site/Pages/Documentation/Diagram/Api.razor | 2 +- .../Positions/Resizing/ResizerProvider.cs | 6 ++++++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/site/Site/Pages/Documentation/Controls/Overview.razor b/site/Site/Pages/Documentation/Controls/Overview.razor index 335838ae5..96ad07b25 100644 --- a/site/Site/Pages/Documentation/Controls/Overview.razor +++ b/site/Site/Pages/Documentation/Controls/Overview.razor @@ -140,7 +140,7 @@ Diagram.Controls.AddFor(SomeModel)

The ResizeControl adds a resizer which is a box that when dragged, can resize the node. The resizer position and movement of the node is controlled using a Resizer Provider.
- There are four ResizerProviders, one for each corner. Custom resizing behavior can be created by implementing IResizerProvider. + There are four ResizerProviders, one for each corner. Custom resizing behavior can be created by inheriting and overriding ResizerProvider.


diff --git a/site/Site/Pages/Documentation/Diagram/Api.razor b/site/Site/Pages/Documentation/Diagram/Api.razor
index af548a11f..a03a6a4d4 100644
--- a/site/Site/Pages/Documentation/Diagram/Api.razor
+++ b/site/Site/Pages/Documentation/Diagram/Api.razor
@@ -148,7 +148,7 @@
         
         
             PanChanged
-            Action?
+            Action<double, double>?
             
         
         
diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
index b63588b17..0ed47ee3f 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
@@ -18,13 +18,19 @@ public abstract class ResizerProvider : IPositionProvider
         private double _totalMovedY = 0;
         private Diagram? _diagram;
 
+        /// Controls the X-Axis position of the resizer control in relation to the node outline 
         abstract public double WidthOffset { get; }
+        /// Controls the Y-Axis position of the resizer control in relation to the node outline 
         abstract public double HeightOffset { get; }
+        /// Factors in the width of the node when calculating the position of the resizer control 
         abstract public bool ShouldUseWidth { get; }
+        /// Factors in the height of the node when calculating the position of the resizer control 
         abstract public bool ShouldUseHeight { get; }
         abstract public bool ShouldChangeXPositionOnResize { get; }
         abstract public bool ShouldChangeYPositionOnResize { get; }
+        /// Controls whether the width should be modified on Node resizing 
         abstract public bool ShouldAddTotalMovedX { get; }
+        /// Controls whether the height should be modified on Node resizing 
         abstract public bool ShouldAddTotalMovedY { get; }
 
         virtual public Point? GetPosition(Model model)

From fc7f05934f50eac4675dfdc8ead35654b7d81367 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Tue, 23 Jan 2024 14:08:05 +1100
Subject: [PATCH 068/159] Fixed using statements

---
 .../Resizing/BottomLeftResizerProvider.cs     | 20 +++++++++++++------
 .../Resizing/BottomRightResizerProvider.cs    | 19 +++++++++++++-----
 .../Positions/Resizing/ResizerProvider.cs     | 17 +---------------
 .../Resizing/TopLeftResizerProvider.cs        | 20 +++++++++++++------
 .../Resizing/TopRightResizerProvider.cs       | 19 +++++++++++++-----
 .../Behaviors/DragNewLinkBehaviorTests.cs     |  9 ++++++++-
 .../Controls/ResizeControlTests.cs            | 15 ++++++++++----
 .../BottomLeftResizerProviderTests.cs         | 11 +++++++++-
 .../BottomRightResizerProviderTests.cs        | 11 +++++++++-
 .../Resizing/TopLeftResizerProviderTests.cs   | 11 +++++++++-
 .../Resizing/TopRightResizerProviderTests.cs  | 11 +++++++++-
 11 files changed, 116 insertions(+), 47 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs
index e5d4f48ae..6b6cc3dc5 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs
@@ -1,17 +1,25 @@
-namespace Blazor.Diagrams.Core.Positions.Resizing
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Models.Base;
+
+namespace Blazor.Diagrams.Core.Positions.Resizing
 {
     public class BottomLeftResizerProvider : ResizerProvider
     {
         override public string? Class => "bottomleft";
-
-        override public double HeightOffset => 5;
-        override public double WidthOffset => -5;
-        override public bool ShouldUseWidth => false;
-        override public bool ShouldUseHeight => true;
         override public bool ShouldChangeXPositionOnResize => true;
         override public bool ShouldChangeYPositionOnResize => false;
         override public bool ShouldAddTotalMovedX => false;
         override public bool ShouldAddTotalMovedY => true;
 
+        public override Point? GetPosition(Model model)
+        {
+            if (model is NodeModel nodeModel && nodeModel.Size is not null)
+            {
+                return new Point(nodeModel.Position.X - 5, nodeModel.Position.Y + nodeModel.Size.Height + 5);
+            }
+            return null;
+        }
+
     }
 }
diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs
index 05a985621..394b882bb 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs
@@ -1,16 +1,25 @@
-namespace Blazor.Diagrams.Core.Positions.Resizing
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Models.Base;
+
+namespace Blazor.Diagrams.Core.Positions.Resizing
 {
     public class BottomRightResizerProvider : ResizerProvider
     {
         override public string? Class => "bottomright";
-        override public double HeightOffset => 5;
-        override public double WidthOffset => 5;
-        override public bool ShouldUseWidth => true;
-        override public bool ShouldUseHeight => true;
         override public bool ShouldChangeXPositionOnResize => false;
         override public bool ShouldChangeYPositionOnResize => false;
         override public bool ShouldAddTotalMovedX => true;
         override public bool ShouldAddTotalMovedY => true;
 
+        public override Point? GetPosition(Model model)
+        {
+            if (model is NodeModel nodeModel && nodeModel.Size is not null)
+            {
+                return new Point(nodeModel.Position.X + nodeModel.Size.Width + 5, nodeModel.Position.Y + nodeModel.Size.Height + 5);
+            }
+            return null;
+        }
+
     }
 }
diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
index 0ed47ee3f..589587b05 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
@@ -18,14 +18,6 @@ public abstract class ResizerProvider : IPositionProvider
         private double _totalMovedY = 0;
         private Diagram? _diagram;
 
-        /// Controls the X-Axis position of the resizer control in relation to the node outline 
-        abstract public double WidthOffset { get; }
-        /// Controls the Y-Axis position of the resizer control in relation to the node outline 
-        abstract public double HeightOffset { get; }
-        /// Factors in the width of the node when calculating the position of the resizer control 
-        abstract public bool ShouldUseWidth { get; }
-        /// Factors in the height of the node when calculating the position of the resizer control 
-        abstract public bool ShouldUseHeight { get; }
         abstract public bool ShouldChangeXPositionOnResize { get; }
         abstract public bool ShouldChangeYPositionOnResize { get; }
         /// Controls whether the width should be modified on Node resizing 
@@ -33,14 +25,7 @@ public abstract class ResizerProvider : IPositionProvider
         /// Controls whether the height should be modified on Node resizing 
         abstract public bool ShouldAddTotalMovedY { get; }
 
-        virtual public Point? GetPosition(Model model)
-        {
-            if (model is NodeModel nodeModel && nodeModel.Size is not null)
-            {
-                return new Point(nodeModel.Position.X + (ShouldUseWidth ? nodeModel.Size.Width : 0) + WidthOffset, nodeModel.Position.Y + (ShouldUseHeight ? nodeModel.Size.Height : 0) + HeightOffset);
-            }
-            return null;
-        }
+        abstract public Point? GetPosition(Model model);
 
         virtual public void ResizeNode(double deltaX, double deltaY)
         {
diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs
index eb787029f..24ccaa132 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs
@@ -1,17 +1,25 @@
-namespace Blazor.Diagrams.Core.Positions.Resizing
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Models.Base;
+
+namespace Blazor.Diagrams.Core.Positions.Resizing
 {
     public class TopLeftResizerProvider : ResizerProvider
     {
         override public string? Class => "topleft";
-
-        override public double HeightOffset => -5;
-        override public double WidthOffset => -5;
-        override public bool ShouldUseWidth => false;
-        override public bool ShouldUseHeight => false;
         override public bool ShouldChangeXPositionOnResize => true;
         override public bool ShouldChangeYPositionOnResize => true;
         override public bool ShouldAddTotalMovedX => false;
         override public bool ShouldAddTotalMovedY => false;
 
+        public override Point? GetPosition(Model model)
+        {
+            if (model is NodeModel nodeModel && nodeModel.Size is not null)
+            {
+                return new Point(nodeModel.Position.X - 5, nodeModel.Position.Y - 5);
+            }
+            return null;
+        }
+
     }
 }
diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs
index b3be45dd1..72fd2a525 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs
@@ -1,16 +1,25 @@
-namespace Blazor.Diagrams.Core.Positions.Resizing
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Models.Base;
+
+namespace Blazor.Diagrams.Core.Positions.Resizing
 {
     public class TopRightResizerProvider : ResizerProvider
     {
         override public string? Class => "topright";
-        override public double HeightOffset => -5;
-        override public double WidthOffset => 5;
-        override public bool ShouldUseWidth => true;
-        override public bool ShouldUseHeight => false;
         override public bool ShouldChangeXPositionOnResize => false;
         override public bool ShouldChangeYPositionOnResize => true;
         override public bool ShouldAddTotalMovedX => true;
         override public bool ShouldAddTotalMovedY => false;
 
+        public override Point? GetPosition(Model model)
+        {
+            if (model is NodeModel nodeModel && nodeModel.Size is not null)
+            {
+                return new Point(nodeModel.Position.X + nodeModel.Size.Width + 5, nodeModel.Position.Y - 5);
+            }
+            return null;
+        }
+
     }
 }
\ No newline at end of file
diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
index ad40df212..d545c864a 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
@@ -1,4 +1,11 @@
-using System.Linq;
+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 FluentAssertions;
+using System.Linq;
+using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests.Behaviors;
 
diff --git a/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs
index c9f1b427b..543ecb6f0 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs
@@ -1,4 +1,11 @@
-namespace Blazor.Diagrams.Core.Tests.Controls;
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Models.Base;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using Moq;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Controls;
 
 public class ResizeControlTests
 {
@@ -17,7 +24,7 @@ public void GetPosition_ShouldUseResizeProviderGetPosition()
     [Fact]
     public void OnPointerDown_ShouldInvokeResizeStart()
     {
-        var resizeProvider = new Mock();
+        var resizeProvider = new Mock();
         var control = new ResizeControl(resizeProvider.Object);
         var diagram = Mock.Of();
         var model = Mock.Of();
@@ -31,7 +38,7 @@ public void OnPointerDown_ShouldInvokeResizeStart()
     [Fact]
     public void OnPointerDown_ShouldAddEventHandlers()
     {
-        var resizeProvider = new Mock();
+        var resizeProvider = new Mock();
         var control = new ResizeControl(resizeProvider.Object);
         var diagram = new TestDiagram();
         var model = Mock.Of();
@@ -49,7 +56,7 @@ public void OnPointerDown_ShouldAddEventHandlers()
     [Fact]
     public void OnPointerUp_ShouldRemoveEventHandlers()
     {
-        var resizeProvider = new Mock();
+        var resizeProvider = new Mock();
         var control = new ResizeControl(resizeProvider.Object);
         var diagram = new TestDiagram();
         var model = Mock.Of();
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
index 992bd1529..251795526 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
@@ -1,4 +1,13 @@
-namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
+using Blazor.Diagrams.Core.Behaviors;
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using FluentAssertions;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
 
 public class BottomLeftResizerProviderTests
 {
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs
index 0c2bc25f6..62ad0270f 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs
@@ -1,4 +1,13 @@
-namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
+using Blazor.Diagrams.Core.Behaviors;
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using FluentAssertions;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
 
 public class BottomRightResizerProviderTests
 {
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
index 0c33d9dc9..75de6d326 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
@@ -1,4 +1,13 @@
-namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
+using Blazor.Diagrams.Core.Behaviors;
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using FluentAssertions;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
 
 public class TopLeftResizerProviderTests
 {
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
index 45c92ca1d..5071a423d 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
@@ -1,4 +1,13 @@
-namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
+using Blazor.Diagrams.Core.Behaviors;
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Events;
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using FluentAssertions;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
 
 public class TopRightResizerProviderTests
 {

From 7e33922d26c87c8c4e7c57b0810be5be7f38a460 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Tue, 23 Jan 2024 14:10:51 +1100
Subject: [PATCH 069/159] Fixed more usings

---
 .../Components/Controls/ResizeControlWidgetTests.cs      | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs
index 5dbd04571..ee4aa80a1 100644
--- a/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs
+++ b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs
@@ -1,4 +1,11 @@
-namespace Blazor.Diagrams.Tests.Components.Controls;
+using Blazor.Diagrams.Components.Controls;
+using Blazor.Diagrams.Core.Controls.Default;
+using Blazor.Diagrams.Core.Positions.Resizing;
+using Bunit;
+using Moq;
+using Xunit;
+
+namespace Blazor.Diagrams.Tests.Components.Controls;
 
 public class ResizeControlWidgetTests
 {

From de5b50f12fb7fa4590a649558c8c0aadf948c55c Mon Sep 17 00:00:00 2001
From: Suraj 
Date: Tue, 30 Jan 2024 10:30:06 +0530
Subject: [PATCH 070/159] fix css for resizers

---
 src/Blazor.Diagrams/wwwroot/default.styles.css     | 1 +
 src/Blazor.Diagrams/wwwroot/default.styles.min.css | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.css b/src/Blazor.Diagrams/wwwroot/default.styles.css
index 2b355029a..0e37330c2 100644
--- a/src/Blazor.Diagrams/wwwroot/default.styles.css
+++ b/src/Blazor.Diagrams/wwwroot/default.styles.css
@@ -133,6 +133,7 @@ g.diagram-group.default.selected > rect {
     background-color: #f5f5f5;
     border: 1px solid #6e9fd4;
     position: absolute;
+    transform: translate(-2.5px, -2.5px);
 }
 
 .default-node-resizer.bottomright {
diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css b/src/Blazor.Diagrams/wwwroot/default.styles.min.css
index e794e823e..781fb0aed 100644
--- a/src/Blazor.Diagrams/wwwroot/default.styles.min.css
+++ b/src/Blazor.Diagrams/wwwroot/default.styles.min.css
@@ -1 +1 @@
-.default-node{width:100px;height:80px;border-radius:10px;background-color:#f5f5f5;border:1px solid #e8e8e8;-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.default-node.selected{border:1px solid #6e9fd4;}.default-node.selected .diagram-port{border:1px solid #6e9fd4;}.default-node .diagram-port,.default.diagram-group .diagram-port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .diagram-port:hover,.default-node .diagram-port.has-links,.default.diagram-group .diagram-port.has-links{background-color:#000;}.default-node .diagram-port.bottom,.default.diagram-group .diagram-port.bottom{bottom:0;left:50%;}.default-node .diagram-port.bottomleft,.default.diagram-group .diagram-port.bottomleft{bottom:0;left:0;}.default-node .diagram-port.bottomright,.default.diagram-group .diagram-port.bottomright{bottom:0;right:0;}.default-node .diagram-port.top,.default.diagram-group .diagram-port.top{top:0;left:50%;}.default-node .diagram-port.topleft,.default.diagram-group .diagram-port.topleft{top:0;left:0;}.default-node .diagram-port.topright,.default.diagram-group .diagram-port.topright{top:0;right:0;}.default-node .diagram-port.left,.default.diagram-group .diagram-port.left{left:0;top:50%;}.default-node .diagram-port.right,.default.diagram-group .diagram-port.right{right:0;top:50%;}.diagram-navigator.default{position:absolute;bottom:10px;right:10px;border:3px solid #9ba8b0;border-radius:15px;padding:20px;background-color:#fff;}div.diagram-group.default{outline:2px solid #000;background:#c6c6c6;}div.diagram-group.default.selected{outline:2px solid #6e9fd4;}g.diagram-group.default rect{outline:2px solid #000;fill:#c6c632;}g.diagram-group.default.selected>rect{outline:2px solid #008000;}.diagram-link div.default-link-label{display:inline-block;color:#fff;background-color:#6e9fd4;border-radius:.25rem;padding:.25rem;text-align:center;font-size:.875rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:3rem;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);}.default-node-resizer{width:5px;height:5px;background-color:#f5f5f5;border:1px solid #6e9fd4;position:absolute;}.default-node-resizer.bottomright{cursor:nwse-resize;}.default-node-resizer.topright{cursor:nesw-resize;}.default-node-resizer.bottomleft{cursor:nesw-resize;}.default-node-resizer.topleft{cursor:nwse-resize;}
\ No newline at end of file
+.default-node{width:100px;height:80px;border-radius:10px;background-color:#f5f5f5;border:1px solid #e8e8e8;-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.default-node.selected{border:1px solid #6e9fd4;}.default-node.selected .diagram-port{border:1px solid #6e9fd4;}.default-node .diagram-port,.default.diagram-group .diagram-port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .diagram-port:hover,.default-node .diagram-port.has-links,.default.diagram-group .diagram-port.has-links{background-color:#000;}.default-node .diagram-port.bottom,.default.diagram-group .diagram-port.bottom{bottom:0;left:50%;}.default-node .diagram-port.bottomleft,.default.diagram-group .diagram-port.bottomleft{bottom:0;left:0;}.default-node .diagram-port.bottomright,.default.diagram-group .diagram-port.bottomright{bottom:0;right:0;}.default-node .diagram-port.top,.default.diagram-group .diagram-port.top{top:0;left:50%;}.default-node .diagram-port.topleft,.default.diagram-group .diagram-port.topleft{top:0;left:0;}.default-node .diagram-port.topright,.default.diagram-group .diagram-port.topright{top:0;right:0;}.default-node .diagram-port.left,.default.diagram-group .diagram-port.left{left:0;top:50%;}.default-node .diagram-port.right,.default.diagram-group .diagram-port.right{right:0;top:50%;}.diagram-navigator.default{position:absolute;bottom:10px;right:10px;border:3px solid #9ba8b0;border-radius:15px;padding:20px;background-color:#fff;}div.diagram-group.default{outline:2px solid #000;background:#c6c6c6;}div.diagram-group.default.selected{outline:2px solid #6e9fd4;}g.diagram-group.default rect{outline:2px solid #000;fill:#c6c632;}g.diagram-group.default.selected>rect{outline:2px solid #008000;}.diagram-link div.default-link-label{display:inline-block;color:#fff;background-color:#6e9fd4;border-radius:.25rem;padding:.25rem;text-align:center;font-size:.875rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:3rem;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);}.default-node-resizer{width:5px;height:5px;background-color:#f5f5f5;border:1px solid #6e9fd4;position:absolute;transform:translate(-2.5px,-2.5px);}.default-node-resizer.bottomright{cursor:nwse-resize;}.default-node-resizer.topright{cursor:nesw-resize;}.default-node-resizer.bottomleft{cursor:nesw-resize;}.default-node-resizer.topleft{cursor:nwse-resize;}
\ No newline at end of file

From 232e9a07628e6c19c156e6b020f1dae53172ba5c Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Wed, 14 Feb 2024 14:55:06 +1100
Subject: [PATCH 071/159] Issue fixed

---
 src/Blazor.Diagrams.Core/Models/NodeModel.cs | 17 ++++++++++++++++-
 1 file changed, 16 insertions(+), 1 deletion(-)

diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
index 610a5aac3..77d1a8489 100644
--- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs
+++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
@@ -104,10 +104,12 @@ public override void SetPosition(double x, double y)
     public void SetSize(double width, double height)
     {
         var newSize = new Size(width, height);
-        if (newSize.Equals(_size) == true)
+        if (newSize.Equals(_size) == true || Size == null)
             return;
+        var oldSize = Size;
 
         Size = newSize;
+        UpdatePortPositions(oldSize.Width, oldSize.Height, newSize.Width, newSize.Height);
         Refresh();
         RefreshLinks();
         SizeChanging?.Invoke(this);
@@ -166,6 +168,19 @@ private void UpdatePortPositions(double deltaX, double deltaY)
         }
     }
 
+    private void UpdatePortPositions(double oldWidth, double oldHeight, double newWidth, double newHeight)
+    {
+        foreach (var port in _ports)
+        {
+            double relativeX = port.Position.X - (oldWidth);
+            double relativeY = port.Position.Y - (oldHeight / 2);
+
+            port.Position = new Point(port.Alignment == PortAlignment.Left ? port.Position.X : relativeX + (newWidth), relativeY + (newHeight / 2));
+
+            port.RefreshLinks();
+        }
+    }
+
     protected void TriggerMoving()
     {
         Moving?.Invoke(this);

From 9a3a2866ff8b6260104647616094dabcaa11a716 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Thu, 15 Feb 2024 15:19:35 +1100
Subject: [PATCH 072/159] Fixed

---
 src/Blazor.Diagrams.Core/Models/NodeModel.cs | 34 ++++++++++++++++----
 1 file changed, 28 insertions(+), 6 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
index 77d1a8489..f2ab8adf9 100644
--- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs
+++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
@@ -106,7 +106,7 @@ public void SetSize(double width, double height)
         var newSize = new Size(width, height);
         if (newSize.Equals(_size) == true || Size == null)
             return;
-        var oldSize = Size;
+        var oldSize = new Size(Size.Width, Size.Height);
 
         Size = newSize;
         UpdatePortPositions(oldSize.Width, oldSize.Height, newSize.Width, newSize.Height);
@@ -172,11 +172,33 @@ private void UpdatePortPositions(double oldWidth, double oldHeight, double newWi
     {
         foreach (var port in _ports)
         {
-            double relativeX = port.Position.X - (oldWidth);
-            double relativeY = port.Position.Y - (oldHeight / 2);
-
-            port.Position = new Point(port.Alignment == PortAlignment.Left ? port.Position.X : relativeX + (newWidth), relativeY + (newHeight / 2));
-
+            switch (port.Alignment)
+            {
+                case PortAlignment.Top:
+                    port.Position = new Point(port.Position.X - (oldWidth / 2) + (newWidth / 2), Position.Y);
+                    break;
+                case PortAlignment.TopRight:
+                    port.Position = new Point(port.Position.X - (oldWidth) + (newWidth), port.Position.Y);
+                    break;
+                case PortAlignment.TopLeft:
+                    port.Position = new Point(port.Position.X, port.Position.Y);
+                    break;
+                case PortAlignment.Right:
+                    port.Position = new Point(port.Position.X - (oldWidth) + (newWidth), port.Position.Y - (oldHeight / 2) + (newHeight / 2));
+                    break;
+                case PortAlignment.Left:
+                    port.Position = new Point(port.Position.X, port.Position.Y - (oldHeight / 2) + (newHeight / 2));
+                    break;
+                case PortAlignment.Bottom:
+                    port.Position = new Point(port.Position.X - (oldWidth / 2) + (newWidth / 2), port.Position.Y - (oldHeight) + (newHeight));
+                    break;
+                case PortAlignment.BottomRight:
+                    port.Position = new Point(port.Position.X - (oldWidth) + (newWidth), port.Position.Y - (oldHeight) + (newHeight));
+                    break;
+                case PortAlignment.BottomLeft:
+                    port.Position = new Point(port.Position.X, port.Position.Y - (oldHeight) + (newHeight));
+                    break;
+            }
             port.RefreshLinks();
         }
     }

From 74af42bba2ababbe03f5adca3e514b8ef1ebf098 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Fri, 16 Feb 2024 10:32:05 +1100
Subject: [PATCH 073/159] Added port Tests

---
 src/Blazor.Diagrams.Core/Models/NodeModel.cs  | 17 ++--
 .../Positions/Ports/PortTests.cs              | 89 +++++++++++++++++++
 2 files changed, 99 insertions(+), 7 deletions(-)
 create mode 100644 tests/Blazor.Diagrams.Core.Tests/Positions/Ports/PortTests.cs

diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
index f2ab8adf9..0fd0bcba6 100644
--- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs
+++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
@@ -170,33 +170,36 @@ private void UpdatePortPositions(double deltaX, double deltaY)
 
     private void UpdatePortPositions(double oldWidth, double oldHeight, double newWidth, double newHeight)
     {
+        var deltaX = newWidth - oldWidth;
+        var deltaY = newHeight - oldHeight;
+
         foreach (var port in _ports)
         {
             switch (port.Alignment)
             {
                 case PortAlignment.Top:
-                    port.Position = new Point(port.Position.X - (oldWidth / 2) + (newWidth / 2), Position.Y);
+                    port.Position = new Point(port.Position.X + deltaX / 2, port.Position.Y);
                     break;
                 case PortAlignment.TopRight:
-                    port.Position = new Point(port.Position.X - (oldWidth) + (newWidth), port.Position.Y);
+                    port.Position = new Point(port.Position.X + deltaX, port.Position.Y);
                     break;
                 case PortAlignment.TopLeft:
                     port.Position = new Point(port.Position.X, port.Position.Y);
                     break;
                 case PortAlignment.Right:
-                    port.Position = new Point(port.Position.X - (oldWidth) + (newWidth), port.Position.Y - (oldHeight / 2) + (newHeight / 2));
+                    port.Position = new Point(port.Position.X + deltaX, port.Position.Y + deltaY / 2);
                     break;
                 case PortAlignment.Left:
-                    port.Position = new Point(port.Position.X, port.Position.Y - (oldHeight / 2) + (newHeight / 2));
+                    port.Position = new Point(port.Position.X, port.Position.Y + deltaY / 2);
                     break;
                 case PortAlignment.Bottom:
-                    port.Position = new Point(port.Position.X - (oldWidth / 2) + (newWidth / 2), port.Position.Y - (oldHeight) + (newHeight));
+                    port.Position = new Point(port.Position.X + deltaX / 2, port.Position.Y + deltaY);
                     break;
                 case PortAlignment.BottomRight:
-                    port.Position = new Point(port.Position.X - (oldWidth) + (newWidth), port.Position.Y - (oldHeight) + (newHeight));
+                    port.Position = new Point(port.Position.X + deltaX, port.Position.Y + deltaY);
                     break;
                 case PortAlignment.BottomLeft:
-                    port.Position = new Point(port.Position.X, port.Position.Y - (oldHeight) + (newHeight));
+                    port.Position = new Point(port.Position.X, port.Position.Y + deltaY);
                     break;
             }
             port.RefreshLinks();
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Ports/PortTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Ports/PortTests.cs
new file mode 100644
index 000000000..5919599bd
--- /dev/null
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Ports/PortTests.cs
@@ -0,0 +1,89 @@
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Positions.Ports
+{
+    public class PortTests
+    {
+        [Theory]
+        [InlineData(PortAlignment.Top)]
+        [InlineData(PortAlignment.TopLeft)]
+        [InlineData(PortAlignment.TopRight)]
+        [InlineData(PortAlignment.Bottom)]
+        [InlineData(PortAlignment.BottomLeft)]
+        [InlineData(PortAlignment.BottomRight)]
+        [InlineData(PortAlignment.Left)]
+        [InlineData(PortAlignment.Right)]
+        public void UpdatePortOnSetPosition(PortAlignment alignment)
+        {
+            //Arrange
+            var diagram = new TestDiagram();
+            diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+            var node = new NodeModel(position: new Point(100, 100));
+            node.Size = new Size(100, 100);
+
+            var port = node.AddPort(alignment);
+
+            var newX = 200;
+            var newY = 300;
+
+            var deltaX = newX - node.Position.X;
+            var deltaY = newY - node.Position.Y;
+
+            var expected = new Point(port.Position.X + deltaX, port.Position.Y + deltaY);
+
+            //Act
+            node.SetPosition(newX, newY);
+
+            //Assert
+            Assert.Equal(expected.X, port.Position.X);
+            Assert.Equal(expected.Y, port.Position.Y);
+        }
+
+        [Theory]
+        [InlineData(PortAlignment.Top)]
+        [InlineData(PortAlignment.TopLeft)]
+        [InlineData(PortAlignment.TopRight)]
+        [InlineData(PortAlignment.Bottom)]
+        [InlineData(PortAlignment.BottomLeft)]
+        [InlineData(PortAlignment.BottomRight)]
+        [InlineData(PortAlignment.Left)]
+        [InlineData(PortAlignment.Right)]
+        public void UpdatePortOnSetSize(PortAlignment alignment)
+        {
+            // Arrange
+            var oldWidth = 100.0;
+            var oldHeight = 100.0;
+            var newWidth = 500.0;
+            var newHeight = 700.0;
+            var node = new NodeModel(position: new Point(100, 100)) { Size = new Size(oldWidth, oldHeight) };
+            var port = node.AddPort(alignment);
+
+            var deltaX = newWidth - oldWidth;
+            var deltaY = newHeight - oldHeight;
+
+            Point expected = alignment switch
+            {
+                PortAlignment.Top => new Point(port.Position.X + deltaX / 2, port.Position.Y),
+                PortAlignment.TopRight => new Point(port.Position.X + deltaX, port.Position.Y),
+                PortAlignment.TopLeft => new Point(port.Position.X, port.Position.Y),
+                PortAlignment.Right => new Point(port.Position.X + deltaX, port.Position.Y + deltaY / 2),
+                PortAlignment.Left => new Point(port.Position.X, port.Position.Y + deltaY / 2),
+                PortAlignment.Bottom => new Point(port.Position.X + deltaX / 2, port.Position.Y + deltaY),
+                PortAlignment.BottomRight => new Point(port.Position.X + deltaX, port.Position.Y + deltaY),
+                PortAlignment.BottomLeft => new Point(port.Position.X, port.Position.Y + deltaY),
+                _ => new Point(port.Position.X, port.Position.Y)
+            };
+
+            // Act
+            node.SetSize(newWidth, newHeight);
+
+            // Assert
+            Assert.Equal(expected.X, port.Position.X);
+            Assert.Equal(expected.Y, port.Position.Y);
+        }
+
+
+    }
+}

From 7710b4d9d95f6842052642dc5dcd031dcc72ccac Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Fri, 16 Feb 2024 10:50:08 +1100
Subject: [PATCH 074/159] Fixed PR comment

---
 .../Positions/Resizing/ResizerProvider.cs                     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
index 589587b05..e720715dc 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
@@ -20,9 +20,9 @@ public abstract class ResizerProvider : IPositionProvider
 
         abstract public bool ShouldChangeXPositionOnResize { get; }
         abstract public bool ShouldChangeYPositionOnResize { get; }
-        /// Controls whether the width should be modified on Node resizing 
+        /// Controls whether the totalMovedX should be added or subtracted 
         abstract public bool ShouldAddTotalMovedX { get; }
-        /// Controls whether the height should be modified on Node resizing 
+        /// Controls whether the totalMovedY should be added or subtracted 
         abstract public bool ShouldAddTotalMovedY { get; }
 
         abstract public Point? GetPosition(Model model);

From 3fd27365f9a008c735128afbfe41751d1ffa5e1b Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Tue, 20 Feb 2024 10:21:35 +1100
Subject: [PATCH 075/159] Fixed some PR comments

---
 src/Blazor.Diagrams.Core/Models/NodeModel.cs           |  6 ++++++
 .../Positions/Ports/PortTests.cs                       | 10 +++-------
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
index 0fd0bcba6..5ab25279a 100644
--- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs
+++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
@@ -158,6 +158,9 @@ public virtual void UpdatePositionSilently(double deltaX, double deltaY)
 
     public virtual bool CanAttachTo(ILinkable other) => other is not PortModel && other is not BaseLinkModel;
 
+    /// 
+    /// Updates port position based on the deltaX and deltaY when the node is moved
+    /// 
     private void UpdatePortPositions(double deltaX, double deltaY)
     {
         // Save some JS calls and update ports directly here
@@ -168,6 +171,9 @@ private void UpdatePortPositions(double deltaX, double deltaY)
         }
     }
 
+    /// 
+    /// Updates the position of the ports by calculating the difference between the old and new size when the node is resized
+    /// 
     private void UpdatePortPositions(double oldWidth, double oldHeight, double newWidth, double newHeight)
     {
         var deltaX = newWidth - oldWidth;
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Ports/PortTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Ports/PortTests.cs
index 5919599bd..421bedbac 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Ports/PortTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Ports/PortTests.cs
@@ -28,19 +28,15 @@ public void UpdatePortOnSetPosition(PortAlignment alignment)
             var newX = 200;
             var newY = 300;
 
-            var deltaX = newX - node.Position.X;
-            var deltaY = newY - node.Position.Y;
-
-            var expected = new Point(port.Position.X + deltaX, port.Position.Y + deltaY);
-
             //Act
             node.SetPosition(newX, newY);
 
             //Assert
-            Assert.Equal(expected.X, port.Position.X);
-            Assert.Equal(expected.Y, port.Position.Y);
+            Assert.Equal(200, port.Position.X);
+            Assert.Equal(300, port.Position.Y);
         }
 
+
         [Theory]
         [InlineData(PortAlignment.Top)]
         [InlineData(PortAlignment.TopLeft)]

From 965a28b9dca01e25b02d3fd70a9055d053339746 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Tue, 20 Feb 2024 10:34:18 +1100
Subject: [PATCH 076/159] reverted

---
 .../Positions/Resizing/ResizerProvider.cs                     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
index 589587b05..e720715dc 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
@@ -20,9 +20,9 @@ public abstract class ResizerProvider : IPositionProvider
 
         abstract public bool ShouldChangeXPositionOnResize { get; }
         abstract public bool ShouldChangeYPositionOnResize { get; }
-        /// Controls whether the width should be modified on Node resizing 
+        /// Controls whether the totalMovedX should be added or subtracted 
         abstract public bool ShouldAddTotalMovedX { get; }
-        /// Controls whether the height should be modified on Node resizing 
+        /// Controls whether the totalMovedY should be added or subtracted 
         abstract public bool ShouldAddTotalMovedY { get; }
 
         abstract public Point? GetPosition(Model model);

From 24d06f19588c589228f1589376deb4ad64897b4e Mon Sep 17 00:00:00 2001
From: dou 
Date: Wed, 21 Feb 2024 12:00:48 +1100
Subject: [PATCH 077/159] GetBoundingClientRect don't throw on time out

---
 .../Extensions/JSRuntimeExtensions.cs         |  9 +++++++-
 .../Extensions/JSRuntimeExtensionsTest.cs     | 21 +++++++++++++++++++
 2 files changed, 29 insertions(+), 1 deletion(-)
 create mode 100644 tests/Blazor.Diagrams.Tests/Extensions/JSRuntimeExtensionsTest.cs

diff --git a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
index 42488844f..e8e5d0529 100644
--- a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
+++ b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
@@ -10,7 +10,14 @@ public static class JSRuntimeExtensions
 {
     public static async Task GetBoundingClientRect(this IJSRuntime jsRuntime, ElementReference element)
     {
-        return await jsRuntime.InvokeAsync("ZBlazorDiagrams.getBoundingClientRect", element);
+        try
+        {
+			return await jsRuntime.InvokeAsync("ZBlazorDiagrams.getBoundingClientRect", element);
+		}
+        catch (TaskCanceledException) 
+        { 
+            return null;
+        }
     }
 
     public static async Task ObserveResizes(this IJSRuntime jsRuntime, ElementReference element,
diff --git a/tests/Blazor.Diagrams.Tests/Extensions/JSRuntimeExtensionsTest.cs b/tests/Blazor.Diagrams.Tests/Extensions/JSRuntimeExtensionsTest.cs
new file mode 100644
index 000000000..bd2596fe1
--- /dev/null
+++ b/tests/Blazor.Diagrams.Tests/Extensions/JSRuntimeExtensionsTest.cs
@@ -0,0 +1,21 @@
+using Blazor.Diagrams.Core.Geometry;
+using Microsoft.JSInterop;
+using Moq;
+using System.Threading.Tasks;
+using Xunit;
+using JSRuntimeExtensions = Blazor.Diagrams.Extensions.JSRuntimeExtensions;
+
+namespace Blazor.Diagrams.Tests.Extensions
+{
+	public class JSRuntimeExtensionsTest
+	{
+		[Fact]
+		public async Task TestGetBoundingClientRectDoesNotThrowOnTimeout()
+		{
+			var jsRuntime = new Mock();
+			jsRuntime.Setup(j => j.InvokeAsync(It.IsAny(), It.IsAny())).ThrowsAsync(new TaskCanceledException());
+			var result = await JSRuntimeExtensions.GetBoundingClientRect(jsRuntime.Object, default);
+			Assert.Null(result);
+		}
+	}
+}

From 2b804ae740174d43330139f6c106cc5d23010e87 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Thu, 22 Feb 2024 09:49:14 +1100
Subject: [PATCH 078/159] Addressed pr comments

---
 .../Models/NodeModelTest.cs                   | 66 ++++++++++++++
 .../Positions/Ports/PortTests.cs              | 85 -------------------
 2 files changed, 66 insertions(+), 85 deletions(-)
 create mode 100644 tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
 delete mode 100644 tests/Blazor.Diagrams.Core.Tests/Positions/Ports/PortTests.cs

diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
new file mode 100644
index 000000000..628264f6b
--- /dev/null
+++ b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
@@ -0,0 +1,66 @@
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Models
+{
+    public class NodeModelTest
+    {
+        [Theory]
+        [InlineData(PortAlignment.Top)]
+        [InlineData(PortAlignment.TopLeft)]
+        [InlineData(PortAlignment.TopRight)]
+        [InlineData(PortAlignment.Bottom)]
+        [InlineData(PortAlignment.BottomLeft)]
+        [InlineData(PortAlignment.BottomRight)]
+        [InlineData(PortAlignment.Left)]
+        [InlineData(PortAlignment.Right)]
+        public void UpdatePortOnSetPosition(PortAlignment alignment)
+        {
+            //Arrange
+            var diagram = new TestDiagram();
+            diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
+            var node = new NodeModel(position: new Point(100, 100));
+            node.Size = new Size(100, 100);
+
+            var port = node.AddPort(alignment);
+
+            var newX = 200;
+            var newY = 300;
+
+            //Act
+            node.SetPosition(newX, newY);
+
+            //Assert
+            Assert.Equal(200, port.Position.X);
+            Assert.Equal(300, port.Position.Y);
+        }
+
+        [Theory]
+        [InlineData(PortAlignment.Top, 300, 100)]
+        [InlineData(PortAlignment.TopLeft, 100, 100)]
+        [InlineData(PortAlignment.TopRight, 500, 100)]
+        [InlineData(PortAlignment.Bottom, 300, 700)]
+        [InlineData(PortAlignment.BottomLeft, 100, 700)]
+        [InlineData(PortAlignment.BottomRight, 500, 700)]
+        [InlineData(PortAlignment.Left, 100, 400)]
+        [InlineData(PortAlignment.Right, 500, 400)]
+        public void UpdatePortOnSetSize(PortAlignment alignment, double expectedX, double expectedY)
+        {
+            // Arrange
+            var oldWidth = 100.0;
+            var oldHeight = 100.0;
+            var newWidth = 500.0;
+            var newHeight = 700.0;
+            var node = new NodeModel(new Point(100, 100)) { Size = new Size(oldWidth, oldHeight) };
+            var port = node.AddPort(alignment);
+
+            // Act
+            node.SetSize(newWidth, newHeight);
+
+            // Assert
+            Assert.Equal(expectedX, port.Position.X);
+            Assert.Equal(expectedY, port.Position.Y);
+        }
+    }
+}
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Ports/PortTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Ports/PortTests.cs
deleted file mode 100644
index 421bedbac..000000000
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Ports/PortTests.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using Blazor.Diagrams.Core.Geometry;
-using Blazor.Diagrams.Core.Models;
-using Xunit;
-
-namespace Blazor.Diagrams.Core.Tests.Positions.Ports
-{
-    public class PortTests
-    {
-        [Theory]
-        [InlineData(PortAlignment.Top)]
-        [InlineData(PortAlignment.TopLeft)]
-        [InlineData(PortAlignment.TopRight)]
-        [InlineData(PortAlignment.Bottom)]
-        [InlineData(PortAlignment.BottomLeft)]
-        [InlineData(PortAlignment.BottomRight)]
-        [InlineData(PortAlignment.Left)]
-        [InlineData(PortAlignment.Right)]
-        public void UpdatePortOnSetPosition(PortAlignment alignment)
-        {
-            //Arrange
-            var diagram = new TestDiagram();
-            diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
-            var node = new NodeModel(position: new Point(100, 100));
-            node.Size = new Size(100, 100);
-
-            var port = node.AddPort(alignment);
-
-            var newX = 200;
-            var newY = 300;
-
-            //Act
-            node.SetPosition(newX, newY);
-
-            //Assert
-            Assert.Equal(200, port.Position.X);
-            Assert.Equal(300, port.Position.Y);
-        }
-
-
-        [Theory]
-        [InlineData(PortAlignment.Top)]
-        [InlineData(PortAlignment.TopLeft)]
-        [InlineData(PortAlignment.TopRight)]
-        [InlineData(PortAlignment.Bottom)]
-        [InlineData(PortAlignment.BottomLeft)]
-        [InlineData(PortAlignment.BottomRight)]
-        [InlineData(PortAlignment.Left)]
-        [InlineData(PortAlignment.Right)]
-        public void UpdatePortOnSetSize(PortAlignment alignment)
-        {
-            // Arrange
-            var oldWidth = 100.0;
-            var oldHeight = 100.0;
-            var newWidth = 500.0;
-            var newHeight = 700.0;
-            var node = new NodeModel(position: new Point(100, 100)) { Size = new Size(oldWidth, oldHeight) };
-            var port = node.AddPort(alignment);
-
-            var deltaX = newWidth - oldWidth;
-            var deltaY = newHeight - oldHeight;
-
-            Point expected = alignment switch
-            {
-                PortAlignment.Top => new Point(port.Position.X + deltaX / 2, port.Position.Y),
-                PortAlignment.TopRight => new Point(port.Position.X + deltaX, port.Position.Y),
-                PortAlignment.TopLeft => new Point(port.Position.X, port.Position.Y),
-                PortAlignment.Right => new Point(port.Position.X + deltaX, port.Position.Y + deltaY / 2),
-                PortAlignment.Left => new Point(port.Position.X, port.Position.Y + deltaY / 2),
-                PortAlignment.Bottom => new Point(port.Position.X + deltaX / 2, port.Position.Y + deltaY),
-                PortAlignment.BottomRight => new Point(port.Position.X + deltaX, port.Position.Y + deltaY),
-                PortAlignment.BottomLeft => new Point(port.Position.X, port.Position.Y + deltaY),
-                _ => new Point(port.Position.X, port.Position.Y)
-            };
-
-            // Act
-            node.SetSize(newWidth, newHeight);
-
-            // Assert
-            Assert.Equal(expected.X, port.Position.X);
-            Assert.Equal(expected.Y, port.Position.Y);
-        }
-
-
-    }
-}

From a6a3f2c556159879ae8b1149e65fb0cc43ae09b8 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Thu, 22 Feb 2024 13:39:21 +1100
Subject: [PATCH 079/159] Addressed new PR comments

---
 src/Blazor.Diagrams.Core/Models/NodeModel.cs | 40 ++++----------------
 src/Blazor.Diagrams.Core/Models/PortModel.cs | 34 +++++++++++++++++
 2 files changed, 41 insertions(+), 33 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
index 5ab25279a..f3db0faf0 100644
--- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs
+++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
@@ -109,7 +109,7 @@ public void SetSize(double width, double height)
         var oldSize = new Size(Size.Width, Size.Height);
 
         Size = newSize;
-        UpdatePortPositions(oldSize.Width, oldSize.Height, newSize.Width, newSize.Height);
+        UpdatePortPositions(oldSize, newSize);
         Refresh();
         RefreshLinks();
         SizeChanging?.Invoke(this);
@@ -159,7 +159,7 @@ public virtual void UpdatePositionSilently(double deltaX, double deltaY)
     public virtual bool CanAttachTo(ILinkable other) => other is not PortModel && other is not BaseLinkModel;
 
     /// 
-    /// Updates port position based on the deltaX and deltaY when the node is moved
+    /// Updates port positions when node position changes.
     /// 
     private void UpdatePortPositions(double deltaX, double deltaY)
     {
@@ -172,42 +172,16 @@ private void UpdatePortPositions(double deltaX, double deltaY)
     }
 
     /// 
-    /// Updates the position of the ports by calculating the difference between the old and new size when the node is resized
+    /// Updates port positions when node size changes.
     /// 
-    private void UpdatePortPositions(double oldWidth, double oldHeight, double newWidth, double newHeight)
+    private void UpdatePortPositions(Size oldSize, Size newSize)
     {
-        var deltaX = newWidth - oldWidth;
-        var deltaY = newHeight - oldHeight;
+        var deltaX = newSize.Width - oldSize.Width;
+        var deltaY = newSize.Height - oldSize.Height;
 
         foreach (var port in _ports)
         {
-            switch (port.Alignment)
-            {
-                case PortAlignment.Top:
-                    port.Position = new Point(port.Position.X + deltaX / 2, port.Position.Y);
-                    break;
-                case PortAlignment.TopRight:
-                    port.Position = new Point(port.Position.X + deltaX, port.Position.Y);
-                    break;
-                case PortAlignment.TopLeft:
-                    port.Position = new Point(port.Position.X, port.Position.Y);
-                    break;
-                case PortAlignment.Right:
-                    port.Position = new Point(port.Position.X + deltaX, port.Position.Y + deltaY / 2);
-                    break;
-                case PortAlignment.Left:
-                    port.Position = new Point(port.Position.X, port.Position.Y + deltaY / 2);
-                    break;
-                case PortAlignment.Bottom:
-                    port.Position = new Point(port.Position.X + deltaX / 2, port.Position.Y + deltaY);
-                    break;
-                case PortAlignment.BottomRight:
-                    port.Position = new Point(port.Position.X + deltaX, port.Position.Y + deltaY);
-                    break;
-                case PortAlignment.BottomLeft:
-                    port.Position = new Point(port.Position.X, port.Position.Y + deltaY);
-                    break;
-            }
+            port.calculateNewPortPosition(deltaX, deltaY);
             port.RefreshLinks();
         }
     }
diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs
index a0c850816..df9e8861e 100644
--- a/src/Blazor.Diagrams.Core/Models/PortModel.cs
+++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs
@@ -67,4 +67,38 @@ public virtual bool CanAttachTo(ILinkable other)
     void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link);
 
     void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link);
+
+    public void calculateNewPortPosition(double deltaX, double deltaY)
+    {
+        switch (Alignment)
+        {
+            case PortAlignment.Top:
+                Position = new Point(Position.X + deltaX / 2, Position.Y);
+                break;
+            case PortAlignment.TopRight:
+                Position = new Point(Position.X + deltaX, Position.Y);
+                break;
+            case PortAlignment.TopLeft:
+                Position = new Point(Position.X, Position.Y);
+                break;
+            case PortAlignment.Right:
+                Position = new Point(Position.X + deltaX, Position.Y + deltaY / 2);
+                break;
+            case PortAlignment.Left:
+                Position = new Point(Position.X, Position.Y + deltaY / 2);
+                break;
+            case PortAlignment.Bottom:
+                Position = new Point(Position.X + deltaX / 2, Position.Y + deltaY);
+                break;
+            case PortAlignment.BottomRight:
+                Position = new Point(Position.X + deltaX, Position.Y + deltaY);
+                break;
+            case PortAlignment.BottomLeft:
+                Position = new Point(Position.X, Position.Y + deltaY);
+                break;
+            default:
+                Position = new Point(Position.X, Position.Y);
+                break;
+        }
+    }
 }

From 3525ce23601248171178c1209d75aa440b1a6aa1 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Fri, 23 Feb 2024 10:38:04 +1100
Subject: [PATCH 080/159] Fixed PR comments

---
 src/Blazor.Diagrams.Core/Models/NodeModel.cs  |  2 +-
 src/Blazor.Diagrams.Core/Models/PortModel.cs  |  2 +-
 .../Models/PortModelTest.cs                   | 66 +++++++++++++++++++
 3 files changed, 68 insertions(+), 2 deletions(-)
 create mode 100644 tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs

diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
index f3db0faf0..31db21b1c 100644
--- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs
+++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
@@ -181,7 +181,7 @@ private void UpdatePortPositions(Size oldSize, Size newSize)
 
         foreach (var port in _ports)
         {
-            port.calculateNewPortPosition(deltaX, deltaY);
+            port.SetPortPositionOnNodeSizeChanged(deltaX, deltaY);
             port.RefreshLinks();
         }
     }
diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs
index df9e8861e..7298e6766 100644
--- a/src/Blazor.Diagrams.Core/Models/PortModel.cs
+++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs
@@ -68,7 +68,7 @@ public virtual bool CanAttachTo(ILinkable other)
 
     void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link);
 
-    public void calculateNewPortPosition(double deltaX, double deltaY)
+    public void SetPortPositionOnNodeSizeChanged(double deltaX, double deltaY)
     {
         switch (Alignment)
         {
diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs
new file mode 100644
index 000000000..5ed5a1894
--- /dev/null
+++ b/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs
@@ -0,0 +1,66 @@
+using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Models;
+using Xunit;
+
+namespace Blazor.Diagrams.Core.Tests.Models
+{
+    public class PortModelTest
+    {
+        [Theory]
+        [InlineData(PortAlignment.Top)]
+        [InlineData(PortAlignment.TopLeft)]
+        [InlineData(PortAlignment.TopRight)]
+        [InlineData(PortAlignment.Bottom)]
+        [InlineData(PortAlignment.BottomLeft)]
+        [InlineData(PortAlignment.BottomRight)]
+        [InlineData(PortAlignment.Left)]
+        [InlineData(PortAlignment.Right)]
+        public void SetPortPositionOnNodeSizeChangedCalculatesCorrectPosition(PortAlignment alignment)
+        {
+            // Arrange
+            var node = new NodeModel();
+            var port = new PortModel(node, alignment, new Point(0, 0));
+            node.Size = new Size(100, 100);
+
+            // Act
+            port.SetPortPositionOnNodeSizeChanged(100, 100);
+
+            // Assert
+            switch (alignment)
+            {
+                case PortAlignment.Top:
+                    Assert.Equal(50, port.Position.X);
+                    Assert.Equal(0, port.Position.Y);
+                    break;
+                case PortAlignment.TopRight:
+                    Assert.Equal(100, port.Position.X);
+                    Assert.Equal(0, port.Position.Y);
+                    break;
+                case PortAlignment.TopLeft:
+                    Assert.Equal(0, port.Position.X);
+                    Assert.Equal(0, port.Position.Y);
+                    break;
+                case PortAlignment.Right:
+                    Assert.Equal(100, port.Position.X);
+                    Assert.Equal(50, port.Position.Y);
+                    break;
+                case PortAlignment.Left:
+                    Assert.Equal(0, port.Position.X);
+                    Assert.Equal(50, port.Position.Y);
+                    break;
+                case PortAlignment.Bottom:
+                    Assert.Equal(50, port.Position.X);
+                    Assert.Equal(100, port.Position.Y);
+                    break;
+                case PortAlignment.BottomRight:
+                    Assert.Equal(100, port.Position.X);
+                    Assert.Equal(100, port.Position.Y);
+                    break;
+                case PortAlignment.BottomLeft:
+                    Assert.Equal(0, port.Position.X);
+                    Assert.Equal(100, port.Position.Y);
+                    break;
+            }
+        }
+    }
+}

From d762ab7655af5ae958acb242bf15f75f3a82b317 Mon Sep 17 00:00:00 2001
From: dou 
Date: Mon, 26 Feb 2024 10:04:47 +1100
Subject: [PATCH 081/159] convert tabs

---
 src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
index e8e5d0529..0a9a7f96b 100644
--- a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
+++ b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
@@ -12,8 +12,8 @@ public static class JSRuntimeExtensions
     {
         try
         {
-			return await jsRuntime.InvokeAsync("ZBlazorDiagrams.getBoundingClientRect", element);
-		}
+            return await jsRuntime.InvokeAsync("ZBlazorDiagrams.getBoundingClientRect", element);
+        }
         catch (TaskCanceledException) 
         { 
             return null;

From 65f33e5c00bb9f31994f886ff50e37d52414a29b Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Tue, 27 Feb 2024 15:11:50 +1100
Subject: [PATCH 082/159] Updated PR

---
 .../Blazor.Diagrams.Core.Tests.csproj              |  4 +++-
 .../Models/NodeModelTest.cs                        | 14 ++++++++++++--
 2 files changed, 15 insertions(+), 3 deletions(-)

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 11c8ea2cb..c34bc51aa 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj
+++ b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj
@@ -1,4 +1,4 @@
-
+
 
   
     net6.0
@@ -9,6 +9,7 @@
   
 
   
+    
     
     
     
@@ -25,6 +26,7 @@
 
   
     
+    
   
 
 
diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
index 628264f6b..2ef45e006 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
@@ -1,7 +1,10 @@
-using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Components;
+using Blazor.Diagrams.Core.Geometry;
 using Blazor.Diagrams.Core.Models;
+using Bunit;
 using Xunit;
 
+
 namespace Blazor.Diagrams.Core.Tests.Models
 {
     public class NodeModelTest
@@ -17,13 +20,20 @@ public class NodeModelTest
         [InlineData(PortAlignment.Right)]
         public void UpdatePortOnSetPosition(PortAlignment alignment)
         {
+
+            using var ctx = new TestContext();
+
             //Arrange
             var diagram = new TestDiagram();
             diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
             var node = new NodeModel(position: new Point(100, 100));
             node.Size = new Size(100, 100);
 
-            var port = node.AddPort(alignment);
+            var port = new PortModel(node, alignment);
+            node.AddPort(port);
+
+            var cut = ctx.RenderComponent(parameters => parameters
+                       .Add(n => n.Node, node).AddCascadingValue(diagram));
 
             var newX = 200;
             var newY = 300;

From 5d6906c36aac3597d1c5a57745ef22dcd38f8978 Mon Sep 17 00:00:00 2001
From: Heather <30523814+Heathermcx@users.noreply.github.com>
Date: Tue, 27 Feb 2024 15:53:53 +1100
Subject: [PATCH 083/159] update the content path (#27)

---
 site/Site/wwwroot/index.html | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/site/Site/wwwroot/index.html b/site/Site/wwwroot/index.html
index 284a46451..f5921f6ae 100644
--- a/site/Site/wwwroot/index.html
+++ b/site/Site/wwwroot/index.html
@@ -7,8 +7,8 @@
     Site
     
     
-    
-    
+    
+    
     
     
     
@@ -79,7 +79,7 @@
         });
     
     
-    
+    
     

From 3057948efc54b63df8ce4ffeaf019876903c91b1 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Wed, 28 Feb 2024 10:15:06 +1100
Subject: [PATCH 084/159] Addressed PR comments

---
 src/Blazor.Diagrams.Core/Models/PortModel.cs  |  2 +-
 .../Models/NodeModelTest.cs                   | 53 ++++++-------------
 2 files changed, 17 insertions(+), 38 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs
index 7298e6766..4513fe049 100644
--- a/src/Blazor.Diagrams.Core/Models/PortModel.cs
+++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs
@@ -68,7 +68,7 @@ public virtual bool CanAttachTo(ILinkable other)
 
     void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link);
 
-    public void SetPortPositionOnNodeSizeChanged(double deltaX, double deltaY)
+    public virtual void SetPortPositionOnNodeSizeChanged(double deltaX, double deltaY)
     {
         switch (Alignment)
         {
diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
index 2ef45e006..48962d1d5 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
@@ -1,7 +1,6 @@
-using Blazor.Diagrams.Components;
-using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Geometry;
 using Blazor.Diagrams.Core.Models;
-using Bunit;
+using Moq;
 using Xunit;
 
 
@@ -9,31 +8,15 @@ namespace Blazor.Diagrams.Core.Tests.Models
 {
     public class NodeModelTest
     {
-        [Theory]
-        [InlineData(PortAlignment.Top)]
-        [InlineData(PortAlignment.TopLeft)]
-        [InlineData(PortAlignment.TopRight)]
-        [InlineData(PortAlignment.Bottom)]
-        [InlineData(PortAlignment.BottomLeft)]
-        [InlineData(PortAlignment.BottomRight)]
-        [InlineData(PortAlignment.Left)]
-        [InlineData(PortAlignment.Right)]
-        public void UpdatePortOnSetPosition(PortAlignment alignment)
+        [Fact]
+        public void UpdatePortOnSetPosition()
         {
-
-            using var ctx = new TestContext();
-
-            //Arrange
-            var diagram = new TestDiagram();
-            diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
             var node = new NodeModel(position: new Point(100, 100));
             node.Size = new Size(100, 100);
 
-            var port = new PortModel(node, alignment);
+            var port = new PortModel(node, PortAlignment.BottomLeft, new Point(50, 50));
             node.AddPort(port);
 
-            var cut = ctx.RenderComponent(parameters => parameters
-                       .Add(n => n.Node, node).AddCascadingValue(diagram));
 
             var newX = 200;
             var newY = 300;
@@ -42,35 +25,31 @@ public void UpdatePortOnSetPosition(PortAlignment alignment)
             node.SetPosition(newX, newY);
 
             //Assert
-            Assert.Equal(200, port.Position.X);
-            Assert.Equal(300, port.Position.Y);
+            Assert.Equal(150, port.Position.X);
+            Assert.Equal(250, port.Position.Y);
         }
 
-        [Theory]
-        [InlineData(PortAlignment.Top, 300, 100)]
-        [InlineData(PortAlignment.TopLeft, 100, 100)]
-        [InlineData(PortAlignment.TopRight, 500, 100)]
-        [InlineData(PortAlignment.Bottom, 300, 700)]
-        [InlineData(PortAlignment.BottomLeft, 100, 700)]
-        [InlineData(PortAlignment.BottomRight, 500, 700)]
-        [InlineData(PortAlignment.Left, 100, 400)]
-        [InlineData(PortAlignment.Right, 500, 400)]
-        public void UpdatePortOnSetSize(PortAlignment alignment, double expectedX, double expectedY)
+        [Fact]
+        public void SetPortPositionOnNodeSizeChangedIsCalledOnSetSize()
         {
             // Arrange
             var oldWidth = 100.0;
             var oldHeight = 100.0;
             var newWidth = 500.0;
             var newHeight = 700.0;
+            var deltaX = newWidth - oldWidth;
+            var deltaY = newHeight - oldHeight;
+
             var node = new NodeModel(new Point(100, 100)) { Size = new Size(oldWidth, oldHeight) };
-            var port = node.AddPort(alignment);
+            var portMock = new Mock(node, PortAlignment.BottomLeft, null, null);
+
+            node.AddPort(portMock.Object);
 
             // Act
             node.SetSize(newWidth, newHeight);
 
             // Assert
-            Assert.Equal(expectedX, port.Position.X);
-            Assert.Equal(expectedY, port.Position.Y);
+            portMock.Verify(m => m.SetPortPositionOnNodeSizeChanged(deltaX, deltaY), Times.Once);
         }
     }
 }

From 81b8b4415b11eb4089769820077c0c89c6e2a2c3 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Wed, 28 Feb 2024 10:17:13 +1100
Subject: [PATCH 085/159] Removed extra blank line

---
 tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
index 48962d1d5..fd451c599 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
@@ -17,7 +17,6 @@ public void UpdatePortOnSetPosition()
             var port = new PortModel(node, PortAlignment.BottomLeft, new Point(50, 50));
             node.AddPort(port);
 
-
             var newX = 200;
             var newY = 300;
 

From 3fb82b5498518452c36d58a2642973e53bea34ca Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Thu, 29 Feb 2024 09:56:19 +1100
Subject: [PATCH 086/159] Resolved all PR comments

---
 src/Blazor.Diagrams.Core/Models/NodeModel.cs  | 16 +++---
 .../Blazor.Diagrams.Core.Tests.csproj         |  2 -
 .../Models/NodeModelTest.cs                   |  1 -
 .../Models/PortModelTest.cs                   | 50 +++++++++----------
 4 files changed, 35 insertions(+), 34 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
index 31db21b1c..102a9fed6 100644
--- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs
+++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs
@@ -104,12 +104,16 @@ public override void SetPosition(double x, double y)
     public void SetSize(double width, double height)
     {
         var newSize = new Size(width, height);
-        if (newSize.Equals(_size) == true || Size == null)
+        if (newSize.Equals(_size) == true)
             return;
-        var oldSize = new Size(Size.Width, Size.Height);
+
+        Size? oldSize = Size != null ? new Size(Size.Width, Size.Height) : null;
 
         Size = newSize;
-        UpdatePortPositions(oldSize, newSize);
+        if (oldSize != null)
+        {
+            UpdatePortPositions(oldSize, newSize);
+        }
         Refresh();
         RefreshLinks();
         SizeChanging?.Invoke(this);
@@ -176,12 +180,12 @@ private void UpdatePortPositions(double deltaX, double deltaY)
     /// 
     private void UpdatePortPositions(Size oldSize, Size newSize)
     {
-        var deltaX = newSize.Width - oldSize.Width;
-        var deltaY = newSize.Height - oldSize.Height;
+        var deltaWidth = newSize.Width - oldSize.Width;
+        var deltaHeight = newSize.Height - oldSize.Height;
 
         foreach (var port in _ports)
         {
-            port.SetPortPositionOnNodeSizeChanged(deltaX, deltaY);
+            port.SetPortPositionOnNodeSizeChanged(deltaWidth, deltaHeight);
             port.RefreshLinks();
         }
     }
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 c34bc51aa..ee63923f9 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj
+++ b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj
@@ -9,7 +9,6 @@
   
 
   
-    
     
     
     
@@ -26,7 +25,6 @@
 
   
     
-    
   
 
 
diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
index fd451c599..b27edc990 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs
@@ -3,7 +3,6 @@
 using Moq;
 using Xunit;
 
-
 namespace Blazor.Diagrams.Core.Tests.Models
 {
     public class NodeModelTest
diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs
index 5ed5a1894..abce812e3 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs
@@ -7,15 +7,15 @@ namespace Blazor.Diagrams.Core.Tests.Models
     public class PortModelTest
     {
         [Theory]
-        [InlineData(PortAlignment.Top)]
-        [InlineData(PortAlignment.TopLeft)]
-        [InlineData(PortAlignment.TopRight)]
-        [InlineData(PortAlignment.Bottom)]
-        [InlineData(PortAlignment.BottomLeft)]
-        [InlineData(PortAlignment.BottomRight)]
-        [InlineData(PortAlignment.Left)]
-        [InlineData(PortAlignment.Right)]
-        public void SetPortPositionOnNodeSizeChangedCalculatesCorrectPosition(PortAlignment alignment)
+        [InlineData(PortAlignment.Top, 50, 0)]
+        [InlineData(PortAlignment.TopLeft, 0, 0)]
+        [InlineData(PortAlignment.TopRight, 100, 0)]
+        [InlineData(PortAlignment.Bottom, 50, 100)]
+        [InlineData(PortAlignment.BottomLeft, 0, 100)]
+        [InlineData(PortAlignment.BottomRight, 100, 100)]
+        [InlineData(PortAlignment.Left, 0, 50)]
+        [InlineData(PortAlignment.Right, 100, 50)]
+        public void SetPortPositionOnNodeSizeChangedCalculatesCorrectPosition(PortAlignment alignment, double expectedXPosition, double expectedYPosition)
         {
             // Arrange
             var node = new NodeModel();
@@ -29,36 +29,36 @@ public void SetPortPositionOnNodeSizeChangedCalculatesCorrectPosition(PortAlignm
             switch (alignment)
             {
                 case PortAlignment.Top:
-                    Assert.Equal(50, port.Position.X);
-                    Assert.Equal(0, port.Position.Y);
+                    Assert.Equal(expectedXPosition, port.Position.X);
+                    Assert.Equal(expectedYPosition, port.Position.Y);
                     break;
                 case PortAlignment.TopRight:
-                    Assert.Equal(100, port.Position.X);
-                    Assert.Equal(0, port.Position.Y);
+                    Assert.Equal(expectedXPosition, port.Position.X);
+                    Assert.Equal(expectedYPosition, port.Position.Y);
                     break;
                 case PortAlignment.TopLeft:
-                    Assert.Equal(0, port.Position.X);
-                    Assert.Equal(0, port.Position.Y);
+                    Assert.Equal(expectedXPosition, port.Position.X);
+                    Assert.Equal(expectedYPosition, port.Position.Y);
                     break;
                 case PortAlignment.Right:
-                    Assert.Equal(100, port.Position.X);
-                    Assert.Equal(50, port.Position.Y);
+                    Assert.Equal(expectedXPosition, port.Position.X);
+                    Assert.Equal(expectedYPosition, port.Position.Y);
                     break;
                 case PortAlignment.Left:
-                    Assert.Equal(0, port.Position.X);
-                    Assert.Equal(50, port.Position.Y);
+                    Assert.Equal(expectedXPosition, port.Position.X);
+                    Assert.Equal(expectedYPosition, port.Position.Y);
                     break;
                 case PortAlignment.Bottom:
-                    Assert.Equal(50, port.Position.X);
-                    Assert.Equal(100, port.Position.Y);
+                    Assert.Equal(expectedXPosition, port.Position.X);
+                    Assert.Equal(expectedYPosition, port.Position.Y);
                     break;
                 case PortAlignment.BottomRight:
-                    Assert.Equal(100, port.Position.X);
-                    Assert.Equal(100, port.Position.Y);
+                    Assert.Equal(expectedXPosition, port.Position.X);
+                    Assert.Equal(expectedYPosition, port.Position.Y);
                     break;
                 case PortAlignment.BottomLeft:
-                    Assert.Equal(0, port.Position.X);
-                    Assert.Equal(100, port.Position.Y);
+                    Assert.Equal(expectedXPosition, port.Position.X);
+                    Assert.Equal(expectedYPosition, port.Position.Y);
                     break;
             }
         }

From 53a39ddb866abff6f39e95bd2753f7ed356ae2f1 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Thu, 29 Feb 2024 10:49:27 +1100
Subject: [PATCH 087/159] Addressed new comments

---
 src/Blazor.Diagrams.Core/Models/PortModel.cs  | 16 ++++----
 .../Models/PortModelTest.cs                   | 37 +------------------
 2 files changed, 10 insertions(+), 43 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs
index 4513fe049..ef2068567 100644
--- a/src/Blazor.Diagrams.Core/Models/PortModel.cs
+++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs
@@ -68,33 +68,33 @@ public virtual bool CanAttachTo(ILinkable other)
 
     void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link);
 
-    public virtual void SetPortPositionOnNodeSizeChanged(double deltaX, double deltaY)
+    public virtual void SetPortPositionOnNodeSizeChanged(double deltaWidth, double deltaHeight)
     {
         switch (Alignment)
         {
             case PortAlignment.Top:
-                Position = new Point(Position.X + deltaX / 2, Position.Y);
+                Position = new Point(Position.X + deltaWidth / 2, Position.Y);
                 break;
             case PortAlignment.TopRight:
-                Position = new Point(Position.X + deltaX, Position.Y);
+                Position = new Point(Position.X + deltaWidth, Position.Y);
                 break;
             case PortAlignment.TopLeft:
                 Position = new Point(Position.X, Position.Y);
                 break;
             case PortAlignment.Right:
-                Position = new Point(Position.X + deltaX, Position.Y + deltaY / 2);
+                Position = new Point(Position.X + deltaWidth, Position.Y + deltaHeight / 2);
                 break;
             case PortAlignment.Left:
-                Position = new Point(Position.X, Position.Y + deltaY / 2);
+                Position = new Point(Position.X, Position.Y + deltaHeight / 2);
                 break;
             case PortAlignment.Bottom:
-                Position = new Point(Position.X + deltaX / 2, Position.Y + deltaY);
+                Position = new Point(Position.X + deltaWidth / 2, Position.Y + deltaHeight);
                 break;
             case PortAlignment.BottomRight:
-                Position = new Point(Position.X + deltaX, Position.Y + deltaY);
+                Position = new Point(Position.X + deltaWidth, Position.Y + deltaHeight);
                 break;
             case PortAlignment.BottomLeft:
-                Position = new Point(Position.X, Position.Y + deltaY);
+                Position = new Point(Position.X, Position.Y + deltaHeight);
                 break;
             default:
                 Position = new Point(Position.X, Position.Y);
diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs
index abce812e3..d4dc57bf5 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs
@@ -26,41 +26,8 @@ public void SetPortPositionOnNodeSizeChangedCalculatesCorrectPosition(PortAlignm
             port.SetPortPositionOnNodeSizeChanged(100, 100);
 
             // Assert
-            switch (alignment)
-            {
-                case PortAlignment.Top:
-                    Assert.Equal(expectedXPosition, port.Position.X);
-                    Assert.Equal(expectedYPosition, port.Position.Y);
-                    break;
-                case PortAlignment.TopRight:
-                    Assert.Equal(expectedXPosition, port.Position.X);
-                    Assert.Equal(expectedYPosition, port.Position.Y);
-                    break;
-                case PortAlignment.TopLeft:
-                    Assert.Equal(expectedXPosition, port.Position.X);
-                    Assert.Equal(expectedYPosition, port.Position.Y);
-                    break;
-                case PortAlignment.Right:
-                    Assert.Equal(expectedXPosition, port.Position.X);
-                    Assert.Equal(expectedYPosition, port.Position.Y);
-                    break;
-                case PortAlignment.Left:
-                    Assert.Equal(expectedXPosition, port.Position.X);
-                    Assert.Equal(expectedYPosition, port.Position.Y);
-                    break;
-                case PortAlignment.Bottom:
-                    Assert.Equal(expectedXPosition, port.Position.X);
-                    Assert.Equal(expectedYPosition, port.Position.Y);
-                    break;
-                case PortAlignment.BottomRight:
-                    Assert.Equal(expectedXPosition, port.Position.X);
-                    Assert.Equal(expectedYPosition, port.Position.Y);
-                    break;
-                case PortAlignment.BottomLeft:
-                    Assert.Equal(expectedXPosition, port.Position.X);
-                    Assert.Equal(expectedYPosition, port.Position.Y);
-                    break;
-            }
+            Assert.Equal(expectedXPosition, port.Position.X);
+            Assert.Equal(expectedYPosition, port.Position.Y);
         }
     }
 }

From e3a4577442d9aa79fe1ce93b0a19279be5b39308 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Tue, 5 Mar 2024 15:18:43 +1100
Subject: [PATCH 088/159] Addressed PR comments

---
 .../Positions/Resizing/ResizerProvider.cs     | 25 ++++++++++++-------
 1 file changed, 16 insertions(+), 9 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
index e720715dc..dcbebdb63 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
@@ -9,14 +9,14 @@ public abstract class ResizerProvider : IPositionProvider
     {
         abstract public string? Class { get; }
 
-        private Size? _originalSize = null;
-        private Point? _originalPosition = null;
+        protected Size? _originalSize = null;
+        protected Point? _originalPosition = null;
         private double? _lastClientX;
         private double? _lastClientY;
-        private NodeModel? _nodeModel = null;
+        protected NodeModel? _nodeModel = null;
         private double _totalMovedX = 0;
         private double _totalMovedY = 0;
-        private Diagram? _diagram;
+        protected Diagram? _diagram;
 
         abstract public bool ShouldChangeXPositionOnResize { get; }
         abstract public bool ShouldChangeYPositionOnResize { get; }
@@ -27,7 +27,7 @@ public abstract class ResizerProvider : IPositionProvider
 
         abstract public Point? GetPosition(Model model);
 
-        virtual public void ResizeNode(double deltaX, double deltaY)
+        virtual public (Size size, Point position) CalculateNewSizeAndPosition(double deltaX, double deltaY)
         {
             _totalMovedX += deltaX;
             _totalMovedY += deltaY;
@@ -49,8 +49,13 @@ virtual public void ResizeNode(double deltaX, double deltaY)
                 positionY = _nodeModel.Position.Y;
             }
 
-            _nodeModel.SetPosition(positionX, positionY);
-            _nodeModel.SetSize(width, height);
+            return (new Size(width, height), new Point(positionX, positionY));
+        }
+
+        virtual public void SetSizeAndPosition(Size size, Point position)
+        {
+            _nodeModel!.SetPosition(position.X, position.Y);
+            _nodeModel.SetSize(size.Width, size.Height);
         }
 
         virtual public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e)
@@ -79,14 +84,16 @@ virtual public void OnPointerMove(Model? model, PointerEventArgs e)
             _lastClientX = e.ClientX;
             _lastClientY = e.ClientY;
 
-            ResizeNode(deltaX, deltaY);
+            var result = CalculateNewSizeAndPosition(deltaX, deltaY);
+            SetSizeAndPosition(result.size, result.position);
         }
 
         virtual public void OnPanChanged(double deltaX, double deltaY)
         {
             if (_nodeModel is null) return;
 
-            ResizeNode(deltaX, deltaY);
+            var result = CalculateNewSizeAndPosition(deltaX, deltaY);
+            SetSizeAndPosition(result.size, result.position);
         }
 
         virtual public void OnResizeEnd(Model? model, PointerEventArgs args)

From 8967512d86263f9dff91e9e974aae3a2be8d1c65 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Wed, 6 Mar 2024 15:21:13 +1100
Subject: [PATCH 089/159] Protected fields are now properties

---
 .../Positions/Resizing/ResizerProvider.cs                 | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
index dcbebdb63..7ca91d904 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
@@ -9,14 +9,14 @@ public abstract class ResizerProvider : IPositionProvider
     {
         abstract public string? Class { get; }
 
-        protected Size? _originalSize = null;
-        protected Point? _originalPosition = null;
+        protected Size? _originalSize { get; set; }
+        protected Point? _originalPosition { get; set; }
         private double? _lastClientX;
         private double? _lastClientY;
-        protected NodeModel? _nodeModel = null;
+        protected NodeModel? _nodeModel { get; set; }
         private double _totalMovedX = 0;
         private double _totalMovedY = 0;
-        protected Diagram? _diagram;
+        protected Diagram? _diagram { get; set; }
 
         abstract public bool ShouldChangeXPositionOnResize { get; }
         abstract public bool ShouldChangeYPositionOnResize { get; }

From 8d2480428b4fd5311b8ff5cf526ec8f1317b10ad Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Thu, 7 Mar 2024 14:03:58 +1100
Subject: [PATCH 090/159] Fixed properties access

---
 .../Positions/Resizing/ResizerProvider.cs     | 74 +++++++++----------
 1 file changed, 37 insertions(+), 37 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
index 7ca91d904..0e2450e48 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
@@ -9,14 +9,14 @@ public abstract class ResizerProvider : IPositionProvider
     {
         abstract public string? Class { get; }
 
-        protected Size? _originalSize { get; set; }
-        protected Point? _originalPosition { get; set; }
-        private double? _lastClientX;
-        private double? _lastClientY;
-        protected NodeModel? _nodeModel { get; set; }
+        protected Size? OriginalSize { get; set; }
+        protected Point? OriginalPosition { get; set; }
+        protected double? LastClientX;
+        protected double? LastClientY;
+        protected NodeModel? NodeModel { get; set; }
+        protected Diagram? Diagram { get; set; }
         private double _totalMovedX = 0;
         private double _totalMovedY = 0;
-        protected Diagram? _diagram { get; set; }
 
         abstract public bool ShouldChangeXPositionOnResize { get; }
         abstract public bool ShouldChangeYPositionOnResize { get; }
@@ -32,21 +32,21 @@ virtual public (Size size, Point position) CalculateNewSizeAndPosition(double de
             _totalMovedX += deltaX;
             _totalMovedY += deltaY;
 
-            var width = _originalSize!.Width + (ShouldAddTotalMovedX ? _totalMovedX : -_totalMovedX) / _diagram!.Zoom;
-            var height = _originalSize.Height + (ShouldAddTotalMovedY ? _totalMovedY : -_totalMovedY) / _diagram!.Zoom;
+            var width = OriginalSize!.Width + (ShouldAddTotalMovedX ? _totalMovedX : -_totalMovedX) / Diagram!.Zoom;
+            var height = OriginalSize.Height + (ShouldAddTotalMovedY ? _totalMovedY : -_totalMovedY) / Diagram!.Zoom;
 
-            var positionX = _originalPosition!.X + (ShouldChangeXPositionOnResize ? _totalMovedX : 0) / _diagram!.Zoom;
-            var positionY = _originalPosition.Y + (ShouldChangeYPositionOnResize ? _totalMovedY : 0) / _diagram!.Zoom;
+            var positionX = OriginalPosition!.X + (ShouldChangeXPositionOnResize ? _totalMovedX : 0) / Diagram!.Zoom;
+            var positionY = OriginalPosition.Y + (ShouldChangeYPositionOnResize ? _totalMovedY : 0) / Diagram!.Zoom;
 
-            if (width < _nodeModel!.MinimumDimensions.Width)
+            if (width < NodeModel!.MinimumDimensions.Width)
             {
-                width = _nodeModel.MinimumDimensions.Width;
-                positionX = _nodeModel.Position.X;
+                width = NodeModel.MinimumDimensions.Width;
+                positionX = NodeModel.Position.X;
             }
-            if (height < _nodeModel.MinimumDimensions.Height)
+            if (height < NodeModel.MinimumDimensions.Height)
             {
-                height = _nodeModel.MinimumDimensions.Height;
-                positionY = _nodeModel.Position.Y;
+                height = NodeModel.MinimumDimensions.Height;
+                positionY = NodeModel.Position.Y;
             }
 
             return (new Size(width, height), new Point(positionX, positionY));
@@ -54,35 +54,35 @@ virtual public (Size size, Point position) CalculateNewSizeAndPosition(double de
 
         virtual public void SetSizeAndPosition(Size size, Point position)
         {
-            _nodeModel!.SetPosition(position.X, position.Y);
-            _nodeModel.SetSize(size.Width, size.Height);
+            NodeModel!.SetPosition(position.X, position.Y);
+            NodeModel.SetSize(size.Width, size.Height);
         }
 
         virtual public void OnResizeStart(Diagram diagram, Model model, PointerEventArgs e)
         {
             if (model is NodeModel nodeModel)
             {
-                _lastClientX = e.ClientX;
-                _lastClientY = e.ClientY;
-                _originalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y);
-                _originalSize = nodeModel.Size;
-                _nodeModel = nodeModel;
-                _diagram = diagram;
+                LastClientX = e.ClientX;
+                LastClientY = e.ClientY;
+                OriginalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y);
+                OriginalSize = nodeModel.Size;
+                this.NodeModel = nodeModel;
+                Diagram = diagram;
             }
         }
 
         virtual public void OnPointerMove(Model? model, PointerEventArgs e)
         {
-            if (_originalSize is null || _originalPosition is null || _nodeModel is null || _diagram is null)
+            if (OriginalSize is null || OriginalPosition is null || NodeModel is null || Diagram is null)
             {
                 return;
             }
 
-            var deltaX = (e.ClientX - _lastClientX!.Value);
-            var deltaY = (e.ClientY - _lastClientY!.Value);
+            var deltaX = (e.ClientX - LastClientX!.Value);
+            var deltaY = (e.ClientY - LastClientY!.Value);
 
-            _lastClientX = e.ClientX;
-            _lastClientY = e.ClientY;
+            LastClientX = e.ClientX;
+            LastClientY = e.ClientY;
 
             var result = CalculateNewSizeAndPosition(deltaX, deltaY);
             SetSizeAndPosition(result.size, result.position);
@@ -90,7 +90,7 @@ virtual public void OnPointerMove(Model? model, PointerEventArgs e)
 
         virtual public void OnPanChanged(double deltaX, double deltaY)
         {
-            if (_nodeModel is null) return;
+            if (NodeModel is null) return;
 
             var result = CalculateNewSizeAndPosition(deltaX, deltaY);
             SetSizeAndPosition(result.size, result.position);
@@ -98,15 +98,15 @@ virtual public void OnPanChanged(double deltaX, double deltaY)
 
         virtual public void OnResizeEnd(Model? model, PointerEventArgs args)
         {
-            _nodeModel?.TriggerSizeChanged();
-            _originalSize = null;
-            _originalPosition = null;
-            _nodeModel = null;
+            NodeModel?.TriggerSizeChanged();
+            OriginalSize = null;
+            OriginalPosition = null;
+            NodeModel = null;
             _totalMovedX = 0;
             _totalMovedY = 0;
-            _lastClientX = null;
-            _lastClientY = null;
-            _diagram = null;
+            LastClientX = null;
+            LastClientY = null;
+            Diagram = null;
         }
     }
 }

From a9ca5a19ac9bfbec12b6621b539b2f6a1f0dc6ce Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Thu, 7 Mar 2024 14:09:14 +1100
Subject: [PATCH 091/159] Converted lastClient to fields

---
 .../Positions/Resizing/ResizerProvider.cs                     | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
index 0e2450e48..8b84ebec6 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
@@ -11,8 +11,8 @@ public abstract class ResizerProvider : IPositionProvider
 
         protected Size? OriginalSize { get; set; }
         protected Point? OriginalPosition { get; set; }
-        protected double? LastClientX;
-        protected double? LastClientY;
+        protected double? LastClientX { get; set; }
+        protected double? LastClientY { get; set; }
         protected NodeModel? NodeModel { get; set; }
         protected Diagram? Diagram { get; set; }
         private double _totalMovedX = 0;

From 0f8d6d0d1922aa0f8eefab678814c362f72d2547 Mon Sep 17 00:00:00 2001
From: Leonam Santos 
Date: Wed, 20 Mar 2024 13:45:59 +1100
Subject: [PATCH 092/159] Change DiagramCanvas to await unsubscription to
 Resizes subscription

---
 .../Components/DiagramCanvas.razor.cs         |  6 ++--
 .../Components/DiagramCanvasTests.cs          | 28 +++++++++++++++++++
 2 files changed, 31 insertions(+), 3 deletions(-)
 create mode 100644 tests/Blazor.Diagrams.Tests/Components/DiagramCanvasTests.cs

diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs
index a8eb0bb6b..2a0751499 100644
--- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs
+++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs
@@ -8,7 +8,7 @@
 
 namespace Blazor.Diagrams.Components;
 
-public partial class DiagramCanvas : IDisposable
+public partial class DiagramCanvas : IAsyncDisposable
 {
     private DotNetObjectReference? _reference;
     private bool _shouldRender;
@@ -28,7 +28,7 @@ public partial class DiagramCanvas : IDisposable
 
     [Inject] public IJSRuntime JSRuntime { get; set; } = null!;
 
-    public void Dispose()
+    public async ValueTask DisposeAsync()
     {
         BlazorDiagram.Changed -= OnDiagramChanged;
 
@@ -36,7 +36,7 @@ public void Dispose()
             return;
 
         if (elementReference.Id != null)
-            _ = JSRuntime.UnobserveResizes(elementReference);
+            await JSRuntime.UnobserveResizes(elementReference);
 
         _reference.Dispose();
     }
diff --git a/tests/Blazor.Diagrams.Tests/Components/DiagramCanvasTests.cs b/tests/Blazor.Diagrams.Tests/Components/DiagramCanvasTests.cs
new file mode 100644
index 000000000..f3852758b
--- /dev/null
+++ b/tests/Blazor.Diagrams.Tests/Components/DiagramCanvasTests.cs
@@ -0,0 +1,28 @@
+using Blazor.Diagrams.Components;
+using Blazor.Diagrams.Core.Geometry;
+using Bunit;
+using Xunit;
+
+namespace Blazor.Diagrams.Tests.Components
+{
+    public class DiagramCanvasTests
+    {
+        [Fact]
+        public void Behavior_WhenDisposing_ShouldUnsubscribeToResizes()
+        {
+            // Arrange
+            JSRuntimeInvocationHandler call;
+            using (var ctx = new TestContext())
+            {
+                ctx.JSInterop.Setup("ZBlazorDiagrams.getBoundingClientRect", _ => true);
+                call = ctx.JSInterop.SetupVoid("ZBlazorDiagrams.unobserve", _ => true).SetVoidResult();
+
+                // Act
+                var cut = ctx.RenderComponent(p => p.Add(n => n.BlazorDiagram, new BlazorDiagram()));
+            }
+
+            // Assert
+            call.VerifyInvoke("ZBlazorDiagrams.unobserve", calledTimes: 1);
+        }
+    }
+}

From faceea4c2f11b719cb0e748e6b5ed8173e8f3933 Mon Sep 17 00:00:00 2001
From: Heather <30523814+Heathermcx@users.noreply.github.com>
Date: Wed, 17 Apr 2024 11:20:07 +1000
Subject: [PATCH 093/159] Revert changes to GetBoundingClientRect (#30)

---
 .../Extensions/JSRuntimeExtensions.cs         | 11 ++--------
 .../Extensions/JSRuntimeExtensionsTest.cs     | 21 -------------------
 2 files changed, 2 insertions(+), 30 deletions(-)
 delete mode 100644 tests/Blazor.Diagrams.Tests/Extensions/JSRuntimeExtensionsTest.cs

diff --git a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
index 0a9a7f96b..459456863 100644
--- a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
+++ b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
@@ -8,16 +8,9 @@ namespace Blazor.Diagrams.Extensions;
 
 public static class JSRuntimeExtensions
 {
-    public static async Task GetBoundingClientRect(this IJSRuntime jsRuntime, ElementReference element)
+    public static async Task GetBoundingClientRect(this IJSRuntime jsRuntime, ElementReference element)
     {
-        try
-        {
-            return await jsRuntime.InvokeAsync("ZBlazorDiagrams.getBoundingClientRect", element);
-        }
-        catch (TaskCanceledException) 
-        { 
-            return null;
-        }
+        return await jsRuntime.InvokeAsync("ZBlazorDiagrams.getBoundingClientRect", element);
     }
 
     public static async Task ObserveResizes(this IJSRuntime jsRuntime, ElementReference element,
diff --git a/tests/Blazor.Diagrams.Tests/Extensions/JSRuntimeExtensionsTest.cs b/tests/Blazor.Diagrams.Tests/Extensions/JSRuntimeExtensionsTest.cs
deleted file mode 100644
index bd2596fe1..000000000
--- a/tests/Blazor.Diagrams.Tests/Extensions/JSRuntimeExtensionsTest.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-using Blazor.Diagrams.Core.Geometry;
-using Microsoft.JSInterop;
-using Moq;
-using System.Threading.Tasks;
-using Xunit;
-using JSRuntimeExtensions = Blazor.Diagrams.Extensions.JSRuntimeExtensions;
-
-namespace Blazor.Diagrams.Tests.Extensions
-{
-	public class JSRuntimeExtensionsTest
-	{
-		[Fact]
-		public async Task TestGetBoundingClientRectDoesNotThrowOnTimeout()
-		{
-			var jsRuntime = new Mock();
-			jsRuntime.Setup(j => j.InvokeAsync(It.IsAny(), It.IsAny())).ThrowsAsync(new TaskCanceledException());
-			var result = await JSRuntimeExtensions.GetBoundingClientRect(jsRuntime.Object, default);
-			Assert.Null(result);
-		}
-	}
-}

From d15aa500fc9035d0038b04ba9a0e84804c78efc4 Mon Sep 17 00:00:00 2001
From: Heather <30523814+Heathermcx@users.noreply.github.com>
Date: Fri, 24 May 2024 16:01:03 +1000
Subject: [PATCH 094/159] Merge upstream/master into master (#35)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* Fix link hover stroke opacity

* Add Radius example to Path Generators documentation

* fix type of diagram

* Adding check for target being a portModel

* Fixing nodes not updating on change of the link target

* fixing NRE of onLinkRemoved in landing showcase diagram

* refactoring link target refreshing in landing showcase diagram

* Demo-site: changing signature of onChange to reflect actual values

* Add Route property to BaseLinkModel

* Adding check for "ShouldDelete"-constraint to remove control

* Adding unit tests

* Fixing #369 by adding invariant culture conversion

* Adding functionality for GroupModel Removal to RemoveControl

* Update Versions and CHANGELOG

* Add new workflow to push to nuget

* Update push.yml

* Update push.yml

* upgrade to net8.0

* multitargetting and central package management

* apply consistent formatting to .csproj and .props files

* Update ReflectionUtils.cs

Fix a NRE in the DemoSite's Options page.

* Add width and height to foreignObject.diagram-link-label for Firefox

* Update README.md

* Update Versions and CHANGELOG

* Change Java version for Sonar

* Update main.yml

* Update push.yml

* Update push.yml

* Update push.yml

* remove package versions from project files

* remove unused action

* update strong name signer

* try add reference to SvgPathProperties in Blazor.Diagrams.csproj

* update bunit

* delete push action

---------

Co-authored-by: Lars Westermann 
Co-authored-by: Haytam Zanid <34218324+zHaytam@users.noreply.github.com>
Co-authored-by: Haytam Zanid 
Co-authored-by: Suraj 
Co-authored-by: Merlin Zöbl <41567572+K0369@users.noreply.github.com>
Co-authored-by: Merlin Zöbl 
Co-authored-by: sebastian-wachsmuth 
Co-authored-by: Robert McLaws <1657085+robertmclaws@users.noreply.github.com>
---
 Blazor.Diagrams.sln                           |   14 +-
 CHANGELOG.md                                  |   11 +
 Directory.Packages.props                      |   34 +
 README.md                                     |    6 +-
 docs/CustomNodesLinks/CustomNodesLinks.csproj |    4 -
 docs/Diagram-Demo/Diagram-Demo.csproj         |    5 -
 docs/Directory.Build.props                    |    8 +
 docs/Layouts/Layouts.csproj                   |    8 +-
 samples/Directory.Build.props                 |    8 +
 samples/ServerSide/ServerSide.csproj          |    4 -
 samples/SharedDemo/ReflectionUtils.cs         |    2 +-
 samples/SharedDemo/SharedDemo.csproj          |    8 +-
 samples/Wasm/Wasm.csproj                      |   11 +-
 site/Site/Site.csproj                         |   39 +-
 site/Site/wwwroot/css/input.output.css        | 1756 +++++++++++++++++
 .../Blazor.Diagrams.Algorithms.csproj         |    8 +-
 .../Blazor.Diagrams.Core.csproj               |   25 +-
 .../tailwind.extension.json                   |    1 +
 src/Blazor.Diagrams/Blazor.Diagrams.csproj    |   50 +-
 src/Blazor.Diagrams/wwwroot/style.css         |    2 +
 src/Blazor.Diagrams/wwwroot/style.min.css     |    2 +-
 src/Blazor.Diagrams/wwwroot/style.min.css.gz  |  Bin 409 -> 416 bytes
 src/Directory.Build.props                     |    8 +
 .../Blazor.Diagrams.Core.Tests.csproj         |   14 +-
 .../Controls/RemoveControlTests.cs            |    6 -
 .../Blazor.Diagrams.Tests.csproj              |   26 +-
 tests/Directory.Build.props                   |    8 +
 27 files changed, 1937 insertions(+), 131 deletions(-)
 create mode 100644 Directory.Packages.props
 create mode 100644 docs/Directory.Build.props
 create mode 100644 samples/Directory.Build.props
 create mode 100644 site/Site/wwwroot/css/input.output.css
 create mode 100644 src/Blazor.Diagrams.Core/tailwind.extension.json
 create mode 100644 src/Directory.Build.props
 create mode 100644 tests/Directory.Build.props

diff --git a/Blazor.Diagrams.sln b/Blazor.Diagrams.sln
index 862fc051e..3cf7b73ed 100644
--- a/Blazor.Diagrams.sln
+++ b/Blazor.Diagrams.sln
@@ -4,10 +4,16 @@ Microsoft Visual Studio Solution File, Format Version 12.00
 VisualStudioVersion = 17.0.32014.148
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EE32E278-A887-454E-987D-FFE9E37169FE}"
+	ProjectSection(SolutionItems) = preProject
+		src\Directory.Build.props = src\Directory.Build.props
+	EndProjectSection
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Diagrams.Core", "src\Blazor.Diagrams.Core\Blazor.Diagrams.Core.csproj", "{1A70B637-9EF8-4C25-970E-A681DEC9061C}"
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DA819127-3EF6-4EB9-A2DA-BC056B284A50}"
+	ProjectSection(SolutionItems) = preProject
+		samples\Directory.Build.props = samples\Directory.Build.props
+	EndProjectSection
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Wasm", "samples\Wasm\Wasm.csproj", "{A43942BD-FDA0-4E3D-8115-BBE9D0D75DE3}"
 EndProject
@@ -23,16 +29,22 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
 	ProjectSection(SolutionItems) = preProject
 		.github\workflows\build.yml = .github\workflows\build.yml
 		CHANGELOG.md = CHANGELOG.md
+		Directory.Packages.props = Directory.Packages.props
 		.github\workflows\main.yml = .github\workflows\main.yml
-		.github\workflows\push.yml = .github\workflows\push.yml
 		README.md = README.md
 	EndProjectSection
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{CEEAE4C2-CE68-4FC3-9E0F-D4781B91F7F4}"
+	ProjectSection(SolutionItems) = preProject
+		tests\Directory.Build.props = tests\Directory.Build.props
+	EndProjectSection
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Blazor.Diagrams.Core.Tests", "tests\Blazor.Diagrams.Core.Tests\Blazor.Diagrams.Core.Tests.csproj", "{36B4DCCD-45AB-4338-9224-DDAF386A23A3}"
 EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{A9FC9B20-A9F1-4066-8B59-83BD26D3B1C8}"
+	ProjectSection(SolutionItems) = preProject
+		docs\Directory.Build.props = docs\Directory.Build.props
+	EndProjectSection
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Diagram-Demo", "docs\Diagram-Demo\Diagram-Demo.csproj", "{5F423724-5319-4DCE-B9F2-8B2D7E1FDC17}"
 EndProject
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bd66b19c5..e15b4bac1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,17 @@ 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).
 
+## Diagram (3.0.0) - 2024-02-24
+
+### Added
+
+- Support for .NET 8 (thanks to @[SebastianWachsmuth](https://github.com/SebastianWachsmuth))
+
+### Fixed
+
+- Link labels not showing in Firefox
+- NRE in the DemoSite's Options page (thanks to @[robertmclaws](https://github.com/robertmclaws))
+
 ## Diagrams (3.0.1) - 2023-10-27
 
 ### Added
diff --git a/Directory.Packages.props b/Directory.Packages.props
new file mode 100644
index 000000000..fd4ea754a
--- /dev/null
+++ b/Directory.Packages.props
@@ -0,0 +1,34 @@
+
+  
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+    
+  
+  
+    
+    
+    
+    
+  
+  
+    
+    
+    
+    
+  
+  
+    
+    
+    
+    
+  
+
\ No newline at end of file
diff --git a/README.md b/README.md
index dcd2df3ca..5bfe94af2 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ Z.Blazor.Diagrams is a fully customizable and extensible all-purpose diagrams li
 - Multi purpose
 - Touch support
 - SVG layer for links/nodes and HTML layer for nodes for maximum customizability
-- Links between nodes directly or node ports
+- Links between nodes, ports and even other links
 - Link routers, path generators, markers and labels
 - Panning, Zooming and Zooming to fit a set of nodes
 - Multi selection, deletion and region selection
@@ -48,9 +48,7 @@ Z.Blazor.Diagrams is a fully customizable and extensible all-purpose diagrams li
 You can get started very easily & quickly using:
 
 - [Documentation](https://blazor-diagrams.zhaytam.com/)
-- [Quick Start](https://blazor-diagrams.zhaytam.com/quickstart)
-- [Samples](https://blazor-diagrams.zhaytam.com/demos/simple)
-- [Docs/Demos](https://github.com/Blazor-Diagrams/Blazor.Diagrams/tree/master/docs)
+- [Installation](https://blazor-diagrams.zhaytam.com/documentation/installation)
 
 ### Sample project
 
diff --git a/docs/CustomNodesLinks/CustomNodesLinks.csproj b/docs/CustomNodesLinks/CustomNodesLinks.csproj
index 0b40cdb67..73fa231b2 100644
--- a/docs/CustomNodesLinks/CustomNodesLinks.csproj
+++ b/docs/CustomNodesLinks/CustomNodesLinks.csproj
@@ -1,9 +1,5 @@
 
 
-  
-    net6.0
-  
-
   
     
     
diff --git a/docs/Diagram-Demo/Diagram-Demo.csproj b/docs/Diagram-Demo/Diagram-Demo.csproj
index 7bc16f503..73fa231b2 100644
--- a/docs/Diagram-Demo/Diagram-Demo.csproj
+++ b/docs/Diagram-Demo/Diagram-Demo.csproj
@@ -1,10 +1,5 @@
 
 
-  
-    net6.0
-    Diagram_Demo
-  
-
   
     
     
diff --git a/docs/Directory.Build.props b/docs/Directory.Build.props
new file mode 100644
index 000000000..c643f68a5
--- /dev/null
+++ b/docs/Directory.Build.props
@@ -0,0 +1,8 @@
+
+  
+    net8.0
+    enable
+    enable
+    true
+  
+
\ No newline at end of file
diff --git a/docs/Layouts/Layouts.csproj b/docs/Layouts/Layouts.csproj
index 3350a9e9e..85cd16694 100644
--- a/docs/Layouts/Layouts.csproj
+++ b/docs/Layouts/Layouts.csproj
@@ -1,12 +1,8 @@
 
 
-  
-    net6.0
-  
-
   
-    
-    
+    
+    
   
 
   
diff --git a/samples/Directory.Build.props b/samples/Directory.Build.props
new file mode 100644
index 000000000..c643f68a5
--- /dev/null
+++ b/samples/Directory.Build.props
@@ -0,0 +1,8 @@
+
+  
+    net8.0
+    enable
+    enable
+    true
+  
+
\ No newline at end of file
diff --git a/samples/ServerSide/ServerSide.csproj b/samples/ServerSide/ServerSide.csproj
index 7a5552fa1..5b9be51b5 100644
--- a/samples/ServerSide/ServerSide.csproj
+++ b/samples/ServerSide/ServerSide.csproj
@@ -1,9 +1,5 @@
 
 
-  
-    net6.0
-  
-
   
     
   
diff --git a/samples/SharedDemo/ReflectionUtils.cs b/samples/SharedDemo/ReflectionUtils.cs
index e81548cbf..0d90ebcfc 100644
--- a/samples/SharedDemo/ReflectionUtils.cs
+++ b/samples/SharedDemo/ReflectionUtils.cs
@@ -30,7 +30,7 @@ private static IEnumerable ExtractPossibleOptions(Type type, str
 
             var typeName = FormatPropertyType(property.PropertyType);
             var @default = propertyValue?.ToString();
-            var description = property.GetCustomAttribute().Description;
+            var description = property.GetCustomAttribute()?.Description;
             yield return new PossibleOption(name, typeName, @default, description);
         }
     }
diff --git a/samples/SharedDemo/SharedDemo.csproj b/samples/SharedDemo/SharedDemo.csproj
index 6fbc3a0bf..d89e07e79 100644
--- a/samples/SharedDemo/SharedDemo.csproj
+++ b/samples/SharedDemo/SharedDemo.csproj
@@ -1,12 +1,8 @@
 
 
-  
-    net6.0
-  
-
   
-    
-    
+    
+    
   
 
   
diff --git a/samples/Wasm/Wasm.csproj b/samples/Wasm/Wasm.csproj
index f88bb08ef..f5cb37a64 100644
--- a/samples/Wasm/Wasm.csproj
+++ b/samples/Wasm/Wasm.csproj
@@ -1,14 +1,9 @@
 
 
-  
-    net6.0
-    enable
-  
-
   
-    
-    
-    
+    
+    
+    
   
 
   
diff --git a/site/Site/Site.csproj b/site/Site/Site.csproj
index a85adb61e..b7b4fd577 100644
--- a/site/Site/Site.csproj
+++ b/site/Site/Site.csproj
@@ -1,26 +1,27 @@
 
 
-    
-        net6.0
-        enable
-        enable
-    
+  
+    net8.0
+    enable
+    enable
+    true
+  
 
-    
-        
-        
-    
+  
+    
+    
+  
 
-    
-      <_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css" />
-      <_ContentIncludedByDefault Remove="wwwroot\css\bootstrap\bootstrap.min.css.map" />
-      <_ContentIncludedByDefault Remove="wwwroot\sample-data\weather.json" />
-    
+  
+    <_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/wwwroot/css/input.output.css b/site/Site/wwwroot/css/input.output.css
new file mode 100644
index 000000000..eb7fe21fd
--- /dev/null
+++ b/site/Site/wwwroot/css/input.output.css
@@ -0,0 +1,1756 @@
+/*
+! tailwindcss v3.4.0 | 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.
+5. Use the user's configured `sans` font-feature-settings by default.
+6. Use the user's configured `sans` font-variation-settings by default.
+7. Disable tap highlights on iOS
+*/
+
+html,
+:host {
+  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, 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 */
+  -webkit-tap-highlight-color: transparent;
+  /* 7 */
+}
+
+/*
+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. Use the user's configured `mono` font-feature-settings by default.
+3. Use the user's configured `mono` font-variation-settings by default.
+4. 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-feature-settings: normal;
+  /* 2 */
+  font-variation-settings: normal;
+  /* 3 */
+  font-size: 1em;
+  /* 4 */
+}
+
+/*
+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-feature-settings: inherit;
+  /* 1 */
+  font-variation-settings: 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;
+}
+
+/*
+Reset default styling for dialogs.
+*/
+
+dialog {
+  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;
+}
+
+/* Make elements with the HTML hidden attribute stay hidden by default */
+
+[hidden] {
+  display: none;
+}
+
+*, ::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-gradient-from-position:  ;
+  --tw-gradient-via-position:  ;
+  --tw-gradient-to-position:  ;
+  --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-gradient-from-position:  ;
+  --tw-gradient-via-position:  ;
+  --tw-gradient-to-position:  ;
+  --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;
+  }
+}
+
+h1 {
+  margin-top: 1rem;
+  margin-bottom: 1rem;
+  font-size: 1.875rem;
+  line-height: 2.25rem;
+  font-weight: 800;
+}
+
+h2 {
+  margin-top: 1rem;
+  margin-bottom: 1rem;
+  font-size: 1.5rem;
+  line-height: 2rem;
+  font-weight: 700;
+}
+
+h3 {
+  margin-top: 1rem;
+  margin-bottom: 1rem;
+  font-size: 1.25rem;
+  line-height: 1.75rem;
+  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;
+  height: 1px;
+  padding: 0;
+  margin: -1px;
+  overflow: hidden;
+  clip: rect(0, 0, 0, 0);
+  white-space: nowrap;
+  border-width: 0;
+}
+
+.visible {
+  visibility: visible;
+}
+
+.static {
+  position: static;
+}
+
+.fixed {
+  position: fixed;
+}
+
+.absolute {
+  position: absolute;
+}
+
+.relative {
+  position: relative;
+}
+
+.sticky {
+  position: sticky;
+}
+
+.inset-x-0 {
+  left: 0px;
+  right: 0px;
+}
+
+.-left-3 {
+  left: -0.75rem;
+}
+
+.-right-3 {
+  right: -0.75rem;
+}
+
+.bottom-0 {
+  bottom: 0px;
+}
+
+.left-0 {
+  left: 0px;
+}
+
+.top-0 {
+  top: 0px;
+}
+
+.top-1\/2 {
+  top: 50%;
+}
+
+.z-20 {
+  z-index: 20;
+}
+
+.z-30 {
+  z-index: 30;
+}
+
+.z-\[60\] {
+  z-index: 60;
+}
+
+.m-8 {
+  margin: 2rem;
+}
+
+.mx-auto {
+  margin-left: auto;
+  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;
+}
+
+.my-6 {
+  margin-top: 1.5rem;
+  margin-bottom: 1.5rem;
+}
+
+.-ml-px {
+  margin-left: -1px;
+}
+
+.mb-10 {
+  margin-bottom: 2.5rem;
+}
+
+.mb-12 {
+  margin-bottom: 3rem;
+}
+
+.mb-2 {
+  margin-bottom: 0.5rem;
+}
+
+.mb-3 {
+  margin-bottom: 0.75rem;
+}
+
+.mb-4 {
+  margin-bottom: 1rem;
+}
+
+.mb-6 {
+  margin-bottom: 1.5rem;
+}
+
+.mb-8 {
+  margin-bottom: 2rem;
+}
+
+.ml-0 {
+  margin-left: 0px;
+}
+
+.ml-0\.5 {
+  margin-left: 0.125rem;
+}
+
+.ml-2 {
+  margin-left: 0.5rem;
+}
+
+.ml-4 {
+  margin-left: 1rem;
+}
+
+.ml-auto {
+  margin-left: auto;
+}
+
+.mr-1 {
+  margin-right: 0.25rem;
+}
+
+.mr-2 {
+  margin-right: 0.5rem;
+}
+
+.mr-3 {
+  margin-right: 0.75rem;
+}
+
+.mt-0 {
+  margin-top: 0px;
+}
+
+.mt-10 {
+  margin-top: 2.5rem;
+}
+
+.mt-2 {
+  margin-top: 0.5rem;
+}
+
+.mt-4 {
+  margin-top: 1rem;
+}
+
+.block {
+  display: block;
+}
+
+.inline-block {
+  display: inline-block;
+}
+
+.inline {
+  display: inline;
+}
+
+.flex {
+  display: flex;
+}
+
+.table {
+  display: table;
+}
+
+.grid {
+  display: grid;
+}
+
+.hidden {
+  display: none;
+}
+
+.h-1 {
+  height: 0.25rem;
+}
+
+.h-4 {
+  height: 1rem;
+}
+
+.h-5 {
+  height: 1.25rem;
+}
+
+.h-6 {
+  height: 1.5rem;
+}
+
+.h-8 {
+  height: 2rem;
+}
+
+.h-full {
+  height: 100%;
+}
+
+.w-1\/6 {
+  width: 16.666667%;
+}
+
+.w-4 {
+  width: 1rem;
+}
+
+.w-5 {
+  width: 1.25rem;
+}
+
+.w-6 {
+  width: 1.5rem;
+}
+
+.w-8 {
+  width: 2rem;
+}
+
+.w-80 {
+  width: 20rem;
+}
+
+.w-full {
+  width: 100%;
+}
+
+.max-w-3xl {
+  max-width: 48rem;
+}
+
+.max-w-5xl {
+  max-width: 64rem;
+}
+
+.max-w-\[90rem\] {
+  max-width: 90rem;
+}
+
+.flex-1 {
+  flex: 1 1 0%;
+}
+
+.flex-grow {
+  flex-grow: 1;
+}
+
+.-translate-x-full {
+  --tw-translate-x: -100%;
+  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));
+}
+
+.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));
+}
+
+.cursor-pointer {
+  cursor: pointer;
+}
+
+.list-inside {
+  list-style-position: inside;
+}
+
+.list-disc {
+  list-style-type: disc;
+}
+
+.grid-cols-4 {
+  grid-template-columns: repeat(4, minmax(0, 1fr));
+}
+
+.flex-row {
+  flex-direction: row;
+}
+
+.flex-col {
+  flex-direction: column;
+}
+
+.flex-wrap {
+  flex-wrap: wrap;
+}
+
+.items-start {
+  align-items: flex-start;
+}
+
+.items-center {
+  align-items: center;
+}
+
+.justify-end {
+  justify-content: flex-end;
+}
+
+.justify-center {
+  justify-content: center;
+}
+
+.justify-between {
+  justify-content: space-between;
+}
+
+.gap-4 {
+  gap: 1rem;
+}
+
+.gap-x-2 {
+  -moz-column-gap: 0.5rem;
+       column-gap: 0.5rem;
+}
+
+.space-y-1 > :not([hidden]) ~ :not([hidden]) {
+  --tw-space-y-reverse: 0;
+  margin-top: calc(0.25rem * calc(1 - var(--tw-space-y-reverse)));
+  margin-bottom: calc(0.25rem * var(--tw-space-y-reverse));
+}
+
+.space-y-2 > :not([hidden]) ~ :not([hidden]) {
+  --tw-space-y-reverse: 0;
+  margin-top: calc(0.5rem * calc(1 - var(--tw-space-y-reverse)));
+  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 {
+  border-radius: 0.25rem;
+}
+
+.rounded-full {
+  border-radius: 9999px;
+}
+
+.rounded-lg {
+  border-radius: 0.5rem;
+}
+
+.rounded-md {
+  border-radius: 0.375rem;
+}
+
+.rounded-l-md {
+  border-top-left-radius: 0.375rem;
+  border-bottom-left-radius: 0.375rem;
+}
+
+.rounded-r-md {
+  border-top-right-radius: 0.375rem;
+  border-bottom-right-radius: 0.375rem;
+}
+
+.rounded-t {
+  border-top-left-radius: 0.25rem;
+  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;
+}
+
+.border-y {
+  border-top-width: 1px;
+  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-black {
+  --tw-border-opacity: 1;
+  border-color: rgb(0 0 0 / var(--tw-border-opacity));
+}
+
+.border-gray-100 {
+  --tw-border-opacity: 1;
+  border-color: rgb(243 244 246 / var(--tw-border-opacity));
+}
+
+.border-palette-main {
+  --tw-border-opacity: 1;
+  border-color: rgb(64 186 189 / var(--tw-border-opacity));
+}
+
+.border-slate-100 {
+  --tw-border-opacity: 1;
+  border-color: rgb(241 245 249 / var(--tw-border-opacity));
+}
+
+.border-slate-200 {
+  --tw-border-opacity: 1;
+  border-color: rgb(226 232 240 / var(--tw-border-opacity));
+}
+
+.border-transparent {
+  border-color: transparent;
+}
+
+.bg-gray-100 {
+  --tw-bg-opacity: 1;
+  background-color: rgb(243 244 246 / var(--tw-bg-opacity));
+}
+
+.bg-gray-700 {
+  --tw-bg-opacity: 1;
+  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;
+}
+
+.p-1 {
+  padding: 0.25rem;
+}
+
+.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-0 {
+  padding-top: 0px;
+  padding-bottom: 0px;
+}
+
+.py-1 {
+  padding-top: 0.25rem;
+  padding-bottom: 0.25rem;
+}
+
+.py-10 {
+  padding-top: 2.5rem;
+  padding-bottom: 2.5rem;
+}
+
+.py-2 {
+  padding-top: 0.5rem;
+  padding-bottom: 0.5rem;
+}
+
+.py-4 {
+  padding-top: 1rem;
+  padding-bottom: 1rem;
+}
+
+.py-6 {
+  padding-top: 1.5rem;
+  padding-bottom: 1.5rem;
+}
+
+.py-8 {
+  padding-top: 2rem;
+  padding-bottom: 2rem;
+}
+
+.pb-10 {
+  padding-bottom: 2.5rem;
+}
+
+.pl-4 {
+  padding-left: 1rem;
+}
+
+.pr-4 {
+  padding-right: 1rem;
+}
+
+.pr-6 {
+  padding-right: 1.5rem;
+}
+
+.pt-12 {
+  padding-top: 3rem;
+}
+
+.pt-24 {
+  padding-top: 6rem;
+}
+
+.pt-6 {
+  padding-top: 1.5rem;
+}
+
+.text-center {
+  text-align: center;
+}
+
+.align-middle {
+  vertical-align: middle;
+}
+
+.text-2xl {
+  font-size: 1.5rem;
+  line-height: 2rem;
+}
+
+.text-3xl {
+  font-size: 1.875rem;
+  line-height: 2.25rem;
+}
+
+.text-5xl {
+  font-size: 3rem;
+  line-height: 1;
+}
+
+.text-sm {
+  font-size: 0.875rem;
+  line-height: 1.25rem;
+}
+
+.text-xs {
+  font-size: 0.75rem;
+  line-height: 1rem;
+}
+
+.font-bold {
+  font-weight: 700;
+}
+
+.font-medium {
+  font-weight: 500;
+}
+
+.font-normal {
+  font-weight: 400;
+}
+
+.font-semibold {
+  font-weight: 600;
+}
+
+.uppercase {
+  text-transform: uppercase;
+}
+
+.leading-none {
+  line-height: 1;
+}
+
+.leading-normal {
+  line-height: 1.5;
+}
+
+.leading-tight {
+  line-height: 1.25;
+}
+
+.tracking-normal {
+  letter-spacing: 0em;
+}
+
+.text-black {
+  --tw-text-opacity: 1;
+  color: rgb(0 0 0 / var(--tw-text-opacity));
+}
+
+.text-gray-500 {
+  --tw-text-opacity: 1;
+  color: rgb(107 114 128 / var(--tw-text-opacity));
+}
+
+.text-gray-600 {
+  --tw-text-opacity: 1;
+  color: rgb(75 85 99 / var(--tw-text-opacity));
+}
+
+.text-gray-800 {
+  --tw-text-opacity: 1;
+  color: rgb(31 41 55 / var(--tw-text-opacity));
+}
+
+.text-palette-main {
+  --tw-text-opacity: 1;
+  color: rgb(64 186 189 / var(--tw-text-opacity));
+}
+
+.text-pink-600 {
+  --tw-text-opacity: 1;
+  color: rgb(219 39 119 / var(--tw-text-opacity));
+}
+
+.text-pink-800 {
+  --tw-text-opacity: 1;
+  color: rgb(157 23 77 / var(--tw-text-opacity));
+}
+
+.text-slate-500 {
+  --tw-text-opacity: 1;
+  color: rgb(100 116 139 / var(--tw-text-opacity));
+}
+
+.text-slate-700 {
+  --tw-text-opacity: 1;
+  color: rgb(51 65 85 / var(--tw-text-opacity));
+}
+
+.text-slate-900 {
+  --tw-text-opacity: 1;
+  color: rgb(15 23 42 / var(--tw-text-opacity));
+}
+
+.text-white {
+  --tw-text-opacity: 1;
+  color: rgb(255 255 255 / var(--tw-text-opacity));
+}
+
+.underline {
+  text-decoration-line: underline;
+}
+
+.no-underline {
+  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);
+}
+
+.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);
+}
+
+.transition {
+  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-backdrop-filter;
+  transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
+  transition-duration: 150ms;
+}
+
+.transition-all {
+  transition-property: all;
+  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;
+}
+
+#header {
+  box-shadow: 0 1px 2px 0 #eee;
+}
+
+#docs-sidebar {
+  box-shadow: 1px 0 2px 0 #eee;
+}
+
+#header img {
+  margin-bottom: 6px;
+}
+
+.bg-main {
+  background-color: #40BABD;
+}
+
+.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;
+}
+
+.diagram-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;
+}
+
+.filename {
+  background: #f5f2f0;
+  margin-bottom: -0.5rem;
+  margin-top: 0.5rem;
+  padding: 0.5rem 0.75rem;
+  border-bottom: 1px solid #999;
+  font-size: 16px;
+}
+
+.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\:border-slate-400:hover {
+  --tw-border-opacity: 1;
+  border-color: rgb(148 163 184 / var(--tw-border-opacity));
+}
+
+.hover\:bg-gray-200:hover {
+  --tw-bg-opacity: 1;
+  background-color: rgb(229 231 235 / var(--tw-bg-opacity));
+}
+
+.hover\:bg-palette-main:hover {
+  --tw-bg-opacity: 1;
+  background-color: rgb(64 186 189 / var(--tw-bg-opacity));
+}
+
+.hover\:text-gray-600:hover {
+  --tw-text-opacity: 1;
+  color: rgb(75 85 99 / var(--tw-text-opacity));
+}
+
+.hover\:text-gray-800:hover {
+  --tw-text-opacity: 1;
+  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-pink-500:hover {
+  --tw-text-opacity: 1;
+  color: rgb(236 72 153 / var(--tw-text-opacity));
+}
+
+.hover\:text-slate-900:hover {
+  --tw-text-opacity: 1;
+  color: rgb(15 23 42 / var(--tw-text-opacity));
+}
+
+.hover\:text-white:hover {
+  --tw-text-opacity: 1;
+  color: rgb(255 255 255 / var(--tw-text-opacity));
+}
+
+.hover\:underline:hover {
+  text-decoration-line: underline;
+}
+
+.hover\:no-underline:hover {
+  text-decoration-line: none;
+}
+
+.focus\:outline-none:focus {
+  outline: 2px solid transparent;
+  outline-offset: 2px;
+}
+
+@media (min-width: 640px) {
+  .sm\:mb-0 {
+    margin-bottom: 0px;
+  }
+
+  .sm\:flex {
+    display: flex;
+  }
+
+  .sm\:px-6 {
+    padding-left: 1.5rem;
+    padding-right: 1.5rem;
+  }
+}
+
+@media (min-width: 768px) {
+  .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;
+  }
+
+  .md\:mr-0 {
+    margin-right: 0px;
+  }
+
+  .md\:block {
+    display: block;
+  }
+
+  .md\:grid {
+    display: grid;
+  }
+
+  .md\:w-2\/5 {
+    width: 40%;
+  }
+
+  .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;
+  }
+
+  .md\:gap-4 {
+    gap: 1rem;
+  }
+
+  .md\:px-8 {
+    padding-left: 2rem;
+    padding-right: 2rem;
+  }
+
+  .md\:text-left {
+    text-align: left;
+  }
+}
+
+@media (min-width: 1024px) {
+  .lg\:bottom-0 {
+    bottom: 0px;
+  }
+
+  .lg\:left-\[max\(0px\2c calc\(50\%-45rem\)\)\] {
+    left: max(0px,calc(50% - 45rem));
+  }
+
+  .lg\:right-auto {
+    right: auto;
+  }
+
+  .lg\:top-0 {
+    top: 0px;
+  }
+
+  .lg\:z-10 {
+    z-index: 10;
+  }
+
+  .lg\:mx-0 {
+    margin-left: 0px;
+    margin-right: 0px;
+  }
+
+  .lg\:mt-0 {
+    margin-top: 0px;
+  }
+
+  .lg\:block {
+    display: block;
+  }
+
+  .lg\:flex {
+    display: flex;
+  }
+
+  .lg\:hidden {
+    display: none;
+  }
+
+  .lg\:w-auto {
+    width: auto;
+  }
+
+  .lg\:translate-x-0 {
+    --tw-translate-x: 0px;
+    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));
+  }
+
+  .lg\:items-center {
+    align-items: center;
+  }
+
+  .lg\:bg-transparent {
+    background-color: transparent;
+  }
+
+  .lg\:p-0 {
+    padding: 0px;
+  }
+
+  .lg\:pl-\[22rem\] {
+    padding-left: 22rem;
+  }
+
+  .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;
+  }
+}
+
+@media (min-width: 1280px) {
+  .xl\:ml-0 {
+    margin-left: 0px;
+  }
+
+  .xl\:mr-64 {
+    margin-right: 16rem;
+  }
+
+  .xl\:max-w-none {
+    max-width: none;
+  }
+
+  .xl\:pr-16 {
+    padding-right: 4rem;
+  }
+}
+
+:is(:where(.dark) .dark\:border-gray-700) {
+  --tw-border-opacity: 1;
+  border-color: rgb(55 65 81 / var(--tw-border-opacity));
+}
+
+:is(:where(.dark) .dark\:border-slate-200\/5) {
+  border-color: rgb(226 232 240 / 0.05);
+}
+
+:is(:where(.dark) .dark\:border-slate-800) {
+  --tw-border-opacity: 1;
+  border-color: rgb(30 41 59 / var(--tw-border-opacity));
+}
+
+:is(:where(.dark) .dark\:bg-gray-800) {
+  --tw-bg-opacity: 1;
+  background-color: rgb(31 41 55 / var(--tw-bg-opacity));
+}
+
+:is(:where(.dark) .dark\:bg-slate-900) {
+  --tw-bg-opacity: 1;
+  background-color: rgb(15 23 42 / var(--tw-bg-opacity));
+}
+
+:is(:where(.dark) .dark\:text-slate-200) {
+  --tw-text-opacity: 1;
+  color: rgb(226 232 240 / var(--tw-text-opacity));
+}
diff --git a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj
index e137e0a10..d5847d04c 100644
--- a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj
+++ b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj
@@ -1,16 +1,14 @@
 
 
   
-    net6.0
-    enable
     true
     MIT
     zHaytam
     Algorithms for Z.Blazor.Diagrams
-    3.0.1
-    3.0.1
+    3.0.2
+    3.0.2
     https://github.com/zHaytam/Blazor.Diagrams
-    3.0.1
+    3.0.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 163567b7d..9c27f1001 100644
--- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj
+++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj
@@ -1,7 +1,6 @@
 
 
 	
-		net6.0
 		enable
 		false
 		MIT
@@ -18,20 +17,20 @@
 		..\Blazor.Diagrams\sgKey.snk
 	
 
-	
-		
-			True
-			\
-		
-		
-			True
-			\
-		
-	
+  
+    
+      True
+      \
+    
+    
+      True
+      \
+    
+  
 
 	
-	  
-    
+	  
+    
 	
 
 
diff --git a/src/Blazor.Diagrams.Core/tailwind.extension.json b/src/Blazor.Diagrams.Core/tailwind.extension.json
new file mode 100644
index 000000000..296284d65
--- /dev/null
+++ b/src/Blazor.Diagrams.Core/tailwind.extension.json
@@ -0,0 +1 @@
+{"ConfigurationFile":"..\\..\\site\\site\\tailwind.config.js","InputCssFile":null,"OutputCssFile":null}
\ No newline at end of file
diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
index c674d4a8a..6bd0c57c8 100644
--- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj
+++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
@@ -1,7 +1,6 @@
 
 
     
-        net6.0
         enable
         zHaytam, WiseTech Global
         MIT
@@ -19,36 +18,41 @@
     
 
     
-        
-        
-        
+        
+        
+        
+        
     
 
     
         
     
 
-    
-        
-            True
-            \
-        
-        
-            True
-            \
-        
-    
+  
+    
+  
 
-    
-        
-        
-            
-        
-    
+  
+    
+      True
+      \
+    
+    
+      True
+      \
+    
+  
 
-    
-        
-    
+  
+    
+    
+      
+    
+  
+
+  
+    
+  
 
   
     $(TargetsForTfmSpecificBuildOutput);GetBinariesForPackage
diff --git a/src/Blazor.Diagrams/wwwroot/style.css b/src/Blazor.Diagrams/wwwroot/style.css
index 6079a2d2f..b2d6a1355 100644
--- a/src/Blazor.Diagrams/wwwroot/style.css
+++ b/src/Blazor.Diagrams/wwwroot/style.css
@@ -80,6 +80,8 @@
 .diagram-link foreignObject.diagram-link-label {
     overflow: visible;
     pointer-events: none;
+    width: 1px;
+    height: 1px;
 }
 
 div.diagram-control {
diff --git a/src/Blazor.Diagrams/wwwroot/style.min.css b/src/Blazor.Diagrams/wwwroot/style.min.css
index 4abcd9d42..8982e6caf 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;}.diagram-node{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.diagram-node.locked{cursor:pointer;}.diagram-link{pointer-events:visiblePainted;cursor:pointer;}.diagram-navigator{z-index:10;}.diagram-navigator .current-view{position:absolute;border:2px solid #000;}.diagram-group{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.diagram-group .children{position:absolute;overflow:visible;pointer-events:none;}.diagram-link foreignObject.diagram-link-label{overflow:visible;pointer-events:none;}div.diagram-control{position:absolute;}.executable.diagram-control{pointer-events:all;cursor:pointer;}
\ No newline at end of file
+.diagram-canvas{width:100%;height:100%;position:relative;outline:0;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;}.diagram-node{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.diagram-node.locked{cursor:pointer;}.diagram-link{pointer-events:visiblePainted;cursor:pointer;}.diagram-navigator{z-index:10;}.diagram-navigator .current-view{position:absolute;border:2px solid #000;}.diagram-group{position:absolute;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;cursor:move;pointer-events:all;}.diagram-group .children{position:absolute;overflow:visible;pointer-events:none;}.diagram-link foreignObject.diagram-link-label{overflow:visible;pointer-events:none;width:1px;height:1px;}div.diagram-control{position:absolute;}.executable.diagram-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 4a312feec6aef85e5620eea68d1cd461ca9d351d..16ae0407444924c24781cba2eef209c2bd2f986e 100644
GIT binary patch
literal 416
zcmV;R0bl+fiwFP!000003eA*3Zi6roMX!QXbr&PrB^;u?g25A*#(0oDIB8K%)#5Bl#7S=2ef1oR>nwY7VVqw&EAUiUMf{wu}h~PLJ?>b?z*DkEd_lm>`
zU0GyBrTUOwNQ%GAYMDpGfVKT@@7%*GUs|00G4N^ljtnF?PfTMF*Z>w9o?&}>OR3>j
zybCCtQ

literal 409
zcmV;K0cQRmiwFP!000003eA*JQiCuMhOfem<6Fa^k8lXRLLggM8?v2bNoyHT=7UG_
z3a*r*ZPOVaeIoox{(S$kg{$d(~(cbr^SOxGNuu;=bwt+m4UpEd^vGS9-9T
zF+xQHTXax5a`Fc2OSOY~gRJC$`lL-`rlIMSZYImg;cWp4u~k;T
zf?q9C$GtsjBh$7lDhq3tho1n8swQUYWw2oCI*=V0L_x>k6-00xj%S^)*lQQoIqEdZGPb9?~NG~{-h0xYsK;@R24VZD>G_U`)NcwX#Uf9g}p7pnCs+)VAj@W7@KOPi11AD1c
zYC6ls!i%+v2)jg`YVrnr`MBL@qM*s{isH9Z>uK2jyZTO4lC48)Bwd29$0&$UI4J=p}{TMm0@2G-(}Sbi1$vK77n8oH^c9R&aY
D4B*Fl

diff --git a/src/Directory.Build.props b/src/Directory.Build.props
new file mode 100644
index 000000000..6cef34442
--- /dev/null
+++ b/src/Directory.Build.props
@@ -0,0 +1,8 @@
+
+  
+    net8.0;net7.0;net6.0;
+    enable
+    enable
+    true
+  
+
\ 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 ee63923f9..bcb63cd6d 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj
+++ b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj
@@ -1,23 +1,21 @@
 
 
   
-    net6.0
-    enable
     false
     True
     ..\..\src\Blazor.Diagrams\sgKey.snk
   
 
   
-    
-    
-    
-    
-    
+    
+    
+    
+    
+    
       runtime; build; native; contentfiles; analyzers; buildtransitive
       all
     
-    
+    
       runtime; build; native; contentfiles; analyzers; buildtransitive
       all
     
diff --git a/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs b/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs
index 22bd86d95..0929bfe4d 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs
@@ -2,14 +2,8 @@
 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.Options;
 using Moq;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
 using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests.Controls
diff --git a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj
index a2895c117..9d94a73ad 100644
--- a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj
+++ b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj
@@ -1,28 +1,20 @@
-
+
 
   
-    net6.0
-    enable
     false
-	  true
+    true
     True
     ..\..\src\Blazor.Diagrams\sgKey.snk
   
 
   
-    
-    
-    
-    
-    
-    
-      runtime; build; native; contentfiles; analyzers; buildtransitive
-      all
-    
-    
-      runtime; build; native; contentfiles; analyzers; buildtransitive
-      all
-    
+    
+    
+    
+    
+    
+    
+    
   
 
   
diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props
new file mode 100644
index 000000000..6cef34442
--- /dev/null
+++ b/tests/Directory.Build.props
@@ -0,0 +1,8 @@
+
+  
+    net8.0;net7.0;net6.0;
+    enable
+    enable
+    true
+  
+
\ No newline at end of file

From 0afdbe7f4d5da9e0c30da2b7f8c91fdd76cfeda3 Mon Sep 17 00:00:00 2001
From: Heather <30523814+Heathermcx@users.noreply.github.com>
Date: Mon, 27 May 2024 14:59:15 +1000
Subject: [PATCH 095/159] Fix Release Action - *.nupkg version not being
 updated (#36)

* move version props import

* update build props
---
 Directory.Build.props     | 3 ---
 src/Directory.Build.props | 2 ++
 2 files changed, 2 insertions(+), 3 deletions(-)
 delete mode 100644 Directory.Build.props

diff --git a/Directory.Build.props b/Directory.Build.props
deleted file mode 100644
index 2c52bf207..000000000
--- a/Directory.Build.props
+++ /dev/null
@@ -1,3 +0,0 @@
-
-  
-
\ No newline at end of file
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 6cef34442..5d277ac69 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -5,4 +5,6 @@
     enable
     true
   
+
+  
 
\ No newline at end of file

From 6478de3fe139c66ebfe672db829b62aefd406113 Mon Sep 17 00:00:00 2001
From: Heather <30523814+Heathermcx@users.noreply.github.com>
Date: Tue, 28 May 2024 10:57:21 +1000
Subject: [PATCH 096/159] Fix references to make package compatible with our
 code (#37)

---
 src/Blazor.Diagrams/Blazor.Diagrams.csproj | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
index 6bd0c57c8..25e769c6f 100644
--- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj
+++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
@@ -20,18 +20,17 @@
     
         
         
-        
-        
+        
+        
+        
+          ..\..\packages\svgpathproperties\1.1.2\lib\netstandard2.0\SvgPathProperties.dll
+        
     
 
     
         
     
 
-  
-    
-  
-
   
     
       True

From 340530db19f339b1fb377c81e4ee9c510c3c51f2 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Wed, 12 Jun 2024 15:25:37 +1000
Subject: [PATCH 097/159] SelecrtionBos now updates when scrolling

---
 .../Behaviors/SelectionBoxBehavior.cs         | 30 ++++++++++++++---
 .../Behaviors/SelectionBoxBehaviorTests.cs    | 32 +++++++++++++++++++
 2 files changed, 58 insertions(+), 4 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs
index 3364becd3..8bface1f5 100644
--- a/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs
+++ b/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs
@@ -12,6 +12,9 @@ public class SelectionBoxBehavior : DragBehavior
         private Point? _initialClientPoint;
 
         public event EventHandler? SelectionBoundsChanged;
+        private double? _lastClientX;
+        private double? _lastClientY;
+        private Point? _initialPan;
 
         public SelectionBoxBehavior(Diagram diagram)
             : base(diagram)
@@ -19,6 +22,7 @@ public SelectionBoxBehavior(Diagram diagram)
             Diagram.PointerDown += OnPointerDown;
             Diagram.PointerMove += OnPointerMove;
             Diagram.PointerUp += OnPointerUp;
+            Diagram.PanChanged += OnPanChanged;
         }
 
         public override void Dispose()
@@ -26,6 +30,7 @@ public override void Dispose()
             Diagram.PointerDown -= OnPointerDown;
             Diagram.PointerMove -= OnPointerMove;
             Diagram.PointerUp -= OnPointerUp;
+            Diagram.PanChanged -= OnPanChanged;
         }
 
         protected override void OnPointerDown(Model? model, PointerEventArgs e)
@@ -34,6 +39,9 @@ protected override void OnPointerDown(Model? model, PointerEventArgs e)
                 return;
 
             _initialClientPoint = new Point(e.ClientX, e.ClientY);
+            _lastClientX = e.ClientX;
+            _lastClientY = e.ClientY;
+            _initialPan = Diagram.Pan;
         }
 
         protected override void OnPointerMove(Model? model, PointerEventArgs e)
@@ -41,7 +49,10 @@ protected override void OnPointerMove(Model? model, PointerEventArgs e)
             if (_initialClientPoint == null)
                 return;
 
-            UpdateSelectionBox(e);
+            _lastClientX = e.ClientX;
+            _lastClientY = e.ClientY;
+
+            UpdateSelectionBox(e.ClientX, e.ClientY);
 
             var start = Diagram.GetRelativeMousePoint(_initialClientPoint.X, _initialClientPoint.Y);
             var end = Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY);
@@ -61,10 +72,10 @@ protected override void OnPointerMove(Model? model, PointerEventArgs e)
             }
         }
 
-        void UpdateSelectionBox(MouseEventArgs e)
+        void UpdateSelectionBox(double clientX, double clientY)
         {
-            var start = Diagram.GetRelativePoint(_initialClientPoint!.X, _initialClientPoint.Y);
-            var end = Diagram.GetRelativePoint(e.ClientX, e.ClientY);
+            var start = Diagram.GetRelativePoint(_initialClientPoint!.X + Diagram.Pan.X - _initialPan.X, _initialClientPoint.Y + Diagram.Pan.Y - _initialPan.Y);
+            var end = Diagram.GetRelativePoint(clientX, clientY);
             var (sX, sY) = (Math.Min(start.X, end.X), Math.Min(start.Y, end.Y));
             var (eX, eY) = (Math.Max(start.X, end.X), Math.Max(start.Y, end.Y));
             SelectionBoundsChanged?.Invoke(this, new Rectangle(sX, sY, eX, eY));
@@ -74,6 +85,17 @@ protected override void OnPointerUp(Model? model, PointerEventArgs e)
         {
             _initialClientPoint = null;
             SelectionBoundsChanged?.Invoke(this, null);
+            _lastClientX = null;
+            _lastClientY = null;
+            _initialPan = null;
+        }
+
+        public void OnPanChanged(double deltaX, double deltaY)
+        {
+            if (_initialClientPoint == null || _lastClientX == null || _lastClientY == null)
+                return;
+
+            UpdateSelectionBox((double) _lastClientX, (double) _lastClientY);
         }
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs
index ba0d94d6d..9286d7ecc 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs
@@ -37,6 +37,38 @@ public void Behavior_WhenBehaviorEnabled_ShouldUpdateSelectionBounds()
             Assert.Equal(50, lastBounds.Height);
             Assert.Equal(100, lastBounds.Top);
             Assert.Equal(100, lastBounds.Left);
+        }        
+        
+        [Fact]
+        public void Behavior_WhenBehaviorEnabled_ShouldUpdateSelectionBoundsOnScroll()
+        {
+            // Arrange
+            var diagram = new TestDiagram();
+            diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior();
+            diagram.BehaviorOptions.DiagramShiftDragBehavior = diagram.GetBehavior();
+            diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior();
+            diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100)));
+
+            var selectionBoxBehavior = diagram.GetBehavior()!;
+            bool boundsChangedEventInvoked = false;
+            Rectangle? lastBounds = null;
+            selectionBoxBehavior.SelectionBoundsChanged += (_, newBounds) =>
+            {
+                boundsChangedEventInvoked = true;
+                lastBounds = newBounds;
+            };
+
+            // Act
+            diagram.TriggerPointerDown(null,
+                new PointerEventArgs(100, 100, 0, 0, false, true, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
+            diagram.TriggerWheel(new WheelEventArgs(100, 100,0,0,false,true,false,200,150,0,0));
+
+            // Assert
+            Assert.True(boundsChangedEventInvoked);
+            Assert.Equal(200, lastBounds!.Width);
+            Assert.Equal(150, lastBounds.Height);
+            Assert.Equal(-50, lastBounds.Top);
+            Assert.Equal(-100, lastBounds.Left);
         }
 
         [Fact]

From fe69e733bbf7a2c3ba0ce5284e21625fc49484c3 Mon Sep 17 00:00:00 2001
From: Gulam Nabi Azad 
Date: Wed, 12 Jun 2024 13:01:06 +0530
Subject: [PATCH 098/159] WI00737678---RemoveDragNodesBehavior

---
 .../Behaviors/DragMovablesBehavior.cs         | 29 +++++++++----------
 1 file changed, 13 insertions(+), 16 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
index 3a01eed76..650fcd9bc 100644
--- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
+++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
@@ -8,25 +8,22 @@
 
 namespace Blazor.Diagrams.Core.Behaviors;
 
-public class DragMovablesBehavior : Behavior
+public class DragMovablesBehavior : DragBehavior
 {
     private readonly Dictionary _initialPositions;
-    private double? _lastClientX;
-    private double? _lastClientY;
-    private bool _moved;
-    private double _totalMovedX = 0;
-    private double _totalMovedY = 0;
+    protected double? _lastClientX;
+    protected double? _lastClientY;
+    protected bool _moved;
+    protected double _totalMovedX = 0;
+    protected double _totalMovedY = 0;
 
     public DragMovablesBehavior(Diagram diagram) : base(diagram)
     {
         _initialPositions = new Dictionary();
-        Diagram.PointerDown += OnPointerDown;
-        Diagram.PointerMove += OnPointerMove;
-        Diagram.PointerUp += OnPointerUp;
         Diagram.PanChanged += OnPanChanged;
     }
 
-    private void OnPointerDown(Model? model, PointerEventArgs e)
+    protected override void OnPointerDown(Model? model, PointerEventArgs e)
     {
         if (model is not MovableModel)
             return;
@@ -56,7 +53,7 @@ private void OnPointerDown(Model? model, PointerEventArgs e)
         _moved = false;
     }
 
-    private void OnPointerMove(Model? model, PointerEventArgs e)
+    protected override void OnPointerMove(Model? model, PointerEventArgs e)
     {
         if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null)
             return;
@@ -68,13 +65,13 @@ private void OnPointerMove(Model? model, PointerEventArgs e)
         _totalMovedX += deltaX;
         _totalMovedY += deltaY;
 
-        MoveNodes(model, _totalMovedX, _totalMovedY);
+        MoveNodes(_totalMovedX, _totalMovedY);
 
         _lastClientX = e.ClientX;
         _lastClientY = e.ClientY;
 
     }
-    public void OnPanChanged(double deltaX, double deltaY)
+    protected virtual void OnPanChanged(double deltaX, double deltaY)
     {
         if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null)
             return;
@@ -84,10 +81,10 @@ public void OnPanChanged(double deltaX, double deltaY)
         _totalMovedX += deltaX;
         _totalMovedY += deltaY;
 
-        MoveNodes(null, _totalMovedX, _totalMovedY);
+        MoveNodes(_totalMovedX, _totalMovedY);
     }
 
-    private void MoveNodes(Model? model, double deltaX, double deltaY)
+    protected virtual void MoveNodes(double deltaX, double deltaY)
     {
         foreach (var (movable, initialPosition) in _initialPositions)
         {
@@ -104,7 +101,7 @@ private void MoveNodes(Model? model, double deltaX, double deltaY)
         }
     }
 
-    private void OnPointerUp(Model? model, PointerEventArgs e)
+    protected override void OnPointerUp(Model? model, PointerEventArgs e)
     {
         if (_initialPositions.Count == 0)
             return;

From 37ee1a0d9830288719bfbcf0e6242fe9a0f07d5a Mon Sep 17 00:00:00 2001
From: Gulam Nabi Azad 
Date: Wed, 12 Jun 2024 13:04:15 +0530
Subject: [PATCH 099/159] WI00737678---RemoveDragNodesBehavior---Update-1

---
 src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
index 650fcd9bc..e8418d021 100644
--- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
+++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
@@ -132,9 +132,6 @@ private double ApplyGridSize(double n)
     public override void Dispose()
     {
         _initialPositions.Clear();
-        Diagram.PointerDown -= OnPointerDown;
-        Diagram.PointerMove -= OnPointerMove;
-        Diagram.PointerUp -= OnPointerUp;
         Diagram.PanChanged -= OnPanChanged;
     }
 }

From c3df21f264eb7bdc094fa1a34458fd512722801d Mon Sep 17 00:00:00 2001
From: Gulam Nabi Azad 
Date: Wed, 12 Jun 2024 13:12:02 +0530
Subject: [PATCH 100/159] WI00737678---RemoveDragNodesBehavior---Update-2

---
 src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
index e8418d021..a48ea29b5 100644
--- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
+++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
@@ -8,7 +8,7 @@
 
 namespace Blazor.Diagrams.Core.Behaviors;
 
-public class DragMovablesBehavior : DragBehavior
+public class DragMovablesBehavior : Behavior
 {
     private readonly Dictionary _initialPositions;
     protected double? _lastClientX;

From 2866a8bce058aa840d0fbec1516c41bec1f9bfbe Mon Sep 17 00:00:00 2001
From: Gulam Nabi Azad 
Date: Wed, 12 Jun 2024 13:13:24 +0530
Subject: [PATCH 101/159] WI00737678---RemoveDragNodesBehavior---Update-3

---
 src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
index a48ea29b5..e8418d021 100644
--- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
+++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
@@ -8,7 +8,7 @@
 
 namespace Blazor.Diagrams.Core.Behaviors;
 
-public class DragMovablesBehavior : Behavior
+public class DragMovablesBehavior : DragBehavior
 {
     private readonly Dictionary _initialPositions;
     protected double? _lastClientX;

From 9d6fc63c776c19d1b91bfb9b3d8c9f66fdc11a57 Mon Sep 17 00:00:00 2001
From: Archer Chang 
Date: Mon, 17 Jun 2024 13:49:34 +1000
Subject: [PATCH 102/159] refine the existing tests for resizing to minimal
 dimension

---
 .../BottomLeftResizerProviderTests.cs         | 19 ++++++++++---------
 .../Resizing/TopLeftResizerProviderTests.cs   | 19 ++++++++++---------
 .../Resizing/TopRightResizerProviderTests.cs  | 19 ++++++++++---------
 3 files changed, 30 insertions(+), 27 deletions(-)

diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
index 251795526..e7ae20dc4 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
@@ -81,7 +81,8 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         var diagram = new TestDiagram();
         diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
         var node = new NodeModel(position: new Point(0, 0));
-        node.Size = new Size(100, 200);
+        node.Size = new Size(300, 300);
+        node.MinimumDimensions = new Size(50, 100);
         var control = new ResizeControl(new BottomLeftResizerProvider());
         diagram.Controls.AddFor(node).Add(control);
         diagram.SelectModel(node, false);
@@ -89,22 +90,22 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         // before resize
         node.Position.X.Should().Be(0);
         node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        node.Size.Width.Should().Be(300);
+        node.Size.Height.Should().Be(300);
 
         // resize
-        var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+        var eventArgs = new PointerEventArgs(0, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
         control.OnPointerDown(diagram, node, eventArgs);
-        eventArgs = new PointerEventArgs(99, -199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+        eventArgs = new PointerEventArgs(150, 150, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
         diagram.TriggerPointerMove(null, eventArgs);
-        eventArgs = new PointerEventArgs(300, -300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+        eventArgs = new PointerEventArgs(400, -100, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(99);
+        node.Position.X.Should().Be(250);
         node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(0);
-        node.Size.Height.Should().Be(0);
+        node.Size.Width.Should().Be(50);
+        node.Size.Height.Should().Be(100);
     }
 
     [Fact]
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
index 75de6d326..4bb44293d 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
@@ -82,7 +82,8 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         var diagram = new TestDiagram();
         diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
         var node = new NodeModel(position: new Point(0, 0));
-        node.Size = new Size(100, 200);
+        node.Size = new Size(300, 300);
+        node.MinimumDimensions = new Size(50, 100);
         var control = new ResizeControl(new TopLeftResizerProvider());
         diagram.Controls.AddFor(node).Add(control);
         diagram.SelectModel(node, false);
@@ -90,22 +91,22 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         // before resize
         node.Position.X.Should().Be(0);
         node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        node.Size.Width.Should().Be(300);
+        node.Size.Height.Should().Be(300);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
         control.OnPointerDown(diagram, node, eventArgs);
-        eventArgs = new PointerEventArgs(99, 199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+        eventArgs = new PointerEventArgs(150, 150, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
         diagram.TriggerPointerMove(null, eventArgs);
-        eventArgs = new PointerEventArgs(300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+        eventArgs = new PointerEventArgs(400, 400, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(99);
-        node.Position.Y.Should().Be(199);
-        node.Size.Width.Should().Be(0);
-        node.Size.Height.Should().Be(0);
+        node.Position.X.Should().Be(250);
+        node.Position.Y.Should().Be(200);
+        node.Size.Width.Should().Be(50);
+        node.Size.Height.Should().Be(100);
     }
 
     [Fact]
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
index 5071a423d..84b1d9d37 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
@@ -80,7 +80,8 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         var diagram = new TestDiagram();
         diagram.SetContainer(new Rectangle(0, 0, 1000, 400));
         var node = new NodeModel(position: new Point(0, 0));
-        node.Size = new Size(100, 200);
+        node.Size = new Size(300, 300);
+        node.MinimumDimensions = new Size(50, 100);
         var control = new ResizeControl(new TopRightResizerProvider());
         diagram.Controls.AddFor(node).Add(control);
         diagram.SelectModel(node, false);
@@ -88,22 +89,22 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         // before resize
         node.Position.X.Should().Be(0);
         node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        node.Size.Width.Should().Be(300);
+        node.Size.Height.Should().Be(300);
 
         // resize
-        var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+        var eventArgs = new PointerEventArgs(300, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
         control.OnPointerDown(diagram, node, eventArgs);
-        eventArgs = new PointerEventArgs(-99, 199, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+        eventArgs = new PointerEventArgs(150, 150, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
         diagram.TriggerPointerMove(null, eventArgs);
-        eventArgs = new PointerEventArgs(-300, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
+        eventArgs = new PointerEventArgs(-100, 400, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
         node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(199);
-        node.Size.Width.Should().Be(0);
-        node.Size.Height.Should().Be(0);
+        node.Position.Y.Should().Be(200);
+        node.Size.Width.Should().Be(50);
+        node.Size.Height.Should().Be(100);
     }
 
     [Fact]

From 1952c74cd35de992fcba35edbc38a73a6dbc6127 Mon Sep 17 00:00:00 2001
From: Archer Chang 
Date: Mon, 17 Jun 2024 13:56:32 +1000
Subject: [PATCH 103/159] fix calculation of resizing to minimal size

---
 .../Positions/Resizing/ResizerProvider.cs            | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
index 8b84ebec6..78e7eebe4 100644
--- a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
+++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs
@@ -41,12 +41,20 @@ virtual public (Size size, Point position) CalculateNewSizeAndPosition(double de
             if (width < NodeModel!.MinimumDimensions.Width)
             {
                 width = NodeModel.MinimumDimensions.Width;
-                positionX = NodeModel.Position.X;
+
+                if (ShouldChangeXPositionOnResize)
+                {
+                    positionX = OriginalPosition.X + OriginalSize.Width - NodeModel.MinimumDimensions.Width;
+                }
             }
             if (height < NodeModel.MinimumDimensions.Height)
             {
                 height = NodeModel.MinimumDimensions.Height;
-                positionY = NodeModel.Position.Y;
+
+                if (ShouldChangeYPositionOnResize)
+                {
+                    positionY = OriginalPosition.Y + OriginalSize.Height - NodeModel.MinimumDimensions.Height;
+                }
             }
 
             return (new Size(width, height), new Point(positionX, positionY));

From 6583d528358b14e422a57d51769b0522b96701c8 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Mon, 17 Jun 2024 14:23:06 +1000
Subject: [PATCH 104/159] Fixed PR comments

---
 .../Behaviors/SelectionBoxBehavior.cs         | 50 ++++++++++++-------
 1 file changed, 32 insertions(+), 18 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs
index 8bface1f5..98a679749 100644
--- a/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs
+++ b/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs
@@ -53,28 +53,17 @@ protected override void OnPointerMove(Model? model, PointerEventArgs e)
             _lastClientY = e.ClientY;
 
             UpdateSelectionBox(e.ClientX, e.ClientY);
-
-            var start = Diagram.GetRelativeMousePoint(_initialClientPoint.X, _initialClientPoint.Y);
-            var end = Diagram.GetRelativeMousePoint(e.ClientX, e.ClientY);
-            var (sX, sY) = (Math.Min(start.X, end.X), Math.Min(start.Y, end.Y));
-            var (eX, eY) = (Math.Max(start.X, end.X), Math.Max(start.Y, end.Y));
-            var bounds = new Rectangle(sX, sY, eX, eY);
-
-            foreach (var node in Diagram.Nodes)
-            {
-                var nodeBounds = node.GetBounds();
-                if (nodeBounds == null)
-                    continue;
-
-                if (bounds.Overlap(nodeBounds))
-                    Diagram.SelectModel(node, false);
-                else if (node.Selected) Diagram.UnselectModel(node);
-            }
+            SelectNodesInBounds(e.ClientX, e.ClientY);
         }
 
         void UpdateSelectionBox(double clientX, double clientY)
         {
-            var start = Diagram.GetRelativePoint(_initialClientPoint!.X + Diagram.Pan.X - _initialPan.X, _initialClientPoint.Y + Diagram.Pan.Y - _initialPan.Y);
+            if(_initialClientPoint == null || _initialPan == null)
+            {
+                return;
+            }
+
+            var start = Diagram.GetRelativePoint(_initialClientPoint.X + Diagram.Pan.X - _initialPan.X, _initialClientPoint.Y + Diagram.Pan.Y - _initialPan.Y);
             var end = Diagram.GetRelativePoint(clientX, clientY);
             var (sX, sY) = (Math.Min(start.X, end.X), Math.Min(start.Y, end.Y));
             var (eX, eY) = (Math.Max(start.X, end.X), Math.Max(start.Y, end.Y));
@@ -96,6 +85,31 @@ public void OnPanChanged(double deltaX, double deltaY)
                 return;
 
             UpdateSelectionBox((double) _lastClientX, (double) _lastClientY);
+            SelectNodesInBounds((double) _lastClientX, (double) _lastClientY);
+        }
+
+        void SelectNodesInBounds(double clientX, double clientY)
+        {
+            if(_initialClientPoint == null || _initialPan == null)
+            {
+                return;
+            }
+
+            var start = Diagram.GetRelativeMousePoint(_initialClientPoint.X + Diagram.Pan.X - _initialPan.X, _initialClientPoint.Y + Diagram.Pan.Y - _initialPan.Y);
+            var end = Diagram.GetRelativeMousePoint(clientX, clientY);
+            var (sX, sY) = (Math.Min(start.X, end.X), Math.Min(start.Y, end.Y));
+            var (eX, eY) = (Math.Max(start.X, end.X), Math.Max(start.Y, end.Y));
+            var bounds = new Rectangle(sX, sY, eX, eY);
+
+            foreach (var node in Diagram.Nodes)
+            {
+                var nodeBounds = node.GetBounds();
+                if (nodeBounds == null)
+                    continue;
+                if (bounds.Overlap(nodeBounds))
+                    Diagram.SelectModel(node, false);
+                else if (node.Selected) Diagram.UnselectModel(node);
+            }
         }
     }
 }

From 7edf131252dc1b4696af7420af3eacd0ecb174c6 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Wed, 19 Jun 2024 16:04:25 +1000
Subject: [PATCH 105/159] Added tests

---
 .../Behaviors/SelectionBoxBehaviorTests.cs    | 66 ++++++++++++++++++-
 1 file changed, 63 insertions(+), 3 deletions(-)

diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs
index 9286d7ecc..6aa112545 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs
@@ -37,8 +37,8 @@ public void Behavior_WhenBehaviorEnabled_ShouldUpdateSelectionBounds()
             Assert.Equal(50, lastBounds.Height);
             Assert.Equal(100, lastBounds.Top);
             Assert.Equal(100, lastBounds.Left);
-        }        
-        
+        }
+
         [Fact]
         public void Behavior_WhenBehaviorEnabled_ShouldUpdateSelectionBoundsOnScroll()
         {
@@ -61,7 +61,7 @@ public void Behavior_WhenBehaviorEnabled_ShouldUpdateSelectionBoundsOnScroll()
             // Act
             diagram.TriggerPointerDown(null,
                 new PointerEventArgs(100, 100, 0, 0, false, true, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
-            diagram.TriggerWheel(new WheelEventArgs(100, 100,0,0,false,true,false,200,150,0,0));
+            diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, true, false, 200, 150, 0, 0));
 
             // Assert
             Assert.True(boundsChangedEventInvoked);
@@ -131,6 +131,37 @@ public void Behavior_WithBoundsChangedDelegate_ShouldSelectNodesInsideArea()
             Assert.False(node.Selected);
         }
 
+        [Fact]
+        public void Behavior_WithBoundsChangedDelegate_ShouldSelectNodesInsideAreaWhenScrolling()
+        {
+            // Arrange
+            var diagram = new TestDiagram();
+            diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior();
+            diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100)));
+
+            var selectionBoxBehavior = diagram.GetBehavior()!;
+            selectionBoxBehavior.SelectionBoundsChanged += (_, _) => { };
+
+            var node = new NodeModel()
+            {
+                Size = new Size(100, 100),
+                Position = new Point(150, 150)
+            };
+            diagram.Nodes.Add(node);
+
+            // Act
+            diagram.TriggerPointerDown(null,
+                new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
+            diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, true, false, 200, 200, 0, 0));
+
+            // Assert
+            Assert.True(node.Selected);
+
+            diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, true, false, -200, -200, 0, 0));
+
+            Assert.False(node.Selected);
+        }
+
         [Fact]
         public void Behavior_WithoutBoundsChangedDelegate_ShouldNotSelectNodesInsideArea()
         {
@@ -159,5 +190,34 @@ public void Behavior_WithoutBoundsChangedDelegate_ShouldNotSelectNodesInsideArea
                 new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
             Assert.False(node.Selected);
         }
+
+        [Fact]
+        public void Behavior_WithoutBoundsChangedDelegate_ShouldNotSelectNodesInsideAreaWhenScrolling()
+        {
+            // Arrange
+            var diagram = new TestDiagram();
+            diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior();
+            diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100)));
+
+            var node = new NodeModel()
+            {
+                Size = new Size(100, 100),
+                Position = new Point(150, 150)
+            };
+            diagram.Nodes.Add(node);
+
+            // Act
+            diagram.TriggerPointerDown(null,
+                new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
+            diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, true, false, 200, 200, 0, 0));
+
+
+            // Assert
+            Assert.False(node.Selected);
+
+            diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, true, false, -200, -200, 0, 0));
+
+            Assert.False(node.Selected);
+        }
     }
 }

From 63b8622ee2b33d3b6d038c41ccb6b22eb6c92166 Mon Sep 17 00:00:00 2001
From: Renan Alvarenga 
Date: Wed, 19 Jun 2024 16:13:10 +1000
Subject: [PATCH 106/159] Fixed test issue

---
 .../Behaviors/SelectionBoxBehaviorTests.cs           | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs
index 6aa112545..a394e0333 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs
@@ -136,7 +136,9 @@ public void Behavior_WithBoundsChangedDelegate_ShouldSelectNodesInsideAreaWhenSc
         {
             // Arrange
             var diagram = new TestDiagram();
-            diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior();
+            diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior();
+            diagram.BehaviorOptions.DiagramShiftDragBehavior = diagram.GetBehavior();
+            diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior();
             diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100)));
 
             var selectionBoxBehavior = diagram.GetBehavior()!;
@@ -151,7 +153,7 @@ public void Behavior_WithBoundsChangedDelegate_ShouldSelectNodesInsideAreaWhenSc
 
             // Act
             diagram.TriggerPointerDown(null,
-                new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
+                new PointerEventArgs(100, 100, 0, 0, false, true, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
             diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, true, false, 200, 200, 0, 0));
 
             // Assert
@@ -196,7 +198,9 @@ public void Behavior_WithoutBoundsChangedDelegate_ShouldNotSelectNodesInsideArea
         {
             // Arrange
             var diagram = new TestDiagram();
-            diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior();
+            diagram.BehaviorOptions.DiagramDragBehavior = diagram.GetBehavior();
+            diagram.BehaviorOptions.DiagramShiftDragBehavior = diagram.GetBehavior();
+            diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior();
             diagram.SetContainer(new Rectangle(Point.Zero, new Size(100, 100)));
 
             var node = new NodeModel()
@@ -208,7 +212,7 @@ public void Behavior_WithoutBoundsChangedDelegate_ShouldNotSelectNodesInsideArea
 
             // Act
             diagram.TriggerPointerDown(null,
-                new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
+                new PointerEventArgs(100, 100, 0, 0, false, true, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
             diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, true, false, 200, 200, 0, 0));
 
 

From dd5ca1ee162bde350e47794b2ca2c075a8ca3f3b Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Tue, 2 Jul 2024 17:26:46 +1000
Subject: [PATCH 107/159] experimenting with release.yml

---
 .github/workflows/release.yml | 50 +++++++++++++++++------------------
 1 file changed, 24 insertions(+), 26 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 6ce136ca1..3ad31f093 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -3,49 +3,49 @@ name: Create release
 on:
   push:
     branches: [ master ]
+    tags:
+      - "*"
   workflow_dispatch:
 
 env:
   PACKAGE_PATH: /home/runner/work/Blazor.Diagrams/Blazor.Diagrams/src/Blazor.Diagrams/bin/Release/*.nupkg
 
 jobs:
-  build:
+  publish-pkg:
     name: Build - Release
 
     runs-on: ubuntu-latest
     steps:
-    - uses: actions/checkout@v3
-  
+    - name: Extract tag name
+      id: tag_name
+      uses: actions/github-script@v3.0.0
+      with:
+        github-token: ${{ secrets.GITHUB_TOKEN }}
+        result-encoding: string
+        script: |
+          return context.payload.ref.replace(/^refs\/tags\//, '');
+
+    - name: Checkout
+      uses: actions/checkout@v3
+
     - name: Setup dotnet
       uses: actions/setup-dotnet@v3
       with:
         dotnet-version: |
             6.0.x
             3.1.x
-      
-      # Finds the latest release and increases the version
-    - name: Get next version
-      uses: reecetech/version-increment@2023.9.3
-      id: version
-      with:
-        scheme: semver
-        increment: patch
-
-    - name: Install version tool
-      run: dotnet tool install dotnetCampus.TagToVersion -g --version 1.0.11
-
-      # Writes the new version number to build/Version.props
-    - name: Set version
-      run: dotnet TagToVersion -t ${{ steps.version.outputs.version }}
 
     - name: Install dependencies
       run: dotnet restore
 
-      # Pacakge is created on build
+    # Pacakge is created on build
     - name: Build
       run: dotnet build --configuration Release
 
-      # Upload package as an atrifact to the GitHub action
+    - name: Create NuGet Packages
+      run: dotnet pack src --configuration Release /p:CommitID=${{ github.sha }} /p:TagVersion=${{ steps.tag_name.outputs.result }}
+
+    # Upload package as an atrifact to the GitHub action
     - name: Upload packages
       uses: actions/upload-artifact@v3
       with:
@@ -53,9 +53,7 @@ jobs:
         path: ${{ env.PACKAGE_PATH }}
         retention-days: 5
 
-      # Create a new release and upload the package to the release
-    - name: Release
-      uses: softprops/action-gh-release@v1
-      with:
-        files: ${{ env.PACKAGE_PATH }}
-        tag_name: ${{ steps.version.outputs.version }}
+    - name: Publish NuGet Package to NuGet Gallery
+      run: |
+        nuget setapikey ${{ secrets.NUGET_API_KEY }}
+        nuget push bin/WTG.Z.Blazor.Diagrams.${{ steps.tag_name.outputs.result }}.nupkg -Source https://api.nuget.org/v3/index.json

From eefdd34f14cf649e1f1721f918d860b967fc1752 Mon Sep 17 00:00:00 2001
From: Gulam Nabi Azad 
Date: Tue, 2 Jul 2024 13:20:04 +0530
Subject: [PATCH 108/159] WI00737678---RemoveDragNodesBehavior

---
 .../Behaviors/DragMovablesBehavior.cs         | 51 ++++++++++---------
 1 file changed, 28 insertions(+), 23 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
index e8418d021..c2d270f51 100644
--- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
+++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
@@ -5,12 +5,17 @@
 using Blazor.Diagrams.Core.Models.Base;
 using System;
 using System.Collections.Generic;
-
+using DiagramPoint = Blazor.Diagrams.Core.Geometry.Point;
 namespace Blazor.Diagrams.Core.Behaviors;
 
 public class DragMovablesBehavior : DragBehavior
 {
-    private readonly Dictionary _initialPositions;
+    public record NodeMoveablePositions(Point position)
+    {
+        public Dictionary ChildPositions { get; } = new();
+    }
+
+    protected readonly Dictionary _initialPositions;
     protected double? _lastClientX;
     protected double? _lastClientY;
     protected bool _moved;
@@ -19,19 +24,22 @@ public class DragMovablesBehavior : DragBehavior
 
     public DragMovablesBehavior(Diagram diagram) : base(diagram)
     {
-        _initialPositions = new Dictionary();
+        _initialPositions = new Dictionary();
         Diagram.PanChanged += OnPanChanged;
     }
 
     protected override void OnPointerDown(Model? model, PointerEventArgs e)
     {
-        if (model is not MovableModel)
+        if (model is null)
             return;
 
+        ResetPan();
+
         _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;
 
             // Special case: groups without auto size on
@@ -45,7 +53,7 @@ protected override void OnPointerDown(Model? model, PointerEventArgs e)
                     movable.Position.Y + (n.Size?.Height ?? 0) / 2);
             }
 
-            _initialPositions.Add(movable, position);
+            _initialPositions.Add(movable, new NodeMoveablePositions(position));
         }
 
         _lastClientX = e.ClientX;
@@ -58,7 +66,6 @@ protected override 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;
 
@@ -67,29 +74,31 @@ protected override void OnPointerMove(Model? model, PointerEventArgs e)
 
         MoveNodes(_totalMovedX, _totalMovedY);
 
+        _moved = true;
         _lastClientX = e.ClientX;
         _lastClientY = e.ClientY;
 
     }
+
     protected virtual void OnPanChanged(double deltaX, double deltaY)
     {
         if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null)
             return;
 
-        _moved = true;
-
-        _totalMovedX += deltaX;
-        _totalMovedY += deltaY;
+        _totalMovedX += deltaX / Diagram.Zoom;
+        _totalMovedY += deltaY / Diagram.Zoom;
 
         MoveNodes(_totalMovedX, _totalMovedY);
+
+        _moved = true;
     }
 
     protected virtual void MoveNodes(double deltaX, double deltaY)
     {
         foreach (var (movable, initialPosition) in _initialPositions)
         {
-            var ndx = ApplyGridSize(deltaX + initialPosition.X);
-            var ndy = ApplyGridSize(deltaY + initialPosition.Y);
+            var ndx = ApplyGridSize(deltaX + initialPosition.position.X);
+            var ndy = ApplyGridSize(deltaY + initialPosition.position.Y);
             if (Diagram.Options.GridSnapToCenter && movable is NodeModel node)
             {
                 node.SetPosition(ndx - (node.Size?.Width ?? 0) / 2, ndy - (node.Size?.Height ?? 0) / 2);
@@ -103,21 +112,12 @@ protected virtual void MoveNodes(double deltaX, double deltaY)
 
     protected override void OnPointerUp(Model? model, PointerEventArgs e)
     {
-        if (_initialPositions.Count == 0)
-            return;
-
-        if (_moved)
-        {
-            foreach (var (movable, _) in _initialPositions)
-            {
-                movable.TriggerMoved();
-            }
-        }
         _initialPositions.Clear();
         _totalMovedX = 0;
         _totalMovedY = 0;
         _lastClientX = null;
         _lastClientY = null;
+        _moved = false;
     }
 
     private double ApplyGridSize(double n)
@@ -129,6 +129,11 @@ private double ApplyGridSize(double n)
         return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize);
     }
 
+    void ResetPan()
+    {
+        Diagram.SetPan(0, 0);
+    }
+
     public override void Dispose()
     {
         _initialPositions.Clear();

From ac98c3d8f3e1f58fb2581fc5b32c8c2c687aab13 Mon Sep 17 00:00:00 2001
From: Gulam Nabi Azad 
Date: Tue, 2 Jul 2024 13:56:39 +0530
Subject: [PATCH 109/159] WI00737678---RemoveDragNodesBehavior---Update-1

---
 .../Behaviors/DragMovablesBehavior.cs                 | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
index c2d270f51..db49df576 100644
--- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
+++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
@@ -112,6 +112,17 @@ protected virtual void MoveNodes(double deltaX, double deltaY)
 
     protected override void OnPointerUp(Model? model, PointerEventArgs e)
     {
+        if (_initialPositions.Count == 0)
+            return;
+
+        if (_moved)
+        {
+            foreach (var (movable, _) in _initialPositions)
+            {
+                movable.TriggerMoved();
+            }
+        }
+
         _initialPositions.Clear();
         _totalMovedX = 0;
         _totalMovedY = 0;

From fa33bfe5732bc4f58afdc962028ebcd342c988f2 Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 11:21:29 +1000
Subject: [PATCH 110/159] attempting to publish-pkg nuget-pack workflow

---
 .github/workflows/release.yml              | 20 +++++--------
 WTG.Z.Blazor.Diagrams.nuspec               | 35 ++++++++++++++++++++++
 src/Blazor.Diagrams/Blazor.Diagrams.csproj |  1 +
 3 files changed, 43 insertions(+), 13 deletions(-)
 create mode 100644 WTG.Z.Blazor.Diagrams.nuspec

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3ad31f093..80fc06fe5 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,8 +1,9 @@
 name: Create release
+
 # On push to master branch. i.e. when we merge a PR.
 on:
   push:
-    branches: [ master ]
+    branches: [ master, VFL/WI00764279/NCN-Diagrams-Move-WTG.Blazor.Diagrams-to-nuget.org ]
     tags:
       - "*"
   workflow_dispatch:
@@ -35,15 +36,8 @@ jobs:
             6.0.x
             3.1.x
 
-    - name: Install dependencies
-      run: dotnet restore
-
-    # Pacakge is created on build
-    - name: Build
-      run: dotnet build --configuration Release
-
     - name: Create NuGet Packages
-      run: dotnet pack src --configuration Release /p:CommitID=${{ github.sha }} /p:TagVersion=${{ steps.tag_name.outputs.result }}
+      run: dotnet pack --configuration Release /p:CommitID=${{ github.sha }} /p:TagVersion=${{ steps.tag_name.outputs.result }}
 
     # Upload package as an atrifact to the GitHub action
     - name: Upload packages
@@ -53,7 +47,7 @@ jobs:
         path: ${{ env.PACKAGE_PATH }}
         retention-days: 5
 
-    - name: Publish NuGet Package to NuGet Gallery
-      run: |
-        nuget setapikey ${{ secrets.NUGET_API_KEY }}
-        nuget push bin/WTG.Z.Blazor.Diagrams.${{ steps.tag_name.outputs.result }}.nupkg -Source https://api.nuget.org/v3/index.json
+    # - name: Publish NuGet Package to NuGet Gallery
+    #   run: |
+    #     nuget setapikey ${{ secrets.NUGET_API_KEY }}
+    #     nuget push bin/WTG.Z.Blazor.Diagrams.${{ steps.tag_name.outputs.result }}.nupkg -Source https://api.nuget.org/v3/index.json
diff --git a/WTG.Z.Blazor.Diagrams.nuspec b/WTG.Z.Blazor.Diagrams.nuspec
new file mode 100644
index 000000000..43ee2fecb
--- /dev/null
+++ b/WTG.Z.Blazor.Diagrams.nuspec
@@ -0,0 +1,35 @@
+
+
+    
+        WTG.Analyzers
+        $version$
+        $company$
+        $company$
+        LICENSE
+        https://github.com/WiseTechGlobal/Blazor.Diagrams
+        Z.Blazor.Diagrams is a fully customizable and extensible all-purpose diagrams
+            library for Blazor (both Server Side and WASM). It was first inspired by the popular
+            React library react-diagrams, but then evolved into something much bigger. ZBD can be
+            used to make advanced diagrams with a custom design. Even the behavior of the library is
+            "hackable" and can be changed to suit your needs.
+        $copyright$
+        
+        
+        true
+    
+
+    
+        
+        
+        
+
+        
+        
+        
+
+        
+        
+        
+    
+
\ No newline at end of file
diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
index 25e769c6f..493287d0b 100644
--- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj
+++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
@@ -15,6 +15,7 @@
         README.md
         True
         sgKey.snk
+        ../../WTG.Z.Blazor.Diagrams.nuspec
     
 
     

From 467a34749a30aac42eb40ebbe18c84d9688494c7 Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 11:36:03 +1000
Subject: [PATCH 111/159] attempting to build first

---
 .github/workflows/release.yml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 80fc06fe5..70de59a81 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -36,6 +36,12 @@ jobs:
             6.0.x
             3.1.x
 
+    - name: Install dependencies
+      run: dotnet restore
+
+    - name: Build
+      run: dotnet build --configuration Release
+
     - name: Create NuGet Packages
       run: dotnet pack --configuration Release /p:CommitID=${{ github.sha }} /p:TagVersion=${{ steps.tag_name.outputs.result }}
 

From 475b8ad0941d19c8e96a06025f160befaab29aec Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 11:38:37 +1000
Subject: [PATCH 112/159] removed whitespace from build.yml

---
 .github/workflows/build.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 1accd03d5..392ef4ef3 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -8,7 +8,7 @@ on:
 jobs:
   build:
     name: Build - ${{ matrix.configuration }}
-  
+
     strategy:
       matrix:
         configuration: [ Debug, Release ]
@@ -16,7 +16,7 @@ jobs:
     runs-on: ubuntu-latest
     steps:
     - uses: actions/checkout@v3
-  
+
     - name: Setup dotnet
       uses: actions/setup-dotnet@v3
       with:

From c9904354d5adb46b5d9730587a9595164f6e21ab Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 11:49:49 +1000
Subject: [PATCH 113/159] alternative tag extraction using battila action

---
 .github/workflows/release.yml | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 70de59a81..4bb6d721d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -16,15 +16,11 @@ jobs:
     name: Build - Release
 
     runs-on: ubuntu-latest
+
     steps:
-    - name: Extract tag name
-      id: tag_name
-      uses: actions/github-script@v3.0.0
-      with:
-        github-token: ${{ secrets.GITHUB_TOKEN }}
-        result-encoding: string
-        script: |
-          return context.payload.ref.replace(/^refs\/tags\//, '');
+    - name: Extract Version
+      id: version
+      uses: battila/get-version-action@v2
 
     - name: Checkout
       uses: actions/checkout@v3
@@ -43,7 +39,7 @@ jobs:
       run: dotnet build --configuration Release
 
     - name: Create NuGet Packages
-      run: dotnet pack --configuration Release /p:CommitID=${{ github.sha }} /p:TagVersion=${{ steps.tag_name.outputs.result }}
+      run: dotnet pack --configuration Release /p:CommitID=${{ github.sha }} /p:TagVersion=${{ steps.version.outputs.version-without-v }}
 
     # Upload package as an atrifact to the GitHub action
     - name: Upload packages

From 4392197173639a550fba456b6cf5561ae97fb3f0 Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 12:07:26 +1000
Subject: [PATCH 114/159] fixed tag extraction uses action

---
 .github/workflows/release.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 4bb6d721d..afcc72da4 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -20,7 +20,7 @@ jobs:
     steps:
     - name: Extract Version
       id: version
-      uses: battila/get-version-action@v2
+      uses: battila7/get-version-action@v2
 
     - name: Checkout
       uses: actions/checkout@v3

From 87fe8e32c00a2c4ceb428ab5a38a9ed392775a01 Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 12:13:53 +1000
Subject: [PATCH 115/159] printing tags

---
 .github/workflows/release.yml | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index afcc72da4..cdcdc41c6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -22,6 +22,9 @@ jobs:
       id: version
       uses: battila7/get-version-action@v2
 
+    - run: echo ${{ steps.version.outputs.version }}
+    - run: echo ${{ steps.version.outputs.version-without-v }}
+
     - name: Checkout
       uses: actions/checkout@v3
 

From 72068a7a717fa91c1dfb2affa1d90904f19148e9 Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 12:18:20 +1000
Subject: [PATCH 116/159] useing sha for tag for testing

---
 .github/workflows/release.yml | 11 ++++-------
 1 file changed, 4 insertions(+), 7 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index cdcdc41c6..0e0379393 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -18,12 +18,9 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    - name: Extract Version
-      id: version
-      uses: battila7/get-version-action@v2
-
-    - run: echo ${{ steps.version.outputs.version }}
-    - run: echo ${{ steps.version.outputs.version-without-v }}
+    # - name: Extract Version
+    #   id: version
+    #   uses: battila7/get-version-action@v2
 
     - name: Checkout
       uses: actions/checkout@v3
@@ -42,7 +39,7 @@ jobs:
       run: dotnet build --configuration Release
 
     - name: Create NuGet Packages
-      run: dotnet pack --configuration Release /p:CommitID=${{ github.sha }} /p:TagVersion=${{ steps.version.outputs.version-without-v }}
+      run: dotnet pack --configuration Release /p:CommitID=${{ github.sha }} /p:TagVersion=${{ github.sha }}
 
     # Upload package as an atrifact to the GitHub action
     - name: Upload packages

From 16f289831b3c12eb15acc534e6da232d5fceb85a Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 12:34:54 +1000
Subject: [PATCH 117/159] removed build from release

---
 .github/workflows/release.yml | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 0e0379393..f8c3b33a3 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -35,9 +35,6 @@ jobs:
     - name: Install dependencies
       run: dotnet restore
 
-    - name: Build
-      run: dotnet build --configuration Release
-
     - name: Create NuGet Packages
       run: dotnet pack --configuration Release /p:CommitID=${{ github.sha }} /p:TagVersion=${{ github.sha }}
 

From 70c2df8e15115c09574da9fe3e02d5cf9830ecac Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 12:38:05 +1000
Subject: [PATCH 118/159] added back build step include --no-restore and
 --no-build flags

---
 .github/workflows/release.yml | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index f8c3b33a3..8ad92c390 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -35,8 +35,11 @@ jobs:
     - name: Install dependencies
       run: dotnet restore
 
+    - name: Build
+      run: dotnet build --no-restore --configuration Release
+
     - name: Create NuGet Packages
-      run: dotnet pack --configuration Release /p:CommitID=${{ github.sha }} /p:TagVersion=${{ github.sha }}
+      run: dotnet pack --no-restore --no-build --configuration Release /p:CommitID=${{ github.sha }} /p:TagVersion=${{ github.sha }}
 
     # Upload package as an atrifact to the GitHub action
     - name: Upload packages

From 3ff95b11caa0c6d0f45b69f218e25426d282aedf Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 12:46:03 +1000
Subject: [PATCH 119/159] moved nuspec and build files, added targets file

---
 src/Directory.Build.props                         | 14 ++++++++++++++
 src/Directory.Build.targets                       | 15 +++++++++++++++
 .../WTG.Z.Blazor.Diagrams.nuspec                  |  0
 3 files changed, 29 insertions(+)
 create mode 100644 src/Directory.Build.targets
 rename WTG.Z.Blazor.Diagrams.nuspec => src/WTG.Z.Blazor.Diagrams.nuspec (100%)

diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 5d277ac69..7c399dac7 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -4,6 +4,20 @@
     enable
     enable
     true
+
+    
+    3.7.2
+    $(TagVersion.Split('-', 2)[0])
+    
+      $(TagVersion.Substring($(ShortVersion.Length)))
+
+    
+      $(ShortVersion).$(GITHUB_RUN_NUMBER)
+    $(ShortVersion).0
   
 
   
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
new file mode 100644
index 000000000..8d4e146f0
--- /dev/null
+++ b/src/Directory.Build.targets
@@ -0,0 +1,15 @@
+
+  
+    false
+  
+  
+    true
+    
+      id=$(PackageId);
+      version=$(ShortVersion)$(VersionSuffix);
+      company=$(Company);
+      copyright=$(Copyright);
+      commitid=$(CommitID);
+    
+  
+
\ No newline at end of file
diff --git a/WTG.Z.Blazor.Diagrams.nuspec b/src/WTG.Z.Blazor.Diagrams.nuspec
similarity index 100%
rename from WTG.Z.Blazor.Diagrams.nuspec
rename to src/WTG.Z.Blazor.Diagrams.nuspec

From 56e41c722cb21a902e9ed0262101fa525ff04d89 Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 12:49:12 +1000
Subject: [PATCH 120/159] release.yml just packs

---
 .github/workflows/release.yml    | 44 ++++++++------------------------
 src/Directory.Build.targets      | 15 -----------
 src/WTG.Z.Blazor.Diagrams.nuspec | 35 -------------------------
 3 files changed, 10 insertions(+), 84 deletions(-)
 delete mode 100644 src/Directory.Build.targets
 delete mode 100644 src/WTG.Z.Blazor.Diagrams.nuspec

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 8ad92c390..9b86da484 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -9,7 +9,7 @@ on:
   workflow_dispatch:
 
 env:
-  PACKAGE_PATH: /home/runner/work/Blazor.Diagrams/Blazor.Diagrams/src/Blazor.Diagrams/bin/Release/*.nupkg
+  NUGET_DIR: '${{ github.workspace }}/nuget'
 
 jobs:
   publish-pkg:
@@ -18,38 +18,14 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-    # - name: Extract Version
-    #   id: version
-    #   uses: battila7/get-version-action@v2
+      - name: Pack Blazor.Diagrams.Core
+        working-directory: src/Blazor.Diagrams.Core
+        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}'
 
-    - name: Checkout
-      uses: actions/checkout@v3
+      - name: Pack Blazor.Diagrams
+        working-directory: src/Blazor.Diagrams
+        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}'
 
-    - name: Setup dotnet
-      uses: actions/setup-dotnet@v3
-      with:
-        dotnet-version: |
-            6.0.x
-            3.1.x
-
-    - name: Install dependencies
-      run: dotnet restore
-
-    - name: Build
-      run: dotnet build --no-restore --configuration Release
-
-    - name: Create NuGet Packages
-      run: dotnet pack --no-restore --no-build --configuration Release /p:CommitID=${{ github.sha }} /p:TagVersion=${{ github.sha }}
-
-    # Upload package as an atrifact to the GitHub action
-    - name: Upload packages
-      uses: actions/upload-artifact@v3
-      with:
-        name: package
-        path: ${{ env.PACKAGE_PATH }}
-        retention-days: 5
-
-    # - name: Publish NuGet Package to NuGet Gallery
-    #   run: |
-    #     nuget setapikey ${{ secrets.NUGET_API_KEY }}
-    #     nuget push bin/WTG.Z.Blazor.Diagrams.${{ steps.tag_name.outputs.result }}.nupkg -Source https://api.nuget.org/v3/index.json
+      - name: Pack Blazor.Diagrams.Algorithms
+        working-directory: src/Blazor.Diagrams.Algorithms
+        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}'
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
deleted file mode 100644
index 8d4e146f0..000000000
--- a/src/Directory.Build.targets
+++ /dev/null
@@ -1,15 +0,0 @@
-
-  
-    false
-  
-  
-    true
-    
-      id=$(PackageId);
-      version=$(ShortVersion)$(VersionSuffix);
-      company=$(Company);
-      copyright=$(Copyright);
-      commitid=$(CommitID);
-    
-  
-
\ No newline at end of file
diff --git a/src/WTG.Z.Blazor.Diagrams.nuspec b/src/WTG.Z.Blazor.Diagrams.nuspec
deleted file mode 100644
index 43ee2fecb..000000000
--- a/src/WTG.Z.Blazor.Diagrams.nuspec
+++ /dev/null
@@ -1,35 +0,0 @@
-
-
-    
-        WTG.Analyzers
-        $version$
-        $company$
-        $company$
-        LICENSE
-        https://github.com/WiseTechGlobal/Blazor.Diagrams
-        Z.Blazor.Diagrams is a fully customizable and extensible all-purpose diagrams
-            library for Blazor (both Server Side and WASM). It was first inspired by the popular
-            React library react-diagrams, but then evolved into something much bigger. ZBD can be
-            used to make advanced diagrams with a custom design. Even the behavior of the library is
-            "hackable" and can be changed to suit your needs.
-        $copyright$
-        
-        
-        true
-    
-
-    
-        
-        
-        
-
-        
-        
-        
-
-        
-        
-        
-    
-
\ No newline at end of file

From 029cbff36af4cb068a9055c97402f89e6378616b Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 12:52:16 +1000
Subject: [PATCH 121/159] copying build.yml to build

---
 .github/workflows/release.yml | 28 +++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 9b86da484..1bff3787d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -18,14 +18,24 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
-      - name: Pack Blazor.Diagrams.Core
-        working-directory: src/Blazor.Diagrams.Core
-        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}'
+      - uses: actions/checkout@v3
 
-      - name: Pack Blazor.Diagrams
-        working-directory: src/Blazor.Diagrams
-        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}'
+      - name: Setup dotnet
+        uses: actions/setup-dotnet@v3
+        with:
+          dotnet-version: |
+              6.0.x
+              3.1.x
 
-      - name: Pack Blazor.Diagrams.Algorithms
-        working-directory: src/Blazor.Diagrams.Algorithms
-        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}'
+      - name: Install dependencies
+        run: dotnet restore
+
+      - name: Build
+        run: dotnet build --configuration Release
+
+      - name: Upload packages
+        uses: actions/upload-artifact@v3
+        with:
+          name: package
+          path: /home/runner/work/Blazor.Diagrams/Blazor.Diagrams/src/Blazor.Diagrams/bin/Release/*.nupkg
+          retention-days: 5

From 4686bf5cf24e33ba1d6893e7a0e4e09b33a62078 Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 13:27:26 +1000
Subject: [PATCH 122/159] removed nuspec reference from blazor.diagram.csproj

---
 src/Blazor.Diagrams/Blazor.Diagrams.csproj | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
index 493287d0b..1b5bf864c 100644
--- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj
+++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
@@ -15,7 +15,7 @@
         README.md
         True
         sgKey.snk
-        ../../WTG.Z.Blazor.Diagrams.nuspec
+        
     
 
     

From 03b381931094c479a5d3681436f4aae7b02ee3bb Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 15:20:55 +1000
Subject: [PATCH 123/159] dotnet pack step

---
 .github/workflows/release.yml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1bff3787d..670e1debe 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -33,6 +33,12 @@ jobs:
       - name: Build
         run: dotnet build --configuration Release
 
+      - name: Pack
+        run: dotnet nuget pack
+
+      - name: Create NuGet Packages
+        run: dotnet pack src --configuration Release
+
       - name: Upload packages
         uses: actions/upload-artifact@v3
         with:

From 5e9437cd11c853068cf5d4b0d3435a04aafa0708 Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 16:18:48 +1000
Subject: [PATCH 124/159] packing seperately

---
 .github/workflows/release.yml              | 30 +++++++++++++---------
 src/Blazor.Diagrams/Blazor.Diagrams.csproj |  1 -
 2 files changed, 18 insertions(+), 13 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 670e1debe..7fe41c296 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -33,15 +33,21 @@ jobs:
       - name: Build
         run: dotnet build --configuration Release
 
-      - name: Pack
-        run: dotnet nuget pack
-
-      - name: Create NuGet Packages
-        run: dotnet pack src --configuration Release
-
-      - name: Upload packages
-        uses: actions/upload-artifact@v3
-        with:
-          name: package
-          path: /home/runner/work/Blazor.Diagrams/Blazor.Diagrams/src/Blazor.Diagrams/bin/Release/*.nupkg
-          retention-days: 5
+      - name: Pack Blazor.Diagrams.Core
+        working-directory: src/Blazor.Diagrams.Core
+        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}'
+
+      - name: Pack Blazor.Diagrams
+        working-directory: src/Blazor.Diagrams
+        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}'
+
+      - name: Pack Blazor.Diagrams.Algorithms
+        working-directory: src/Blazor.Diagrams.Algorithms
+        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}'
+
+      - name: Push
+        run: >-
+          for i in $(find ${{ env.NUGET_DIR }} -type f -name "*.nupkg");
+          do
+           echo "File: ${i} \n";
+          done
diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
index 1b5bf864c..25e769c6f 100644
--- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj
+++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
@@ -15,7 +15,6 @@
         README.md
         True
         sgKey.snk
-        
     
 
     

From 1fd8415e1b3aa12b2ffdc504c1fd93ba0c9260ac Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 16:28:03 +1000
Subject: [PATCH 125/159] only packing blazor.diagrams

---
 .github/workflows/release.yml | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 7fe41c296..a4e3a7f1c 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -33,18 +33,10 @@ jobs:
       - name: Build
         run: dotnet build --configuration Release
 
-      - name: Pack Blazor.Diagrams.Core
-        working-directory: src/Blazor.Diagrams.Core
-        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}'
-
       - name: Pack Blazor.Diagrams
         working-directory: src/Blazor.Diagrams
         run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}'
 
-      - name: Pack Blazor.Diagrams.Algorithms
-        working-directory: src/Blazor.Diagrams.Algorithms
-        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}'
-
       - name: Push
         run: >-
           for i in $(find ${{ env.NUGET_DIR }} -type f -name "*.nupkg");

From 227fad9bf4a54f0b4c2208ce40fd2ab3305714e3 Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 16:34:15 +1000
Subject: [PATCH 126/159] gets version from tag

---
 .github/workflows/release.yml | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a4e3a7f1c..38bb7f9c2 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -5,7 +5,7 @@ on:
   push:
     branches: [ master, VFL/WI00764279/NCN-Diagrams-Move-WTG.Blazor.Diagrams-to-nuget.org ]
     tags:
-      - "*"
+      - "v*"
   workflow_dispatch:
 
 env:
@@ -20,6 +20,10 @@ jobs:
     steps:
       - uses: actions/checkout@v3
 
+      - name: Get Version
+        id: version
+        uses: battila7/get-version-action@v2
+
       - name: Setup dotnet
         uses: actions/setup-dotnet@v3
         with:
@@ -35,7 +39,7 @@ jobs:
 
       - name: Pack Blazor.Diagrams
         working-directory: src/Blazor.Diagrams
-        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release --output ${{ env.NUGET_DIR }}'
+        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release -p:PackageVersion=${{ steps.version.outputs.version-without-v }} --output ${{ env.NUGET_DIR }}'
 
       - name: Push
         run: >-

From 2f6e70ed2609084ffe366fed5c158cdcdc8ea940 Mon Sep 17 00:00:00 2001
From: Vinnie Fialok 
Date: Wed, 3 Jul 2024 16:46:47 +1000
Subject: [PATCH 127/159] testing github ref_name

---
 .github/workflows/release.yml | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 38bb7f9c2..8f7563eee 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -18,6 +18,11 @@ jobs:
     runs-on: ubuntu-latest
 
     steps:
+      - name: GitHub Tag Name example
+        run: |
+          echo "Tag name from GITHUB_REF_NAME: $GITHUB_REF_NAME"
+          echo "Tag name from github.ref_name: ${{  github.ref_name }}"
+
       - uses: actions/checkout@v3
 
       - name: Get Version
@@ -39,7 +44,7 @@ jobs:
 
       - name: Pack Blazor.Diagrams
         working-directory: src/Blazor.Diagrams
-        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release -p:PackageVersion=${{ steps.version.outputs.version-without-v }} --output ${{ env.NUGET_DIR }}'
+        run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release -p:PackageVersion=${{ steps.version.outputs.version-without-v }}--output ${{ env.NUGET_DIR }}'
 
       - name: Push
         run: >-

From 579b8764f8e0655baecc02dac831383a5ce9cc64 Mon Sep 17 00:00:00 2001
From: Gulam Nabi Azad 
Date: Wed, 3 Jul 2024 17:05:29 +0530
Subject: [PATCH 128/159] WI00737678---RemoveDragNodesBehavior---Update-2

---
 .../Behaviors/DragMovablesBehavior.cs          | 18 ++++++------------
 1 file changed, 6 insertions(+), 12 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
index db49df576..16769128b 100644
--- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
+++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
@@ -5,17 +5,11 @@
 using Blazor.Diagrams.Core.Models.Base;
 using System;
 using System.Collections.Generic;
-using DiagramPoint = Blazor.Diagrams.Core.Geometry.Point;
 namespace Blazor.Diagrams.Core.Behaviors;
 
 public class DragMovablesBehavior : DragBehavior
 {
-    public record NodeMoveablePositions(Point position)
-    {
-        public Dictionary ChildPositions { get; } = new();
-    }
-
-    protected readonly Dictionary _initialPositions;
+    protected readonly Dictionary _initialPositions;
     protected double? _lastClientX;
     protected double? _lastClientY;
     protected bool _moved;
@@ -24,7 +18,7 @@ public record NodeMoveablePositions(Point position)
 
     public DragMovablesBehavior(Diagram diagram) : base(diagram)
     {
-        _initialPositions = new Dictionary();
+        _initialPositions = new Dictionary();
         Diagram.PanChanged += OnPanChanged;
     }
 
@@ -53,7 +47,7 @@ protected override void OnPointerDown(Model? model, PointerEventArgs e)
                     movable.Position.Y + (n.Size?.Height ?? 0) / 2);
             }
 
-            _initialPositions.Add(movable, new NodeMoveablePositions(position));
+            _initialPositions.Add(movable, position);
         }
 
         _lastClientX = e.ClientX;
@@ -79,7 +73,7 @@ protected override void OnPointerMove(Model? model, PointerEventArgs e)
         _lastClientY = e.ClientY;
 
     }
-
+    
     protected virtual void OnPanChanged(double deltaX, double deltaY)
     {
         if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null)
@@ -97,8 +91,8 @@ protected virtual void MoveNodes(double deltaX, double deltaY)
     {
         foreach (var (movable, initialPosition) in _initialPositions)
         {
-            var ndx = ApplyGridSize(deltaX + initialPosition.position.X);
-            var ndy = ApplyGridSize(deltaY + initialPosition.position.Y);
+            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);

From b852ab229a0ae8887f668d44b2efcaa955c8ba7c Mon Sep 17 00:00:00 2001
From: Gulam Nabi Azad 
Date: Wed, 3 Jul 2024 17:14:12 +0530
Subject: [PATCH 129/159] WI00737678---RemoveDragNodesBehavior---Update-3

---
 src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
index 16769128b..0bd146b19 100644
--- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
+++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs
@@ -24,16 +24,15 @@ public DragMovablesBehavior(Diagram diagram) : base(diagram)
 
     protected override void OnPointerDown(Model? model, PointerEventArgs e)
     {
-        if (model is null)
+        if (model is not MovableModel)
             return;
 
         ResetPan();
 
         _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;
 
             // Special case: groups without auto size on
@@ -116,7 +115,6 @@ protected override void OnPointerUp(Model? model, PointerEventArgs e)
                 movable.TriggerMoved();
             }
         }
-
         _initialPositions.Clear();
         _totalMovedX = 0;
         _totalMovedY = 0;

From cfa5fecb93c57fea67e5fda777eed6255561cba7 Mon Sep 17 00:00:00 2001
From: Shams Azam 
Date: Thu, 4 Jul 2024 08:07:31 +0530
Subject: [PATCH 130/159] WI00761833 - Updates for Blazor Diagrams mouse
 configuration

---
 .../Pages/Documentation/Diagram/Behaviors.razor    | 14 ++++++++++++++
 .../Components/DiagramCanvas.razor.cs              |  3 ++-
 .../Extensions/JSRuntimeExtensions.cs              |  5 +++++
 src/Blazor.Diagrams/wwwroot/script.js              |  3 +++
 src/Blazor.Diagrams/wwwroot/script.min.js          |  2 +-
 5 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/site/Site/Pages/Documentation/Diagram/Behaviors.razor b/site/Site/Pages/Documentation/Diagram/Behaviors.razor
index 540daba7e..4f3785adc 100644
--- a/site/Site/Pages/Documentation/Diagram/Behaviors.razor
+++ b/site/Site/Pages/Documentation/Diagram/Behaviors.razor
@@ -114,6 +114,20 @@ Diagram.UnregisterBehavior<SelectionBehavior>();
 Diagram.RegisterBehavior(new MySelectionBehavior(Diagram));
 
+

Using Different Behaviors

+ +You can use the following ways to add different behaviors to the diagram. + +

Scroll Behavior

+ +

To scroll the diagram using the mouse wheel you can register the Scroll Behavior.

+ +

+Diagram.UnregisterBehavior<WheelBehavior>();
+Diagram.RegisterBehavior(new ScrollBehavior(Diagram));
+
+ + { + element.addEventListener(eventName, e => e.preventDefault(), { passive: false }); } }; window.ZBlazorDiagrams = s; diff --git a/src/Blazor.Diagrams/wwwroot/script.min.js b/src/Blazor.Diagrams/wwwroot/script.min.js index a4065277c..a900d0005 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)=>{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 +var s = { canvases: {}, tracked: {}, getBoundingClientRect: e => e.getBoundingClientRect(), mo: new MutationObserver((() => { for (id in s.canvases) { const e = s.canvases[id], t = e.lastBounds, n = e.elem.getBoundingClientRect(); t.left === n.left && t.top === n.top && t.width === n.width && t.height === n.height || (e.lastBounds = n, e.ref.invokeMethodAsync("OnResize", n)) } })), ro: new ResizeObserver((e => { for (const t of e) { let e = Array.from(t.target.attributes).find((e => e.name.startsWith("_bl"))).name.substring(4), n = s.tracked[e]; n && n.ref.invokeMethodAsync("OnResize", t.target.getBoundingClientRect()) } })), observe: (e, t, n) => { e && (s.ro.observe(e), s.tracked[n] = { ref: t }, e.classList.contains("diagram-canvas") && (s.canvases[n] = { elem: e, ref: t, lastBounds: e.getBoundingClientRect() })) }, unobserve: (e, t) => { e && s.ro.unobserve(e), delete s.tracked[t], delete s.canvases[t] }, addDefaultPreventingHandler: (e, s) => { e.addEventListener(s, (e => e.preventDefault()), { passive: !1 }) } }; window.ZBlazorDiagrams = s, window.addEventListener("scroll", (() => { for (id in s.canvases) { const e = s.canvases[id]; e.lastBounds = e.elem.getBoundingClientRect(), e.ref.invokeMethodAsync("OnResize", e.lastBounds) } })), s.mo.observe(document.body, { childList: !0, subtree: !0 }); \ No newline at end of file From 4737280c57fba12e4462ed831f70a046c7d61a2c Mon Sep 17 00:00:00 2001 From: Vinnie Fialok Date: Fri, 5 Jul 2024 09:30:59 +1000 Subject: [PATCH 131/159] testing with tags --- .github/workflows/release.yml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8f7563eee..2827790e3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,12 +18,8 @@ jobs: runs-on: ubuntu-latest steps: - - name: GitHub Tag Name example - run: | - echo "Tag name from GITHUB_REF_NAME: $GITHUB_REF_NAME" - echo "Tag name from github.ref_name: ${{ github.ref_name }}" - - - uses: actions/checkout@v3 + - name: Checkout + uses: actions/checkout@v3 - name: Get Version id: version @@ -44,7 +40,7 @@ jobs: - name: Pack Blazor.Diagrams working-directory: src/Blazor.Diagrams - run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release -p:PackageVersion=${{ steps.version.outputs.version-without-v }}--output ${{ env.NUGET_DIR }}' + run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release -p:PackageVersion=${{ steps.version.outputs.version-without-v }} --output ${{ env.NUGET_DIR }}' - name: Push run: >- From 2b7df6aad9ffd88bf83304487a1eb3c91ccc4d8c Mon Sep 17 00:00:00 2001 From: Vinnie Fialok Date: Fri, 5 Jul 2024 09:37:57 +1000 Subject: [PATCH 132/159] testing pushing to nuget --- .github/workflows/release.yml | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2827790e3..6d1ec1fa8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,11 +40,9 @@ jobs: - name: Pack Blazor.Diagrams working-directory: src/Blazor.Diagrams - run: 'dotnet pack -p:GeneratePackageOnBuild=false --configuration Release -p:PackageVersion=${{ steps.version.outputs.version-without-v }} --output ${{ env.NUGET_DIR }}' - - - name: Push - run: >- - for i in $(find ${{ env.NUGET_DIR }} -type f -name "*.nupkg"); - do - echo "File: ${i} \n"; - done + run: 'dotnet pack --no-restore --no-build -p:GeneratePackageOnBuild=false --configuration Release -p:PackageVersion=${{ steps.version.outputs.version-without-v }} --output ${{ env.NUGET_DIR }}' + + - name: Push NuGet Package to NuGet Gallery + run: | + nuget setapikey ${{ secrets.NUGET_API_KEY }} + nuget push bin/WTG.Analyzers.${{ steps.version.outputs.version-without-v }}.nupkg -Source https://api.nuget.org/v3/index.json From e7352b9bd3adccd19c09f78455690b90a9d28961 Mon Sep 17 00:00:00 2001 From: Vinnie Fialok Date: Fri, 5 Jul 2024 09:42:21 +1000 Subject: [PATCH 133/159] fixed .nupkg directory --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d1ec1fa8..0d580ecf8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -45,4 +45,4 @@ jobs: - name: Push NuGet Package to NuGet Gallery run: | nuget setapikey ${{ secrets.NUGET_API_KEY }} - nuget push bin/WTG.Analyzers.${{ steps.version.outputs.version-without-v }}.nupkg -Source https://api.nuget.org/v3/index.json + nuget push ${{ env.NUGET_DIR }}/WTG.Z.Blazor.Diagrams.${{ steps.version.outputs.version-without-v }}.nupkg -Source https://api.nuget.org/v3/index.json From 1cf9029a71fddc2d951d440ebf431051eb1567ad Mon Sep 17 00:00:00 2001 From: Vinnie Fialok Date: Fri, 5 Jul 2024 10:03:11 +1000 Subject: [PATCH 134/159] removed this branch from on:push: event --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0d580ecf8..124dcd662 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Create release # On push to master branch. i.e. when we merge a PR. on: push: - branches: [ master, VFL/WI00764279/NCN-Diagrams-Move-WTG.Blazor.Diagrams-to-nuget.org ] + branches: [ master ] tags: - "v*" workflow_dispatch: From 9c30c921b5942a8a42c97da0838ba77b83f1a19c Mon Sep 17 00:00:00 2001 From: Vinnie Fialok Date: Fri, 5 Jul 2024 10:26:30 +1000 Subject: [PATCH 135/159] re-added auto-versioning + testing it works --- .github/workflows/release.yml | 87 ++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 124dcd662..dfbeaece8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,46 +3,69 @@ name: Create release # On push to master branch. i.e. when we merge a PR. on: push: - branches: [ master ] + branches: [ master, VFL/WI00764279/NCN-Diagrams-Move-WTG.Blazor.Diagrams-to-nuget.org ] tags: - "v*" workflow_dispatch: env: - NUGET_DIR: '${{ github.workspace }}/nuget' + PACKAGE_PATH: /home/runner/work/Blazor.Diagrams/Blazor.Diagrams/src/Blazor.Diagrams/bin/Release/*.nupkg jobs: - publish-pkg: + release: name: Build - Release runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Get Version - id: version - uses: battila7/get-version-action@v2 - - - name: Setup dotnet - uses: actions/setup-dotnet@v3 - with: - dotnet-version: | - 6.0.x - 3.1.x - - - name: Install dependencies - run: dotnet restore - - - name: Build - run: dotnet build --configuration Release - - - name: Pack Blazor.Diagrams - working-directory: src/Blazor.Diagrams - run: 'dotnet pack --no-restore --no-build -p:GeneratePackageOnBuild=false --configuration Release -p:PackageVersion=${{ steps.version.outputs.version-without-v }} --output ${{ env.NUGET_DIR }}' - - - name: Push NuGet Package to NuGet Gallery - run: | - nuget setapikey ${{ secrets.NUGET_API_KEY }} - nuget push ${{ env.NUGET_DIR }}/WTG.Z.Blazor.Diagrams.${{ steps.version.outputs.version-without-v }}.nupkg -Source https://api.nuget.org/v3/index.json + + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup dotnet + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 6.0.x + 3.1.x + + # Finds the latest release and increases the version + - name: Get next version + uses: reecetech/version-increment@2023.9.3 + id: version + with: + scheme: semver + increment: patch + + - name: Install version tool + run: dotnet tool install dotnetCampus.TagToVersion -g --version 1.0.11 + + # Writes the new version number to build/Version.props + - name: Set version + run: dotnet TagToVersion -t ${{ steps.version.outputs.version }} + + - name: Install dependencies + run: dotnet restore + + # Pacakge is created on build + - name: Build + run: dotnet build --configuration Release + + # Upload package as an atrifact to the GitHub action + - name: Upload packages + uses: actions/upload-artifact@v3 + with: + name: package + path: ${{ env.PACKAGE_PATH }} + retention-days: 5 + + # Create a new release and upload the package to the release + # - name: Release + # uses: softprops/action-gh-release@v1 + # with: + # files: ${{ env.PACKAGE_PATH }} + # tag_name: ${{ steps.version.outputs.version }} + + # - name: Push NuGet Package to NuGet Gallery + # run: | + # nuget setapikey ${{ secrets.NUGET_API_KEY }} + # nuget push ${{ env.PACKAGE_PATH }} -Source https://api.nuget.org/v3/index.json \ No newline at end of file From 13eb1469c06119cd36d6e3fb43926e57498fd9e6 Mon Sep 17 00:00:00 2001 From: Vinnie Fialok Date: Fri, 5 Jul 2024 10:38:23 +1000 Subject: [PATCH 136/159] removed this branch from on:push, ready --- .github/workflows/release.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dfbeaece8..7b18e2fcc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ name: Create release # On push to master branch. i.e. when we merge a PR. on: push: - branches: [ master, VFL/WI00764279/NCN-Diagrams-Move-WTG.Blazor.Diagrams-to-nuget.org ] + branches: [ master ] tags: - "v*" workflow_dispatch: @@ -59,13 +59,13 @@ jobs: retention-days: 5 # Create a new release and upload the package to the release - # - name: Release - # uses: softprops/action-gh-release@v1 - # with: - # files: ${{ env.PACKAGE_PATH }} - # tag_name: ${{ steps.version.outputs.version }} + - name: Release + uses: softprops/action-gh-release@v1 + with: + files: ${{ env.PACKAGE_PATH }} + tag_name: ${{ steps.version.outputs.version }} - # - name: Push NuGet Package to NuGet Gallery - # run: | - # nuget setapikey ${{ secrets.NUGET_API_KEY }} - # nuget push ${{ env.PACKAGE_PATH }} -Source https://api.nuget.org/v3/index.json \ No newline at end of file + - name: Push NuGet Package to NuGet Gallery + run: | + nuget setapikey ${{ secrets.NUGET_API_KEY }} + nuget push ${{ env.PACKAGE_PATH }} -Source https://api.nuget.org/v3/index.json \ No newline at end of file From 4a72b6827ea4e8d45620dc47d57d437dcae9f066 Mon Sep 17 00:00:00 2001 From: Vinnie Fialok Date: Fri, 5 Jul 2024 10:40:57 +1000 Subject: [PATCH 137/159] removed tags from workflow on: --- .github/workflows/release.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b18e2fcc..d9678d31d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,8 +4,6 @@ name: Create release on: push: branches: [ master ] - tags: - - "v*" workflow_dispatch: env: From 227ee25148c1dc4006a6869e699d03abbe92bfa8 Mon Sep 17 00:00:00 2001 From: Vinnie Fialok Date: Fri, 5 Jul 2024 10:48:30 +1000 Subject: [PATCH 138/159] removed version data from Directory.Build.props --- src/Directory.Build.props | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 7c399dac7..5d277ac69 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -4,20 +4,6 @@ enable enable true - - - 3.7.2 - $(TagVersion.Split('-', 2)[0]) - - $(TagVersion.Substring($(ShortVersion.Length))) - - - $(ShortVersion).$(GITHUB_RUN_NUMBER) - $(ShortVersion).0 From 4275541b5fa83b0ce507585507de9faa41fcc37b Mon Sep 17 00:00:00 2001 From: Vinnie Fialok Date: Fri, 5 Jul 2024 11:48:38 +1000 Subject: [PATCH 139/159] added to readme --- README.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5bfe94af2..cda0e4fbe 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,16 @@ Repository: https://github.com/Blazor-Diagrams/Blazor.DatabaseDesigner ### Contributing -All kinds of contributions are welcome! +All kinds of contributions are welcome! If you're interested in helping, please create an issue or comment on an existing one to explain what you will be doing. This is because multiple people can be working on the same problem. ## Feedback If you find a bug or you want to see a functionality in this library, feel free to open an issue. + +## Deployment + +This project is hosted on NuGet. Merges to master will trigger a release action that will +- Build WTG.Z.Blazor.Diagrams +- Host this on Github as a release +- Push it to the WTG.Z.Blazor.Diagrams NuGet Gallery From 45f167594bde6ce27a407a51b2e208dbc951219c Mon Sep 17 00:00:00 2001 From: Vinnie Fialok Date: Fri, 5 Jul 2024 11:52:03 +1000 Subject: [PATCH 140/159] updated readme with WTG.Z.Blazor.Diagrams --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cda0e4fbe..3fa55751d 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,11 @@ ![](ZBD.png) -Z.Blazor.Diagrams is a fully customizable and extensible all-purpose diagrams library for Blazor (both Server Side and WASM). It was first inspired by the popular React library [react-diagrams](https://github.com/projectstorm/react-diagrams), but then evolved into something much bigger. ZBD can be used to make advanced diagrams with a custom design. Even the behavior of the library is "hackable" and can be changed to suit your needs. +WTG.Z.Blazor.Diagrams is a fully customizable and extensible all-purpose diagrams library for Blazor (both Server Side and WASM). It was first inspired by the popular React library [react-diagrams](https://github.com/projectstorm/react-diagrams), but then evolved into something much bigger. ZBD can be used to make advanced diagrams with a custom design. Even the behavior of the library is "hackable" and can be changed to suit your needs. | NuGet Package | Version | Download | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| Z.Blazor.Diagrams.Core | [![NuGet](https://img.shields.io/nuget/v/Z.Blazor.Diagrams.Core.svg)](https://www.nuget.org/packages/Z.Blazor.Diagrams.Core) | [![Nuget](https://img.shields.io/nuget/dt/Z.Blazor.Diagrams.Core.svg)](https://www.nuget.org/packages/Z.Blazor.Diagrams.Core) | -| Z.Blazor.Diagrams | [![NuGet](https://img.shields.io/nuget/v/Z.Blazor.Diagrams.svg)](https://www.nuget.org/packages/Z.Blazor.Diagrams) | [![Nuget](https://img.shields.io/nuget/dt/Z.Blazor.Diagrams.svg)](https://www.nuget.org/packages/Z.Blazor.Diagrams) | -| Z.Blazor.Diagrams.Algorithms | [![NuGet](https://img.shields.io/nuget/v/Z.Blazor.Diagrams.Algorithms.svg)](https://www.nuget.org/packages/Z.Blazor.Diagrams.Algorithms) | [![Nuget](https://img.shields.io/nuget/dt/Z.Blazor.Diagrams.Algorithms.svg)](https://www.nuget.org/packages/Z.Blazor.Diagrams.Algorithms) | +| WTG.Z.Blazor.Diagrams | [![NuGet](https://img.shields.io/nuget/v/WTG.Z.Blazor.Diagrams.svg)](https://www.nuget.org/packages/WTG.Z.Blazor.Diagrams) | [![Nuget](https://img.shields.io/nuget/dt/WTG.Z.Blazor.Diagrams.svg)](https://www.nuget.org/packages/WTG.Z.Blazor.Diagrams) | | Badges | | | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | From 225b54d3f2e612823c4883fbc388b7cc314a91b0 Mon Sep 17 00:00:00 2001 From: Vinnie Fialok Date: Fri, 5 Jul 2024 14:35:49 +1000 Subject: [PATCH 141/159] updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3fa55751d..d0876d83c 100644 --- a/README.md +++ b/README.md @@ -67,5 +67,5 @@ If you find a bug or you want to see a functionality in this library, feel free This project is hosted on NuGet. Merges to master will trigger a release action that will - Build WTG.Z.Blazor.Diagrams -- Host this on Github as a release +- Host the package on Github as a release - Push it to the WTG.Z.Blazor.Diagrams NuGet Gallery From 1fe18c6208d0758408eee941e0220c914fcd6d43 Mon Sep 17 00:00:00 2001 From: vinnie-fialok Date: Mon, 8 Jul 2024 09:55:41 +1000 Subject: [PATCH 142/159] updated readment and release.yml --- .github/workflows/release.yml | 6 ++---- README.md | 4 +++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d9678d31d..1d97c231a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,9 +22,7 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v3 with: - dotnet-version: | - 6.0.x - 3.1.x + dotnet-version: '6.0.x' # Finds the latest release and increases the version - name: Get next version @@ -66,4 +64,4 @@ jobs: - name: Push NuGet Package to NuGet Gallery run: | nuget setapikey ${{ secrets.NUGET_API_KEY }} - nuget push ${{ env.PACKAGE_PATH }} -Source https://api.nuget.org/v3/index.json \ No newline at end of file + nuget push ${{ env.PACKAGE_PATH }} -Source https://api.nuget.org/v3/index.json diff --git a/README.md b/README.md index d0876d83c..43b656c55 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ ![](ZBD.png) -WTG.Z.Blazor.Diagrams is a fully customizable and extensible all-purpose diagrams library for Blazor (both Server Side and WASM). It was first inspired by the popular React library [react-diagrams](https://github.com/projectstorm/react-diagrams), but then evolved into something much bigger. ZBD can be used to make advanced diagrams with a custom design. Even the behavior of the library is "hackable" and can be changed to suit your needs. +Z.Blazor.Diagrams is a fully customizable and extensible all-purpose diagrams library for Blazor (both Server Side and WASM). It was first inspired by the popular React library [react-diagrams](https://github.com/projectstorm/react-diagrams), but then evolved into something much bigger. ZBD can be used to make advanced diagrams with a custom design. Even the behavior of the library is "hackable" and can be changed to suit your needs. + +WTG.Z.Blazor.Diagarms is a fork of Z.Blazor.Diagrams with new features and bug fixes first targeting WiseTech Global products. | NuGet Package | Version | Download | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | From 42411c39fb295757c92c8201c12986b5a5c407fb Mon Sep 17 00:00:00 2001 From: Gulam Nabi Azad Date: Mon, 8 Jul 2024 10:44:49 +0530 Subject: [PATCH 143/159] WI00737678---RemoveDragNodesBehavior---Update-4 --- .../Behaviors/DragMovablesBehavior.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 0bd146b19..15106cd22 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -27,8 +27,6 @@ protected override void OnPointerDown(Model? model, PointerEventArgs e) if (model is not MovableModel) return; - ResetPan(); - _initialPositions.Clear(); foreach (var sm in Diagram.GetSelectedModels()) { @@ -72,7 +70,7 @@ protected override void OnPointerMove(Model? model, PointerEventArgs e) _lastClientY = e.ClientY; } - + protected virtual void OnPanChanged(double deltaX, double deltaY) { if (_initialPositions.Count == 0 || _lastClientX == null || _lastClientY == null) @@ -132,14 +130,9 @@ private double ApplyGridSize(double n) return gridSize * Math.Floor((n + gridSize / 2.0) / gridSize); } - void ResetPan() - { - Diagram.SetPan(0, 0); - } - public override void Dispose() { _initialPositions.Clear(); Diagram.PanChanged -= OnPanChanged; } -} +} \ No newline at end of file From 87b6778fd0d90502706f326c8b2b106d48b3de69 Mon Sep 17 00:00:00 2001 From: Gulam Nabi Azad Date: Mon, 8 Jul 2024 10:47:13 +0530 Subject: [PATCH 144/159] WI00737678---RemoveDragNodesBehavior---Update-5 --- src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs index 15106cd22..250a5365e 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -5,6 +5,7 @@ using Blazor.Diagrams.Core.Models.Base; using System; using System.Collections.Generic; + namespace Blazor.Diagrams.Core.Behaviors; public class DragMovablesBehavior : DragBehavior From 3d0517ef21d97cd692116792a3aaebe65f2cb23c Mon Sep 17 00:00:00 2001 From: Shams Azam Date: Wed, 10 Jul 2024 09:50:12 +0530 Subject: [PATCH 145/159] enabled wheel preventing only for scroll behavior, other pr suggestions addressed --- site/Site/Pages/Documentation/Diagram/Behaviors.razor | 5 ++--- src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs | 8 ++++++-- src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/site/Site/Pages/Documentation/Diagram/Behaviors.razor b/site/Site/Pages/Documentation/Diagram/Behaviors.razor index 4f3785adc..ff9a5f7d2 100644 --- a/site/Site/Pages/Documentation/Diagram/Behaviors.razor +++ b/site/Site/Pages/Documentation/Diagram/Behaviors.razor @@ -120,11 +120,10 @@ You can use the following ways to add different behaviors to the diagram.

Scroll Behavior

-

To scroll the diagram using the mouse wheel you can register the Scroll Behavior.

+

To scroll the diagram using the mouse wheel use ScrollBehavior.


-Diagram.UnregisterBehavior<WheelBehavior>();
-Diagram.RegisterBehavior(new ScrollBehavior(Diagram));
+_diagram.BehaviorOptions.DiagramWheelBehavior = _diagram.GetBehavior<ScrollBehavior>();
 
diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index a970780d9..815e29be8 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Blazor.Diagrams.Core.Behaviors; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Extensions; using Microsoft.AspNetCore.Components; @@ -64,7 +65,10 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { BlazorDiagram.SetContainer(await JSRuntime.GetBoundingClientRect(elementReference)); await JSRuntime.ObserveResizes(elementReference, _reference!); - await JSRuntime.AddDefaultPreventingHandler(elementReference); + if (BlazorDiagram.GetBehavior() != null) + { + await JSRuntime.AddDefaultPreventingForWheelHandler(elementReference); + } } } @@ -102,7 +106,7 @@ private void OnKeyDown(KeyboardEventArgs e) BlazorDiagram.TriggerKeyDown(e.ToCore()); } - private async void OnWheel(WheelEventArgs e) + private void OnWheel(WheelEventArgs e) { BlazorDiagram.TriggerWheel(e.ToCore()); } diff --git a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs index c11886223..5509f7428 100644 --- a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs +++ b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs @@ -31,7 +31,7 @@ public static async Task UnobserveResizes(this IJSRuntime jsRuntime, ElementRefe await jsRuntime.InvokeVoidAsync("ZBlazorDiagrams.unobserve", element, element.Id); } - public static async Task AddDefaultPreventingHandler(this IJSRuntime jsRuntime, ElementReference element) + public static async Task AddDefaultPreventingForWheelHandler(this IJSRuntime jsRuntime, ElementReference element) { await jsRuntime.InvokeVoidAsync("ZBlazorDiagrams.addDefaultPreventingHandler", element, "wheel"); } From 98da37e10a7542d4b882451d318bcfef93449e39 Mon Sep 17 00:00:00 2001 From: Shams Azam Date: Wed, 10 Jul 2024 10:29:24 +0530 Subject: [PATCH 146/159] fixed scroll behavior checking for preventing default wheel behavior --- src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs index 815e29be8..a6bfda573 100644 --- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs +++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs @@ -65,7 +65,7 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { BlazorDiagram.SetContainer(await JSRuntime.GetBoundingClientRect(elementReference)); await JSRuntime.ObserveResizes(elementReference, _reference!); - if (BlazorDiagram.GetBehavior() != null) + if (BlazorDiagram.BehaviorOptions.DiagramWheelBehavior is ScrollBehavior) { await JSRuntime.AddDefaultPreventingForWheelHandler(elementReference); } From 09f824d23dd0dd08d8c244e8580f6ba35995c4d2 Mon Sep 17 00:00:00 2001 From: Shams Azam Date: Thu, 11 Jul 2024 11:38:23 +0530 Subject: [PATCH 147/159] updated documentation for BehaviorOptions. --- site/Site/Pages/Documentation/Diagram/Behaviors.razor | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/site/Site/Pages/Documentation/Diagram/Behaviors.razor b/site/Site/Pages/Documentation/Diagram/Behaviors.razor index ff9a5f7d2..094b33112 100644 --- a/site/Site/Pages/Documentation/Diagram/Behaviors.razor +++ b/site/Site/Pages/Documentation/Diagram/Behaviors.razor @@ -114,13 +114,14 @@ Diagram.UnregisterBehavior<SelectionBehavior>(); Diagram.RegisterBehavior(new MySelectionBehavior(Diagram)); -

Using Different Behaviors

+

Configure behaviors for different actions on input

-You can use the following ways to add different behaviors to the diagram. +You can configure a behavior to perform different actions for an input. Such as scrolling a diagram on mouse wheel instead of zooming in and out. +This can be done using the BehaviorOptions. See the below example. -

Scroll Behavior

+

Scrolling a diagram on mouse wheel

-

To scroll the diagram using the mouse wheel use ScrollBehavior.

+

To scroll a diagram using the mouse wheel set the DiagramWheelBehavior property of BehaviorOptions to use ScrollBehavior.


 _diagram.BehaviorOptions.DiagramWheelBehavior = _diagram.GetBehavior<ScrollBehavior>();

From 5ac3162a8bff657b0511f869107197da0d6e2c6e Mon Sep 17 00:00:00 2001
From: Leonam Santos 
Date: Fri, 16 Aug 2024 13:54:28 +1000
Subject: [PATCH 148/159] Change how DiagramCanvas is disposed

---
 .../Components/DiagramCanvas.razor.cs               |  7 ++++---
 .../Extensions/JSRuntimeExtensions.cs               | 13 +++++++++----
 2 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs
index a6bfda573..2c177fffc 100644
--- a/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs
+++ b/src/Blazor.Diagrams/Components/DiagramCanvas.razor.cs
@@ -1,6 +1,4 @@
-using System;
-using System.Threading.Tasks;
-using Blazor.Diagrams.Core.Behaviors;
+using Blazor.Diagrams.Core.Behaviors;
 using Blazor.Diagrams.Core.Geometry;
 using Blazor.Diagrams.Extensions;
 using Microsoft.AspNetCore.Components;
@@ -40,6 +38,9 @@ public async ValueTask DisposeAsync()
             await JSRuntime.UnobserveResizes(elementReference);
 
         _reference.Dispose();
+        _reference = null!;
+
+        GC.SuppressFinalize(this); // CA1816
     }
 
     private string GetLayerStyle(int order)
diff --git a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
index 5509f7428..b77b902f8 100644
--- a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
+++ b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
@@ -1,6 +1,4 @@
-using System;
-using System.Threading.Tasks;
-using Blazor.Diagrams.Core.Geometry;
+using Blazor.Diagrams.Core.Geometry;
 using Microsoft.AspNetCore.Components;
 using Microsoft.JSInterop;
 
@@ -28,7 +26,14 @@ public static async Task ObserveResizes(this IJSRuntime jsRuntime, ElementRef
 
     public static async Task UnobserveResizes(this IJSRuntime jsRuntime, ElementReference element)
     {
-        await jsRuntime.InvokeVoidAsync("ZBlazorDiagrams.unobserve", element, element.Id);
+        try
+        {
+            await jsRuntime.InvokeVoidAsync("ZBlazorDiagrams.unobserve", element, element.Id);
+        }
+        catch (ObjectDisposedException)
+        {
+            // Ignore, DotNetObjectReference was likely disposed
+        }
     }
 
     public static async Task AddDefaultPreventingForWheelHandler(this IJSRuntime jsRuntime, ElementReference element)

From f8c24c12bd0ad40d8316d3f26d1df2c4ccad0058 Mon Sep 17 00:00:00 2001
From: Leonam Santos 
Date: Mon, 19 Aug 2024 09:40:50 +1000
Subject: [PATCH 149/159] Catch JSDisconnected exceptions on Un-observeResizes
 call

---
 src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
index b77b902f8..3e32f8e1b 100644
--- a/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
+++ b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs
@@ -30,9 +30,9 @@ public static async Task UnobserveResizes(this IJSRuntime jsRuntime, ElementRefe
         {
             await jsRuntime.InvokeVoidAsync("ZBlazorDiagrams.unobserve", element, element.Id);
         }
-        catch (ObjectDisposedException)
+        catch (JSDisconnectedException)
         {
-            // Ignore, DotNetObjectReference was likely disposed
+            // Ignore, JSRuntime was already disconnected
         }
     }
 

From 88e8cab45c876117e84c648729d85fbeb97f3fab Mon Sep 17 00:00:00 2001
From: Srude-Vena 
Date: Tue, 4 Feb 2025 06:47:48 +0530
Subject: [PATCH 150/159] WI00856225 - Remove FluentAssertions from
 Blazor.Diagrams.Core.Tests

* FluentAssertion removed from chunk1 files

* FluentAssertion removed from chunk2 files

* Final FluentAssertion removed from chunk3 files

* Updated build.xml and release.xml to .net8 version

* updated build.xml for .net7 version

* Spelling change of PackageReference

*  removed form Blazor.Diagram.csproj

* changes made in build.yml and Blazor.Diagram.csproj

* removed net7.0 from Directory.Build.props

* debugging

* changes made in Directory.Build.props

* Removal of trailing spaces

* Spaces and warnings removed

* actions/upload-artifact version changed from v3 to v4

* extra spaces removed
---
 .github/workflows/build.yml                   |  4 +-
 .github/workflows/release.yml                 |  4 +-
 .../Blazor.Diagrams.Core.csproj               |  8 +-
 src/Blazor.Diagrams/Blazor.Diagrams.csproj    |  6 +-
 src/Directory.Build.props                     |  2 +-
 .../Anchors/DynamicAnchorTests.cs             | 27 +++---
 .../Anchors/ShapeIntersectionAnchorTests.cs   | 21 +++--
 .../Anchors/SinglePortAnchorTests.cs          | 17 ++--
 .../Behaviors/DragMovablesBehaviorTests.cs    |  5 +-
 .../Behaviors/DragNewLinkBehaviorTests.cs     | 68 ++++++++-------
 .../Behaviors/EventsBehaviorTests.cs          | 13 ++-
 .../KeyboardShortcutsBehaviorTests.cs         |  9 +-
 .../KeyboardShortcutsDefaultsTests.cs         | 23 +++---
 .../Blazor.Diagrams.Core.Tests.csproj         |  1 -
 .../DiagramOrderingTests.cs                   | 50 ++++++-----
 .../DiagramTests.cs                           | 26 +++---
 .../Geometry/PointTests.cs                    |  4 +-
 .../Layers/GroupLayerTests.cs                 | 27 +++---
 .../Layers/NodeLayerTests.cs                  | 15 ++--
 .../Models/Base/BaseLinkModelTests.cs         | 33 ++++----
 .../BottomLeftResizerProviderTests.cs         | 82 +++++++++----------
 .../BottomRightResizerProviderTests.cs        | 81 +++++++++---------
 .../Resizing/TopLeftResizerProviderTests.cs   | 81 +++++++++---------
 .../Resizing/TopRightResizerProviderTests.cs  | 81 +++++++++---------
 .../ShapeAnglePositionProviderTests.cs        |  5 +-
 tests/Directory.Build.props                   |  2 +-
 26 files changed, 335 insertions(+), 360 deletions(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 392ef4ef3..b14dc58ec 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -21,8 +21,8 @@ jobs:
       uses: actions/setup-dotnet@v3
       with:
         dotnet-version: |
+            8.0.x
             6.0.x
-            3.1.x
 
     - name: Install dependencies
       run: dotnet restore
@@ -36,7 +36,7 @@ jobs:
 
     - name: Upload packages
       if: matrix.configuration == 'Release'
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
         name: package
         path: /home/runner/work/Blazor.Diagrams/Blazor.Diagrams/src/Blazor.Diagrams/bin/Release/*.nupkg
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 1d97c231a..10df12f74 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -22,7 +22,9 @@ jobs:
     - name: Setup dotnet
       uses: actions/setup-dotnet@v3
       with:
-        dotnet-version: '6.0.x'
+        dotnet-version: |
+            8.0.x
+            6.0.x
 
       # Finds the latest release and increases the version
     - name: Get next version
diff --git a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj
index 9c27f1001..3079ef2dd 100644
--- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj
+++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj
@@ -28,9 +28,9 @@
     
   
 
-	
-	  
-    
-	
+
+  
+  
+
 
 
diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
index 25e769c6f..306f1ee48 100644
--- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj
+++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
@@ -20,11 +20,9 @@
     
         
         
-        
+        
         
-        
-          ..\..\packages\svgpathproperties\1.1.2\lib\netstandard2.0\SvgPathProperties.dll
-        
+        
     
 
     
diff --git a/src/Directory.Build.props b/src/Directory.Build.props
index 5d277ac69..888d6a9b7 100644
--- a/src/Directory.Build.props
+++ b/src/Directory.Build.props
@@ -1,6 +1,6 @@
 
   
-    net8.0;net7.0;net6.0;
+    net8.0;net6.0
     enable
     enable
     true
diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs
index 97a7460b4..0f8bfde0a 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs
@@ -2,7 +2,6 @@
 using Blazor.Diagrams.Core.Geometry;
 using Blazor.Diagrams.Core.Models;
 using Blazor.Diagrams.Core.Positions;
-using FluentAssertions;
 using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests.Anchors;
@@ -37,7 +36,7 @@ public void GetPosition_ShouldReturnNull_WhenNodesSizeIsNull()
         var position = anchor1.GetPosition(link);
 
         // Assert
-        position.Should().BeNull();
+        Assert.Null(position);
     }
 
     [Fact]
@@ -72,9 +71,9 @@ public void GetPosition_ShouldReturnClosestPositionToOtherNodesCenter_WhenRouteI
         var position = anchor1.GetPosition(link);
 
         // Assert
-        position.Should().NotBeNull();
-        position!.X.Should().Be(220);
-        position.Y.Should().Be(95);
+        Assert.NotNull(position);
+        Assert.Equal(220,position!.X);
+        Assert.Equal(95, position.Y);
     }
 
     [Fact]
@@ -109,9 +108,9 @@ public void GetPosition_ShouldReturnClosestPositionToOtherNodesCenterWithOffset_
         var position = anchor1.GetPosition(link);
 
         // Assert
-        position.Should().NotBeNull();
-        position!.X.Should().Be(230);
-        position.Y.Should().Be(85);
+        Assert.NotNull(position);
+        Assert.Equal(230, position!.X);
+        Assert.Equal(85, position.Y);
     }
 
     [Fact]
@@ -149,9 +148,9 @@ public void GetPosition_ShouldReturnClosestPositionToFirstVertex_WhenRouteIsNotE
         });
 
         // Assert
-        position.Should().NotBeNull();
-        position!.X.Should().Be(220);
-        position.Y.Should().Be(125);
+        Assert.NotNull(position);
+        Assert.Equal(220, position!.X);
+        Assert.Equal(125, position.Y);
     }
 
     [Fact]
@@ -189,8 +188,8 @@ public void GetPosition_ShouldReturnClosestPositionToLastVertex_WhenRouteIsNotEm
         });
 
         // Assert
-        position.Should().NotBeNull();
-        position!.X.Should().Be(300);
-        position.Y.Should().Be(120);
+        Assert.NotNull(position);
+        Assert.Equal(300, position!.X);
+        Assert.Equal(120, position.Y);
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/ShapeIntersectionAnchorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/ShapeIntersectionAnchorTests.cs
index 71726a926..70cfe7844 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Anchors/ShapeIntersectionAnchorTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/ShapeIntersectionAnchorTests.cs
@@ -3,7 +3,6 @@
 using Blazor.Diagrams.Core.Anchors;
 using Blazor.Diagrams.Core.Geometry;
 using Blazor.Diagrams.Core.Models;
-using FluentAssertions;
 using Moq;
 using Xunit;
 
@@ -27,8 +26,8 @@ public void GetPlainPosition_ShouldReturnNodeCenter()
 
         // Assert
         var center = node.GetBounds()!.Center;
-        position.X.Should().Be(center.X);
-        position.Y.Should().Be(center.Y);
+        Assert.Equal(center.X, position.X);
+        Assert.Equal(center.Y, position.Y);
     }
 
     [Fact]
@@ -43,7 +42,7 @@ public void GetPosition_ShouldReturnNull_WhenNodeSizeIsNull()
         var position = anchor.GetPosition(link);
 
         // Assert
-        position.Should().BeNull();
+        Assert.Null(position);
     }
 
     [Fact]
@@ -67,8 +66,8 @@ public void GetPosition_ShouldUseRouteToFindOtherPositionForIntersection_WhenSou
 
         // Assert
         var line = args.Single();
-        line.Start.Should().BeEquivalentTo(route[0]);
-        line.End.Should().BeEquivalentTo(node.GetBounds()!.Center);
+        Assert.Equal(route[0],line.Start);
+        Assert.Equal(node.GetBounds()!.Center, line.End);
     }
 
     [Fact]
@@ -93,8 +92,8 @@ public void GetPosition_ShouldUseRouteToFindOtherPositionForIntersection_WhenTar
 
         // Assert
         var line = args.Single();
-        line.Start.Should().BeEquivalentTo(route[^1]);
-        line.End.Should().BeEquivalentTo(node.GetBounds()!.Center);
+        Assert.Equal(route[^1],line.Start);
+        Assert.Equal(node.GetBounds()!.Center,line.End);
     }
 
     [Fact]
@@ -120,8 +119,8 @@ public void GetPosition_ShouldCallOtherGetPlainPosition_WhenNoRoute()
 
         // Assert
         var line = args.Single();
-        line.Start.Should().BeEquivalentTo(pt);
-        line.End.Should().BeEquivalentTo(node.GetBounds()!.Center);
+        Assert.Equal(pt,line.Start);
+        Assert.Equal(node.GetBounds()!.Center, line.End);
     }
 
     [Fact]
@@ -145,7 +144,7 @@ public void GetPosition_ShouldReturnNull_WhenOtherPositionIsNull()
         var position = source.GetPosition(link);
 
         // Assert
-        position.Should().BeNull();
+        Assert.Null(position);
     }
 
     private class CustomNodeModel : NodeModel
diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/SinglePortAnchorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/SinglePortAnchorTests.cs
index e01d746f7..4d64d1a9f 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Anchors/SinglePortAnchorTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Anchors/SinglePortAnchorTests.cs
@@ -1,7 +1,6 @@
 using Blazor.Diagrams.Core.Anchors;
 using Blazor.Diagrams.Core.Geometry;
 using Blazor.Diagrams.Core.Models;
-using FluentAssertions;
 using Moq;
 using Xunit;
 
@@ -26,8 +25,8 @@ public void GetPlainPosition_ShouldReturnMiddlePosition()
 
         // Assert
         var mp = port.MiddlePosition;
-        position.X.Should().Be(mp.X);
-        position.Y.Should().Be(mp.Y);
+        Assert.Equal(mp.X, position.X);
+        Assert.Equal(mp.Y, position.Y);
     }
 
     [Fact]
@@ -47,7 +46,7 @@ public void GetPosition_ShouldReturnNull_WhenPortNotInitialized()
         var position = anchor.GetPosition(link);
 
         // Assert
-        position.Should().BeNull();
+        Assert.Null(position);
     }
 
     [Fact]
@@ -72,8 +71,8 @@ public void GetPosition_ShouldReturnMiddlePosition_WhenMiddleIfNoMarker()
 
         // Assert
         var mp = port.MiddlePosition;
-        position.X.Should().Be(mp.X);
-        position.Y.Should().Be(mp.Y);
+        Assert.Equal(mp.X, position.X);
+        Assert.Equal(mp.Y, position.Y);
     }
 
     [Theory]
@@ -106,8 +105,8 @@ public void GetPosition_ShouldReturnAlignmentBasedPosition_WhenUseShapeAndAlignm
         var position = anchor.GetPosition(link)!;
 
         // Assert
-        position.X.Should().Be(x);
-        position.Y.Should().Be(y);
+        Assert.Equal(x, position.X);
+        Assert.Equal(y, position.Y);
     }
 
     [Theory]
@@ -139,7 +138,7 @@ public void GetPosition_ShouldUsePointAtAngle_WhenUseShapeAndAlignmentIsTrue(Por
 
         // Act
         var position = anchor.GetPosition(link)!;
-        
+
         // Assert
         shapeMock.Verify(s => s.GetPointAtAngle(angle), Times.Once);
     }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs
index 813322dcc..457ba46fb 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs
@@ -3,7 +3,6 @@
 using Blazor.Diagrams.Core.Geometry;
 using Blazor.Diagrams.Core.Models;
 using Blazor.Diagrams.Core.Options;
-using FluentAssertions;
 using Moq;
 using Xunit;
 
@@ -79,7 +78,7 @@ public void Behavior_ShouldTriggerMoved()
             new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
 
         // Assert
-        movedTrigger.Should().BeTrue();
+        Assert.True(movedTrigger);
     }
 
     [Fact]
@@ -99,7 +98,7 @@ public void Behavior_ShouldNotTriggerMoved_WhenMovableDidntMove()
             new PointerEventArgs(150, 150, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
 
         // Assert
-        movedTrigger.Should().BeFalse();
+        Assert.False(movedTrigger);
     }
 
     [Fact]
diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
index d545c864a..753335cdf 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs
@@ -3,8 +3,6 @@
 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;
@@ -32,11 +30,11 @@ public void Behavior_ShouldCreateLinkWithSinglePortAnchorSource_WhenMouseDownOnP
         // Assert
         var link = diagram.Links.Single();
         var source = link.Source as SinglePortAnchor;
-        source.Should().NotBeNull();
-        source!.Port.Should().BeSameAs(port);
+        Assert.NotNull(source);
+        Assert.Same(port, source!.Port);
         var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!;
-        ongoingPosition.X.Should().Be(100);
-        ongoingPosition.Y.Should().Be(100);
+        Assert.Equal(100, ongoingPosition.X);
+        Assert.Equal(100, ongoingPosition.Y);
     }
 
     [Fact]
@@ -64,14 +62,14 @@ public void Behavior_ShouldCreateLinkUsingFactory_WhenMouseDownOnPort()
             new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
 
         // Assert
-        factoryCalled.Should().BeTrue();
+        Assert.True(factoryCalled);
         var link = diagram.Links.Single();
         var source = link.Source as SinglePortAnchor;
-        source.Should().NotBeNull();
-        source!.Port.Should().BeSameAs(port);
+        Assert.NotNull(source);
+        Assert.Same(port, source!.Port);
         var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!;
-        ongoingPosition.X.Should().Be(100);
-        ongoingPosition.Y.Should().Be(100);
+        Assert.Equal(100, ongoingPosition.X);
+        Assert.Equal(100, ongoingPosition.Y);
     }
 
     [Fact]
@@ -100,9 +98,9 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggered()
         // Assert
         var source = link.Source as SinglePortAnchor;
         var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!;
-        ongoingPosition.X.Should().BeGreaterThan(145);
-        ongoingPosition.Y.Should().BeGreaterThan(145);
-        linkRefreshed.Should().BeTrue();
+        Assert.True(ongoingPosition.X > 145);
+        Assert.True(ongoingPosition.Y > 145);
+        Assert.True(linkRefreshed);
     }
 
     [Fact]
@@ -132,9 +130,9 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenMouseMoveIsTriggeredAndZoom
         // Assert
         var source = link.Source as SinglePortAnchor;
         var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!;
-        ongoingPosition.X.Should().BeApproximately(107.7, 0.1);
-        ongoingPosition.Y.Should().BeApproximately(101.7, 0.1);
-        linkRefreshed.Should().BeTrue();
+        Assert.InRange(ongoingPosition.X, 107.6, 107.8);
+        Assert.InRange(ongoingPosition.Y, 101.6, 101.8);
+        Assert.True(linkRefreshed);
     }
 
     [Fact]
@@ -172,9 +170,9 @@ public void Behavior_ShouldSnapToClosestPortAndRefreshPort_WhenSnappingIsEnabled
         // Assert
         var link = diagram.Links.Single();
         var target = link.Target as SinglePortAnchor;
-        target.Should().NotBeNull();
-        target!.Port.Should().BeSameAs(port2);
-        port2Refreshed.Should().BeTrue();
+        Assert.NotNull(target);
+        Assert.Same(port2, target!.Port);
+        Assert.True(port2Refreshed);
     }
 
     [Fact]
@@ -209,7 +207,7 @@ public void Behavior_ShouldNotSnapToPort_WhenSnappingIsEnabledAndPortIsNotInRadi
 
         // Assert
         var link = diagram.Links.Single();
-        link.Target.Should().BeOfType();
+        Assert.IsType(link.Target);
     }
 
     [Fact]
@@ -251,8 +249,8 @@ public void Behavior_ShouldUnSnapAndRefreshPort_WhenSnappingIsEnabledAndPortIsNo
         // Assert
         var link = diagram.Links.Single();
         var target = link.Target as SinglePortAnchor;
-        target.Should().BeNull();
-        port2Refreshes.Should().Be(2);
+        Assert.Null(target);
+        Assert.Equal(2, port2Refreshes);
     }
 
     [Fact]
@@ -276,7 +274,7 @@ public void Behavior_ShouldRemoveLink_WhenMouseUpOnCanvasAndRequireTargetIsTrue(
             new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
 
         // Assert
-        diagram.Links.Should().BeEmpty();
+        Assert.Empty(diagram.Links);
     }
 
     [Fact]
@@ -300,7 +298,7 @@ public void Behavior_ShouldRemoveLink_WhenMouseUpOnSamePort()
             new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
 
         // Assert
-        diagram.Links.Should().BeEmpty();
+        Assert.Empty(diagram.Links);
     }
 
     [Fact]
@@ -336,9 +334,9 @@ public void Behavior_ShouldSetTarget_WhenMouseUp()
         // Assert
         var link = diagram.Links.Single();
         var target = link.Target as SinglePortAnchor;
-        target.Should().NotBeNull();
-        target!.Port.Should().BeSameAs(port2);
-        port2Refreshes.Should().Be(1);
+        Assert.NotNull(target);
+        Assert.Same(port2, target!.Port);
+        Assert.Equal(1, port2Refreshes);
     }
 
     [Fact]
@@ -363,7 +361,7 @@ public void Behavior_ShouldNotCreateOngoingLink_WhenFactoryReturnsNull()
             new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
 
         // Assert
-        diagram.Links.Should().HaveCount(0);
+        Assert.Empty(diagram.Links);
     }
 
     [Fact]
@@ -399,7 +397,7 @@ public void Behavior_ShouldTriggerLinkTargetAttached_WhenMouseUpOnOtherPort()
             new PointerEventArgs(105, 105, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
 
         // Assert
-        targetAttachedTriggers.Should().Be(1);
+        Assert.Equal(1, targetAttachedTriggers);
     }
 
     [Fact]
@@ -440,7 +438,7 @@ public void Behavior_ShouldTriggerLinkTargetAttached_WhenLinkSnappedToPortAndMou
             new PointerEventArgs(140, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
 
         // Assert
-        targetAttachedTriggers.Should().Be(1);
+        Assert.Equal(1, targetAttachedTriggers);
     }
 
     [Fact]
@@ -463,7 +461,7 @@ public void Behavior_ShouldNotCreateLinkWithSinglePortAnchorSource_WhenMouseDown
             new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true));
 
         // Assert
-        diagram.Links.Count.Should().Be(0);
+        Assert.Empty(diagram.Links);
     }
 
     [Fact]
@@ -494,8 +492,8 @@ public void Behavior_ShouldUpdateOngoingPosition_WhenPanChanges()
         // Assert
         var source = link.Source as SinglePortAnchor;
         var ongoingPosition = (link.Target as PositionAnchor)!.GetPlainPosition()!;
-        ongoingPosition.X.Should().BeApproximately(expectedValue: 246, 1);
-        ongoingPosition.Y.Should().BeApproximately(expectedValue: 246, 1);
-        linkRefreshed.Should().BeTrue();
+        Assert.InRange(ongoingPosition.X, 245, 247);
+        Assert.InRange(ongoingPosition.Y, 245, 247);
+        Assert.True(linkRefreshed);
     }
 }
\ 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 5df0724d8..b514549aa 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/EventsBehaviorTests.cs
@@ -1,6 +1,5 @@
 using Blazor.Diagrams.Core.Behaviors;
 using Blazor.Diagrams.Core.Events;
-using FluentAssertions;
 using System.Threading.Tasks;
 using Xunit;
 
@@ -22,7 +21,7 @@ public void Behavior_ShouldNotTriggerMouseClick_WhenItsRemoved()
         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();
+        Assert.False(eventTriggered);
     }
 
     [Fact]
@@ -38,7 +37,7 @@ public void Behavior_ShouldTriggerMouseClick_WhenMouseDownThenUpWithoutMove()
         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();
+        Assert.True(eventTriggered);
     }
 
     [Fact]
@@ -55,7 +54,7 @@ public void Behavior_ShouldNotTriggerMouseClick_WhenMouseMoves()
         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();
+        Assert.False(eventTriggered);
     }
 
     [Fact]
@@ -71,7 +70,7 @@ public void Behavior_ShouldTriggerMouseDoubleClick_WhenTwoMouseClicksHappenWithi
         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();
+        Assert.True(eventTriggered);
     }
 
     [Fact]
@@ -88,7 +87,7 @@ public async Task Behavior_ShouldNotTriggerMouseDoubleClick_WhenTimeExceeds500()
         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();
+        Assert.False(eventTriggered);
     }
 
     [Fact]
@@ -103,6 +102,6 @@ public void Behavior_ShouldTriggerMouseClick_OnlyWhenMouseDownWasAlsoTriggered_I
         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();
+        Assert.False(eventTriggered);
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs
index 42fe887b7..baa7c30ad 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsBehaviorTests.cs
@@ -1,6 +1,5 @@
 using Blazor.Diagrams.Core.Behaviors;
 using Blazor.Diagrams.Core.Events;
-using FluentAssertions;
 using System.Threading.Tasks;
 using Xunit;
 
@@ -33,7 +32,7 @@ public void Behavior_ShouldExecuteAction_WhenCombinationIsPressed(string key, bo
         diagram.TriggerKeyDown(new KeyboardEventArgs(key, key, 0, ctrl, shift, alt));
 
         // Assert
-        executed.Should().BeTrue();
+        Assert.True(executed);
     }
 
     [Fact]
@@ -55,7 +54,7 @@ public void Behavior_ShouldDoNothing_WhenRemoved()
         diagram.TriggerKeyDown(new KeyboardEventArgs("A", "A", 0, false, false, false));
 
         // Assert
-        executed.Should().BeFalse();
+        Assert.False(executed);
     }
 
     [Fact]
@@ -83,7 +82,7 @@ public void SetShortcut_ShouldOverride()
         diagram.TriggerKeyDown(new KeyboardEventArgs("A", "A", 0, false, false, false));
 
         // Assert
-        executed1.Should().BeFalse();
-        executed2.Should().BeTrue();
+        Assert.False(executed1);
+        Assert.True(executed2);
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs
index 3a30fc040..9d2b500de 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/KeyboardShortcutsDefaultsTests.cs
@@ -1,8 +1,5 @@
 using Blazor.Diagrams.Core.Behaviors;
 using Blazor.Diagrams.Core.Models;
-using FluentAssertions;
-using System;
-using System.Threading.Tasks;
 using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests.Behaviors;
@@ -24,7 +21,7 @@ public async Task DeleteSelection_ShouldNotDeleteModel_WhenItsLocked()
         await KeyboardShortcutsDefaults.DeleteSelection(diagram);
 
         // Assert
-        diagram.Nodes.Count.Should().Be(1);
+        Assert.Single(diagram.Nodes);
     }
 
     [Fact]
@@ -47,8 +44,8 @@ public async Task DeleteSelection_ShouldTakeIntoAccountGroupConstraint()
         await KeyboardShortcutsDefaults.DeleteSelection(diagram);
 
         // Assert
-        funcCalled.Should().BeTrue();
-        diagram.Groups.Count.Should().Be(1);
+        Assert.True(funcCalled);
+        Assert.Single(diagram.Groups);
     }
 
     [Fact]
@@ -71,8 +68,8 @@ public async Task DeleteSelection_ShouldTakeIntoAccountNodeConstraint()
         await KeyboardShortcutsDefaults.DeleteSelection(diagram);
 
         // Assert
-        funcCalled.Should().BeTrue();
-        diagram.Nodes.Count.Should().Be(1);
+        Assert.True(funcCalled);
+        Assert.Single(diagram.Nodes);
     }
 
     [Fact]
@@ -100,8 +97,8 @@ public async Task DeleteSelection_ShouldTakeIntoAccountLinkConstraint()
         await KeyboardShortcutsDefaults.DeleteSelection(diagram);
 
         // Assert
-        funcCalled.Should().BeTrue();
-        diagram.Links.Count.Should().Be(1);
+        Assert.True(funcCalled);
+        Assert.Single(diagram.Links);
     }
 
     [Fact]
@@ -126,8 +123,8 @@ public async Task DeleteSelection_ShouldResultInSingleRefresh()
         await KeyboardShortcutsDefaults.DeleteSelection(diagram);
 
         // Assert
-        diagram.Nodes.Count.Should().Be(0);
-        diagram.Links.Count.Should().Be(0);
-        refreshes.Should().Be(1);
+        Assert.Empty(diagram.Nodes);
+        Assert.Empty(diagram.Links);
+        Assert.Equal(1, refreshes);
     }
 }
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 bcb63cd6d..879a96c52 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj
+++ b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj
@@ -7,7 +7,6 @@
   
 
   
-    
     
     
     
diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs
index 5493e51fa..b5db2c566 100644
--- a/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/DiagramOrderingTests.cs
@@ -1,6 +1,4 @@
 using Blazor.Diagrams.Core.Models;
-using FluentAssertions;
-using System;
 using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests;
@@ -17,7 +15,7 @@ public void GetMinOrder_ShouldReturnZeroWhenNoModelsHaveBeenAdded()
         var minOrder = diagram.GetMinOrder();
 
         // Assert
-        minOrder.Should().Be(0);
+        Assert.Equal(0, minOrder);
     }
 
     [Fact]
@@ -33,7 +31,7 @@ public void GetMinOrder_ShouldReturnCorrectValue()
         var minOrder = diagram.GetMinOrder();
 
         // Assert
-        minOrder.Should().Be(1);
+        Assert.Equal(1, minOrder);
     }
 
     [Fact]
@@ -46,7 +44,7 @@ public void GetMaxOrder_ShouldReturnZeroWhenNoModelsHaveBeenAdded()
         var maxOrder = diagram.GetMaxOrder();
 
         // Assert
-        maxOrder.Should().Be(0);
+        Assert.Equal(0, maxOrder);
     }
 
     [Fact]
@@ -62,7 +60,7 @@ public void GetMaxOrder_ShouldReturnCorrectValue()
         var maxOrder = diagram.GetMaxOrder();
 
         // Assert
-        maxOrder.Should().Be(3);
+        Assert.Equal(3, maxOrder);
     }
 
     [Fact]
@@ -77,8 +75,8 @@ public void Diagram_ShouldReSortWhenModelOrderChanges()
         node1.Order = 10;
 
         // Assert
-        diagram.OrderedSelectables[0].Should().Be(node2);
-        diagram.OrderedSelectables[1].Should().Be(node1);
+        Assert.Equal(node2, diagram.OrderedSelectables[0]);
+        Assert.Equal(node1, diagram.OrderedSelectables[1]);
     }
 
     [Fact]
@@ -95,7 +93,7 @@ public void Diagram_ShouldRefreshOnceWhenModelOrderChanges()
         node1.Order = 10;
 
         // Assert
-        refreshes.Should().Be(1);
+        Assert.Equal(1, refreshes);
     }
 
     [Fact]
@@ -111,14 +109,14 @@ public void SendToBack_ShouldInsertAtZeroAndFixOrders()
         diagram.SendToBack(node3);
 
         // Assert
-        diagram.OrderedSelectables[0].Should().Be(node3);
-        diagram.OrderedSelectables[0].Order.Should().Be(1);
+        Assert.Equal(node3, diagram.OrderedSelectables[0]);
+        Assert.Equal(1, diagram.OrderedSelectables[0].Order);
 
-        diagram.OrderedSelectables[1].Should().Be(node1);
-        diagram.OrderedSelectables[1].Order.Should().Be(2);
+        Assert.Equal(node1, diagram.OrderedSelectables[1]);
+        Assert.Equal(2, diagram.OrderedSelectables[1].Order);
 
-        diagram.OrderedSelectables[2].Should().Be(node2);
-        diagram.OrderedSelectables[2].Order.Should().Be(3);
+        Assert.Equal(node2, diagram.OrderedSelectables[2]);
+        Assert.Equal(3, diagram.OrderedSelectables[2].Order);
     }
 
     [Fact]
@@ -134,14 +132,14 @@ public void SendToFront_ShouldAddAndFixOrder()
         diagram.SendToFront(node1);
 
         // Assert
-        diagram.OrderedSelectables[0].Should().Be(node2);
-        diagram.OrderedSelectables[0].Order.Should().Be(2);
+        Assert.Equal(node2, diagram.OrderedSelectables[0]);
+        Assert.Equal(2, diagram.OrderedSelectables[0].Order);
 
-        diagram.OrderedSelectables[1].Should().Be(node3);
-        diagram.OrderedSelectables[1].Order.Should().Be(3);
+        Assert.Equal(node3, diagram.OrderedSelectables[1]);
+        Assert.Equal(3, diagram.OrderedSelectables[1].Order);
 
-        diagram.OrderedSelectables[2].Should().Be(node1);
-        diagram.OrderedSelectables[2].Order.Should().Be(4);
+        Assert.Equal(node1, diagram.OrderedSelectables[2]);
+        Assert.Equal(4, diagram.OrderedSelectables[2].Order);
     }
 
     [Fact]
@@ -159,7 +157,7 @@ public void Diagram_ShouldRefreshOnceWhenMultipleModelsWereRemoved()
         diagram.Nodes.Remove(node1);
 
         // Assert
-        refreshes.Should().Be(1);
+        Assert.Equal(1, refreshes);
     }
 
     [Fact]
@@ -175,8 +173,8 @@ public void Diagram_ShouldNotUpdateOrders_WhenSuspendSortingIsTrue()
         node1.Order = 10;
 
         // Assert
-        diagram.OrderedSelectables[0].Should().Be(node1);
-        diagram.OrderedSelectables[1].Should().Be(node2);
+        Assert.Equal(node1, diagram.OrderedSelectables[0]);
+        Assert.Equal(node2, diagram.OrderedSelectables[1]);
     }
 
     [Fact]
@@ -191,7 +189,7 @@ public void RefreshOrders_ShouldSortModels()
         diagram.RefreshOrders();
 
         // Assert
-        diagram.OrderedSelectables[0].Should().Be(node2);
-        diagram.OrderedSelectables[1].Should().Be(node1);
+        Assert.Equal(node2, diagram.OrderedSelectables[0]);
+        Assert.Equal(node1, diagram.OrderedSelectables[1]);
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs
index c129c8bde..54fe9b983 100644
--- a/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/DiagramTests.cs
@@ -1,7 +1,5 @@
 using Blazor.Diagrams.Core.Geometry;
 using Blazor.Diagrams.Core.Models;
-using FluentAssertions;
-using System;
 using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests;
@@ -21,8 +19,8 @@ public void GetScreenPoint_ShouldReturnCorrectPoint()
         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
+        Assert.Equal(203.4, pt.X);// 2*X + panX + left
+        Assert.Equal(361.8, pt.Y);// 2*Y + panY + top
     }
 
     [Fact]
@@ -41,9 +39,9 @@ public void ZoomToFit_ShouldUseSelectedNodesIfAny()
         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);
+        Assert.InRange(diagram.Zoom, 7.679, 7.681);
+        Assert.Equal(-307.2, diagram.Pan.X);
+        Assert.Equal(-307.2, diagram.Pan.Y);
     }
 
     [Fact]
@@ -61,9 +59,9 @@ public void ZoomToFit_ShouldUseNodesWhenNoneSelected()
         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);
+        Assert.InRange(diagram.Zoom, 7.679, 7.681);
+        Assert.Equal(-307.2, diagram.Pan.X);
+        Assert.Equal(-307.2, diagram.Pan.Y);
     }
 
     [Fact]
@@ -88,9 +86,9 @@ public void ZoomToFit_ShouldTriggerAppropriateEvents()
         diagram.ZoomToFit(10);
 
         // Assert
-        refreshes.Should().Be(1);
-        zoomChanges.Should().Be(1);
-        panChanges.Should().Be(1);
+        Assert.Equal(1, refreshes);
+        Assert.Equal(1, zoomChanges);
+        Assert.Equal(1, panChanges);
     }
 
     [Theory]
@@ -133,6 +131,6 @@ public void SetContainer_ShouldAcceptNullGracefully()
         var exception = Record.Exception(() => diagram.SetContainer(null));
 
         // Assert
-        exception.Should().BeNull();
+        Assert.Null(exception);
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Geometry/PointTests.cs b/tests/Blazor.Diagrams.Core.Tests/Geometry/PointTests.cs
index 104103578..0f5af52c7 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Geometry/PointTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Geometry/PointTests.cs
@@ -1,7 +1,5 @@
 using Blazor.Diagrams.Core.Geometry;
-using FluentAssertions;
 using Xunit;
-
 namespace Blazor.Diagrams.Core.Tests.Geometry;
 
 public class PointTests
@@ -15,6 +13,6 @@ public void DistanceTo(double x1, double y1, double x2, double y2, double expect
     {
         var pt1 = new Point(x1, y1);
         var pt2 = new Point(x2, y2);
-        pt1.DistanceTo(pt2).Should().BeApproximately(expected, 0.0001);
+        Assert.InRange(pt1.DistanceTo(pt2), expected - 0.0001, expected + 0.0001);
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs b/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs
index c17398c6b..2c4a49d7d 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Layers/GroupLayerTests.cs
@@ -1,5 +1,4 @@
 using Blazor.Diagrams.Core.Models;
-using FluentAssertions;
 using System;
 using Xunit;
 
@@ -24,7 +23,7 @@ public void Group_ShouldCallFactoryThenAddMethod()
         diagram.Groups.Group(Array.Empty());
 
         // Assert
-        factoryCalled.Should().BeTrue();
+        Assert.True(factoryCalled);
     }
 
     [Fact]
@@ -42,7 +41,7 @@ public void Remove_ShouldRemoveAllPortLinks()
         diagram.Groups.Remove(group);
 
         // Assert
-        diagram.Links.Should().BeEmpty();
+        Assert.Empty(diagram.Links);
     }
 
     [Fact]
@@ -58,7 +57,7 @@ public void Remove_ShouldRemoveAllLinks()
         diagram.Groups.Remove(group);
 
         // Assert
-        diagram.Links.Should().BeEmpty();
+        Assert.Empty(diagram.Links);
     }
 
     [Fact]
@@ -73,8 +72,8 @@ public void Remove_ShouldRemoveItselfFromParentGroup()
         diagram.Groups.Remove(group1);
 
         // Assert
-        group2.Children.Should().BeEmpty();
-        group1.Group.Should().BeNull();
+        Assert.Empty(group2.Children);
+        Assert.Null(group1.Group);
     }
 
     [Fact]
@@ -89,8 +88,8 @@ public void Remove_ShouldUngroup()
         diagram.Groups.Remove(group);
 
         // Assert
-        group.Children.Should().BeEmpty();
-        node.Group.Should().BeNull();
+        Assert.Empty(group.Children);
+        Assert.Null(node.Group);
     }
 
     [Fact]
@@ -105,7 +104,7 @@ public void Delete_ShouldDeleteChildGroup()
         diagram.Groups.Delete(group2);
 
         // Assert
-        diagram.Groups.Should().BeEmpty();
+        Assert.Empty(diagram.Groups);
     }
 
     [Fact]
@@ -120,8 +119,8 @@ public void Delete_ShouldRemoveChild()
         diagram.Groups.Delete(group);
 
         // Assert
-        diagram.Groups.Should().BeEmpty();
-        diagram.Nodes.Should().BeEmpty();
+        Assert.Empty(diagram.Groups);
+        Assert.Empty(diagram.Nodes);
     }
 
     [Fact]
@@ -136,7 +135,7 @@ public void Add_ShouldRefreshDiagramOnce()
         var group = diagram.Groups.Add(new GroupModel(Array.Empty()));
 
         // Assert
-        refreshes.Should().Be(1);
+        Assert.Equal(1, refreshes);
     }
 
     [Fact]
@@ -152,7 +151,7 @@ public void Remove_ShouldRefreshDiagramOnce()
         diagram.Groups.Remove(group);
 
         // Assert
-        refreshes.Should().Be(1);
+        Assert.Equal(1,refreshes);
     }
 
     [Fact]
@@ -169,6 +168,6 @@ public void Delete_ShouldRefreshDiagramOnce()
         diagram.Groups.Delete(group);
 
         // Assert
-        refreshes.Should().Be(1);
+        Assert.Equal(1,refreshes);
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Layers/NodeLayerTests.cs b/tests/Blazor.Diagrams.Core.Tests/Layers/NodeLayerTests.cs
index 7c5718ef4..3316f3e00 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Layers/NodeLayerTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Layers/NodeLayerTests.cs
@@ -1,5 +1,4 @@
 using Blazor.Diagrams.Core.Models;
-using FluentAssertions;
 using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests.Layers;
@@ -21,7 +20,7 @@ public void Remove_ShouldRemoveAllPortLinks()
         diagram.Nodes.Remove(node1);
 
         // Assert
-        diagram.Links.Should().BeEmpty();
+        Assert.Empty(diagram.Links);
     }
 
     [Fact]
@@ -37,7 +36,7 @@ public void Remove_ShouldRemoveAllLinks()
         diagram.Nodes.Remove(node1);
 
         // Assert
-        diagram.Links.Should().BeEmpty();
+        Assert.Empty(diagram.Links);
     }
 
     [Fact]
@@ -52,8 +51,8 @@ public void Remove_ShouldRemoveItselfFromParentGroup()
         diagram.Nodes.Remove(node);
 
         // Assert
-        group.Children.Should().BeEmpty();
-        node.Group.Should().BeNull();
+        Assert.Empty(group.Children);
+        Assert.Null(node.Group);
     }
 
     [Fact]
@@ -68,7 +67,7 @@ public void Add_ShouldRefreshDiagramOnce()
         var node = diagram.Nodes.Add(new NodeModel());
 
         // Assert
-        refreshes.Should().Be(1);
+        Assert.Equal(1,refreshes);
     }
 
     [Fact]
@@ -86,7 +85,7 @@ public void Remove_ShouldRefreshDiagramOnce()
         diagram.Nodes.Remove(node1);
 
         // Assert
-        refreshes.Should().Be(1);
+        Assert.Equal(1,refreshes);
     }
 
     [Fact]
@@ -101,6 +100,6 @@ public void Remove_ShouldRemoveControls()
         diagram.Nodes.Remove(node);
 
         // Assert
-        diagram.Controls.GetFor(node).Should().BeNull();
+        Assert.Null(diagram.Controls.GetFor(node));
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs
index c4566e861..0d6e339f3 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Models/Base/BaseLinkModelTests.cs
@@ -4,7 +4,6 @@
 using Blazor.Diagrams.Core.Models.Base;
 using Blazor.Diagrams.Core.PathGenerators;
 using Blazor.Diagrams.Core.Routers;
-using FluentAssertions;
 using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests.Models.Base;
@@ -36,12 +35,12 @@ public void SetSource_ShouldChangePropertiesAndTriggerEvent()
         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.Equal(1, eventsTriggered);
+        Assert.Same(sp, link.Source);
+        Assert.NotNull(oldSp);
+        Assert.Same(sp, newSp);
+        Assert.Same(link, linkInstance);
+        Assert.Same(port, link.Source.Model);
     }
 
     [Fact]
@@ -69,12 +68,12 @@ public void SetTarget_ShouldChangePropertiesAndTriggerEvent()
         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.Equal(1, eventsTriggered);
+        Assert.Same(tp, link.Target);
+        Assert.IsType(oldTp);
+        Assert.Same(tp, newTp);
+        Assert.Same(link, linkInstance);
+        Assert.Same(port, link.Target!.Model);
     }
 
     [Fact]
@@ -91,9 +90,9 @@ public void GetBounds_ShouldReturnPathBBox()
         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.Equal(10, bounds.Left);
+        Assert.Equal(5, bounds.Top);
+        Assert.Equal(90, bounds.Width);
+        Assert.Equal(75, bounds.Height);
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
index e7ae20dc4..5287508da 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs
@@ -4,7 +4,6 @@
 using Blazor.Diagrams.Core.Geometry;
 using Blazor.Diagrams.Core.Models;
 using Blazor.Diagrams.Core.Positions.Resizing;
-using FluentAssertions;
 using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
@@ -24,10 +23,11 @@ public void DragResizer_ShouldResizeNode()
         diagram.SelectModel(node, false);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0, node.Position.X);
+        Assert.Equal(0, node.Position.Y);
+        Assert.Equal(100, node.Size.Width);
+        Assert.Equal(200, node.Size.Height);
+
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -36,10 +36,10 @@ public void DragResizer_ShouldResizeNode()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(10);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(90);
-        node.Size.Height.Should().Be(215);
+        Assert.Equal(10, node.Position.X);
+        Assert.Equal(0, node.Position.Y);
+        Assert.Equal(90, node.Size.Width);
+        Assert.Equal(215, node.Size.Height);
     }
 
     [Fact]
@@ -56,10 +56,10 @@ public void PanChanged_ShouldResizeNode()
         diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior();
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0, node.Position.X);
+        Assert.Equal(0, node.Position.Y);
+        Assert.Equal(100, node.Size.Width);
+        Assert.Equal(200, node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -68,10 +68,10 @@ public void PanChanged_ShouldResizeNode()
 
 
         // after resize
-        node.Position.X.Should().Be(10);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(90);
-        node.Size.Height.Should().Be(300);
+        Assert.Equal(10, node.Position.X);
+        Assert.Equal(0, node.Position.Y);
+        Assert.Equal(90, node.Size.Width);
+        Assert.Equal(300, node.Size.Height);
     }
 
     [Fact]
@@ -88,10 +88,10 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         diagram.SelectModel(node, false);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(300);
-        node.Size.Height.Should().Be(300);
+        Assert.Equal(0, node.Position.X);
+        Assert.Equal(0, node.Position.Y);
+        Assert.Equal(300, node.Size.Width);
+        Assert.Equal(300, node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 300, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -102,10 +102,10 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(250);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(50);
-        node.Size.Height.Should().Be(100);
+        Assert.Equal(250, node.Position.X);
+        Assert.Equal(0, node.Position.Y);
+        Assert.Equal(50, node.Size.Width);
+        Assert.Equal(100, node.Size.Height);
     }
 
     [Fact]
@@ -122,10 +122,10 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut()
         diagram.SetZoom(0.5);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0, node.Position.X);
+        Assert.Equal(0, node.Position.Y);
+        Assert.Equal(100, node.Size.Width);
+        Assert.Equal(200, node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -134,10 +134,10 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(20);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(80);
-        node.Size.Height.Should().Be(230);
+        Assert.Equal(20, node.Position.X);
+        Assert.Equal(0, node.Position.Y);
+        Assert.Equal(80, node.Size.Width);
+        Assert.Equal(230, node.Size.Height);
     }
 
     [Fact]
@@ -154,10 +154,10 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn()
         diagram.SetZoom(2);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0, node.Position.X);
+        Assert.Equal(0, node.Position.Y);
+        Assert.Equal(100, node.Size.Width);
+        Assert.Equal(200, node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -166,9 +166,9 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(5);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(95);
-        node.Size.Height.Should().Be(207.5);
+        Assert.Equal(5, node.Position.X);
+        Assert.Equal(0, node.Position.Y);
+        Assert.Equal(95, node.Size.Width);
+        Assert.Equal(207.5, node.Size.Height);
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs
index 62ad0270f..e2a69adba 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs
@@ -4,7 +4,6 @@
 using Blazor.Diagrams.Core.Geometry;
 using Blazor.Diagrams.Core.Models;
 using Blazor.Diagrams.Core.Positions.Resizing;
-using FluentAssertions;
 using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
@@ -24,10 +23,10 @@ public void DragResizer_ShouldResizeNode()
         diagram.SelectModel(node, false);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(100,node.Size.Width);
+        Assert.Equal(200,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -36,10 +35,10 @@ public void DragResizer_ShouldResizeNode()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(110);
-        node.Size.Height.Should().Be(215);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(110,node.Size.Width);
+        Assert.Equal(215,node.Size.Height);
     }
 
     [Fact]
@@ -57,10 +56,10 @@ public void PanChanged_ShouldResizeNode()
 
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(100,node.Size.Width);
+        Assert.Equal(200,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -69,10 +68,10 @@ public void PanChanged_ShouldResizeNode()
 
 
         // after resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(110);
-        node.Size.Height.Should().Be(300);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(110,node.Size.Width);
+        Assert.Equal(300,node.Size.Height);
     }
 
     [Fact]
@@ -88,10 +87,10 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         diagram.SelectModel(node, false);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(100,node.Size.Width);
+        Assert.Equal(200,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -100,10 +99,10 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(0);
-        node.Size.Height.Should().Be(0);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(0,node.Size.Width);
+        Assert.Equal(0,node.Size.Height);
     }
 
     [Fact]
@@ -120,10 +119,10 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut()
         diagram.SetZoom(0.5);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(100,node.Size.Width);
+        Assert.Equal(200,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -132,10 +131,10 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(120);
-        node.Size.Height.Should().Be(230);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(120,node.Size.Width);
+        Assert.Equal(230,node.Size.Height);
     }
 
     [Fact]
@@ -152,10 +151,10 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn()
         diagram.SetZoom(2);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(100,node.Size.Width);
+        Assert.Equal(200,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -164,9 +163,9 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(105);
-        node.Size.Height.Should().Be(207.5);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(105,node.Size.Width);
+        Assert.Equal(207.5,node.Size.Height);
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
index 4bb44293d..07a4ee815 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs
@@ -4,7 +4,6 @@
 using Blazor.Diagrams.Core.Geometry;
 using Blazor.Diagrams.Core.Models;
 using Blazor.Diagrams.Core.Positions.Resizing;
-using FluentAssertions;
 using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
@@ -24,10 +23,10 @@ public void DragResizer_ShouldResizeNode()
         diagram.SelectModel(node, false);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(100,node.Size.Width);
+        Assert.Equal(200,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -36,10 +35,10 @@ public void DragResizer_ShouldResizeNode()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(10);
-        node.Position.Y.Should().Be(15);
-        node.Size.Width.Should().Be(90);
-        node.Size.Height.Should().Be(185);
+        Assert.Equal(10,node.Position.X);
+        Assert.Equal(15,node.Position.Y);
+        Assert.Equal(90,node.Size.Width);
+        Assert.Equal(185,node.Size.Height);
     }
 
     [Fact]
@@ -57,10 +56,10 @@ public void PanChanged_ShouldResizeNode()
 
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(100,node.Size.Width);
+        Assert.Equal(200,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -69,10 +68,10 @@ public void PanChanged_ShouldResizeNode()
 
 
         // after resize
-        node.Position.X.Should().Be(10);
-        node.Position.Y.Should().Be(-100);
-        node.Size.Width.Should().Be(90);
-        node.Size.Height.Should().Be(300);
+        Assert.Equal(10,node.Position.X);
+        Assert.Equal(-100,node.Position.Y);
+        Assert.Equal(90,node.Size.Width);
+        Assert.Equal(300,node.Size.Height);
     }
 
     [Fact]
@@ -89,10 +88,10 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         diagram.SelectModel(node, false);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(300);
-        node.Size.Height.Should().Be(300);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(300,node.Size.Width);
+        Assert.Equal(300,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -103,10 +102,10 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(250);
-        node.Position.Y.Should().Be(200);
-        node.Size.Width.Should().Be(50);
-        node.Size.Height.Should().Be(100);
+        Assert.Equal(250,node.Position.X);
+        Assert.Equal(200,node.Position.Y);
+        Assert.Equal(50,node.Size.Width);
+        Assert.Equal(100,node.Size.Height);
     }
 
     [Fact]
@@ -123,10 +122,10 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut()
         diagram.SetZoom(0.5);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(100,node.Size.Width);
+        Assert.Equal(200,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -135,10 +134,10 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(20);
-        node.Position.Y.Should().Be(30);
-        node.Size.Width.Should().Be(80);
-        node.Size.Height.Should().Be(170);
+        Assert.Equal(20,node.Position.X);
+        Assert.Equal(30,node.Position.Y);
+        Assert.Equal(80,node.Size.Width);
+        Assert.Equal(170,node.Size.Height);
     }
 
     [Fact]
@@ -155,10 +154,10 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn()
         diagram.SetZoom(2);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(100,node.Size.Width);
+        Assert.Equal(200,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -167,9 +166,9 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(5);
-        node.Position.Y.Should().Be(7.5);
-        node.Size.Width.Should().Be(95);
-        node.Size.Height.Should().Be(192.5);
+        Assert.Equal(5,node.Position.X);
+        Assert.Equal(7.5,node.Position.Y);
+        Assert.Equal(95,node.Size.Width);
+        Assert.Equal(192.5,node.Size.Height);
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
index 84b1d9d37..9aaa1b566 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs
@@ -4,7 +4,6 @@
 using Blazor.Diagrams.Core.Geometry;
 using Blazor.Diagrams.Core.Models;
 using Blazor.Diagrams.Core.Positions.Resizing;
-using FluentAssertions;
 using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests.Positions.Resizing;
@@ -24,10 +23,10 @@ public void DragResizer_ShouldResizeNode()
         diagram.SelectModel(node, false);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(100,node.Size.Width);
+        Assert.Equal(200,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -36,10 +35,10 @@ public void DragResizer_ShouldResizeNode()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(15);
-        node.Size.Width.Should().Be(110);
-        node.Size.Height.Should().Be(185);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(15,node.Position.Y);
+        Assert.Equal(110,node.Size.Width);
+        Assert.Equal(185,node.Size.Height);
     }
 
     [Fact]
@@ -56,10 +55,10 @@ public void PanChanged_ShouldResizeNode()
         diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior();
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(100,node.Size.Width);
+        Assert.Equal(200,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -67,10 +66,10 @@ public void PanChanged_ShouldResizeNode()
         diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, -100, 0, 0));
 
         // after resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(-100);
-        node.Size.Width.Should().Be(110);
-        node.Size.Height.Should().Be(300);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(-100,node.Position.Y);
+        Assert.Equal(110,node.Size.Width);
+        Assert.Equal(300,node.Size.Height);
     }
 
     [Fact]
@@ -87,10 +86,10 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         diagram.SelectModel(node, false);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(300);
-        node.Size.Height.Should().Be(300);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(300,node.Size.Width);
+        Assert.Equal(300,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(300, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -101,10 +100,10 @@ public void DragResizer_SmallerThanMinSize_SetsNodeToMinSize()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(200);
-        node.Size.Width.Should().Be(50);
-        node.Size.Height.Should().Be(100);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(200,node.Position.Y);
+        Assert.Equal(50,node.Size.Width);
+        Assert.Equal(100,node.Size.Height);
     }
 
     [Fact]
@@ -121,10 +120,10 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut()
         diagram.SetZoom(0.5);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(100,node.Size.Width);
+        Assert.Equal(200,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -133,10 +132,10 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(30);
-        node.Size.Width.Should().Be(120);
-        node.Size.Height.Should().Be(170);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(30,node.Position.Y);
+        Assert.Equal(120,node.Size.Width);
+        Assert.Equal(170,node.Size.Height);
     }
 
     [Fact]
@@ -153,10 +152,10 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn()
         diagram.SetZoom(2);
 
         // before resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(0);
-        node.Size.Width.Should().Be(100);
-        node.Size.Height.Should().Be(200);
+        Assert.Equal(0,node.Position.X);
+        Assert.Equal(0,node.Position.Y);
+        Assert.Equal(100,node.Size.Width);
+        Assert.Equal(200,node.Size.Height);
 
         // resize
         var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true);
@@ -165,9 +164,9 @@ public void DragResizer_ShouldResizeNode_WhenDiagramZoomedIn()
         diagram.TriggerPointerMove(null, eventArgs);
 
         // after resize
-        node.Position.X.Should().Be(0);
-        node.Position.Y.Should().Be(7.5);
-        node.Size.Width.Should().Be(105);
-        node.Size.Height.Should().Be(192.5);
+        Assert.Equal(0, node.Position.X);
+        Assert.Equal(7.5, node.Position.Y);
+        Assert.Equal(105, node.Size.Width);
+        Assert.Equal(192.5, node.Size.Height);
     }
 }
diff --git a/tests/Blazor.Diagrams.Core.Tests/Positions/ShapeAnglePositionProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/ShapeAnglePositionProviderTests.cs
index 205cad5b8..bb915b333 100644
--- a/tests/Blazor.Diagrams.Core.Tests/Positions/ShapeAnglePositionProviderTests.cs
+++ b/tests/Blazor.Diagrams.Core.Tests/Positions/ShapeAnglePositionProviderTests.cs
@@ -1,7 +1,6 @@
 using Blazor.Diagrams.Core.Geometry;
 using Blazor.Diagrams.Core.Models;
 using Blazor.Diagrams.Core.Positions;
-using FluentAssertions;
 using Moq;
 using Xunit;
 
@@ -39,7 +38,7 @@ public void GetPosition_ShouldUseOffset_WhenProvided()
         var position = provider.GetPosition(nodeMock.Object);
 
         // Assert
-        position!.X.Should().Be(105);
-        position.Y.Should().Be(40);
+        Assert.Equal(105, position!.X);
+        Assert.Equal(40, position.Y);
     }
 }
\ No newline at end of file
diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props
index 6cef34442..ce4aec516 100644
--- a/tests/Directory.Build.props
+++ b/tests/Directory.Build.props
@@ -1,6 +1,6 @@
 
   
-    net8.0;net7.0;net6.0;
+    net8.0;net6.0
     enable
     enable
     true

From 5e6a578c4da4ed6b022a0d2a04a4abfe635ced45 Mon Sep 17 00:00:00 2001
From: Srude-Vena 
Date: Tue, 4 Feb 2025 10:29:58 +0530
Subject: [PATCH 151/159] WI00856226 - FluentAssertions removal from
 Blazor.Diagrams.Tests  (#51)

* FluentAssertion removed from Blazor.Diagrams.Tests.csproj

* .net7.0 removed from Directory.Build.props

* spacing and spelling fixed

* release.yml file updated
---
 .github/workflows/release.yml                         |  2 +-
 .../Blazor.Diagrams.Tests.csproj                      |  1 -
 .../Components/DiagramCursorTests.cs                  |  5 ++---
 .../Components/LinkVertexWidgetTests.cs               |  9 ++++-----
 .../Components/NodeWidgetTests.cs                     |  9 ++++-----
 tests/Blazor.Diagrams.Tests/DiagramTests.cs           | 11 +++++------
 6 files changed, 16 insertions(+), 21 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 10df12f74..a783eb6ed 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -50,7 +50,7 @@ jobs:
 
       # Upload package as an atrifact to the GitHub action
     - name: Upload packages
-      uses: actions/upload-artifact@v3
+      uses: actions/upload-artifact@v4
       with:
         name: package
         path: ${{ env.PACKAGE_PATH }}
diff --git a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj
index 9d94a73ad..949bf1c97 100644
--- a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj
+++ b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj
@@ -9,7 +9,6 @@
 
   
     
-    
     
     
     
diff --git a/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs b/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs
index ed6ad41e7..c81538730 100644
--- a/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs
+++ b/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs
@@ -2,7 +2,6 @@
 using Blazor.Diagrams.Components;
 using Blazor.Diagrams.Core.Geometry;
 using Bunit;
-using FluentAssertions;
 using Xunit;
 
 namespace Blazor.Diagrams.Core.Tests.Behaviors
@@ -24,7 +23,7 @@ public void Behavior_WhenPanningOptionIsAllowed_CursorShouldBeGrab()
             var diagramCanvas = cut.Find(".diagram-canvas");
 
             // Assert
-            diagramCanvas.ToMarkup().Should().Contain("cursor: grab; cursor: -webkit-grab;");
+            Assert.Contains("cursor: grab; cursor: -webkit-grab;", diagramCanvas.ToMarkup());
         }
 
         [Fact]
@@ -43,7 +42,7 @@ public void Behavior_WhenPanningOptionIsNotAllowed_CursorShouldBeDefault()
             var canvasStyle = diagramCanvas.GetStyle().CssText;
 
             // Assert
-            canvasStyle.Should().Contain("cursor: default");
+            Assert.Contains("cursor: default", canvasStyle);
         }
     }
 }
diff --git a/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs
index 9c4678099..29b985ebc 100644
--- a/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs
+++ b/tests/Blazor.Diagrams.Tests/Components/LinkVertexWidgetTests.cs
@@ -3,7 +3,6 @@
 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;
@@ -76,9 +75,9 @@ public void ShouldRerender_WhenVertexIsRefreshed()
             .Add(n => n.BlazorDiagram, new BlazorDiagram()));
 
         // Assert
-        cut.RenderCount.Should().Be(1);
+        Assert.Equal(1, cut.RenderCount);
         vertex.Refresh();
-        cut.RenderCount.Should().Be(2);
+        Assert.Equal(2, cut.RenderCount);
     }
 
     [Fact]
@@ -104,8 +103,8 @@ public async Task ShouldDeleteItselfAndRefreshParent_WhenDoubleClicked()
         await cut.Find("circle").DoubleClickAsync(new MouseEventArgs());
 
         // Assert
-        link.Vertices.Should().BeEmpty();
-        linkRefreshes.Should().Be(1);
+        Assert.Empty(link.Vertices);
+        Assert.Equal(1,linkRefreshes);
     }
 
     [Fact]
diff --git a/tests/Blazor.Diagrams.Tests/Components/NodeWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/NodeWidgetTests.cs
index dfabb012d..b3cb656bb 100644
--- a/tests/Blazor.Diagrams.Tests/Components/NodeWidgetTests.cs
+++ b/tests/Blazor.Diagrams.Tests/Components/NodeWidgetTests.cs
@@ -5,7 +5,6 @@
 
 using Bunit;
 
-using FluentAssertions;
 
 using Xunit;
 
@@ -26,11 +25,11 @@ public void DefaultNodeWidget_ShouldHaveSingleClassAndNoPorts_WhenItHasNoPortsAn
 
         // 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");
+        Assert.Single(content.ClassList);
+        Assert.Equal("default-node", content.ClassList[0]);
+        Assert.Equal("Title", content.TextContent.Trim());
 
         var ports = cut.FindComponents();
-        ports.Should().BeEmpty();
+        Assert.Empty(ports);
     }
 }
diff --git a/tests/Blazor.Diagrams.Tests/DiagramTests.cs b/tests/Blazor.Diagrams.Tests/DiagramTests.cs
index a61abc64b..5e6ba0d17 100644
--- a/tests/Blazor.Diagrams.Tests/DiagramTests.cs
+++ b/tests/Blazor.Diagrams.Tests/DiagramTests.cs
@@ -1,7 +1,6 @@
 using Blazor.Diagrams.Components;
 using Blazor.Diagrams.Core.Models;
 using Blazor.Diagrams.Core.Models.Base;
-using FluentAssertions;
 using Microsoft.AspNetCore.Components;
 using Xunit;
 
@@ -20,7 +19,7 @@ public void GetComponentForModel_ShouldReturnComponentType_WhenModelTypeWasRegis
         var componentType = diagram.GetComponent();
 
         // Assert
-        componentType.Should().Be(typeof(NodeWidget));
+        Assert.Equal(typeof(NodeWidget), componentType);
     }
 
     [Fact]
@@ -33,7 +32,7 @@ public void GetComponentForModel_ShouldReturnNull_WhenModelTypeWasNotRegistered(
         var componentType = diagram.GetComponent();
 
         // Assert
-        componentType.Should().BeNull();
+        Assert.Null(componentType);
     }
 
     [Fact]
@@ -47,7 +46,7 @@ public void GetComponentForModel_ShouldReturnComponentType_WhenInheritedModelTyp
         var componentType = diagram.GetComponent();
 
         // Assert
-        componentType.Should().Be(typeof(NodeWidget));
+        Assert.Equal(typeof(NodeWidget), componentType);
     }
 
     [Fact]
@@ -62,7 +61,7 @@ public void GetComponentForModel_ShouldReturnSpecificComponentType_WhenInherited
         var componentType = diagram.GetComponent();
 
         // Assert
-        componentType.Should().Be(typeof(CustomWidget));
+        Assert.Equal(typeof(CustomWidget), componentType);
     }
 
     [Fact]
@@ -76,7 +75,7 @@ public void GetComponentForModel_ShouldReturnNull_WhenCheckSubclassesIsFalse()
         var componentType = diagram.GetComponent(false);
 
         // Assert
-        componentType.Should().BeNull();
+        Assert.Null(componentType);
     }
 
     private class CustomModel : Model { }

From 827fa87e67ed2f13c44ac4432c00ed38af6225ed Mon Sep 17 00:00:00 2001
From: OttoDobretsberger 
Date: Tue, 25 Mar 2025 14:52:56 -0500
Subject: [PATCH 152/159] new file, new implementation with separate thread to
 modify the collection of models

---
 .../Controls/ControlsLayerRenderer.razor      |  2 +-
 .../Components/ControlsLayerRendererTests.cs  | 95 +++++++++++++++++++
 2 files changed, 96 insertions(+), 1 deletion(-)
 create mode 100644 tests/Blazor.Diagrams.Tests/Components/ControlsLayerRendererTests.cs

diff --git a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor
index 7146c2316..3b74d4a83 100644
--- a/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor
+++ b/src/Blazor.Diagrams/Components/Controls/ControlsLayerRenderer.razor
@@ -1,4 +1,4 @@
-@foreach (var model in BlazorDiagram.Controls.Models)
+@foreach (var model in BlazorDiagram.Controls.Models.ToList())
 {
     var controls = BlazorDiagram.Controls.GetFor(model)!;
     if (!controls.Visible || controls.Count == 0)
diff --git a/tests/Blazor.Diagrams.Tests/Components/ControlsLayerRendererTests.cs b/tests/Blazor.Diagrams.Tests/Components/ControlsLayerRendererTests.cs
new file mode 100644
index 000000000..bf922ad38
--- /dev/null
+++ b/tests/Blazor.Diagrams.Tests/Components/ControlsLayerRendererTests.cs
@@ -0,0 +1,95 @@
+using Bunit;
+using Xunit;
+using Blazor.Diagrams.Components.Controls;
+using Blazor.Diagrams.Core.Models.Base;
+
+namespace Blazor.Diagrams.Tests.Components
+{
+    public class ControlsLayerRendererTests
+    {
+        [Fact]
+        public async Task Rendering_WithChangingModels_ShouldNotThrowException()
+        {
+            // Arrange
+            using var ctx = new TestContext();
+            var diagram = new BlazorDiagram();
+
+            var model1 = new CustomModel();
+            var model2 = new CustomModel();
+
+            diagram.Controls.AddFor(model1);
+            diagram.Controls.AddFor(model2);
+
+            var renderStarted = new ManualResetEventSlim(false);
+            var modificationStarted = new ManualResetEventSlim(false);
+            var exceptionThrown = false;
+
+            IRenderedComponent? cut = null;
+
+            // Task to render the component
+            var renderTask = Task.Run(async () =>
+            {
+                try
+                {
+                    cut = ctx.RenderComponent(parameters => parameters
+                        .Add(c => c.BlazorDiagram, diagram));
+
+                    renderStarted.Set(); // Indicate that rendering has started
+
+                    // Force Blazor to update while modifications happen
+                    for (int i = 0; i < 10; i++)
+                    {
+                        await cut.InvokeAsync(() =>
+                        {
+                            cut.Render();
+                        });
+                        Thread.Sleep(5); // Let it process
+                    }
+
+                    modificationStarted.Wait(); // Wait for modifications
+                }
+                catch (InvalidOperationException)
+                {
+                    exceptionThrown = true;
+                }
+            });
+
+            // Wait to ensure rendering starts first
+            renderStarted.Wait();
+            await Task.Delay(10); // Allow time for rendering
+
+            // Task to modify the collection while rendering happens
+            var modifyTask = Task.Run(async () =>
+            {
+                try
+                {
+                    foreach (var model in diagram.Controls.Models)
+                    {
+                        if (model == model1)
+                        {
+                            await cut.InvokeAsync(() =>
+                            {
+                                diagram.Controls.RemoveFor(model1);
+                            });
+                        }
+                    }
+                }
+                catch (InvalidOperationException)
+                {
+                    exceptionThrown = true;
+                }
+                finally
+                {
+                    modificationStarted.Set();
+                }
+            });
+
+            // Wait for tasks to complete
+            await Task.WhenAll(renderTask, modifyTask);
+
+            // Assert
+            Assert.False(exceptionThrown, "Iteration should not throw an exception when using .ToList()");
+        }
+        private class CustomModel : Model { }
+    }
+}

From adeeea4e7c1aa8b932572b34e11cb53ba3cf04df Mon Sep 17 00:00:00 2001
From: OttoDobretsberger 
Date: Mon, 14 Apr 2025 15:24:29 -0500
Subject: [PATCH 153/159] added reference to SvgPathProperties

---
 src/Blazor.Diagrams/Blazor.Diagrams.csproj | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
index 306f1ee48..ffcc6904a 100644
--- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj
+++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
@@ -22,6 +22,9 @@
         
         
         
+		
+			..\..\packages\svgpathproperties\1.1.2\lib\netstandard2.0\SvgPathProperties.dll
+		
         
     
 

From f9e3dd14e246de0180453e0d8552558417b46d7c Mon Sep 17 00:00:00 2001
From: OttoDobretsberger 
Date: Tue, 15 Apr 2025 11:27:00 -0500
Subject: [PATCH 154/159] indentation change and hintpath removed to test if it
 builds without it

---
 src/Blazor.Diagrams/Blazor.Diagrams.csproj | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
index ffcc6904a..158bdd722 100644
--- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj
+++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
@@ -22,9 +22,7 @@
         
         
         
-		
-			..\..\packages\svgpathproperties\1.1.2\lib\netstandard2.0\SvgPathProperties.dll
-		
+		
         
     
 

From 49056c5ff8285f845031696527e8980a8bfd6f7b Mon Sep 17 00:00:00 2001
From: OttoDobretsberger 
Date: Tue, 15 Apr 2025 11:30:17 -0500
Subject: [PATCH 155/159] put the Hintpath back  - GitHub did not build without
 it

---
 src/Blazor.Diagrams/Blazor.Diagrams.csproj | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
index 158bdd722..ffcc6904a 100644
--- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj
+++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
@@ -22,7 +22,9 @@
         
         
         
-		
+		
+			..\..\packages\svgpathproperties\1.1.2\lib\netstandard2.0\SvgPathProperties.dll
+		
         
     
 

From 29487a3851e937b99ec8ab4d74f3df1097e2836d Mon Sep 17 00:00:00 2001
From: OttoDobretsberger 
Date: Tue, 15 Apr 2025 11:34:03 -0500
Subject: [PATCH 156/159] reset commit without Reference

---
 src/Blazor.Diagrams/Blazor.Diagrams.csproj | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/src/Blazor.Diagrams/Blazor.Diagrams.csproj b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
index ffcc6904a..306f1ee48 100644
--- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj
+++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj
@@ -22,9 +22,6 @@
         
         
         
-		
-			..\..\packages\svgpathproperties\1.1.2\lib\netstandard2.0\SvgPathProperties.dll
-		
         
     
 

From 308024c2677a030f1e31875451139135dde53398 Mon Sep 17 00:00:00 2001
From: OttoDobretsberger 
Date: Tue, 15 Apr 2025 11:50:21 -0500
Subject: [PATCH 157/159] add manual trigger abulity for github actions

---
 .github/workflows/build.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b14dc58ec..f8e654e38 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,5 +1,6 @@
 name: Build and Test
 on:
+  workflow_dispatch:
   push:
     branches: [ master ]
   pull_request:

From 4a59e787ef8ffd01ad4fe34442ccf5c18c6aa873 Mon Sep 17 00:00:00 2001
From: OttoDobretsberger 
Date: Tue, 15 Apr 2025 12:02:13 -0500
Subject: [PATCH 158/159] remove  manual trigger (did not work)

---
 .github/workflows/build.yml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index f8e654e38..b14dc58ec 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -1,6 +1,5 @@
 name: Build and Test
 on:
-  workflow_dispatch:
   push:
     branches: [ master ]
   pull_request:

From f852d2505de28421ff45328c4aa1e6a27f0dd709 Mon Sep 17 00:00:00 2001
From: Otto Dobretsberger <159489763+dobretsberger@users.noreply.github.com>
Date: Wed, 23 Apr 2025 22:46:20 -0500
Subject: [PATCH 159/159] Update release.yml

updated the release.yml to allow the use of dotnet nuget push instead of nuget push, which is cross platform a more modern version to publish, and since we're on linux, this also does not require mono to be installed
---
 .github/workflows/release.yml | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index a783eb6ed..277059e01 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -64,6 +64,4 @@ jobs:
         tag_name: ${{ steps.version.outputs.version }}
 
     - name: Push NuGet Package to NuGet Gallery
-      run: |
-        nuget setapikey ${{ secrets.NUGET_API_KEY }}
-        nuget push ${{ env.PACKAGE_PATH }} -Source https://api.nuget.org/v3/index.json
+      run: dotnet nuget push ${{ env.PACKAGE_PATH }} --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json