diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 069a8f419..b14dc58ec 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,47 +1,43 @@ -name: Build +name: Build and Test on: push: - branches: - - master - - develop - - 3.0.0 + branches: [ master ] pull_request: - types: [opened, synchronize, reopened] + branches: [ master ] + jobs: build: - name: Build + name: Build - ${{ matrix.configuration }} + + strategy: + matrix: + configuration: [ Debug, Release ] + 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: | + 8.0.x + 6.0.x + + - name: Install dependencies + run: dotnet restore + + - name: Build + run: dotnet build --configuration ${{ matrix.configuration }} + + - name: Test + if: matrix.configuration == 'Debug' + run: dotnet test --no-build + + - name: Upload packages + if: matrix.configuration == 'Release' + uses: actions/upload-artifact@v4 + with: + name: package + path: /home/runner/work/Blazor.Diagrams/Blazor.Diagrams/src/Blazor.Diagrams/bin/Release/*.nupkg + retention-days: 5 diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml deleted file mode 100644 index 77b2f8f80..000000000 --- a/.github/workflows/main.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: DeployDemoToGitHubPages -env: - PUBLISH_DIR: site/Site/bin/Release/net6.0/publish/wwwroot - -on: - push: - branches: [ master ] - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Publish app - run: cd site/Site && 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 }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..277059e01 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,67 @@ +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: + release: + name: Build - Release + + runs-on: ubuntu-latest + steps: + + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup dotnet + uses: actions/setup-dotnet@v3 + with: + dotnet-version: | + 8.0.x + 6.0.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@v4 + 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: dotnet nuget push ${{ env.PACKAGE_PATH }} --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json diff --git a/Blazor.Diagrams.sln b/Blazor.Diagrams.sln index 5f73d1027..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,15 +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 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 e5358a9a8..e15b4bac1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,30 @@ 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 + +- `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/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/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/README.md b/README.md index dcd2df3ca..43b656c55 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,13 @@ ![](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. + +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 | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| 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 | | | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -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 @@ -60,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 the package on Github as a release +- Push it to the WTG.Z.Blazor.Diagrams NuGet Gallery 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/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/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() { 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/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/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 79272cab8..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/Components/Landing/LandingShowcaseDiagram.razor.cs b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs index 835c8fdcd..d2933a427 100644 --- a/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs +++ b/site/Site/Components/Landing/LandingShowcaseDiagram.razor.cs @@ -65,18 +65,24 @@ 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) { - if (oldTarget == null && newTarget != null) // First attach + // 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(); } } 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 is SinglePortAnchor anchor && anchor.Model is PortModel portModel) + { + portModel.Parent.Refresh(); + } link.TargetChanged -= OnLinKTargetChanged; } } \ No newline at end of file diff --git a/site/Site/Pages/Documentation/Controls/Overview.razor b/site/Site/Pages/Documentation/Controls/Overview.razor index bd87fa9a1..3016d7108 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 @@ -39,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()); @@ -135,6 +137,26 @@ Diagram.Controls.AddFor(SomeModel) +

Resize Control

+ +

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

+ +

+Diagram.Controls.AddFor(SomeModel).Add(new ResizeControl(new BottomRightResizerProvider()));
+Diagram.Controls.AddFor(SomeModel).Add(new ResizeControl(new BottomLeftResizerProvider()));
+Diagram.Controls.AddFor(SomeModel).Add(new ResizeControl(new TopRightResizerProvider()));
+Diagram.Controls.AddFor(SomeModel).Add(new ResizeControl(new TopLeftResizerProvider()));
+
+ +
+ + + +
+ @@ -143,6 +165,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 +221,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/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/site/Site/Pages/Documentation/Diagram/Behaviors.razor b/site/Site/Pages/Documentation/Diagram/Behaviors.razor index 540daba7e..094b33112 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)); +

Configure behaviors for different actions on input

+ +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. + +

Scrolling a diagram on mouse wheel

+ +

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

+ +

+_diagram.BehaviorOptions.DiagramWheelBehavior = _diagram.GetBehavior<ScrollBehavior>();
+
+ + 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 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/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 @@ }); - + diff --git a/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj b/src/Blazor.Diagrams.Algorithms/Blazor.Diagrams.Algorithms.csproj index 8681e4d5c..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.0 - 3.0.0 + 3.0.2 + 3.0.2 https://github.com/zHaytam/Blazor.Diagrams - 3.0.0 + 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/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..cd8018477 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; @@ -51,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 db5aa2c0d..250a5365e 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragMovablesBehavior.cs @@ -1,28 +1,29 @@ -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; 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; + protected readonly Dictionary _initialPositions; + 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; @@ -52,15 +53,40 @@ 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; - _moved = true; var deltaX = (e.ClientX - _lastClientX.Value) / Diagram.Zoom; var deltaY = (e.ClientY - _lastClientY.Value) / Diagram.Zoom; + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + 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; + + _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); @@ -76,7 +102,7 @@ private void OnPointerMove(Model? model, PointerEventArgs e) } } - private void OnPointerUp(Model? model, PointerEventArgs e) + protected override void OnPointerUp(Model? model, PointerEventArgs e) { if (_initialPositions.Count == 0) return; @@ -88,10 +114,12 @@ private void OnPointerUp(Model? model, PointerEventArgs e) movable.TriggerMoved(); } } - _initialPositions.Clear(); + _totalMovedX = 0; + _totalMovedY = 0; _lastClientX = null; _lastClientY = null; + _moved = false; } private double ApplyGridSize(double n) @@ -106,9 +134,6 @@ private double ApplyGridSize(double n) public override void Dispose() { _initialPositions.Clear(); - - Diagram.PointerDown -= OnPointerDown; - Diagram.PointerMove -= OnPointerMove; - Diagram.PointerUp -= OnPointerUp; + Diagram.PanChanged -= OnPanChanged; } -} +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs index 31d0c5ede..79717107c 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/DragNewLinkBehavior.cs @@ -1,9 +1,10 @@ -using Blazor.Diagrams.Core.Models; -using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Anchors; +using Blazor.Diagrams.Core.Behaviors.Base; using Blazor.Diagrams.Core.Events; -using System.Linq; -using Blazor.Diagrams.Core.Anchors; using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; +using System.Linq; namespace Blazor.Diagrams.Core.Behaviors; @@ -12,12 +13,15 @@ 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) { Diagram.PointerDown += OnPointerDown; Diagram.PointerMove += OnPointerMove; Diagram.PointerUp += OnPointerUp; + Diagram.PanChanged += OnPanChanged; } public void StartFrom(ILinkable source, double clientX, double clientY) @@ -55,7 +59,7 @@ private void OnPointerDown(Model? model, MouseEventArgs e) if (model is PortModel port) { - if (port.Locked) + if (port.Locked || !port.Enabled) return; _targetPositionAnchor = new PositionAnchor(CalculateTargetPosition(e.ClientX, e.ClientY)); @@ -66,14 +70,35 @@ 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; + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + UpdateLinkPosition((double)_lastClientX, (double)_lastClientY); + } + + private void OnPanChanged(double deltaX, double deltaY) + { + if (OngoingLink == null || _lastClientX == null || _lastClientY == null) + return; + + UpdateLinkPosition((double)_lastClientX, (double)_lastClientY); + } + + private void UpdateLinkPosition(double clientX, double clientY) + { + if (OngoingLink == null) return; - _targetPositionAnchor!.SetPosition(CalculateTargetPosition(e.ClientX, e.ClientY)); + _targetPositionAnchor!.SetPosition(CalculateTargetPosition(clientX, clientY)); if (Diagram.Options.Links.EnableSnapping) { @@ -118,6 +143,8 @@ private void OnPointerUp(Model? model, MouseEventArgs e) } OngoingLink = null; + _lastClientX = null; + _lastClientY = null; } private Point CalculateTargetPosition(double clientX, double clientY) @@ -167,5 +194,6 @@ public override void Dispose() Diagram.PointerDown -= OnPointerDown; Diagram.PointerMove -= OnPointerMove; Diagram.PointerUp -= OnPointerUp; + Diagram.PanChanged -= OnPanChanged; } } \ No newline at end of file diff --git a/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/EventsBehavior.cs index 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..4dab58e9a 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.Behaviors.Base; using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.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) - { - 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) + protected override void OnPointerDown(Model? model, PointerEventArgs e) { - 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..36e5e6d52 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Behaviors/ScrollBehavior.cs @@ -0,0 +1,21 @@ +using Blazor.Diagrams.Core.Behaviors.Base; +using Blazor.Diagrams.Core.Events; + +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; + + Diagram.UpdatePan(-e.DeltaX / Diagram.Zoom, -e.DeltaY / Diagram.Zoom); + } + } +} 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..98a679749 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Behaviors/SelectionBoxBehavior.cs @@ -0,0 +1,115 @@ +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; + private double? _lastClientX; + private double? _lastClientY; + private Point? _initialPan; + + public SelectionBoxBehavior(Diagram diagram) + : base(diagram) + { + Diagram.PointerDown += OnPointerDown; + Diagram.PointerMove += OnPointerMove; + Diagram.PointerUp += OnPointerUp; + Diagram.PanChanged += OnPanChanged; + } + + public override void Dispose() + { + Diagram.PointerDown -= OnPointerDown; + Diagram.PointerMove -= OnPointerMove; + Diagram.PointerUp -= OnPointerUp; + Diagram.PanChanged -= OnPanChanged; + } + + protected override void OnPointerDown(Model? model, PointerEventArgs e) + { + if (SelectionBoundsChanged is null || model != null || !IsBehaviorEnabled(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) + { + if (_initialClientPoint == null) + return; + + _lastClientX = e.ClientX; + _lastClientY = e.ClientY; + + UpdateSelectionBox(e.ClientX, e.ClientY); + SelectNodesInBounds(e.ClientX, e.ClientY); + } + + void UpdateSelectionBox(double clientX, double clientY) + { + 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)); + SelectionBoundsChanged?.Invoke(this, new Rectangle(sX, sY, eX, eY)); + } + + 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); + 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); + } + } + } +} diff --git a/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs b/src/Blazor.Diagrams.Core/Behaviors/VirtualizationBehavior.cs index acd4dd7b0..c9d03bfba 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; @@ -11,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; @@ -48,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 3da507b75..830b6d695 100644 --- a/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs +++ b/src/Blazor.Diagrams.Core/Behaviors/ZoomBehavior.cs @@ -1,22 +1,18 @@ -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; @@ -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/Blazor.Diagrams.Core.csproj b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj index 496607a3b..3079ef2dd 100644 --- a/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj +++ b/src/Blazor.Diagrams.Core/Blazor.Diagrams.Core.csproj @@ -1,37 +1,36 @@  - net6.0 enable - true + false MIT - zHaytam + zHaytam, WiseTech Global A fully customizable and extensible all-purpose diagrams library for Blazor - 3.0.0 - 3.0.0 - https://github.com/Blazor-Diagrams/Blazor.Diagrams - 3.0.0 - 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 - - - True - \ - - - True - \ - - + + + True + \ + + + True + \ + + - - - + + + + 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/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/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs index 1e1704256..9227fcf98 100644 --- a/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs +++ b/src/Blazor.Diagrams.Core/Controls/Default/RemoveControl.cs @@ -23,18 +23,44 @@ 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) + 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: diagram.Nodes.Remove(node); - break; + return; + case BaseLinkModel link: diagram.Links.Remove(link); - break; + return; + } + } + + private static async ValueTask ShouldDeleteModel(Diagram diagram, Model model) + { + if (model.Locked) + { + return false; } - return ValueTask.CompletedTask; + 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/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs new file mode 100644 index 000000000..1818f996e --- /dev/null +++ b/src/Blazor.Diagrams.Core/Controls/Default/ResizeControl.cs @@ -0,0 +1,40 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Positions.Resizing; +using System.Threading.Tasks; + +namespace Blazor.Diagrams.Core.Controls.Default +{ + public class ResizeControl : ExecutableControl + { + private readonly ResizerProvider _resizeProvider; + + public ResizeControl(ResizerProvider resizeProvider) + { + _resizeProvider = resizeProvider; + } + + public override Point? GetPosition(Model model) => _resizeProvider.GetPosition(model); + + public string? Class => _resizeProvider.Class; + + public override ValueTask OnPointerDown(Diagram diagram, Model model, PointerEventArgs e) + { + _resizeProvider.OnResizeStart(diagram, model, e); + diagram.PointerMove += _resizeProvider.OnPointerMove; + diagram.PanChanged += _resizeProvider.OnPanChanged; + diagram.PointerUp += _resizeProvider.OnResizeEnd; + diagram.PointerUp += (_, _) => OnResizeEnd(diagram); + + return ValueTask.CompletedTask; + } + + void OnResizeEnd(Diagram diagram) + { + diagram.PointerMove -= _resizeProvider.OnPointerMove; + diagram.PanChanged -= _resizeProvider.OnPanChanged; + diagram.PointerUp -= _resizeProvider.OnResizeEnd; + } + } +} diff --git a/src/Blazor.Diagrams.Core/Diagram.cs b/src/Blazor.Diagrams.Core/Diagram.cs index a2fffeacb..fd88d8ac5 100644 --- a/src/Blazor.Diagrams.Core/Diagram.cs +++ b/src/Blazor.Diagrams.Core/Diagram.cs @@ -1,20 +1,20 @@ 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; - -[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 @@ -33,7 +33,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; @@ -47,6 +47,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 +57,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,6 +167,20 @@ public void UnselectAll() #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) { @@ -226,15 +238,18 @@ 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(); + PanChanged?.Invoke(oldPanX - Pan.X, oldPanY - Pan.Y); Refresh(); } public void UpdatePan(double deltaX, double deltaY) { Pan = Pan.Add(deltaX, deltaY); - PanChanged?.Invoke(); + PanChanged?.Invoke(-deltaX, -deltaY); Refresh(); } @@ -251,9 +266,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; @@ -346,7 +361,7 @@ public int GetMaxOrder() public void RefreshOrders(bool refresh = true) { _orderedSelectables.Sort((a, b) => a.Order.CompareTo(b.Order)); - + if (refresh) { Refresh(); 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.Core/Models/Base/Model.cs b/src/Blazor.Diagrams.Core/Models/Base/Model.cs index 738b45091..0c40525df 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 Enabled { get; set; } = true; public bool Visible { get => _visible; diff --git a/src/Blazor.Diagrams.Core/Models/GroupModel.cs b/src/Blazor.Diagrams.Core/Models/GroupModel.cs index 041d872ec..4a450be1b 100644 --- a/src/Blazor.Diagrams.Core/Models/GroupModel.cs +++ b/src/Blazor.Diagrams.Core/Models/GroupModel.cs @@ -27,13 +27,10 @@ public void AddChild(NodeModel child) { _children.Add(child); child.Group = this; - child.SizeChanged += OnNodeChanged; + child.SizeChanging += OnNodeChanged; child.Moving += OnNodeChanged; - if (UpdateDimensions()) - { - Refresh(); - } + UpdateDimensions(); } public void RemoveChild(NodeModel child) @@ -42,14 +39,10 @@ public void RemoveChild(NodeModel child) return; child.Group = null; - child.SizeChanged -= OnNodeChanged; + child.SizeChanging -= OnNodeChanged; child.Moving -= OnNodeChanged; - if (UpdateDimensions()) - { - Refresh(); - RefreshLinks(); - } + UpdateDimensions(); } public override void SetPosition(double x, double y) @@ -83,7 +76,7 @@ public void Ungroup() foreach (var child in Children) { child.Group = null; - child.SizeChanged -= OnNodeChanged; + child.SizeChanging -= OnNodeChanged; child.Moving -= OnNodeChanged; } @@ -96,7 +89,7 @@ private void Initialize(IEnumerable children) { _children.Add(child); child.Group = this; - child.SizeChanged += OnNodeChanged; + child.SizeChanging += OnNodeChanged; child.Moving += OnNodeChanged; } @@ -105,10 +98,7 @@ private void Initialize(IEnumerable children) private void OnNodeChanged(NodeModel node) { - if (UpdateDimensions()) - { - Refresh(); - } + UpdateDimensions(); } private bool UpdateDimensions() @@ -128,7 +118,7 @@ private bool UpdateDimensions() TriggerMoving(); } - Size = new Size(bounds.Width + Padding * 2, bounds.Height + Padding * 2); + SetSize(bounds.Width + Padding * 2, bounds.Height + Padding * 2); return true; } } diff --git a/src/Blazor.Diagrams.Core/Models/NodeModel.cs b/src/Blazor.Diagrams.Core/Models/NodeModel.cs index 053c5b3d8..102a9fed6 100644 --- a/src/Blazor.Diagrams.Core/Models/NodeModel.cs +++ b/src/Blazor.Diagrams.Core/Models/NodeModel.cs @@ -11,7 +11,9 @@ public class NodeModel : MovableModel, IHasBounds, IHasShape, ILinkable private readonly List _ports = new(); private readonly List _links = new(); private Size? _size; + public Size MinimumDimensions { get; set; } = new Size(0, 0); + public event Action? SizeChanging; public event Action? SizeChanged; public event Action? Moving; @@ -28,11 +30,7 @@ public Size? Size get => _size; set { - if (value?.Equals(_size) == true) - return; - _size = value; - SizeChanged?.Invoke(this); } } public bool ControlledSize { get; init; } @@ -103,6 +101,29 @@ public override void SetPosition(double x, double y) Moving?.Invoke(this); } + public void SetSize(double width, double height) + { + var newSize = new Size(width, height); + if (newSize.Equals(_size) == true) + return; + + Size? oldSize = Size != null ? new Size(Size.Width, Size.Height) : null; + + Size = newSize; + if (oldSize != null) + { + UpdatePortPositions(oldSize, newSize); + } + Refresh(); + RefreshLinks(); + SizeChanging?.Invoke(this); + } + + public void TriggerSizeChanged() + { + SizeChanged?.Invoke(this); + } + public virtual void UpdatePositionSilently(double deltaX, double deltaY) { base.SetPosition(Position.X + deltaX, Position.Y + deltaY); @@ -141,6 +162,9 @@ public virtual void UpdatePositionSilently(double deltaX, double deltaY) public virtual bool CanAttachTo(ILinkable other) => other is not PortModel && other is not BaseLinkModel; + /// + /// Updates port positions when node position changes. + /// private void UpdatePortPositions(double deltaX, double deltaY) { // Save some JS calls and update ports directly here @@ -151,6 +175,21 @@ private void UpdatePortPositions(double deltaX, double deltaY) } } + /// + /// Updates port positions when node size changes. + /// + private void UpdatePortPositions(Size oldSize, Size newSize) + { + var deltaWidth = newSize.Width - oldSize.Width; + var deltaHeight = newSize.Height - oldSize.Height; + + foreach (var port in _ports) + { + port.SetPortPositionOnNodeSizeChanged(deltaWidth, deltaHeight); + port.RefreshLinks(); + } + } + protected void TriggerMoving() { Moving?.Invoke(this); diff --git a/src/Blazor.Diagrams.Core/Models/PortModel.cs b/src/Blazor.Diagrams.Core/Models/PortModel.cs index a0c850816..ef2068567 100644 --- a/src/Blazor.Diagrams.Core/Models/PortModel.cs +++ b/src/Blazor.Diagrams.Core/Models/PortModel.cs @@ -67,4 +67,38 @@ public virtual bool CanAttachTo(ILinkable other) void ILinkable.AddLink(BaseLinkModel link) => _links.Add(link); void ILinkable.RemoveLink(BaseLinkModel link) => _links.Remove(link); + + public virtual void SetPortPositionOnNodeSizeChanged(double deltaWidth, double deltaHeight) + { + switch (Alignment) + { + case PortAlignment.Top: + Position = new Point(Position.X + deltaWidth / 2, Position.Y); + break; + case PortAlignment.TopRight: + Position = new Point(Position.X + deltaWidth, Position.Y); + break; + case PortAlignment.TopLeft: + Position = new Point(Position.X, Position.Y); + break; + case PortAlignment.Right: + Position = new Point(Position.X + deltaWidth, Position.Y + deltaHeight / 2); + break; + case PortAlignment.Left: + Position = new Point(Position.X, Position.Y + deltaHeight / 2); + break; + case PortAlignment.Bottom: + Position = new Point(Position.X + deltaWidth / 2, Position.Y + deltaHeight); + break; + case PortAlignment.BottomRight: + Position = new Point(Position.X + deltaWidth, Position.Y + deltaHeight); + break; + case PortAlignment.BottomLeft: + Position = new Point(Position.X, Position.Y + deltaHeight); + break; + default: + Position = new Point(Position.X, Position.Y); + break; + } + } } diff --git a/src/Blazor.Diagrams.Core/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.Core/Positions/Resizing/BottomLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs new file mode 100644 index 000000000..6b6cc3dc5 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomLeftResizerProvider.cs @@ -0,0 +1,25 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions.Resizing +{ + public class BottomLeftResizerProvider : ResizerProvider + { + override public string? Class => "bottomleft"; + override public bool ShouldChangeXPositionOnResize => true; + override public bool ShouldChangeYPositionOnResize => false; + override public bool ShouldAddTotalMovedX => false; + override public bool ShouldAddTotalMovedY => true; + + public override Point? GetPosition(Model model) + { + if (model is NodeModel nodeModel && nodeModel.Size is not null) + { + return new Point(nodeModel.Position.X - 5, nodeModel.Position.Y + nodeModel.Size.Height + 5); + } + return null; + } + + } +} diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs new file mode 100644 index 000000000..394b882bb --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/BottomRightResizerProvider.cs @@ -0,0 +1,25 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions.Resizing +{ + public class BottomRightResizerProvider : ResizerProvider + { + override public string? Class => "bottomright"; + override public bool ShouldChangeXPositionOnResize => false; + override public bool ShouldChangeYPositionOnResize => false; + override public bool ShouldAddTotalMovedX => true; + override public bool ShouldAddTotalMovedY => true; + + public override Point? GetPosition(Model model) + { + if (model is NodeModel nodeModel && nodeModel.Size is not null) + { + return new Point(nodeModel.Position.X + nodeModel.Size.Width + 5, nodeModel.Position.Y + nodeModel.Size.Height + 5); + } + return null; + } + + } +} diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs new file mode 100644 index 000000000..78e7eebe4 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/ResizerProvider.cs @@ -0,0 +1,120 @@ +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions.Resizing +{ + public abstract class ResizerProvider : IPositionProvider + { + abstract public string? Class { get; } + + protected Size? OriginalSize { get; set; } + protected Point? OriginalPosition { get; set; } + protected double? LastClientX { get; set; } + protected double? LastClientY { get; set; } + protected NodeModel? NodeModel { get; set; } + protected Diagram? Diagram { get; set; } + private double _totalMovedX = 0; + private double _totalMovedY = 0; + + abstract public bool ShouldChangeXPositionOnResize { get; } + abstract public bool ShouldChangeYPositionOnResize { get; } + /// Controls whether the totalMovedX should be added or subtracted + abstract public bool ShouldAddTotalMovedX { get; } + /// Controls whether the totalMovedY should be added or subtracted + abstract public bool ShouldAddTotalMovedY { get; } + + abstract public Point? GetPosition(Model model); + + virtual public (Size size, Point position) CalculateNewSizeAndPosition(double deltaX, double deltaY) + { + _totalMovedX += deltaX; + _totalMovedY += deltaY; + + var width = OriginalSize!.Width + (ShouldAddTotalMovedX ? _totalMovedX : -_totalMovedX) / Diagram!.Zoom; + var height = OriginalSize.Height + (ShouldAddTotalMovedY ? _totalMovedY : -_totalMovedY) / Diagram!.Zoom; + + var positionX = OriginalPosition!.X + (ShouldChangeXPositionOnResize ? _totalMovedX : 0) / Diagram!.Zoom; + var positionY = OriginalPosition.Y + (ShouldChangeYPositionOnResize ? _totalMovedY : 0) / Diagram!.Zoom; + + if (width < NodeModel!.MinimumDimensions.Width) + { + width = NodeModel.MinimumDimensions.Width; + + if (ShouldChangeXPositionOnResize) + { + positionX = OriginalPosition.X + OriginalSize.Width - NodeModel.MinimumDimensions.Width; + } + } + if (height < NodeModel.MinimumDimensions.Height) + { + height = NodeModel.MinimumDimensions.Height; + + if (ShouldChangeYPositionOnResize) + { + positionY = OriginalPosition.Y + OriginalSize.Height - NodeModel.MinimumDimensions.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) + { + if (model is NodeModel nodeModel) + { + LastClientX = e.ClientX; + LastClientY = e.ClientY; + OriginalPosition = new Point(nodeModel.Position.X, nodeModel.Position.Y); + OriginalSize = nodeModel.Size; + this.NodeModel = nodeModel; + Diagram = diagram; + } + } + + virtual public void OnPointerMove(Model? model, PointerEventArgs e) + { + if (OriginalSize is null || OriginalPosition is null || NodeModel is null || Diagram is null) + { + return; + } + + var deltaX = (e.ClientX - LastClientX!.Value); + var deltaY = (e.ClientY - LastClientY!.Value); + + LastClientX = e.ClientX; + LastClientY = e.ClientY; + + var result = CalculateNewSizeAndPosition(deltaX, deltaY); + SetSizeAndPosition(result.size, result.position); + } + + virtual public void OnPanChanged(double deltaX, double deltaY) + { + if (NodeModel is null) return; + + var result = CalculateNewSizeAndPosition(deltaX, deltaY); + SetSizeAndPosition(result.size, result.position); + } + + virtual public void OnResizeEnd(Model? model, PointerEventArgs args) + { + NodeModel?.TriggerSizeChanged(); + OriginalSize = null; + OriginalPosition = null; + NodeModel = null; + _totalMovedX = 0; + _totalMovedY = 0; + LastClientX = null; + LastClientY = null; + Diagram = null; + } + } +} diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs new file mode 100644 index 000000000..24ccaa132 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopLeftResizerProvider.cs @@ -0,0 +1,25 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions.Resizing +{ + public class TopLeftResizerProvider : ResizerProvider + { + override public string? Class => "topleft"; + override public bool ShouldChangeXPositionOnResize => true; + override public bool ShouldChangeYPositionOnResize => true; + override public bool ShouldAddTotalMovedX => false; + override public bool ShouldAddTotalMovedY => false; + + public override Point? GetPosition(Model model) + { + if (model is NodeModel nodeModel && nodeModel.Size is not null) + { + return new Point(nodeModel.Position.X - 5, nodeModel.Position.Y - 5); + } + return null; + } + + } +} diff --git a/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs new file mode 100644 index 000000000..72fd2a525 --- /dev/null +++ b/src/Blazor.Diagrams.Core/Positions/Resizing/TopRightResizerProvider.cs @@ -0,0 +1,25 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Blazor.Diagrams.Core.Models.Base; + +namespace Blazor.Diagrams.Core.Positions.Resizing +{ + public class TopRightResizerProvider : ResizerProvider + { + override public string? Class => "topright"; + override public bool ShouldChangeXPositionOnResize => false; + override public bool ShouldChangeYPositionOnResize => true; + override public bool ShouldAddTotalMovedX => true; + override public bool ShouldAddTotalMovedY => false; + + public override Point? GetPosition(Model model) + { + if (model is NodeModel nodeModel && nodeModel.Size is not null) + { + return new Point(nodeModel.Position.X + nodeModel.Size.Width + 5, nodeModel.Position.Y - 5); + } + return null; + } + + } +} \ No newline at end of file diff --git a/src/Blazor.Diagrams.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 099acd468..306f1ee48 100644 --- a/src/Blazor.Diagrams/Blazor.Diagrams.csproj +++ b/src/Blazor.Diagrams/Blazor.Diagrams.csproj @@ -1,53 +1,65 @@  - net6.0 enable - zHaytam + zHaytam, WiseTech Global MIT - 3.0.0 - 3.0.0 - 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.0 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 - - + + + + + - + - - - True - \ - - - True - \ - - + + + True + \ + + + True + \ + + + + + + + + + - - - - - - + + + - - - + + $(TargetsForTfmSpecificBuildOutput);GetBinariesForPackage + + + + + + + + diff --git a/src/Blazor.Diagrams/BlazorDiagram.cs b/src/Blazor.Diagrams/BlazorDiagram.cs index ab61d98a8..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; @@ -19,7 +20,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/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/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/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; @@ -27,7 +27,7 @@ public partial class DiagramCanvas : IDisposable [Inject] public IJSRuntime JSRuntime { get; set; } = null!; - public void Dispose() + public async ValueTask DisposeAsync() { BlazorDiagram.Changed -= OnDiagramChanged; @@ -35,9 +35,12 @@ public void Dispose() return; if (elementReference.Id != null) - _ = JSRuntime.UnobserveResizes(elementReference); + await JSRuntime.UnobserveResizes(elementReference); _reference.Dispose(); + _reference = null!; + + GC.SuppressFinalize(this); // CA1816 } private string GetLayerStyle(int order) @@ -52,6 +55,7 @@ protected override void OnInitialized() _reference = DotNetObjectReference.Create(this); BlazorDiagram.Changed += OnDiagramChanged; + Style = BlazorDiagram.Options.AllowPanning ? "cursor: grab; cursor: -webkit-grab;" : "cursor: default;"; } protected override async Task OnAfterRenderAsync(bool firstRender) @@ -62,6 +66,10 @@ protected override async Task OnAfterRenderAsync(bool firstRender) { BlazorDiagram.SetContainer(await JSRuntime.GetBoundingClientRect(elementReference)); await JSRuntime.ObserveResizes(elementReference, _reference!); + if (BlazorDiagram.BehaviorOptions.DiagramWheelBehavior is ScrollBehavior) + { + await JSRuntime.AddDefaultPreventingForWheelHandler(elementReference); + } } } 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 +} 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/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/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/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/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs b/src/Blazor.Diagrams/Extensions/JSRuntimeExtensions.cs index 459456863..3e32f8e1b 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,6 +26,18 @@ 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 (JSDisconnectedException) + { + // Ignore, JSRuntime was already disconnected + } + } + + public static async Task AddDefaultPreventingForWheelHandler(this IJSRuntime jsRuntime, ElementReference element) + { + await jsRuntime.InvokeVoidAsync("ZBlazorDiagrams.addDefaultPreventingHandler", element, "wheel"); } } \ No newline at end of file diff --git a/src/Blazor.Diagrams/sgKey.snk b/src/Blazor.Diagrams/sgKey.snk new file mode 100644 index 000000000..4ff3c2f7a Binary files /dev/null and b/src/Blazor.Diagrams/sgKey.snk differ diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.css b/src/Blazor.Diagrams/wwwroot/default.styles.css index b095bbe4e..0e37330c2 100644 --- a/src/Blazor.Diagrams/wwwroot/default.styles.css +++ b/src/Blazor.Diagrams/wwwroot/default.styles.css @@ -127,4 +127,29 @@ g.diagram-group.default.selected > rect { transform: translate(-50%, -50%); } +.default-node-resizer { + width: 5px; + height: 5px; + background-color: #f5f5f5; + border: 1px solid #6e9fd4; + position: absolute; + transform: translate(-2.5px, -2.5px); +} + +.default-node-resizer.bottomright { + cursor: nwse-resize; +} + +.default-node-resizer.topright { + cursor: nesw-resize; +} + +.default-node-resizer.bottomleft { + cursor: nesw-resize; +} + +.default-node-resizer.topleft { + cursor: nwse-resize; +} + /*# sourceMappingURL=wwwroot\default.styles.css.map */ diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css b/src/Blazor.Diagrams/wwwroot/default.styles.min.css index c44adedc3..781fb0aed 100644 --- a/src/Blazor.Diagrams/wwwroot/default.styles.min.css +++ b/src/Blazor.Diagrams/wwwroot/default.styles.min.css @@ -1 +1 @@ -.default-node{width:100px;height:80px;border-radius:10px;background-color:#f5f5f5;border:1px solid #e8e8e8;-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.default-node.selected{border:1px solid #6e9fd4;}.default-node.selected .diagram-port{border:1px solid #6e9fd4;}.default-node .diagram-port,.default.diagram-group .diagram-port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .diagram-port:hover,.default-node .diagram-port.has-links,.default.diagram-group .diagram-port.has-links{background-color:#000;}.default-node .diagram-port.bottom,.default.diagram-group .diagram-port.bottom{bottom:0;left:50%;}.default-node .diagram-port.bottomleft,.default.diagram-group .diagram-port.bottomleft{bottom:0;left:0;}.default-node .diagram-port.bottomright,.default.diagram-group .diagram-port.bottomright{bottom:0;right:0;}.default-node .diagram-port.top,.default.diagram-group .diagram-port.top{top:0;left:50%;}.default-node .diagram-port.topleft,.default.diagram-group .diagram-port.topleft{top:0;left:0;}.default-node .diagram-port.topright,.default.diagram-group .diagram-port.topright{top:0;right:0;}.default-node .diagram-port.left,.default.diagram-group .diagram-port.left{left:0;top:50%;}.default-node .diagram-port.right,.default.diagram-group .diagram-port.right{right:0;top:50%;}.diagram-navigator.default{position:absolute;bottom:10px;right:10px;border:3px solid #9ba8b0;border-radius:15px;padding:20px;background-color:#fff;}div.diagram-group.default{outline:2px solid #000;background:#c6c6c6;}div.diagram-group.default.selected{outline:2px solid #6e9fd4;}g.diagram-group.default rect{outline:2px solid #000;fill:#c6c632;}g.diagram-group.default.selected>rect{outline:2px solid #008000;}.diagram-link div.default-link-label{display:inline-block;color:#fff;background-color:#6e9fd4;border-radius:.25rem;padding:.25rem;text-align:center;font-size:.875rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:3rem;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);} \ No newline at end of file +.default-node{width:100px;height:80px;border-radius:10px;background-color:#f5f5f5;border:1px solid #e8e8e8;-webkit-box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);box-shadow:0 2px 1px -1px rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 1px 3px 0 rgba(0,0,0,.12);position:relative;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-align:center;-ms-flex-align:center;align-items:center;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;}.default-node.selected{border:1px solid #6e9fd4;}.default-node.selected .diagram-port{border:1px solid #6e9fd4;}.default-node .diagram-port,.default.diagram-group .diagram-port{width:20px;height:20px;margin:-10px;border-radius:50%;background-color:#f5f5f5;border:1px solid #d4d4d4;cursor:pointer;position:absolute;}.default-node .diagram-port:hover,.default-node .diagram-port.has-links,.default.diagram-group .diagram-port.has-links{background-color:#000;}.default-node .diagram-port.bottom,.default.diagram-group .diagram-port.bottom{bottom:0;left:50%;}.default-node .diagram-port.bottomleft,.default.diagram-group .diagram-port.bottomleft{bottom:0;left:0;}.default-node .diagram-port.bottomright,.default.diagram-group .diagram-port.bottomright{bottom:0;right:0;}.default-node .diagram-port.top,.default.diagram-group .diagram-port.top{top:0;left:50%;}.default-node .diagram-port.topleft,.default.diagram-group .diagram-port.topleft{top:0;left:0;}.default-node .diagram-port.topright,.default.diagram-group .diagram-port.topright{top:0;right:0;}.default-node .diagram-port.left,.default.diagram-group .diagram-port.left{left:0;top:50%;}.default-node .diagram-port.right,.default.diagram-group .diagram-port.right{right:0;top:50%;}.diagram-navigator.default{position:absolute;bottom:10px;right:10px;border:3px solid #9ba8b0;border-radius:15px;padding:20px;background-color:#fff;}div.diagram-group.default{outline:2px solid #000;background:#c6c6c6;}div.diagram-group.default.selected{outline:2px solid #6e9fd4;}g.diagram-group.default rect{outline:2px solid #000;fill:#c6c632;}g.diagram-group.default.selected>rect{outline:2px solid #008000;}.diagram-link div.default-link-label{display:inline-block;color:#fff;background-color:#6e9fd4;border-radius:.25rem;padding:.25rem;text-align:center;font-size:.875rem;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;min-width:3rem;-webkit-transform:translate(-50%,-50%);-ms-transform:translate(-50%,-50%);transform:translate(-50%,-50%);}.default-node-resizer{width:5px;height:5px;background-color:#f5f5f5;border:1px solid #6e9fd4;position:absolute;transform:translate(-2.5px,-2.5px);}.default-node-resizer.bottomright{cursor:nwse-resize;}.default-node-resizer.topright{cursor:nesw-resize;}.default-node-resizer.bottomleft{cursor:nesw-resize;}.default-node-resizer.topleft{cursor:nwse-resize;} \ No newline at end of file diff --git a/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz b/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz index 905f20cd3..3c43a8a10 100644 Binary files a/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz and b/src/Blazor.Diagrams/wwwroot/default.styles.min.css.gz differ diff --git a/src/Blazor.Diagrams/wwwroot/script.js b/src/Blazor.Diagrams/wwwroot/script.js index 293b8768d..3f25c16f8 100644 --- a/src/Blazor.Diagrams/wwwroot/script.js +++ b/src/Blazor.Diagrams/wwwroot/script.js @@ -40,10 +40,14 @@ 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]; + }, + addDefaultPreventingHandler: (element, eventName) => { + 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 f295e538c..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 diff --git a/src/Blazor.Diagrams/wwwroot/script.min.js.gz b/src/Blazor.Diagrams/wwwroot/script.min.js.gz index e444f12bd..a4db96478 100644 Binary files a/src/Blazor.Diagrams/wwwroot/script.min.js.gz and b/src/Blazor.Diagrams/wwwroot/script.min.js.gz differ diff --git a/src/Blazor.Diagrams/wwwroot/style.css b/src/Blazor.Diagrams/wwwroot/style.css index 35c0aaa30..b2d6a1355 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; } @@ -82,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 e72cb7d8a..16ae04074 100644 Binary files a/src/Blazor.Diagrams/wwwroot/style.min.css.gz and b/src/Blazor.Diagrams/wwwroot/style.min.css.gz differ diff --git a/src/Directory.Build.props b/src/Directory.Build.props new file mode 100644 index 000000000..888d6a9b7 --- /dev/null +++ b/src/Directory.Build.props @@ -0,0 +1,10 @@ + + + net8.0;net6.0 + enable + enable + true + + + + \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs b/tests/Blazor.Diagrams.Core.Tests/Anchors/DynamicAnchorTests.cs index 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 ba73788b3..457ba46fb 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragMovablesBehaviorTests.cs @@ -1,8 +1,8 @@ +using Blazor.Diagrams.Core.Behaviors; using Blazor.Diagrams.Core.Events; using Blazor.Diagrams.Core.Geometry; using Blazor.Diagrams.Core.Models; using Blazor.Diagrams.Core.Options; -using FluentAssertions; using Moq; using Xunit; @@ -78,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] @@ -98,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] @@ -140,4 +140,24 @@ public void Behavior_ShouldCallSetPosition_WhenGroupHasAutoSize() // Assert nodeMock.Verify(n => n.SetPosition(50, 50), Times.Once); } + + [Fact] + 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, + 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 18f466eec..753335cdf 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/DragNewLinkBehaviorTests.cs @@ -1,9 +1,8 @@ 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; @@ -31,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] @@ -63,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] @@ -99,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] @@ -131,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] @@ -171,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] @@ -208,7 +207,7 @@ public void Behavior_ShouldNotSnapToPort_WhenSnappingIsEnabledAndPortIsNotInRadi // Assert var link = diagram.Links.Single(); - link.Target.Should().BeOfType(); + Assert.IsType(link.Target); } [Fact] @@ -250,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] @@ -275,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] @@ -299,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] @@ -335,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] @@ -362,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] @@ -398,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] @@ -439,6 +438,62 @@ 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] + 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), + Enabled = false + }); + + // Act + diagram.TriggerPointerDown(port, + new PointerEventArgs(100, 100, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true)); + + // Assert + Assert.Empty(diagram.Links); + } + + [Fact] + public void Behavior_ShouldUpdateOngoingPosition_WhenPanChanges() + { + // 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(100, 50), + Size = new Size(10, 20) + }); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + + // 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()!; + 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/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..d18be5ed3 --- /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(-100, diagram.Pan.X); + Assert.Equal(-200, diagram.Pan.Y); + } + + [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..a394e0333 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Behaviors/SelectionBoxBehaviorTests.cs @@ -0,0 +1,227 @@ +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_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] + 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_WithBoundsChangedDelegate_ShouldSelectNodesInsideAreaWhenScrolling() + { + // 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()!; + 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, 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 + 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() + { + // 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); + } + + [Fact] + public void Behavior_WithoutBoundsChangedDelegate_ShouldNotSelectNodesInsideAreaWhenScrolling() + { + // 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 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, 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 + Assert.False(node.Selected); + + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, true, false, -200, -200, 0, 0)); + + 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/Blazor.Diagrams.Core.Tests.csproj b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj index 0e1f0d2ff..879a96c52 100644 --- a/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj +++ b/tests/Blazor.Diagrams.Core.Tests/Blazor.Diagrams.Core.Tests.csproj @@ -1,21 +1,20 @@ - + - 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/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); + } + } +} 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..0929bfe4d --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Controls/RemoveControlTests.cs @@ -0,0 +1,267 @@ +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.Options; +using Moq; +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 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 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 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 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); + } + + [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 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 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); + } + + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs new file mode 100644 index 000000000..543ecb6f0 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Controls/ResizeControlTests.cs @@ -0,0 +1,74 @@ +using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Events; +using Blazor.Diagrams.Core.Models.Base; +using Blazor.Diagrams.Core.Positions.Resizing; +using Moq; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Controls; + +public class ResizeControlTests +{ + [Fact] + public void GetPosition_ShouldUseResizeProviderGetPosition() + { + var resizeProvider = new Mock(); + var control = new ResizeControl(resizeProvider.Object); + var model = new Mock(); + + control.GetPosition(model.Object); + + resizeProvider.Verify(m => m.GetPosition(model.Object), Times.Once); + } + + [Fact] + public void OnPointerDown_ShouldInvokeResizeStart() + { + var resizeProvider = new Mock(); + var control = new ResizeControl(resizeProvider.Object); + var diagram = Mock.Of(); + var model = Mock.Of(); + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); + + control.OnPointerDown(diagram, model, eventArgs); + + resizeProvider.Verify(m => m.OnResizeStart(diagram, model, eventArgs), Times.Once); + } + + [Fact] + public void OnPointerDown_ShouldAddEventHandlers() + { + var resizeProvider = new Mock(); + var control = new ResizeControl(resizeProvider.Object); + var diagram = new TestDiagram(); + var model = Mock.Of(); + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); + + control.OnPointerDown(diagram, model, eventArgs); + + diagram.TriggerPointerMove(model, eventArgs); + resizeProvider.Verify(m => m.OnPointerMove(model, eventArgs), Times.Once); + + diagram.TriggerPointerUp(model, eventArgs); + resizeProvider.Verify(m => m.OnResizeEnd(model, eventArgs), Times.Once); + } + + [Fact] + public void OnPointerUp_ShouldRemoveEventHandlers() + { + var resizeProvider = new Mock(); + var control = new ResizeControl(resizeProvider.Object); + var diagram = new TestDiagram(); + var model = Mock.Of(); + var eventArgs = new PointerEventArgs(0, 0, 0, 0, false, false, false, 0, 0, 0, 0, 0, 0, string.Empty, true); + + control.OnPointerDown(diagram, model, eventArgs); + diagram.TriggerPointerUp(model, eventArgs); + + diagram.TriggerPointerMove(model, eventArgs); + resizeProvider.Verify(m => m.OnPointerMove(model, eventArgs), Times.Never); + + diagram.TriggerPointerUp(model, eventArgs); + resizeProvider.Verify(m => m.OnResizeEnd(model, eventArgs), Times.Once); + } +} \ No newline at end of file diff --git a/tests/Blazor.Diagrams.Core.Tests/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 85a812dfc..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] @@ -84,13 +82,13 @@ public void ZoomToFit_ShouldTriggerAppropriateEvents() // Act diagram.Changed += () => refreshes++; diagram.ZoomChanged += () => zoomChanges++; - diagram.PanChanged += () => panChanges++; + diagram.PanChanged += (deltaX, deltaY) => panChanges++; 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] @@ -122,4 +120,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 + 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/Models/NodeModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs new file mode 100644 index 000000000..b27edc990 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Models/NodeModelTest.cs @@ -0,0 +1,53 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Moq; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Models +{ + public class NodeModelTest + { + [Fact] + public void UpdatePortOnSetPosition() + { + var node = new NodeModel(position: new Point(100, 100)); + node.Size = new Size(100, 100); + + var port = new PortModel(node, PortAlignment.BottomLeft, new Point(50, 50)); + node.AddPort(port); + + var newX = 200; + var newY = 300; + + //Act + node.SetPosition(newX, newY); + + //Assert + Assert.Equal(150, port.Position.X); + Assert.Equal(250, port.Position.Y); + } + + [Fact] + public void SetPortPositionOnNodeSizeChangedIsCalledOnSetSize() + { + // Arrange + var oldWidth = 100.0; + var oldHeight = 100.0; + var newWidth = 500.0; + var newHeight = 700.0; + var deltaX = newWidth - oldWidth; + var deltaY = newHeight - oldHeight; + + var node = new NodeModel(new Point(100, 100)) { Size = new Size(oldWidth, oldHeight) }; + var portMock = new Mock(node, PortAlignment.BottomLeft, null, null); + + node.AddPort(portMock.Object); + + // Act + node.SetSize(newWidth, newHeight); + + // Assert + portMock.Verify(m => m.SetPortPositionOnNodeSizeChanged(deltaX, deltaY), Times.Once); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs b/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs new file mode 100644 index 000000000..d4dc57bf5 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Models/PortModelTest.cs @@ -0,0 +1,33 @@ +using Blazor.Diagrams.Core.Geometry; +using Blazor.Diagrams.Core.Models; +using Xunit; + +namespace Blazor.Diagrams.Core.Tests.Models +{ + public class PortModelTest + { + [Theory] + [InlineData(PortAlignment.Top, 50, 0)] + [InlineData(PortAlignment.TopLeft, 0, 0)] + [InlineData(PortAlignment.TopRight, 100, 0)] + [InlineData(PortAlignment.Bottom, 50, 100)] + [InlineData(PortAlignment.BottomLeft, 0, 100)] + [InlineData(PortAlignment.BottomRight, 100, 100)] + [InlineData(PortAlignment.Left, 0, 50)] + [InlineData(PortAlignment.Right, 100, 50)] + public void SetPortPositionOnNodeSizeChangedCalculatesCorrectPosition(PortAlignment alignment, double expectedXPosition, double expectedYPosition) + { + // Arrange + var node = new NodeModel(); + var port = new PortModel(node, alignment, new Point(0, 0)); + node.Size = new Size(100, 100); + + // Act + port.SetPortPositionOnNodeSizeChanged(100, 100); + + // Assert + Assert.Equal(expectedXPosition, port.Position.X); + Assert.Equal(expectedYPosition, port.Position.Y); + } + } +} diff --git a/tests/Blazor.Diagrams.Core.Tests/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.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs new file mode 100644 index 000000000..5287508da --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomLeftResizerProviderTests.cs @@ -0,0 +1,174 @@ +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 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 + 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); + 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 + 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] + public void PanChanged_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); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + + // before resize + 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); + control.OnPointerDown(diagram, node, eventArgs); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + + + // after resize + 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] + 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(300, 300); + node.MinimumDimensions = new Size(50, 100); + var control = new ResizeControl(new BottomLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + 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); + control.OnPointerDown(diagram, node, eventArgs); + 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(400, -100, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + 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] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + diagram.SetZoom(0.5); + + // before resize + 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); + 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 + 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] + 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 + 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); + 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 + 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 new file mode 100644 index 000000000..e2a69adba --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/BottomRightResizerProviderTests.cs @@ -0,0 +1,171 @@ +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 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 + 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); + 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 + 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] + public void PanChanged_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); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + + + // before resize + 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); + control.OnPointerDown(diagram, node, eventArgs); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, 100, 0, 0)); + + + // after resize + 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] + 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 + 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); + 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 + 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] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new BottomRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + diagram.SetZoom(0.5); + + // before resize + 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); + 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 + 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] + 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 + 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); + 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 + 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 new file mode 100644 index 000000000..07a4ee815 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopLeftResizerProviderTests.cs @@ -0,0 +1,174 @@ +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 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 + 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); + 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 + 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] + public void PanChanged_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); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + + + // before resize + 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); + control.OnPointerDown(diagram, node, eventArgs); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, -100, 0, 0)); + + + // after resize + 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] + 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(300, 300); + node.MinimumDimensions = new Size(50, 100); + var control = new ResizeControl(new TopLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + 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); + control.OnPointerDown(diagram, node, eventArgs); + 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(400, 400, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + 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] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopLeftResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + diagram.SetZoom(0.5); + + // before resize + 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); + 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 + 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] + 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 + 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); + 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 + 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 new file mode 100644 index 000000000..9aaa1b566 --- /dev/null +++ b/tests/Blazor.Diagrams.Core.Tests/Positions/Resizing/TopRightResizerProviderTests.cs @@ -0,0 +1,172 @@ +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 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 + 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); + 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 + 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] + public void PanChanged_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); + diagram.BehaviorOptions.DiagramWheelBehavior = diagram.GetBehavior(); + + // before resize + 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); + control.OnPointerDown(diagram, node, eventArgs); + diagram.TriggerWheel(new WheelEventArgs(100, 100, 0, 0, false, false, false, 10, -100, 0, 0)); + + // after resize + 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] + 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(300, 300); + node.MinimumDimensions = new Size(50, 100); + var control = new ResizeControl(new TopRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + + // before resize + 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); + control.OnPointerDown(diagram, node, eventArgs); + 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(-100, 400, 0, 0, false, false, false, 1, 1, 1, 1, 1, 1, "arrow", true); + diagram.TriggerPointerMove(null, eventArgs); + + // after resize + 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] + public void DragResizer_ShouldResizeNode_WhenDiagramZoomedOut() + { + // setup + var diagram = new TestDiagram(); + diagram.SetContainer(new Rectangle(0, 0, 1000, 400)); + var node = new NodeModel(position: new Point(0, 0)); + node.Size = new Size(100, 200); + var control = new ResizeControl(new TopRightResizerProvider()); + diagram.Controls.AddFor(node).Add(control); + diagram.SelectModel(node, false); + diagram.SetZoom(0.5); + + // before resize + 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); + 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 + 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] + 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 + 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); + 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 + 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/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj index dead3f03e..949bf1c97 100644 --- a/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj +++ b/tests/Blazor.Diagrams.Tests/Blazor.Diagrams.Tests.csproj @@ -1,30 +1,24 @@ - + - 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/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs new file mode 100644 index 000000000..ee4aa80a1 --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/Components/Controls/ResizeControlWidgetTests.cs @@ -0,0 +1,24 @@ +using Blazor.Diagrams.Components.Controls; +using Blazor.Diagrams.Core.Controls.Default; +using Blazor.Diagrams.Core.Positions.Resizing; +using Bunit; +using Moq; +using Xunit; + +namespace Blazor.Diagrams.Tests.Components.Controls; + +public class ResizeControlWidgetTests +{ + [Fact] + public void ShouldRenderDiv() + { + using var ctx = new TestContext(); + var providerMock = Mock.Of(); + + var cut = ctx.RenderComponent(parameters => + parameters.Add(w => w.Control, new ResizeControl(providerMock)) + ); + + cut.MarkupMatches("
"); + } +} 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 { } + } +} 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); + } + } +} diff --git a/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs b/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs new file mode 100644 index 000000000..c81538730 --- /dev/null +++ b/tests/Blazor.Diagrams.Tests/Components/DiagramCursorTests.cs @@ -0,0 +1,48 @@ +using AngleSharp.Css.Dom; +using Blazor.Diagrams.Components; +using Blazor.Diagrams.Core.Geometry; +using Bunit; +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"); + + // Assert + Assert.Contains("cursor: grab; cursor: -webkit-grab;", diagramCanvas.ToMarkup()); + } + + [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 + 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/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 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()); + } + } +} 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 { } 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!; diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 000000000..ce4aec516 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,8 @@ + + + net8.0;net6.0 + enable + enable + true + + \ No newline at end of file