Repository: BAndysc/nodify-avalonia Branch: avalonia_port Commit: af468753371d Files: 389 Total size: 1.7 MB Directory structure: gitextract_apgfzb0f/ ├── .editorconfig ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── ask-a-question.md │ │ ├── bug_report.md │ │ └── config.yml │ └── workflows/ │ ├── build.yml │ ├── codeql-analysis.yml │ ├── create-release-branch.yml │ ├── publish-package.yml │ └── sync-docs.yml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Directory.Build.props ├── Examples/ │ ├── Nodify.Calculator/ │ │ ├── App.xaml │ │ ├── App.xaml.cs │ │ ├── ApplicationViewModel.cs │ │ ├── AssemblyInfo.cs │ │ ├── CalculatorInputOperationViewModel.cs │ │ ├── CalculatorOperationViewModel.cs │ │ ├── CalculatorViewModel.cs │ │ ├── ConnectionViewModel.cs │ │ ├── ConnectorViewModel.cs │ │ ├── Converters/ │ │ │ └── ItemToListConverter.cs │ │ ├── CreateOperationInfoViewModel.cs │ │ ├── EditorView.xaml │ │ ├── EditorView.xaml.cs │ │ ├── EditorViewModel.cs │ │ ├── ExpandoOperationViewModel.cs │ │ ├── ExpressionOperationViewModel.cs │ │ ├── GlobalUsings.cs │ │ ├── MainWindow.xaml │ │ ├── MainWindow.xaml.cs │ │ ├── Nodify.Calculator.csproj │ │ ├── OperationGraphViewModel.cs │ │ ├── OperationGroupViewModel.cs │ │ ├── OperationInfoViewModel.cs │ │ ├── OperationViewModel.cs │ │ ├── Operations/ │ │ │ ├── BinaryOperation.cs │ │ │ ├── IOperation.cs │ │ │ ├── OperationFactory.cs │ │ │ ├── OperationsContainer.cs │ │ │ ├── ParamsOperation.cs │ │ │ ├── UnaryOperation.cs │ │ │ └── ValueOperation.cs │ │ ├── OperationsExtensions.cs │ │ ├── OperationsMenuView.xaml │ │ ├── OperationsMenuView.xaml.cs │ │ ├── OperationsMenuViewModel.cs │ │ ├── PendingConnectionViewModel.cs │ │ ├── Program.cs │ │ └── Themes/ │ │ ├── Dark.xaml │ │ ├── Light.xaml │ │ └── Nodify.xaml │ ├── Nodify.Playground/ │ │ ├── App.xaml │ │ ├── App.xaml.cs │ │ ├── AssemblyInfo.cs │ │ ├── BaseSettingViewModel.cs │ │ ├── Converters/ │ │ │ ├── FlowToConnectorPositionConverter.cs │ │ │ ├── FlowToDirectionConverter.cs │ │ │ ├── UIntToRectConverter.cs │ │ │ └── UIntToRelativeRectConverter.cs │ │ ├── Editor/ │ │ │ ├── CommentNodeViewModel.cs │ │ │ ├── ConnectionViewModel.cs │ │ │ ├── ConnectorViewModel.cs │ │ │ ├── FlowNodeViewModel.cs │ │ │ ├── GraphSchema.cs │ │ │ ├── KnotNodeViewModel.cs │ │ │ ├── NodeViewModel.cs │ │ │ ├── NodifyEditorView.xaml │ │ │ ├── NodifyEditorView.xaml.cs │ │ │ ├── NodifyEditorViewModel.cs │ │ │ ├── PendingConnectionViewModel.cs │ │ │ ├── VerticalNodeViewModel.cs │ │ │ └── WpfComboBox.cs │ │ ├── EditorInputMode.cs │ │ ├── EditorSettings.cs │ │ ├── EditorSettingsView.xaml │ │ ├── EditorSettingsView.xaml.cs │ │ ├── GlobalUsings.cs │ │ ├── Helpers/ │ │ │ ├── NodeViewModelExtensions.cs │ │ │ └── RandomNodesGenerator.cs │ │ ├── ISettingViewModel.cs │ │ ├── MainWindow.xaml │ │ ├── MainWindow.xaml.cs │ │ ├── Nodify.Playground.csproj │ │ ├── PlaygroundSettings.cs │ │ ├── PlaygroundViewModel.cs │ │ ├── PointEditor.cs │ │ ├── PointEditorView.xaml │ │ ├── PointEditorView.xaml.cs │ │ ├── Program.cs │ │ ├── ProxySettingViewModel.cs │ │ ├── SettingsView.xaml │ │ ├── SettingsView.xaml.cs │ │ └── Themes/ │ │ ├── Brushes.xaml │ │ ├── Dark.xaml │ │ ├── Light.xaml │ │ └── Nodify.xaml │ ├── Nodify.Shapes/ │ │ ├── App.xaml │ │ ├── App.xaml.cs │ │ ├── AppShellViewModel.cs │ │ ├── AssemblyInfo.cs │ │ ├── Avalonia/ │ │ │ ├── Cursors.cs │ │ │ └── ToolItemSelector.cs │ │ ├── Canvas/ │ │ │ ├── CanvasToolbarViewModel.cs │ │ │ ├── CanvasView.xaml │ │ │ ├── CanvasView.xaml.cs │ │ │ ├── CanvasViewModel.cs │ │ │ ├── ConnectionViewModel.cs │ │ │ ├── ConnectorViewModel.cs │ │ │ ├── Decorators/ │ │ │ │ ├── ICanvasDecorator.cs │ │ │ │ ├── ShapeToolbarViewModel.cs │ │ │ │ └── UserCursorViewModel.cs │ │ │ ├── Gestures/ │ │ │ │ ├── DrawingGesturesMappings.cs │ │ │ │ ├── LockedGestureMappings.cs │ │ │ │ └── UnboundGestureMappings.cs │ │ │ ├── Shapes/ │ │ │ │ ├── EllipseViewModel.cs │ │ │ │ ├── RectangleViewModel.cs │ │ │ │ ├── ShapeViewModel.cs │ │ │ │ └── TriangleViewModel.cs │ │ │ └── UndoRedo/ │ │ │ ├── MoveShapesAction.cs │ │ │ ├── ResizeShapesAction.cs │ │ │ └── SelectShapesAction.cs │ │ ├── Controls/ │ │ │ └── ResizableContainer.cs │ │ ├── GlobalUsings.cs │ │ ├── MainView.axaml │ │ ├── MainView.axaml.cs │ │ ├── MainWindow.xaml │ │ ├── MainWindow.xaml.cs │ │ └── Nodify.Shapes.csproj │ ├── Nodify.Shapes.Desktop/ │ │ ├── Nodify.Shapes.Desktop.csproj │ │ └── Program.cs │ ├── Nodify.Shapes.Web/ │ │ ├── AppBundle/ │ │ │ ├── app.css │ │ │ ├── index.html │ │ │ └── main.js │ │ ├── Nodify.Shapes.Web.csproj │ │ ├── Program.cs │ │ ├── Properties/ │ │ │ └── launchSettings.json │ │ └── runtimeconfig.template.json │ ├── Nodify.Shared/ │ │ ├── Behaviours/ │ │ │ ├── DataTrigger.cs │ │ │ ├── PropertySetter.cs │ │ │ └── WpfBtn.cs │ │ ├── BindingProxy.cs │ │ ├── BoxValue.cs │ │ ├── Controls/ │ │ │ ├── EditableTextBlock.cs │ │ │ ├── ResizablePanel.cs │ │ │ ├── Swatches.xaml.cs │ │ │ ├── TabControlEx.cs │ │ │ └── TabItemEx.cs │ │ ├── Converters/ │ │ │ ├── BooleanToVisibilityConverter.cs │ │ │ ├── ColorToSolidColorBrushConverter.cs │ │ │ ├── DebugConverter.cs │ │ │ ├── EnumValuesConverter.cs │ │ │ ├── InverseBooleanConverter.cs │ │ │ ├── MultiValueEqualityConverter.cs │ │ │ ├── RandomBrushConverter.cs │ │ │ ├── ResizeDirectionToVisiblityConverter.cs │ │ │ ├── StringToVisibilityConverter.cs │ │ │ └── ToStringConverter.cs │ │ ├── DelegateCommand.cs │ │ ├── FluentSyntax.cs │ │ ├── GlobalUsings.cs │ │ ├── Nodify.Shared.csproj │ │ ├── NodifyObservableCollection.cs │ │ ├── ObservableObject.cs │ │ ├── Properties/ │ │ │ └── AssemblyInfo.cs │ │ ├── RequeryCommand.cs │ │ ├── StringExtensions.cs │ │ ├── ThemeManager.cs │ │ ├── Themes/ │ │ │ ├── Brushes.xaml │ │ │ ├── Controls.xaml │ │ │ ├── Dark.xaml │ │ │ ├── Generic.xaml │ │ │ ├── Icons.xaml │ │ │ ├── Light.xaml │ │ │ └── Nodify.xaml │ │ └── UndoRedo/ │ │ ├── ActionsHistory.cs │ │ ├── BatchAction.cs │ │ ├── DelegateAction.cs │ │ ├── PropertyCache.cs │ │ └── Undoable.cs │ └── Nodify.StateMachine/ │ ├── App.xaml │ ├── App.xaml.cs │ ├── BlackboardItemReferenceViewModel.cs │ ├── BlackboardItemViewModel.cs │ ├── BlackboardKeyEditorView.xaml │ ├── BlackboardKeyEditorView.xaml.cs │ ├── BlackboardKeyEditorViewModel.cs │ ├── BlackboardKeyViewModel.cs │ ├── BlackboardViewModel.cs │ ├── Converters/ │ │ ├── BlackboardKeyEditorConverter.cs │ │ ├── ConnectorOffsetConverter.cs │ │ ├── DrawingBrushToRectangleConverter.cs │ │ └── FilterBlackboardKeysConverter.cs │ ├── GlobalUsings.cs │ ├── Helpers/ │ │ └── BlackboardDescriptor.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Nodify.StateMachine.csproj │ ├── Program.cs │ ├── Runner/ │ │ ├── Actions/ │ │ │ ├── CopyKeyAction.cs │ │ │ ├── SetKeyValueAction.cs │ │ │ └── SetStateDelayAction.cs │ │ ├── Blackboard/ │ │ │ ├── Blackboard.cs │ │ │ ├── BlackboardConditionSet.cs │ │ │ ├── BlackboardItemAttribute.cs │ │ │ ├── BlackboardKey.cs │ │ │ ├── BlackboardProperty.cs │ │ │ ├── BlackboardPropertyAttribute.cs │ │ │ ├── IBlackboardAction.cs │ │ │ └── IBlackboardCondition.cs │ │ ├── Conditions/ │ │ │ ├── AreEqualCondition.cs │ │ │ ├── HasKeyCondition.cs │ │ │ └── HasValueCondition.cs │ │ ├── Debugging/ │ │ │ ├── DebugBlackboardDecorator.cs │ │ │ ├── DebugStateDecorator.cs │ │ │ └── DebugTransitionDecorator.cs │ │ ├── State.cs │ │ ├── StateMachine.cs │ │ └── Transition.cs │ ├── StateMachineRunnerViewModel.cs │ ├── StateMachineViewModel.cs │ ├── StateViewModel.cs │ ├── Themes/ │ │ ├── Brushes.xaml │ │ ├── Dark.xaml │ │ ├── Light.xaml │ │ └── Nodify.xaml │ └── TransitionViewModel.cs ├── LICENSE ├── Nodify/ │ ├── Compatibility/ │ │ ├── Attributes/ │ │ │ ├── ContentPropertyAttribute.cs │ │ │ └── StyleTypedPropertyAttribute.cs │ │ ├── Commands/ │ │ │ ├── ApplicationCommands.cs │ │ │ ├── CanExecuteRoutedEventArgs.cs │ │ │ ├── CommandBinding.cs │ │ │ ├── CommandManager.cs │ │ │ ├── ExecutedRoutedEventArgs.cs │ │ │ ├── RoutedCommand.cs │ │ │ └── RoutedUICommand.cs │ │ ├── Controls/ │ │ │ ├── GroupStyle.cs │ │ │ ├── MultiSelector.cs │ │ │ ├── Primitives/ │ │ │ │ └── IScrollInfo.cs │ │ │ ├── WpfControl.cs │ │ │ └── WpfShape.cs │ │ ├── DefaultStyleKeyProperty.cs │ │ ├── Dragging/ │ │ │ ├── DragCompletedEventArgs.cs │ │ │ ├── DragDeltaEventArgs.cs │ │ │ └── DragStartedEventArgs.cs │ │ ├── EmptyNamespaces.cs │ │ ├── Extensions/ │ │ │ ├── ControlCaptureExtensions.cs │ │ │ ├── PointExtensions.cs │ │ │ ├── SizeExtensions.cs │ │ │ ├── StreamGeometryContextExtensions.cs │ │ │ └── VisualExtensions.cs │ │ ├── Input/ │ │ │ ├── InputGesture.cs │ │ │ ├── InputGestureCollection.cs │ │ │ ├── InputKeyGesture.cs │ │ │ ├── MouseAction.cs │ │ │ ├── MouseButtonEventArgs.cs │ │ │ ├── MouseButtonState.cs │ │ │ ├── MouseEventArgs.cs │ │ │ ├── MouseGesture.cs │ │ │ ├── MouseMoveEventArgs.cs │ │ │ └── MouseWheelEventArgs.cs │ │ ├── PanelUtilities.cs │ │ └── VisualTreeHelper.cs │ ├── Connections/ │ │ ├── BaseConnection.Avalonia.cs │ │ ├── BaseConnection.cs │ │ ├── CircuitConnection.cs │ │ ├── Connection.cs │ │ ├── ConnectionContainer.Avalonia.cs │ │ ├── ConnectionContainer.cs │ │ ├── ConnectionsMultiSelector.Avalonia.cs │ │ ├── ConnectionsMultiSelector.cs │ │ ├── Connector.Avalonia.cs │ │ ├── Connector.cs │ │ ├── CuttingLine.cs │ │ ├── LineConnection.cs │ │ ├── PendingConnection.Avalonia.cs │ │ ├── PendingConnection.cs │ │ └── StepConnection.cs │ ├── DecoratorContainer.cs │ ├── DecoratorsControl.cs │ ├── EditorCommands.cs │ ├── EditorGestures.cs │ ├── EditorStates/ │ │ ├── ContainerDefaultState.cs │ │ ├── ContainerDraggingState.cs │ │ ├── ContainerState.cs │ │ ├── EditorCuttingState.cs │ │ ├── EditorDefaultState.cs │ │ ├── EditorPanningState.cs │ │ ├── EditorPushingItemsState.cs │ │ ├── EditorSelectingState.cs │ │ └── EditorState.cs │ ├── Events/ │ │ ├── ConnectionEventArgs.cs │ │ ├── ConnectorEventArgs.cs │ │ ├── PendingConnectionEventArgs.cs │ │ └── ResizeEventArgs.cs │ ├── GlobalUsings.cs │ ├── Helpers/ │ │ ├── BindableStyleClasses.cs │ │ ├── BoxValue.cs │ │ ├── DependencyObjectExtensions.cs │ │ ├── DraggingOptimized.cs │ │ ├── DraggingSimple.cs │ │ ├── MathExtensions.cs │ │ ├── MultiGesture.cs │ │ ├── PushItemsStrategy.cs │ │ ├── SelectionHelper.cs │ │ └── UnscaleTransformConverter.cs │ ├── ItemContainer.Avalonia.cs │ ├── ItemContainer.cs │ ├── Minimap/ │ │ ├── Minimap.Avalonia.cs │ │ ├── Minimap.cs │ │ ├── MinimapItem.cs │ │ ├── MinimapPanel.Avalonia.cs │ │ ├── MinimapPanel.cs │ │ ├── SubtractConverter.cs │ │ └── ZoomEventArgs.cs │ ├── Nodes/ │ │ ├── GroupingNode.cs │ │ ├── KnotNode.cs │ │ ├── Node.Avalonia.cs │ │ ├── Node.cs │ │ ├── NodeInput.Avalonia.cs │ │ ├── NodeInput.cs │ │ ├── NodeOutput.Avalonia.cs │ │ ├── NodeOutput.cs │ │ └── StateNode.cs │ ├── Nodify.csproj │ ├── Nodify.csproj.DotSettings │ ├── NodifyCanvas.cs │ ├── NodifyEditor.Avalonia.cs │ ├── NodifyEditor.PushingItems.cs │ ├── NodifyEditor.Scrolling.cs │ ├── NodifyEditor.cs │ ├── Properties/ │ │ └── AssemblyInfo.cs │ ├── Theme.axaml │ └── Themes/ │ ├── Brushes.xaml │ ├── Controls.xaml │ ├── Dark.xaml │ ├── Generic.xaml │ ├── Light.xaml │ ├── Nodify.xaml │ ├── NodifyStyle.axaml │ └── Styles/ │ ├── Connection.xaml │ ├── Connector.xaml │ ├── Controls.xaml │ ├── CuttingLine.xaml │ ├── DecoratorContainer.xaml │ ├── GroupingNode.xaml │ ├── ItemContainer.xaml │ ├── KnotNode.xaml │ ├── Minimap.xaml │ ├── Node.xaml │ ├── NodeInput.xaml │ ├── NodeOutput.xaml │ ├── NodifyEditor.xaml │ ├── PendingConnection.xaml │ └── StateNode.xaml ├── Nodify.sln ├── Nodify.sln.DotSettings ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── build/ │ ├── Nodify.public.snk │ └── Nodify.snk ├── build_cloudflare.sh └── docs/ ├── API-Reference.md ├── Connections-Overview.md ├── Connectors-Overview.md ├── CuttingLine-Overview.md ├── Documentation.md ├── Editor-Overview.md ├── FAQ.md ├── Getting-Started.md ├── Home.md ├── ItemContainer-Overview.md ├── Minimap-Overview.md ├── Nodes-Overview.md ├── _Sidebar.md └── localization/ └── zh-CN/ ├── API-Reference.md ├── _Sidebar.md ├── 主页.md ├── 开始.md ├── 编辑器概述.md ├── 节点概述.md ├── 连接器概述.md ├── 连接概述.md ├── 问答.md └── 项目容器概述.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ [*.{c,c++,cc,cginc,compute,cp,cpp,cu,cuh,cxx,h,hh,hlsl,hlsli,hlslinc,hpp,hxx,inc,inl,ino,ipp,mpp,mq4,mq5,mqh,tpp,usf,ush}] indent_style = tab indent_size = tab tab_width = 4 [*.{asax,ascx,aspx,axaml,cs,cshtml,css,htm,html,js,jsx,master,paml,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}] indent_style = space indent_size = 4 tab_width = 4 [*.{appxmanifest,axml,build,config,csproj,dbml,discomap,dtd,json,jsproj,lsproj,njsproj,nuspec,proj,props,resjson,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}] indent_style = space indent_size = 2 tab_width = 2 [*] # Microsoft .NET properties csharp_new_line_before_members_in_object_initializers = false csharp_preferred_modifier_order = public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion csharp_style_var_for_built_in_types = false:suggestion dotnet_style_parentheses_in_arithmetic_binary_operators = never_if_unnecessary:none dotnet_style_parentheses_in_other_binary_operators = never_if_unnecessary:none dotnet_style_parentheses_in_relational_binary_operators = never_if_unnecessary:none dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion dotnet_style_predefined_type_for_member_access = true:suggestion dotnet_style_qualification_for_event = false:suggestion dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_method = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_require_accessibility_modifiers = for_non_interface_members:suggestion # ReSharper properties resharper_for_built_in_types = use_var_when_evident resharper_for_simple_types = use_var_when_evident # ReSharper inspection severities resharper_arrange_object_creation_when_type_evident_highlighting = none resharper_arrange_redundant_parentheses_highlighting = hint resharper_arrange_this_qualifier_highlighting = hint resharper_arrange_type_member_modifiers_highlighting = hint resharper_arrange_type_modifiers_highlighting = hint resharper_built_in_type_reference_style_for_member_access_highlighting = hint resharper_built_in_type_reference_style_highlighting = hint resharper_merge_sequential_patterns_highlighting = none resharper_redundant_base_qualifier_highlighting = warning resharper_suggest_var_or_type_built_in_types_highlighting = hint ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: miroiu custom: ["https://www.buymeacoffee.com/miroiu", "https://paypal.me/miroiuemanuel"] ================================================ FILE: .github/ISSUE_TEMPLATE/ask-a-question.md ================================================ --- name: "❓ Ask a question" about: Need help or have a question? title: "[Question]" labels: question assignees: miroiu --- ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: "\U0001F41B Bug report" about: Create a bug report to help this project improve title: "[Bug]" labels: bug assignees: miroiu --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior. **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Does it happen in WPF version?** If possible, please try to reproduce the bug in the [WPF version](https://github.com/miroiu/nodify). If it also happen in WPF's, it has to be fixed in the upstream and then it can be merged to this Avalonia port. **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: false contact_links: - name: 📝 Read the docs url: https://github.com/miroiu/nodify/wiki about: Be sure you've read the docs! ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: push: branches: ["master", "release-v*", "avalonia_port"] paths-ignore: - "docs/**" pull_request: paths-ignore: - "docs/**" jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup .NET uses: actions/setup-dotnet@v4 with: dotnet-version: "9.0.x" - name: Build run: | dotnet workload restore dotnet build --configuration Release ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ name: "CodeQL" on: push: branches: ["master", "release-v*", "avalonia_port"] paths: - Nodify/** pull_request: branches: ["master", "release-v*", "avalonia_port"] schedule: - cron: "27 6 * * 2" jobs: analyze: name: Analyze runs-on: ubuntu-latest strategy: fail-fast: false matrix: include: - language: csharp build-mode: none steps: - name: Checkout repository uses: actions/checkout@v4 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v3 with: category: "/language:${{matrix.language}}" ================================================ FILE: .github/workflows/create-release-branch.yml ================================================ name: Create release branch on: push: tags: - "v*.0.0" jobs: create-release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Save tag version uses: little-core-labs/get-git-tag@v3.0.1 id: tagName - name: Setup .NET Core uses: actions/setup-dotnet@v2.1.0 with: dotnet-version: '9.0.x' - name: Install dependencies run: | dotnet workload restore dotnet restore - name: Build run: dotnet build --configuration Release --no-restore - name: Create release branch uses: peterjgrainger/action-create-branch@v2.2.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: branch: release-${{ steps.tagName.outputs.tag }} ================================================ FILE: .github/workflows/publish-package.yml ================================================ name: Publish package on: workflow_dispatch: push: tags: - "v*.*.*" jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup .NET Core uses: actions/setup-dotnet@v4 with: dotnet-version: '9.0.x' - name: Install dependencies run: | dotnet workload restore dotnet restore - name: Build run: dotnet build --configuration Release --no-restore - name: Publish the package run: dotnet nuget push "*/bin/Release/*.nupkg" -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json ================================================ FILE: .github/workflows/sync-docs.yml ================================================ name: Documentation on: push: branches: - master paths: - "docs/**" workflow_dispatch: jobs: job-sync-docs-to-wiki: runs-on: ubuntu-latest steps: - name: Checkout Repo uses: actions/checkout@v2 - name: Sync docs to wiki uses: newrelic/wiki-sync-action@main with: source: docs destination: wiki token: ${{ secrets.DOCS_TOKEN }} ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ [Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # CodeRush personal settings .cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # Local History for Visual Studio .localhistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ .idea ================================================ FILE: CHANGELOG.md ================================================ ## Changelog #### **In development** > - Breaking Changes: > - Features: > - Bugfixes: #### **Version 6.6.0** > - Breaking Changes: > - Features: > - Added InputGroupStyle and OutputGroupStyle to Node (not supported in Avalonia) > - Added PanWithMouseWheel, PanHorizontalModifierKey and PanVerticalModifierKey to EditorGestures.Editor > - Added CornerRadius dependency property to LineConnection, CircuitConnection and StepConnection > - Added EditorGestures.Editor.PushItems gesture used to start pushing ItemContainers vertically or horizontally > - Added PushedAreaStyle, PushedAreaOrientation and IsPushingItems dependency properties to NodifyEditor > - Added NodifyEditor.SnapToGrid utility function > - Bugfixes: > - Fixed ItemContainer.BorderBrush and ItemContainer.SelectedBrush not reacting to theme changes #### **Version 6.5.0** > - Features: > - Added SelectedConnection, SelectedConnections, CanSelectMultipleConnections and CanSelectMultipleItems dependency properties to NodifyEditor > - Added IsSelected and IsSelectable attached dependency properties to BaseConnection > - Added PrioritizeBaseConnectionForSelection static field to BaseConnection > - Added EditorGestures.Connection.Selection > - Added support for ScrollViewer in NodifyEditor (implements IScrollInfo) > - Added NodifyEditor.ScrollIncrement dependency property #### **Version 6.4.0** > - Features: > - Added OutlineBrush and OutlineThickness dependency properties to BaseConnection to support increasing the selection area without increasing the stroke thickness > - Added IsAnimatingDirectionalArrows and DirectionalArrowsAnimationDuration dependency properties to BaseConnection to support controlling the animation from XAML #### **Version 6.3.0** > - Features: > - Added a CuttingLine control that removes intersecting connections > - Added CuttingLineStyle, CuttingStartedCommand, CuttingCompletedCommand, IsCutting, EnableCuttingLinePreview and CuttingConnectionTypes to NodifyEditor > - Added EditorGestures.Editor.Cutting and EditorGestures.Editor.CancelAction > - Bugfixes: > - Fixed connection styles not inheriting from the BaseConnection style #### **Version 6.2.0** > - Features: > - Added a Minimap control and EditorGestures.Minimap > - Added ContentContainerStyle, HeaderContainerStyle and FooterContainerStyle dependency properties to Node > - Added BringIntoView that takes a Rect parameter to NodifyEditor > - Added the NodifyEditor's DataContext as the parameter of the ItemsSelectStartedCommand, ItemsSelectCompletedCommand, ItemsDragStartedCommand and ItemsDragCompletedCommand commands > - Bugfixes: > - Fixed hover effect and padding of NodeInput and NodeOutput for vertical orientation > - Fixed ItemContainers being selected sometimes when double clicking the canvas #### **Version 6.1.0** > - Features: > - Added new built-in connection type: StepConnection > - Bugfixes: > - Fixed CircuitConnection directional arrows not interpolating correctly > - Fixed BaseConnection SplitEvent and DisconnectEvent not being raised if the corresponding command is null > - Fixed DecoratorContainer scaling with zoom when not referencing a theme in App.xaml > - Fixed style not applying to the default Connection template outside App.xaml #### **Version 6.0.0** > - Breaking Changes: > - Added a parameter for the orientation to DrawArrowGeometry, DrawDefaultArrowhead, DrawRectangleArrowhead and DrawEllipseArrowhead in BaseConnection > - Added source and target parameters to GetTextPosition in BaseConnection > - EditorGestures is now a singleton instead of a static class (can be inherited to create custom mappings) > - Selection gestures for ItemContainer and GroupingNode are now separated from the NodifyEditor selection gestures > - Renamed EditorGestures.Editor.Zoom to ZoomModifierKey > - Features: > - Added SourceOrientation and TargetOrientation to BaseConnection to support vertical connectors (vertical/mixed connection orientation) > - Added DirectionalArrowsCount to BaseConnection to allow drawing multipe arrows on a connection flowing in the connection direction > - Added DrawDirectionalArrowsGeometry and DrawDirectionalArrowheadGeometry to BaseConnection to allow customizing the directional arrows > - Improved EditorGestures to allow changing input gestures at runtime > - Added new gesture types: AnyGesture, AllGestures, and InputGestureRef > - Added Orientation dependency property to NodeInput and NodeOutput > - Added DirectionalArrowsOffset dependency property to BaseConnection > - Added StartAnimation and StopAnimation methods to BaseConnection > - Bugfixes: > - Fixed BaseConnection.Text not always displaying in the center of the connection > - Fixed a bug where the item container would incorrectly transition to the dragging state on mouse over #### **Version 5.2.0** > - Features: > - Added Text to BaseConnection, allowing displaying of text on connections > - Added Foreground, FontSize, FontWeight, FontStyle, FontStretch and FontFamily to BaseConnection, allowing styling the displaying text > - Bugfixes: > - Fixed MouseCapture not being released when EnableStickyConnections is enabled and the PendingConnection is canceled by a key gesture #### **Version 5.1.0** > - Features: > - Added ItemContainer.SelectedBorderThickness dependency property > - Added NodifyEditor.GetLocationInsideEditor > - Bugfixes: > - Fixed PendingConnection.PreviewTarget not being set to null when there is no actual target > - Fixed PendingConnection.PreviewTarget not being set on Connector.PendingConnectionStartedEvent > - Fixed PendingConnection.PreviewTarget not being set to null on Connector.PendingConnectionCompletedEvent > - Fixed connectors panel not being affected by Node.VerticalAlignment > - Changing BorderThickness causes layout shift when selecting an item container > - Fixed the unintentional movement caused by snapping correction #### **Version 5.0.2** > - Bugfixes: > - Fixed NodeOutput content horizontal alignment > - Fixed Connector not opening Context Menu #### **Version 5.0.1** > - Bugfixes: > - Returning false from PendingConnection.StartedCommand.CanExecute does not stop the creation of a pending connection > - BaseConnection.ArrowEnds does not display correctly when BaseConnection.Direction is ConnectionDirection.Backward #### **Version 5.0.0** > - Breaking Changes: > - Removed BaseConnection.GetArrowHeadPoints > - Removed BaseConnection.OffsetMode > - Changed return type of BaseConnection.DrawLineGeometry to support both arrowheads no matter the number of points on the line > - Changed the default for BaseConnection.SourceOffset and BaseConnection.TargetOffset from Size(0, 0) to Size(14, 0) > - Changed the default for BaseConnection.ArrowSize from Size(7, 6) to Size(8, 8) > - Features: > - Added BaseConnection.SourceOffsetMode and BaseConnection.TargetOffsetMode > - Added BaseConnection.ArrowEnds dependency property to allow configurable arrowhead ends > - Added BaseConnection.ArrowShape dependency property to allow configurable arrowhead shape > - Added NodifyEditor.EnableDraggingContainersOptimizations to allow receiving ItemContainer.Location updates in realtime > - Added ConnectionOffsetMode.Static to allow offsetting the source and target points of the connection on the X and the Y axis without revolving around the source or target points #### **Version 4.1.0** > - Features: > - Added EditorGestures.Selection.DefaultMouseAction to make it easier to change between mouse buttons for selection > - Added EditorGestures.Selection.Cancel gesture to cancel the selection operation reverting to the previous selection > - Added ItemsSelectStartedCommand and ItemsSelectCompletedCommand dependency properties to NodifyEditor for better undo/redo support > - Bugfixes: > - Fixed NodifyEditor.SelectedItems being empty after selection is completed > - Fixed drag canceling when Drag and CancelAction are bound to the same gesture #### **Version 4.0.1** > - Bugfixes: > - Fixed DisablePanning not working anymore #### **Version 4.0.0** > - Breaking Changes: > - Removed Selection field from NodifyEditor > - Removed InitialMousePosition, CurrentMousePosition, PreviousMousePosition fields from NodifyEditor > - Removed ItemContainer.DraggableHost (use Editor.ItemsHost instead) > - Made SelectionType required in SelectionHelper > - Moved GroupingNode.SwitchMovementModeModifierKey to EditorGestures.GroupingNode > - Pending connections are now restricted to connect only to Connectors or to NodifyEditors and ItemContainers if PendingConnection.AllowOnlyConnectors is false > - Features: > - Added Connector.EnableStickyConnections to allow completing pending connections in two steps > - Added editor states which can be overriden by inheriting from NodifyEditor and implementing NodifyEditor.GetInitialState() > - EditorState - base class for all editor states > - EditorDefaultState > - EditorSelectingState > - EditorPanningState > - Added container states which can be overriden by inheriting from ItemContainer and implementing ItemContainer.GetInitialState() > - ContainerState - base class for all container states > - ContainerDefaultState > - ContainerDraggingState > - Added MultiGesture utility that can combine multiple input gestures into one gesture > - Added configurable input gestures for NodifyEditor, ItemContainer, Connector, BaseConnection and GroupingNode to EditorGestures > - Added State, PushState, PopState and PopAllStates to NodifyEditor and ItemContainer > - Changed the default AutoPanSpeed to 15 from 10 pixels per tick > - Allow setting ItemContainer.IsPreviewingLocation from derived classes > - Bugfixes: > - Fixed HandleRightClickAfterPanningThreshold not working as expected > - Fixed DisablePanning not disabling auto panning in certain situations > - Fixed GroupingNode selection not working with multiple selection modes > - Fixed PendingConnection connecting cross editors #### **Version 3.0.0** > - Breaking Changes: > - Changed Decorators from UIElement collection to IEnumerable > - Features: > - Added ItemsExtent and DecoratorsExtent dependency properties to NodifyEditor > - Added DecoratorTemplate dependency property to NodifyEditor > - Added FitToScreenExtentMargin static field to NodifyEditor > - Added Extent dependency property to NodifyCanvas > - Bugfixes: > - Selection rectangle and Decorators are no longer scaled with the viewport zoom > - Fixed connector anchor not updating when container size changed #### **Version 2.0.1** > - Bugfixes: > - Fixed pending connection default style #### **Version 2.0.0** > - Breaking Changes: > - Renamed Offset to ViewportLocation in NodifyEditor > - Renamed Scale to ViewportZoom in NodifyEditor > - Renamed MinScale to MinViewportZoom in NodifyEditor > - Renamed MaxScale to MaxViewportZoom in NodifyEditor > - Renamed AppliedTransform to ViewportTransform in NodifyEditor > - Renamed DirectionalConnection to LineConnection > - Removed BringIntoViewAnimationDuration from NodifyEditor > - Removed Viewport dependency property from NodifyEditor > - Removed ActualSize dependency property from StateNode > - Removed Icon dependency property from Node as the icon can _(and should)_ be added in the HeaderTemplate if necessary > - PART_ItemsHost is now required for NodifyEditor to work > - ItemContainers cannot be used outside a NodifyEditor anymore > - ZoomAtPosition now requires graph space coordinates instead of screen space coordinates > - Removed custom value converters > - Made DependencyObjectExtensions internal > - Removed the xaml prefix > - Features: > - Added ResizeStartedEvent routed event to GroupingNode > - Added ViewportSize - **OneWayToSource** dependency property to NodifyEditor > - Added ActualSize - **OneWayToSource** dependency property to ItemContainer > - Added DecoratorContainer and DecoratorContainerStyle dependency properties to NodifyEditor > - Added RemoveConnectionCommand command to NodifyEditor > - Added DisconnectCommand and SplitCommand commands to BaseConnection > - Added ContentBrush dependency property to NodifyEditor > - Added HasFooter dependency property to Node > - Added FitToScreen command to NodifyEditor and EditorCommands > - Added onFinish callback to BringIntoView in NodifyEditor > - Added ArrowSize and Spacing dependency properties to all connections inheriting from BaseConnection > - Added BringIntoViewMaxDuration dependency property to NodifyEditor > - Added BringIntoViewSpeed dependency property to NodifyEditor > - Auto panning speed now scales with the zoom factor > - Bugfixes: > - Every public property or method should work with graph space coordinates > - Disable auto panning when panning is disabled > - Min zoom could be set to a very small value > - Bring into view was not disabling all interfering operations ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at miroiu.emanuel@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: CONTRIBUTING.md ================================================ # 👋 **Welcome to Nodify!** 👋 👍🎉 First off, thanks for taking the time to contribute! Your contributions help make Nodify better for everyone. 👍🎉 If you find Nodify useful, please consider giving us a ⭐ **star** ⭐ on our GitHub repository! Code of Conduct: By contributing to Nodify, you agree to uphold our [Code of Conduct](CODE_OF_CONDUCT.md). We expect all contributors to be respectful and inclusive. (Don't worry, it's all common sense 😎) ## How you can contribute - ❓ [Ask a question](https://github.com/miroiu/nodify/issues/new?assignees=miroiu&labels=question&template=ask-a-question.md&title=%5BQuestion%5D) - If you're unsure about anything related to Nodify, feel free to ask! No question is too small. - 🐛 [Create a bug report](https://github.com/miroiu/nodify/issues/new?assignees=miroiu&labels=bug&template=bug_report.md&title=%5BBug%5D) - Noticed something not working as expected? Let us know by creating a bug report. Please provide as much detail as possible to help us address the issue. - 🌺 [Suggest an enhancement](https://github.com/miroiu/nodify/issues/new?assignees=miroiu&labels=enhancement&template=feature_request.md&title=%5BFeature%5D) - Have an idea to make Nodify even better? We'd love to hear it! Share your suggestions for new features or improvements. - ✨ [Explore example applications](https://github.com/miroiu/nodify/tree/master/Examples) - Check out the example applications provided with Nodify. They're great for learning how to use the library in different scenarios. - 🎉 [Showcase your application](https://github.com/miroiu/nodify/issues/56) - Built something cool with Nodify? Share it with the community! We'd love to see what you've created. - 📝 [Help with the documentation](https://github.com/miroiu/nodify/wiki) - Documentation is crucial for making Nodify accessible to everyone. If you spot errors or have suggestions for improvement, please let us know or update the docs yourself! - 🔧 [Fix a bug](https://github.com/miroiu/nodify/labels/bug) - If you're a developer, you can contribute by fixing bugs in Nodify. Simply locate an open issue tagged as a bug and submit a pull request with your fix. - 🔗 [Create a pull request linking to a feature](https://github.com/miroiu/nodify/labels/enhancement) - Implemented a new feature or enhancement? Fantastic! Submit a pull request linking to the relevant feature or enhancement issue. ## Some tips - Write clear and descriptive issues and try to avoid duplication - If you find a **Closed** issue that relates to yours, open a new issue and include a link to the original issue in the body of your new one. - The easiest way to update documentation is to navigate [to the docs website](https://github.com/miroiu/nodify/wiki) and click 'Edit this page' which is found at the top right of any page. - If you want to create an example application that others can use to learn from, then [create an issue](https://github.com/miroiu/nodify/issues/new?assignees=miroiu&labels=application&template=add_example_app.md&title=%5BApplication%5D) describing what your application is doing and if you need help with anything. - The application you showcase can use any license. ================================================ FILE: Directory.Build.props ================================================ 11.1.0 ================================================ FILE: Examples/Nodify.Calculator/App.xaml ================================================  ================================================ FILE: Examples/Nodify.Calculator/App.xaml.cs ================================================ using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Threading.Tasks; using System.Windows; namespace Nodify.Calculator { /// /// Interaction logic for App.xaml /// public partial class App : Application { public override void Initialize() { AvaloniaXamlLoader.Load(this); } public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { desktop.MainWindow = new MainWindow(); } base.OnFrameworkInitializationCompleted(); } } } ================================================ FILE: Examples/Nodify.Calculator/ApplicationViewModel.cs ================================================ using System; using System.Linq; using System.Windows.Input; namespace Nodify.Calculator { public class ApplicationViewModel : ObservableObject { public NodifyObservableCollection Editors { get; } = new NodifyObservableCollection(); public ApplicationViewModel() { AddEditorCommand = new DelegateCommand(() => Editors.Add(new EditorViewModel { Name = $"Editor {Editors.Count + 1}" })); CloseEditorCommand = new DelegateCommand( id => Editors.RemoveOne(editor => editor.Id == id), _ => Editors.Count > 0 && SelectedEditor != null); Editors.WhenAdded((editor) => { if (AutoSelectNewEditor || Editors.Count == 1) { SelectedEditor = editor; } editor.OnOpenInnerCalculator += OnOpenInnerCalculator; }) .WhenRemoved((editor) => { editor.OnOpenInnerCalculator -= OnOpenInnerCalculator; var childEditors = Editors.Where(ed => ed.Parent == editor).ToList(); childEditors.ForEach(ed => Editors.Remove(ed)); }); Editors.Add(new EditorViewModel { Name = $"Editor {Editors.Count + 1}" }); } private void OnOpenInnerCalculator(EditorViewModel parentEditor, CalculatorViewModel calculator) { var editor = Editors.FirstOrDefault(e => e.Calculator == calculator); if (editor != null) { SelectedEditor = editor; } else { var childEditor = new EditorViewModel { Parent = parentEditor, Calculator = calculator, Name = $"[Inner] Editor {Editors.Count + 1}" }; Editors.Add(childEditor); } } public ICommand AddEditorCommand { get; } public ICommand CloseEditorCommand { get; } private EditorViewModel? _selectedEditor; public EditorViewModel? SelectedEditor { get => _selectedEditor; set => SetProperty(ref _selectedEditor, value); } private bool _autoSelectNewEditor = true; public bool AutoSelectNewEditor { get => _autoSelectNewEditor; set => SetProperty(ref _autoSelectNewEditor , value); } } } ================================================ FILE: Examples/Nodify.Calculator/AssemblyInfo.cs ================================================ using System.Windows; [assembly: ThemeInfo( ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located //(used if a resource is not found in the page, // or application resource dictionaries) ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located //(used if a resource is not found in the page, // app, or any theme specific resource dictionaries) )] ================================================ FILE: Examples/Nodify.Calculator/CalculatorInputOperationViewModel.cs ================================================ namespace Nodify.Calculator { public class CalculatorInputOperationViewModel : OperationViewModel { public CalculatorInputOperationViewModel() { AddOutputCommand = new RequeryCommand( () => Output.Add(new ConnectorViewModel { Title = $"In {Output.Count}" }), () => Output.Count < 10); RemoveOutputCommand = new RequeryCommand( () => Output.RemoveAt(Output.Count - 1), () => Output.Count > 1); Output.Add(new ConnectorViewModel { Title = $"In {Output.Count}" }); } public new NodifyObservableCollection Output { get; set; } = new NodifyObservableCollection(); public INodifyCommand AddOutputCommand { get; } public INodifyCommand RemoveOutputCommand { get; } } } ================================================ FILE: Examples/Nodify.Calculator/CalculatorOperationViewModel.cs ================================================ using System.Windows; namespace Nodify.Calculator { public class CalculatorOperationViewModel : OperationViewModel { public CalculatorViewModel InnerCalculator { get; } = new CalculatorViewModel(); private OperationViewModel InnerOutput { get; } = new OperationViewModel { Title = "Output Parameters", Input = { new ConnectorViewModel() }, Location = new Point(500, 300), IsReadOnly = true }; private CalculatorInputOperationViewModel InnerInput { get; } = new CalculatorInputOperationViewModel { Title = "Input Parameters", Location = new Point(300, 300), IsReadOnly = true }; public CalculatorOperationViewModel() { InnerCalculator.Operations.Add(InnerInput); InnerCalculator.Operations.Add(InnerOutput); Output = new ConnectorViewModel(); InnerOutput.Input[0].ValueObservers.Add(Output); InnerInput.Output.ForEach(x => Input.Add(new ConnectorViewModel { Title = x.Title })); InnerInput.Output .WhenAdded(x => Input.Add(new ConnectorViewModel { Title = x.Title })) .WhenRemoved(x => Input.RemoveOne(i => i.Title == x.Title)); } protected override void OnInputValueChanged() { for (var i = 0; i < Input.Count; i++) { InnerInput.Output[i].Value = Input[i].Value; } } } } ================================================ FILE: Examples/Nodify.Calculator/CalculatorViewModel.cs ================================================ using System.Linq; using System.Windows; namespace Nodify.Calculator { public class CalculatorViewModel : ObservableObject { public CalculatorViewModel() { CreateConnectionCommand = new DelegateCommand( _ => CreateConnection(PendingConnection.Source, PendingConnection.Target), _ => CanCreateConnection(PendingConnection.Source, PendingConnection.Target)); StartConnectionCommand = new DelegateCommand(_ => PendingConnection.IsVisible = true, (c) => !(c.IsConnected && c.IsInput)); DisconnectConnectorCommand = new DelegateCommand(DisconnectConnector); DeleteSelectionCommand = new DelegateCommand(DeleteSelection); GroupSelectionCommand = new DelegateCommand(GroupSelectedOperations, () => SelectedOperations.Count > 0); Connections.WhenAdded(c => { c.Input.IsConnected = true; c.Output.IsConnected = true; c.Input.Value = c.Output.Value; c.Output.ValueObservers.Add(c.Input); }) .WhenRemoved(c => { var ic = Connections.Count(con => con.Input == c.Input || con.Output == c.Input); var oc = Connections.Count(con => con.Input == c.Output || con.Output == c.Output); if (ic == 0) { c.Input.IsConnected = false; } if (oc == 0) { c.Output.IsConnected = false; } c.Output.ValueObservers.Remove(c.Input); }); Operations.WhenAdded(x => { x.Input.WhenRemoved(RemoveConnection); if (x is CalculatorInputOperationViewModel ci) { ci.Output.WhenRemoved(RemoveConnection); } void RemoveConnection(ConnectorViewModel i) { var c = Connections.Where(con => con.Input == i || con.Output == i).ToArray(); c.ForEach(con => Connections.Remove(con)); } }) .WhenRemoved(x => { foreach (var input in x.Input) { DisconnectConnector(input); } if (x.Output != null) { DisconnectConnector(x.Output); } }); OperationsMenu = new OperationsMenuViewModel(this); } private NodifyObservableCollection _operations = new NodifyObservableCollection(); public NodifyObservableCollection Operations { get => _operations; set => SetProperty(ref _operations, value); } private NodifyObservableCollection _selectedOperations = new NodifyObservableCollection(); public NodifyObservableCollection SelectedOperations { get => _selectedOperations; set => SetProperty(ref _selectedOperations, value); } public NodifyObservableCollection Connections { get; } = new NodifyObservableCollection(); public PendingConnectionViewModel PendingConnection { get; set; } = new PendingConnectionViewModel(); public OperationsMenuViewModel OperationsMenu { get; set; } public INodifyCommand StartConnectionCommand { get; } public INodifyCommand CreateConnectionCommand { get; } public INodifyCommand DisconnectConnectorCommand { get; } public INodifyCommand DeleteSelectionCommand { get; } public INodifyCommand GroupSelectionCommand { get; } private void DisconnectConnector(ConnectorViewModel connector) { var connections = Connections.Where(c => c.Input == connector || c.Output == connector).ToList(); connections.ForEach(c => Connections.Remove(c)); } internal bool CanCreateConnection(ConnectorViewModel source, ConnectorViewModel? target) => target == null || (source != target && source.Operation != target.Operation && source.IsInput != target.IsInput); internal void CreateConnection(ConnectorViewModel source, ConnectorViewModel? target) { if (target == null) { PendingConnection.IsVisible = true; OperationsMenu.OpenAt(PendingConnection.TargetLocation); OperationsMenu.Closed += OnOperationsMenuClosed; return; } var input = source.IsInput ? source : target; var output = target.IsInput ? source : target; PendingConnection.IsVisible = false; DisconnectConnector(input); Connections.Add(new ConnectionViewModel { Input = input, Output = output }); } private void OnOperationsMenuClosed() { PendingConnection.IsVisible = false; OperationsMenu.Closed -= OnOperationsMenuClosed; } private void DeleteSelection() { var selected = SelectedOperations.ToList(); selected.ForEach(o => Operations.Remove(o)); } private void GroupSelectedOperations() { var selected = SelectedOperations.ToList(); var bounding = selected.GetBoundingBox(50); Operations.Add(new OperationGroupViewModel { Title = "Operations", Location = bounding.Position, GroupSize = new Size(bounding.Width, bounding.Height) }); } } } ================================================ FILE: Examples/Nodify.Calculator/ConnectionViewModel.cs ================================================ namespace Nodify.Calculator { public class ConnectionViewModel : ObservableObject { private ConnectorViewModel _input = default!; public ConnectorViewModel Input { get => _input; set => SetProperty(ref _input, value); } private ConnectorViewModel _output = default!; public ConnectorViewModel Output { get => _output; set => SetProperty(ref _output, value); } } } ================================================ FILE: Examples/Nodify.Calculator/ConnectorViewModel.cs ================================================ using System.Collections.Generic; using System.Windows; namespace Nodify.Calculator { public class ConnectorViewModel : ObservableObject { private string? _title; public string? Title { get => _title; set => SetProperty(ref _title, value); } private double _value; public double Value { get => _value; set => SetProperty(ref _value, value) .Then(() => ValueObservers.ForEach(o => o.Value = value)); } private bool _isConnected; public bool IsConnected { get => _isConnected; set => SetProperty(ref _isConnected, value); } private bool _isInput; public bool IsInput { get => _isInput; set => SetProperty(ref _isInput, value); } private Point _anchor; public Point Anchor { get => _anchor; set => SetProperty(ref _anchor, value); } private OperationViewModel _operation = default!; public OperationViewModel Operation { get => _operation; set => SetProperty(ref _operation, value); } public List ValueObservers { get; } = new List(); } } ================================================ FILE: Examples/Nodify.Calculator/Converters/ItemToListConverter.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Windows.Data; namespace Nodify.Calculator { public class ItemToListConverter : IValueConverter { public object? Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value != null) { var argType = value.GetType(); var listType = typeof(List<>).MakeGenericType(argType); var list = Activator.CreateInstance(listType) as IList; list?.Add(value); return list; } return value; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException(); } } } ================================================ FILE: Examples/Nodify.Calculator/CreateOperationInfoViewModel.cs ================================================ using System.Windows; namespace Nodify.Calculator { public class CreateOperationInfoViewModel { public CreateOperationInfoViewModel(OperationInfoViewModel info, Point location) { Info = info; Location = location; } public OperationInfoViewModel Info { get; } public Point Location { get; } } } ================================================ FILE: Examples/Nodify.Calculator/EditorView.xaml ================================================  ================================================ FILE: Examples/Nodify.Calculator/OperationsMenuView.xaml.cs ================================================ using System.Windows.Controls; namespace Nodify.Calculator { public partial class OperationsMenuView : UserControl { public OperationsMenuView() { InitializeComponent(); } } } ================================================ FILE: Examples/Nodify.Calculator/OperationsMenuViewModel.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Windows; namespace Nodify.Calculator { public class OperationsMenuViewModel : ObservableObject { private bool _isVisible; public bool IsVisible { get => _isVisible; set { SetProperty(ref _isVisible, value); if (!value) { Closed?.Invoke(); } } } private Point _location; public Point Location { get => _location; set => SetProperty(ref _location, value); } public event Action? Closed; public void OpenAt(Point targetLocation) { Close(); Location = targetLocation; IsVisible = true; } public void Close() { IsVisible = false; } public NodifyObservableCollection AvailableOperations { get; } public INodifyCommand CreateOperationCommand { get; } private readonly CalculatorViewModel _calculator; public OperationsMenuViewModel(CalculatorViewModel calculator) { _calculator = calculator; List operations = new List { new OperationInfoViewModel { Type = OperationType.Graph, Title = "Operation Graph", }, new OperationInfoViewModel { Type = OperationType.Calculator, Title = "Calculator" }, new OperationInfoViewModel { Type = OperationType.Expression, Title = "Custom", } }; operations.AddRange(OperationFactory.GetOperationsInfo(typeof(OperationsContainer))); AvailableOperations = new NodifyObservableCollection(operations); CreateOperationCommand = new DelegateCommand(CreateOperation); } private void CreateOperation(OperationInfoViewModel operationInfo) { OperationViewModel op = OperationFactory.GetOperation(operationInfo); op.Location = Location; _calculator.Operations.Add(op); var pending = _calculator.PendingConnection; if (pending.IsVisible) { var connector = pending.Source.IsInput ? op.Output : op.Input.FirstOrDefault(); if (connector != null && _calculator.CanCreateConnection(pending.Source, connector)) { _calculator.CreateConnection(pending.Source, connector); } } Close(); } } } ================================================ FILE: Examples/Nodify.Calculator/PendingConnectionViewModel.cs ================================================ using System.Windows; namespace Nodify.Calculator { public class PendingConnectionViewModel : ObservableObject { private ConnectorViewModel _source = default!; public ConnectorViewModel Source { get => _source; set => SetProperty(ref _source, value); } private ConnectorViewModel? _target; public ConnectorViewModel? Target { get => _target; set => SetProperty(ref _target, value); } private bool _isVisible; public bool IsVisible { get => _isVisible; set => SetProperty(ref _isVisible, value); } private Point _targetLocation; public Point TargetLocation { get => _targetLocation; set => SetProperty(ref _targetLocation, value); } } } ================================================ FILE: Examples/Nodify.Calculator/Program.cs ================================================ using System; using Avalonia; namespace Nodify.Calculator; class Program { // Initialization code. Don't use any Avalonia, third-party APIs or any // SynchronizationContext-reliant code before AppMain is called: things aren't initialized // yet and stuff might break. [STAThread] public static void Main(string[] args) => BuildAvaloniaApp() .StartWithClassicDesktopLifetime(args); // Avalonia configuration, don't remove; also used by visual designer. public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure() .UsePlatformDetect() .WithInterFont() .LogToTrace(); } ================================================ FILE: Examples/Nodify.Calculator/Themes/Dark.xaml ================================================  ================================================ FILE: Examples/Nodify.Calculator/Themes/Light.xaml ================================================  ================================================ FILE: Examples/Nodify.Calculator/Themes/Nodify.xaml ================================================  ================================================ FILE: Examples/Nodify.Playground/App.xaml ================================================  ================================================ FILE: Examples/Nodify.Playground/App.xaml.cs ================================================ using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Linq; using System.Threading.Tasks; using System.Windows; namespace Nodify.Playground { /// /// Interaction logic for App.xaml /// public partial class App : Application { public override void Initialize() { AvaloniaXamlLoader.Load(this); } public override void OnFrameworkInitializationCompleted() { if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop) { desktop.MainWindow = new MainWindow(); } base.OnFrameworkInitializationCompleted(); } } } ================================================ FILE: Examples/Nodify.Playground/AssemblyInfo.cs ================================================ using System.Windows; [assembly: ThemeInfo( ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located //(used if a resource is not found in the page, // or application resource dictionaries) ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located //(used if a resource is not found in the page, // app, or any theme specific resource dictionaries) )] ================================================ FILE: Examples/Nodify.Playground/BaseSettingViewModel.cs ================================================ using System; namespace Nodify.Playground { public class BaseSettingViewModel : ObservableObject, ISettingViewModel { public string Name { get; } public string? Description { get; } private object? _value; object? ISettingViewModel.Value { get => _value; set => SetProperty(ref _value, value); } public SettingsType Type { get;} public T Value { get => (T)((ISettingViewModel)this).Value!; set => ((ISettingViewModel)this).Value = value; } public BaseSettingViewModel(string name, string? description = default) { Name = name; Description = description; Type = typeof(T) switch { { } t when t == typeof(string) => SettingsType.Text, { } t when t == typeof(bool) => SettingsType.Boolean, { } t when t == typeof(uint) || t == typeof(double) => SettingsType.Number, { } t when t == typeof(PointEditor) => SettingsType.Point, { IsEnum: true } => SettingsType.Option, _ => throw new InvalidOperationException($"Type {typeof(T).Name} does not have a matching {nameof(SettingsType)}.") }; } } } ================================================ FILE: Examples/Nodify.Playground/Converters/FlowToConnectorPositionConverter.cs ================================================ using System; using System.Globalization; using System.Windows.Controls; using System.Windows.Data; namespace Nodify.Playground { public class FlowToConnectorPositionConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is ConnectionViewModel connection) { var connector = parameter is "Input" ? connection.Input : connection.Output; if (connector.Node is KnotNodeViewModel) { var otherConnector = connection.Input == connector ? connection.Output : connection.Input; if (otherConnector.Node is KnotNodeViewModel) { return ToPosition(connector == connection.Input ? ConnectorFlow.Input : ConnectorFlow.Output, connector.Node.Orientation); } return ToPosition(otherConnector.Flow == ConnectorFlow.Output ? ConnectorFlow.Input : ConnectorFlow.Output, connector.Node.Orientation); } return ToPosition(connector.Flow, connector.Node.Orientation); } return value; } private ConnectorPosition ToPosition(ConnectorFlow flow, Orientation orientation) { if (orientation == Orientation.Horizontal) { return flow == ConnectorFlow.Output ? ConnectorPosition.Right : ConnectorPosition.Left; } return flow == ConnectorFlow.Output ? ConnectorPosition.Bottom : ConnectorPosition.Top; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } } ================================================ FILE: Examples/Nodify.Playground/Converters/FlowToDirectionConverter.cs ================================================ using System; using System.Globalization; using System.Windows.Data; namespace Nodify.Playground { public class FlowToDirectionConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is ConnectorFlow flow) { return flow == ConnectorFlow.Output ? ConnectionDirection.Forward : ConnectionDirection.Backward; } return value; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { if (value is ConnectionDirection dir) { return dir == ConnectionDirection.Forward ? ConnectorFlow.Output : ConnectorFlow.Input; } return value; } } } ================================================ FILE: Examples/Nodify.Playground/Converters/UIntToRectConverter.cs ================================================ using System; using System.Globalization; using System.Windows; using System.Windows.Data; using System.Windows.Markup; namespace Nodify.Playground { public class UIntToRectConverter : MarkupExtension, IValueConverter { public uint Multiplier { get; set; } = 1; public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { uint size = System.Convert.ToUInt32(value) * Multiplier; return new Rect(0d, 0d, size, size); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException(); } public override object ProvideValue(IServiceProvider serviceProvider) => this; } } ================================================ FILE: Examples/Nodify.Playground/Converters/UIntToRelativeRectConverter.cs ================================================ using System; using System.Globalization; using System.Windows; using System.Windows.Data; using System.Windows.Markup; namespace Nodify.Playground { public class UIntToRelativeRectConverter : MarkupExtension, IValueConverter { public static UIntToRelativeRectConverter Absolute { get; } = new UIntToRelativeRectConverter() { Unit = RelativeUnit.Absolute }; public static UIntToRelativeRectConverter Relative { get; } = new UIntToRelativeRectConverter() { Unit = RelativeUnit.Relative }; public uint Multiplier { get; set; } = 1; public RelativeUnit Unit { get; set; } = RelativeUnit.Absolute; public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { uint size = System.Convert.ToUInt32(value) * Multiplier; return new RelativeRect(0d, 0d, size, size, Unit); } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotSupportedException(); } public override object ProvideValue(IServiceProvider serviceProvider) => this; } } ================================================ FILE: Examples/Nodify.Playground/Editor/CommentNodeViewModel.cs ================================================ using System.Windows; namespace Nodify.Playground { public class CommentNodeViewModel : NodeViewModel { private string? _title; public string? Title { get => _title; set => SetProperty(ref _title, value); } private Size _size; public Size Size { get => _size; set => SetProperty(ref _size, value); } } } ================================================ FILE: Examples/Nodify.Playground/Editor/ConnectionViewModel.cs ================================================ using System.Windows; using System.Windows.Input; namespace Nodify.Playground { public class ConnectionViewModel : ObservableObject { private NodifyEditorViewModel _graph = default!; public NodifyEditorViewModel Graph { get => _graph; internal set => SetProperty(ref _graph, value); } private ConnectorViewModel _input = default!; public ConnectorViewModel Input { get => _input; set => SetProperty(ref _input, value); } private ConnectorViewModel _output = default!; public ConnectorViewModel Output { get => _output; set => SetProperty(ref _output, value); } private bool _isSelected; public bool IsSelected { get => _isSelected; set => SetProperty(ref _isSelected, value); } public ICommand SplitCommand { get; } public ICommand DisconnectCommand { get; } public ConnectionViewModel() { SplitCommand = new DelegateCommand(Split); DisconnectCommand = new DelegateCommand(Remove); } public void Split(Point point) => Graph.Schema.SplitConnection(this, point); public void Remove() => Graph.Connections.Remove(this); } } ================================================ FILE: Examples/Nodify.Playground/Editor/ConnectorViewModel.cs ================================================ using System.Linq; using System.Windows; namespace Nodify.Playground { public enum ConnectorFlow { Input, Output } public enum ConnectorShape { Circle, Triangle, Square, } public class ConnectorViewModel : ObservableObject { private string? _title; public string? Title { get => _title; set => SetProperty(ref _title, value); } private bool _isConnected; public bool IsConnected { get => _isConnected; set => SetProperty(ref _isConnected, value); } private Point _anchor; public Point Anchor { get => _anchor; set => SetProperty(ref _anchor, value); } private NodeViewModel _node = default!; public NodeViewModel Node { get => _node; internal set { if (SetProperty(ref _node, value)) { OnNodeChanged(); } } } private ConnectorShape _shape; public ConnectorShape Shape { get => _shape; set => SetProperty(ref _shape, value); } public ConnectorFlow Flow { get; private set; } public int MaxConnections { get; set; } = 2; public NodifyObservableCollection Connections { get; } = new NodifyObservableCollection(); public ConnectorViewModel() { Connections.WhenAdded(c => { c.Input.IsConnected = true; c.Output.IsConnected = true; }).WhenRemoved(c => { if (c.Input.Connections.Count == 0) { c.Input.IsConnected = false; } if (c.Output.Connections.Count == 0) { c.Output.IsConnected = false; } }); } protected virtual void OnNodeChanged() { if (Node is FlowNodeViewModel flow) { Flow = flow.Input.Contains(this) ? ConnectorFlow.Input : ConnectorFlow.Output; } else if (Node is KnotNodeViewModel knot) { Flow = knot.Flow; } } public bool IsConnectedTo(ConnectorViewModel con) => Connections.Any(c => c.Input == con || c.Output == con); public virtual bool AllowsNewConnections() => Connections.Count < MaxConnections; public void Disconnect() => Node.Graph.Schema.DisconnectConnector(this); } } ================================================ FILE: Examples/Nodify.Playground/Editor/FlowNodeViewModel.cs ================================================ using System.Windows.Controls; namespace Nodify.Playground { public class FlowNodeViewModel : NodeViewModel { private string? _title; public string? Title { get => _title; set => SetProperty(ref _title, value); } public NodifyObservableCollection Input { get; } = new NodifyObservableCollection(); public NodifyObservableCollection Output { get; } = new NodifyObservableCollection(); public FlowNodeViewModel() { Orientation = Orientation.Horizontal; Input.WhenAdded(c => c.Node = this) .WhenRemoved(c => c.Disconnect()); Output.WhenAdded(c => c.Node = this) .WhenRemoved(c => c.Disconnect()); } public void Disconnect() { Input.Clear(); Output.Clear(); } } } ================================================ FILE: Examples/Nodify.Playground/Editor/GraphSchema.cs ================================================ using System.Collections.Generic; using System.Linq; using System.Windows; namespace Nodify.Playground { public class GraphSchema { #region Add Connection public bool CanAddConnection(ConnectorViewModel source, object target) { if (target is ConnectorViewModel con) { return source != con && source.Node != con.Node && source.Node.Graph == con.Node.Graph && source.Shape == con.Shape && source.AllowsNewConnections() && con.AllowsNewConnections() && (source.Flow != con.Flow || con.Node is KnotNodeViewModel) && !source.IsConnectedTo(con); } else if (source.AllowsNewConnections() && target is FlowNodeViewModel node) { var allConnectors = source.Flow == ConnectorFlow.Input ? node.Output : node.Input; return allConnectors.Any(c => c.AllowsNewConnections()); } return false; } public bool TryAddConnection(ConnectorViewModel source, object? target) { if (target != null && CanAddConnection(source, target)) { if (target is ConnectorViewModel connector) { AddConnection(source, connector); return true; } else if (target is FlowNodeViewModel node) { AddConnection(source, node); return true; } } return false; } private void AddConnection(ConnectorViewModel source, ConnectorViewModel target) { var sourceIsInput = source.Flow == ConnectorFlow.Input; source.Node.Graph.Connections.Add(new ConnectionViewModel { Input = sourceIsInput ? source : target, Output = sourceIsInput ? target : source }); } private void AddConnection(ConnectorViewModel source, FlowNodeViewModel target) { var allConnectors = source.Flow == ConnectorFlow.Input ? target.Output : target.Input; var connector = allConnectors.First(c => c.AllowsNewConnections()); AddConnection(source, connector); } #endregion public void DisconnectConnector(ConnectorViewModel connector) { var graph = connector.Node.Graph; var connections = connector.Connections.ToList(); connections.ForEach(c => graph.Connections.Remove(c)); } public void SplitConnection(ConnectionViewModel connection, Point location) { var knot = new KnotNodeViewModel(connection.Output.Node.Orientation) { Location = location, Flow = connection.Output.Flow, Connector = new ConnectorViewModel { MaxConnections = connection.Output.MaxConnections + connection.Input.MaxConnections, Shape = connection.Input.Shape } }; connection.Graph.Nodes.Add(knot); AddConnection(connection.Output, knot.Connector); AddConnection(knot.Connector, connection.Input); connection.Remove(); } public void AddCommentAroundNodes(IList nodes, string? text = default) { var rect = nodes.GetBoundingBox(50); var comment = new CommentNodeViewModel { Location = rect.Position, Size = rect.Size, Title = text ?? "New comment" }; nodes[0].Graph.Nodes.Add(comment); } } } ================================================ FILE: Examples/Nodify.Playground/Editor/KnotNodeViewModel.cs ================================================ using System.Windows.Controls; namespace Nodify.Playground { public class KnotNodeViewModel : NodeViewModel { public KnotNodeViewModel(Orientation orientation) { Orientation = orientation; } public KnotNodeViewModel() : this(Orientation.Horizontal) { } private ConnectorViewModel _connector = default!; public ConnectorViewModel Connector { get => _connector; set { if (SetProperty(ref _connector, value)) { _connector.Node = this; } } } public ConnectorFlow Flow { get; set; } } } ================================================ FILE: Examples/Nodify.Playground/Editor/NodeViewModel.cs ================================================ using System.Windows; using System.Windows.Controls; using System.Windows.Input; namespace Nodify.Playground { public abstract class NodeViewModel : ObservableObject { private NodifyEditorViewModel _graph = default!; public NodifyEditorViewModel Graph { get => _graph; internal set => SetProperty(ref _graph, value); } private Point _location; public Point Location { get => _location; set => SetProperty(ref _location, value); } public Orientation Orientation { get; protected set; } public ICommand DeleteCommand { get; } public NodeViewModel() { DeleteCommand = new DelegateCommand(() => Graph.Nodes.Remove(this)); } } } ================================================ FILE: Examples/Nodify.Playground/Editor/NodifyEditorView.xaml ================================================  From - to -