Add project files.
This commit is contained in:
48
.editorconfig
Normal file
48
.editorconfig
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
|
||||||
|
[*.{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
|
||||||
63
.gitattributes
vendored
Normal file
63
.gitattributes
vendored
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
###############################################################################
|
||||||
|
# Set default behavior to automatically normalize line endings.
|
||||||
|
###############################################################################
|
||||||
|
* text=auto
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set default behavior for command prompt diff.
|
||||||
|
#
|
||||||
|
# This is need for earlier builds of msysgit that does not have it on by
|
||||||
|
# default for csharp files.
|
||||||
|
# Note: This is only used by command line
|
||||||
|
###############################################################################
|
||||||
|
#*.cs diff=csharp
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# Set the merge driver for project and solution files
|
||||||
|
#
|
||||||
|
# Merging from the command prompt will add diff markers to the files if there
|
||||||
|
# are conflicts (Merging from VS is not affected by the settings below, in VS
|
||||||
|
# the diff markers are never inserted). Diff markers may cause the following
|
||||||
|
# file extensions to fail to load in VS. An alternative would be to treat
|
||||||
|
# these files as binary and thus will always conflict and require user
|
||||||
|
# intervention with every merge. To do so, just uncomment the entries below
|
||||||
|
###############################################################################
|
||||||
|
#*.sln merge=binary
|
||||||
|
#*.csproj merge=binary
|
||||||
|
#*.vbproj merge=binary
|
||||||
|
#*.vcxproj merge=binary
|
||||||
|
#*.vcproj merge=binary
|
||||||
|
#*.dbproj merge=binary
|
||||||
|
#*.fsproj merge=binary
|
||||||
|
#*.lsproj merge=binary
|
||||||
|
#*.wixproj merge=binary
|
||||||
|
#*.modelproj merge=binary
|
||||||
|
#*.sqlproj merge=binary
|
||||||
|
#*.wwaproj merge=binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# behavior for image files
|
||||||
|
#
|
||||||
|
# image files are treated as binary by default.
|
||||||
|
###############################################################################
|
||||||
|
#*.jpg binary
|
||||||
|
#*.png binary
|
||||||
|
#*.gif binary
|
||||||
|
|
||||||
|
###############################################################################
|
||||||
|
# diff behavior for common document formats
|
||||||
|
#
|
||||||
|
# Convert binary document formats to text before diffing them. This feature
|
||||||
|
# is only available from the command line. Turn it on by uncommenting the
|
||||||
|
# entries below.
|
||||||
|
###############################################################################
|
||||||
|
#*.doc diff=astextplain
|
||||||
|
#*.DOC diff=astextplain
|
||||||
|
#*.docx diff=astextplain
|
||||||
|
#*.DOCX diff=astextplain
|
||||||
|
#*.dot diff=astextplain
|
||||||
|
#*.DOT diff=astextplain
|
||||||
|
#*.pdf diff=astextplain
|
||||||
|
#*.PDF diff=astextplain
|
||||||
|
#*.rtf diff=astextplain
|
||||||
|
#*.RTF diff=astextplain
|
||||||
4
.github/FUNDING.yml
vendored
Normal file
4
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# These are supported funding model platforms
|
||||||
|
|
||||||
|
github: miroiu
|
||||||
|
custom: ["https://www.buymeacoffee.com/miroiu", "https://paypal.me/miroiuemanuel"]
|
||||||
17
.github/ISSUE_TEMPLATE/add_example_app.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/add_example_app.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: "\U0001F680 Example application"
|
||||||
|
about: Suggest an example application that others can benefit from
|
||||||
|
title: "[Application]"
|
||||||
|
labels: application
|
||||||
|
assignees: miroiu
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the application**
|
||||||
|
A clear and concise description of what the application is doing.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context here.
|
||||||
10
.github/ISSUE_TEMPLATE/ask-a-question.md
vendored
Normal file
10
.github/ISSUE_TEMPLATE/ask-a-question.md
vendored
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
name: "❓ Ask a question"
|
||||||
|
about: Need help or have a question?
|
||||||
|
title: "[Question]"
|
||||||
|
labels: question
|
||||||
|
assignees: miroiu
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
|
||||||
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
30
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
---
|
||||||
|
name: "\U0001F41B Bug report"
|
||||||
|
about: Create a bug report to help this project improve
|
||||||
|
title: "[Bug]"
|
||||||
|
labels: bug
|
||||||
|
assignees: miroiu
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Hi!
|
||||||
|
|
||||||
|
All fields are optional and are present only to guide you.
|
||||||
|
Thanks for contributing!
|
||||||
|
-->
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context about the problem here.
|
||||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
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!
|
||||||
17
.github/ISSUE_TEMPLATE/documentation.md
vendored
Normal file
17
.github/ISSUE_TEMPLATE/documentation.md
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
name: 📖 Documentation
|
||||||
|
about: Report an issue related to documentation
|
||||||
|
title: "[Docs]"
|
||||||
|
labels: documentation
|
||||||
|
assignees: miroiu
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Describe the issue**
|
||||||
|
A clear and concise description of what the issue is.
|
||||||
|
|
||||||
|
**Screenshots**
|
||||||
|
If applicable, add screenshots.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context here.
|
||||||
27
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
27
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
---
|
||||||
|
name: "⭐️ Feature request"
|
||||||
|
about: Submit a request or a proposal for this project
|
||||||
|
title: "[Feature]"
|
||||||
|
labels: enhancement
|
||||||
|
assignees: miroiu
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Hi!
|
||||||
|
|
||||||
|
All fields are optional and are present only to guide you.
|
||||||
|
Thanks for contributing!
|
||||||
|
-->
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
22
.github/workflows/build.yml
vendored
Normal file
22
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["master", "release-v*"]
|
||||||
|
paths-ignore:
|
||||||
|
- "docs/**"
|
||||||
|
pull_request:
|
||||||
|
paths-ignore:
|
||||||
|
- "docs/**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: windows-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Setup .NET
|
||||||
|
uses: actions/setup-dotnet@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: "9.0.x"
|
||||||
|
- name: Build
|
||||||
|
run: dotnet build --configuration Release
|
||||||
49
.github/workflows/codeql-analysis.yml
vendored
Normal file
49
.github/workflows/codeql-analysis.yml
vendored
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
name: "CodeQL"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: ["master", "release-v*"]
|
||||||
|
paths:
|
||||||
|
- Nodify/**
|
||||||
|
pull_request:
|
||||||
|
branches: ["master", "release-v*"]
|
||||||
|
schedule:
|
||||||
|
- cron: "27 6 * * 2"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
analyze:
|
||||||
|
name: Analyze (${{ matrix.language }})
|
||||||
|
runs-on: windows-latest
|
||||||
|
permissions:
|
||||||
|
# required for all workflows
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
# required to fetch internal or private CodeQL packs
|
||||||
|
packages: read
|
||||||
|
|
||||||
|
# only required for workflows in private repositories
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
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}}"
|
||||||
29
.github/workflows/create-release-branch.yml
vendored
Normal file
29
.github/workflows/create-release-branch.yml
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
name: Create release branch
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.0.0"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
create-release:
|
||||||
|
runs-on: windows-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@v4
|
||||||
|
with:
|
||||||
|
dotnet-version: "9.0.x"
|
||||||
|
- name: Install dependencies
|
||||||
|
run: 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 }}
|
||||||
24
.github/workflows/publish-package.yml
vendored
Normal file
24
.github/workflows/publish-package.yml
vendored
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
name: Publish package
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
push:
|
||||||
|
tags:
|
||||||
|
- "v*.*.*"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
publish:
|
||||||
|
runs-on: windows-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 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
|
||||||
22
.github/workflows/sync-docs.yml
vendored
Normal file
22
.github/workflows/sync-docs.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
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 }}
|
||||||
350
.gitignore
vendored
Normal file
350
.gitignore
vendored
Normal file
@@ -0,0 +1,350 @@
|
|||||||
|
## 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/
|
||||||
BIN
APIVisualExecutor-master.7z
Normal file
BIN
APIVisualExecutor-master.7z
Normal file
Binary file not shown.
353
CHANGELOG.md
Normal file
353
CHANGELOG.md
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
## Changelog
|
||||||
|
|
||||||
|
#### **In development**
|
||||||
|
|
||||||
|
> - Breaking Changes:
|
||||||
|
> - Features:
|
||||||
|
> - Bugfixes:
|
||||||
|
|
||||||
|
#### **Version 7.1.0**
|
||||||
|
|
||||||
|
> - Breaking Changes:
|
||||||
|
> - Added ProcessHandledEvents to IInputHandler and removed it from InputProcessor
|
||||||
|
> - Renamed EditorGestures.Editor.ResetViewportLocation to EditorGestures.Editor.ResetViewport
|
||||||
|
> - Features:
|
||||||
|
> - Introduced a new BringIntoView method overload in NodifyEditor that accepts an offset from the viewport edges
|
||||||
|
> - Added BringIntoViewEdgeOffset to NodifyEditor to control the viewport edge offset when bringing the focused element into view
|
||||||
|
> - Added ResetViewport to NodifyEditor to reset the viewport's location and zoom
|
||||||
|
> - Improved tab and directional navigation, ensuring that focused elements are automatically brought into view
|
||||||
|
> - Added keyboard navigation layers for nodes, connections and decorators; restricting keyboard navigation to the active layer
|
||||||
|
> - Added ActiveNavigationLayer, ActivateNextNavigationLayer, ActivatePreviousNavigationLayer, RegisterNavigationLayer, RemoveNavigationLayer and ActivateNavigationLayer to NodifyEditor for keyboard layers management
|
||||||
|
> - Added KeyboardNavigationLayer property to NodifyEditor that allows navigating through the ItemContainers
|
||||||
|
> - Added AutoRegisterConnectionsLayer, AutoRegisterDecoratorsLayer, AutoFocusFirstElement, AutoPanOnNodeFocus, PanViewportOnKeyboardDrag and MinimumNavigationStepSize to NodifyEditor
|
||||||
|
> - Added EditorGestures.Editor.Keyboard for keyboard navigation gestures
|
||||||
|
> - Added FindNextFocusTarget, OnElementFocused and OnKeyboardNavigationLayerActivated virtual methods to NodifyEditor
|
||||||
|
> - Added new gestures for keyboard navigation available in EditorGestures.Editor.Keyboard
|
||||||
|
> - Added ToggleContentSelection to GroupingNode and its corresponding gesture to toggle the selection of nodes inside the group
|
||||||
|
> - Added ZoomIn, ZoomOut and ResetViewport methods to the Minimap control
|
||||||
|
> - Added ZoomIn, ZoomOut, ResetViewport and Pan gestures to EditorGestures.Minimap
|
||||||
|
> - Added NavigationStepSize static property to Minimap
|
||||||
|
> - Added Unbind to all gestures inside EditorGestures
|
||||||
|
> - Added the KeyComboGesture that requires a trigger key to be held down before pressing a combo key
|
||||||
|
> - Added FocusVisualPen and FocusVisualPadding dependency properties to BaseConnection
|
||||||
|
> - Added default focus visuals for base editor controls that can be included by referencing the FocusVisual.xaml file
|
||||||
|
> - Added MaxHotKeys and HotKeysDisplayMode static configuration fields to PendingConnection
|
||||||
|
> - Added HotKeyControl with its corresponding theme resources to display the hotkeys for a pending connection
|
||||||
|
|
||||||
|
#### **Version 7.0.4**
|
||||||
|
|
||||||
|
> - Features:
|
||||||
|
> - Added AsRef extension method to InputGesture to convert it to an InputGestureRef
|
||||||
|
> - Bugfixes:
|
||||||
|
> - Fixed an issue where the gesture used for EditorGestures.Editor.SelectAll extracted from the ApplicationCommands was assumed to be a KeyGesture
|
||||||
|
> - Fixed overrides of DrawDirectionalArrowheadGeometry virtual method not working in subclasses of the built in connections
|
||||||
|
> - Fixed a memory leak caused by the auto panning timer
|
||||||
|
|
||||||
|
#### **Version 7.0.3**
|
||||||
|
|
||||||
|
> - Bugfixes:
|
||||||
|
> - Fixed an issue where the SelectedEvent and UnselectedEvent events on the ItemContainer were not raised when the selection was completed
|
||||||
|
|
||||||
|
#### **Version 7.0.2**
|
||||||
|
|
||||||
|
> - Features:
|
||||||
|
> - Added EditorGestures.Editor.SelectAll
|
||||||
|
> - Bugfixes:
|
||||||
|
> - Fixed an issue where the EditorCommands.SelectAll gesture could not be customized
|
||||||
|
|
||||||
|
#### **Version 7.0.1**
|
||||||
|
|
||||||
|
> - Bugfixes:
|
||||||
|
> - Fixed an issue where connections would not gain focus when selected, which could prevent editor keybindings from functioning in certain scenarios
|
||||||
|
> - Resolved an issue where selecting a node did not deselect connections and vice versa
|
||||||
|
> - Fixed a bug preventing ItemContainers from being selected when the mouse could not be captured
|
||||||
|
> - Fixed an issue with key detection in Japanese IME environments, causing issues with the MouseGesture
|
||||||
|
|
||||||
|
#### **Version 7.0.0**
|
||||||
|
|
||||||
|
> - Breaking Changes:
|
||||||
|
> - Made the setter of NodifyEditor.IsPanning private
|
||||||
|
> - Made SelectionHelper internal
|
||||||
|
> - Renamed HandleRightClickAfterPanningThreshold to MouseActionSuppressionThreshold in NodifyEditor
|
||||||
|
> - Renamed StartCutting to BeginCutting in NodifyEditor
|
||||||
|
> - Renamed Connector.EnableStickyConnections to ConnectorState.EnabledToggledConnectingMode
|
||||||
|
> - Renamed PushItems to UpdatePushedArea and StartPushingItems to BeginPushingItems in NodifyEditor
|
||||||
|
> - Renamed UnselectAllConnection to UnselectAllConnections in NodifyEditor
|
||||||
|
> - Removed DragStarted, DragDelta and DragCompleted routed events from ItemContainer
|
||||||
|
> - Replaced the System.Windows.Input.MouseGesture with Nodify.Interactivity.MouseGesture for default EditorGesture mappings
|
||||||
|
> - Removed State, GetInitialState, PushState, PopState and PopAllStates from NodifyEditor and ItemContainer
|
||||||
|
> - Replaced EditorState and ContainerState with InputElementState
|
||||||
|
> - Moved AllowCuttingCancellation from CuttingLine to NodifyEditor
|
||||||
|
> - Moved AllowDraggingCancellation from ItemContainer to NodifyEditor
|
||||||
|
> - Moved EditorGestures under the Nodify.Interactivity namespace
|
||||||
|
> - Moved editor events under the Nodify.Events namespace
|
||||||
|
> - Features:
|
||||||
|
> - Added BeginPanning, UpdatePanning, EndPanning, CancelPanning and AllowPanningCancellation to NodifyEditor and Minimap
|
||||||
|
> - Added MouseLocation, ZoomAtPosition and GetLocationInsideMinimap to Minimap
|
||||||
|
> - Added UpdateCuttingLine to NodifyEditor
|
||||||
|
> - Added Select, BeginSelecting, UpdateSelection, EndSelecting, CancelSelecting and AllowSelectionCancellation to NodifyEditor
|
||||||
|
> - Added IsDragging, BeginDragging, UpdateDragging, EndDragging and CancelDragging to NodifyEditor
|
||||||
|
> - Added AlignSelection and AlignContainers methods to NodifyEditor
|
||||||
|
> - Added LockSelection and UnlockSelection methods to NodifyEditor and EditorCommands
|
||||||
|
> - Added ItemsMoved routed event to NodifyEditor
|
||||||
|
> - Added HasCustomContextMenu dependency property to NodifyEditor, ItemContainer, Connector and BaseConnection
|
||||||
|
> - Added Select, BeginDragging, UpdateDragging, EndDragging and CancelDragging to ItemContainer
|
||||||
|
> - Added PreserveSelectionOnRightClick configuration field to ItemContainer
|
||||||
|
> - Added BeginConnecting, UpdatePendingConnection, EndConnecting, CancelConnecting and RemoveConnections methods to Connector
|
||||||
|
> - Added FindTargetConnector and FindConnectionTarget methods to Connector
|
||||||
|
> - Added a custom MouseGesture with support for key combinations
|
||||||
|
> - Added InputProcessor to NodifyEditor, ItemContainer, Connector, BaseConnection and Minimap, enabling the extension of controls with custom states
|
||||||
|
> - Added DragState to simplify creating click-and-drag interactions, with support for initiating and completing them using the keyboard
|
||||||
|
> - Added InputElementStateStack, InputElementStateStack.DragState and InputElementStateStack.InputElementState to manage transitions between states in UI elements
|
||||||
|
> - Added InputProcessor.Shared to enable the addition of global input handlers
|
||||||
|
> - Move the viewport to the mouse position when zooming on the Minimap if ResizeToViewport is false
|
||||||
|
> - Added SplitAtLocation and Remove methods to BaseConnection
|
||||||
|
> - Added AllowPanningWhileSelecting, AllowPanningWhileCutting and AllowPanningWhilePushingItems to EditorState
|
||||||
|
> - Added AllowZoomingWhilePanning, AllowZoomingWhileSelecting, AllowZoomingWhileCutting and AllowZoomingWhilePushingItems to EditorState
|
||||||
|
> - Added EnableToggledSelectingMode, EnableToggledPanningMode, EnableToggledPushingItemsMode and EnableToggledCuttingMode to EditorState
|
||||||
|
> - Added MinimapState.EnableToggledPanningMode
|
||||||
|
> - Added ContainerState.EnableToggledDraggingMode
|
||||||
|
> - Added Unbind to InputGestureRef and EditorGestures.SelectionGestures
|
||||||
|
> - Added EnableHitTesting to PendingConnection
|
||||||
|
> - Bugfixes:
|
||||||
|
> - Fixed an issue where the ItemContainer was selected by releasing the mouse button on it, even when the mouse was not captured
|
||||||
|
> - Fixed an issue where the ItemContainer could open its context menu even when it was not selected
|
||||||
|
> - Fixed an issue where the Home button caused the editor to fail to display items when contained within a ScrollViewer
|
||||||
|
> - Fixed an issue where connector optimization did not work when SelectedItems was not data-bound
|
||||||
|
> - Fixed EditorCommands.Align to perform a single arrange invalidation instead of one for each aligned container
|
||||||
|
> - Fixed an issue where controls would capture the mouse unnecessarily; they now capture it only in response to a defined gesture
|
||||||
|
> - Fixed an issue where the minimap could update the viewport without having the mouse captured
|
||||||
|
> - Fixed ItemContainer.Select and NodifyEditor.SelectArea to clear the existing selection and select the containers within the same transaction
|
||||||
|
> - Fixed an issue where editor interactions failed to cancel upon losing mouse capture
|
||||||
|
> - Fixed an issue where selecting a new connection would not clear the previous selection within the same transaction
|
||||||
|
|
||||||
|
#### **Version 6.6.0**
|
||||||
|
|
||||||
|
> - Features:
|
||||||
|
> - Added InputGroupStyle and OutputGroupStyle to Node
|
||||||
|
> - 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 <http://miroiu.github.io/winfx/xaml/nodify> 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
|
||||||
76
CODE_OF_CONDUCT.md
Normal file
76
CODE_OF_CONDUCT.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# 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
|
||||||
26
CONTRIBUTING.md
Normal file
26
CONTRIBUTING.md
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# 👋 **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.
|
||||||
BIN
Examples/Nodify.Calculator.7z
Normal file
BIN
Examples/Nodify.Calculator.7z
Normal file
Binary file not shown.
24
Examples/Nodify.Calculator/APIOperationViewModel.cs
Normal file
24
Examples/Nodify.Calculator/APIOperationViewModel.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class APIOperationViewModel : OperationViewModel
|
||||||
|
{
|
||||||
|
public APIOperationViewModel()
|
||||||
|
{
|
||||||
|
_operationType = "GET";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _operationType;
|
||||||
|
|
||||||
|
public string OperationType
|
||||||
|
{
|
||||||
|
get => _operationType;
|
||||||
|
set => SetProperty(ref _operationType, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
Examples/Nodify.Calculator/AddVariableDialog.xaml
Normal file
52
Examples/Nodify.Calculator/AddVariableDialog.xaml
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<Window x:Class="Nodify.Calculator.AddVariableDialog"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
Title="Add New Variable"
|
||||||
|
Width="350"
|
||||||
|
Height="280"
|
||||||
|
WindowStartupLocation="CenterOwner"
|
||||||
|
ResizeMode="NoResize"
|
||||||
|
Background="#2D2D30"
|
||||||
|
Foreground="White">
|
||||||
|
<Grid Margin="15">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Text="Variable Name:" Grid.Row="0" Margin="0 0 0 4" />
|
||||||
|
<TextBox x:Name="VariableNameBox" Grid.Row="1" Margin="0 0 0 10"
|
||||||
|
Background="#3E3E42" Foreground="White" Padding="4" />
|
||||||
|
|
||||||
|
<TextBlock Text="Variable Type:" Grid.Row="2" Margin="0 0 0 4" />
|
||||||
|
<ComboBox x:Name="VariableTypeBox" Grid.Row="3" Margin="0 0 0 10"
|
||||||
|
Background="#3E3E42" Foreground="White" SelectedIndex="0">
|
||||||
|
<ComboBoxItem Content="string" />
|
||||||
|
<ComboBoxItem Content="int" />
|
||||||
|
<ComboBoxItem Content="double" />
|
||||||
|
<ComboBoxItem Content="bool" />
|
||||||
|
<ComboBoxItem Content="float" />
|
||||||
|
<ComboBoxItem Content="decimal" />
|
||||||
|
<ComboBoxItem Content="long" />
|
||||||
|
<ComboBoxItem Content="DateTime" />
|
||||||
|
<ComboBoxItem Content="object" />
|
||||||
|
</ComboBox>
|
||||||
|
|
||||||
|
<TextBlock Text="Default Value (optional):" Grid.Row="4" Margin="0 0 0 4" />
|
||||||
|
<TextBox x:Name="DefaultValueBox" Grid.Row="5" Margin="0 0 0 10"
|
||||||
|
Background="#3E3E42" Foreground="White" Padding="4" />
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Grid.Row="7">
|
||||||
|
<Button Content="Add" Width="80" Margin="0 0 10 0" Padding="4"
|
||||||
|
Click="OnAddClick" IsDefault="True" />
|
||||||
|
<Button Content="Cancel" Width="80" Padding="4"
|
||||||
|
Click="OnCancelClick" IsCancel="True" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
36
Examples/Nodify.Calculator/AddVariableDialog.xaml.cs
Normal file
36
Examples/Nodify.Calculator/AddVariableDialog.xaml.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public partial class AddVariableDialog : Window
|
||||||
|
{
|
||||||
|
public string VariableName { get; private set; } = string.Empty;
|
||||||
|
public string VariableType { get; private set; } = "string";
|
||||||
|
public string DefaultValue { get; private set; } = string.Empty;
|
||||||
|
|
||||||
|
public AddVariableDialog()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnAddClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(VariableNameBox.Text))
|
||||||
|
{
|
||||||
|
MessageBox.Show("Please enter a variable name.", "Validation", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
VariableName = VariableNameBox.Text.Trim();
|
||||||
|
VariableType = ((ComboBoxItem)VariableTypeBox.SelectedItem).Content.ToString()!;
|
||||||
|
DefaultValue = DefaultValueBox.Text.Trim();
|
||||||
|
DialogResult = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnCancelClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
Examples/Nodify.Calculator/App.xaml
Normal file
44
Examples/Nodify.Calculator/App.xaml
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<Application x:Class="Nodify.Calculator.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||||
|
StartupUri="MainWindow.xaml">
|
||||||
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Nodify;component/Themes/Dark.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Nodify;component/Themes/FocusVisual.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Nodify.Shared;component/Themes/Icons.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Nodify.Shared;component/Themes/Dark.xaml" />
|
||||||
|
|
||||||
|
<ResourceDictionary>
|
||||||
|
|
||||||
|
<Style x:Key="{x:Static SystemParameters.FocusVisualStyleKey}">
|
||||||
|
<Setter Property="Control.Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate>
|
||||||
|
<Rectangle StrokeThickness="1"
|
||||||
|
StrokeDashArray="2"
|
||||||
|
Margin="-2"
|
||||||
|
RadiusX="3"
|
||||||
|
RadiusY="3"
|
||||||
|
Stroke="DodgerBlue" />
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!--WPF is wonderful-->
|
||||||
|
<Style x:Key="OriginalNodeInputStyle"
|
||||||
|
TargetType="{x:Type nodify:NodeInput}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:NodeInput}}" />
|
||||||
|
|
||||||
|
<Style x:Key="OriginalNodeOutputStyle"
|
||||||
|
TargetType="{x:Type nodify:NodeOutput}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:NodeOutput}}" />
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
17
Examples/Nodify.Calculator/App.xaml.cs
Normal file
17
Examples/Nodify.Calculator/App.xaml.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
142
Examples/Nodify.Calculator/ApplicationViewModel.cs
Normal file
142
Examples/Nodify.Calculator/ApplicationViewModel.cs
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
using Newtonsoft.Json;
|
||||||
|
using Nodify.Calculator.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class ApplicationViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public NodifyObservableCollection<EditorViewModel> Editors { get; } = new NodifyObservableCollection<EditorViewModel>();
|
||||||
|
|
||||||
|
public ApplicationViewModel()
|
||||||
|
{
|
||||||
|
AddEditorCommand = new DelegateCommand(() => Editors.Add(new EditorViewModel
|
||||||
|
{
|
||||||
|
Name = $"Editor {Editors.Count + 1}"
|
||||||
|
}));
|
||||||
|
CloseEditorCommand = new DelegateCommand<Guid>(
|
||||||
|
id => Editors.RemoveOne(editor => editor.Id == id),
|
||||||
|
_ => Editors.Count > 0 && SelectedEditor != null);
|
||||||
|
RunFlowCommand = new DelegateCommand(() =>
|
||||||
|
{
|
||||||
|
Executor ex = new Executor(Editors.First());
|
||||||
|
var result = ex.PerformPreCheck();
|
||||||
|
if (!string.IsNullOrEmpty(result))
|
||||||
|
{
|
||||||
|
MessageBox.Show("Error occured while executing the request : " + result);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
FlowRunner runner = new FlowRunner(ex);
|
||||||
|
runner.ShowDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
SaveFileCommand = new DelegateCommand(() =>
|
||||||
|
{
|
||||||
|
var firstEditor = Editors.First();
|
||||||
|
var allNodes = firstEditor.Calculator.Operations;
|
||||||
|
|
||||||
|
SaveGraphModel svm = new SaveGraphModel();
|
||||||
|
foreach (var item in allNodes)
|
||||||
|
{
|
||||||
|
SaveNodes svn = new SaveNodes()
|
||||||
|
{
|
||||||
|
Location = item.Location,
|
||||||
|
};
|
||||||
|
svm.Nodes.Add(svn);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var jsonEditors = JsonConvert.SerializeObject(svm, new JsonSerializerSettings
|
||||||
|
{
|
||||||
|
ReferenceLoopHandling = ReferenceLoopHandling.Ignore
|
||||||
|
});
|
||||||
|
//var jsonEditors = JsonConvert.SerializeObject(Editors);
|
||||||
|
File.WriteAllText("SaveFile.AEXN", jsonEditors);
|
||||||
|
});
|
||||||
|
OpenFileCommand = new DelegateCommand(() =>
|
||||||
|
{
|
||||||
|
if (File.Exists("SaveFile.AEXN"))
|
||||||
|
{
|
||||||
|
var allText = File.ReadAllText("SaveFile.AEXN");
|
||||||
|
var edts = JsonConvert.DeserializeObject<SaveGraphModel>(allText);
|
||||||
|
var firstEditor = Editors.First();
|
||||||
|
var nodesToAdd = edts.Nodes;
|
||||||
|
foreach (var item in nodesToAdd)
|
||||||
|
{
|
||||||
|
firstEditor.Calculator.Operations.Add(new()
|
||||||
|
{
|
||||||
|
Location = item.Location,
|
||||||
|
NodeId = "H",
|
||||||
|
Title = "HHA"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
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; }
|
||||||
|
public ICommand RunFlowCommand { get; }
|
||||||
|
public ICommand SaveFileCommand { get; }
|
||||||
|
public ICommand OpenFileCommand { 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Examples/Nodify.Calculator/AssemblyInfo.cs
Normal file
10
Examples/Nodify.Calculator/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
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)
|
||||||
|
)]
|
||||||
25
Examples/Nodify.Calculator/AuthOperationViewModel.cs
Normal file
25
Examples/Nodify.Calculator/AuthOperationViewModel.cs
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class AuthOperationViewModel : SystemOperationViewModel
|
||||||
|
{
|
||||||
|
public AuthOperationViewModel()
|
||||||
|
{
|
||||||
|
Title = "Auth";
|
||||||
|
SystemOperationType = SystemOperations.AUTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _baseUrl = string.Empty;
|
||||||
|
public string BaseUrl
|
||||||
|
{
|
||||||
|
get => _baseUrl;
|
||||||
|
set => SetProperty(ref _baseUrl, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _authType = "Bearer Token";
|
||||||
|
public string AuthType
|
||||||
|
{
|
||||||
|
get => _authType;
|
||||||
|
set => SetProperty(ref _authType, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
178
Examples/Nodify.Calculator/BinarySerializationHelper.cs
Normal file
178
Examples/Nodify.Calculator/BinarySerializationHelper.cs
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
public static class BinarySerializationHelper
|
||||||
|
{
|
||||||
|
public static void SerializeObject<T>(T obj, BinaryWriter writer)
|
||||||
|
{
|
||||||
|
if (obj == null)
|
||||||
|
{
|
||||||
|
writer.Write(false); // Null flag
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(true); // Not null
|
||||||
|
|
||||||
|
Type type = typeof(T);
|
||||||
|
|
||||||
|
// Serialize all non-indexed properties
|
||||||
|
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
if (property.CanRead && property.GetIndexParameters().Length == 0) // Skip indexers
|
||||||
|
{
|
||||||
|
var value = property.GetValue(obj);
|
||||||
|
SerializeValue(value, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize all public fields
|
||||||
|
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
var value = field.GetValue(obj);
|
||||||
|
SerializeValue(value, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T DeserializeObject<T>(BinaryReader reader) where T : new()
|
||||||
|
{
|
||||||
|
if (!reader.ReadBoolean()) // Null flag
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
T obj = new T();
|
||||||
|
Type type = typeof(T);
|
||||||
|
|
||||||
|
foreach (var property in type.GetProperties(BindingFlags.Public | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
if (property.CanWrite && property.GetIndexParameters().Length == 0) // Skip indexers
|
||||||
|
{
|
||||||
|
var value = DeserializeValue(property.PropertyType, reader);
|
||||||
|
property.SetValue(obj, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var field in type.GetFields(BindingFlags.Public | BindingFlags.Instance))
|
||||||
|
{
|
||||||
|
var value = DeserializeValue(field.FieldType, reader);
|
||||||
|
field.SetValue(obj, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SerializeValue(object value, BinaryWriter writer)
|
||||||
|
{
|
||||||
|
if (value == null)
|
||||||
|
{
|
||||||
|
writer.Write(false); // Null flag for the value
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write(true); // Not null!
|
||||||
|
|
||||||
|
Type type = value.GetType();
|
||||||
|
|
||||||
|
if (type == typeof(int))
|
||||||
|
{
|
||||||
|
writer.Write((int)value);
|
||||||
|
}
|
||||||
|
else if (type == typeof(string))
|
||||||
|
{
|
||||||
|
writer.Write((string)value);
|
||||||
|
}
|
||||||
|
else if (type == typeof(bool))
|
||||||
|
{
|
||||||
|
writer.Write((bool)value);
|
||||||
|
}
|
||||||
|
else if (type == typeof(double))
|
||||||
|
{
|
||||||
|
writer.Write((double)value);
|
||||||
|
}
|
||||||
|
else if (type == typeof(float))
|
||||||
|
{
|
||||||
|
writer.Write((float)value);
|
||||||
|
}
|
||||||
|
else if (type == typeof(long))
|
||||||
|
{
|
||||||
|
writer.Write((long)value);
|
||||||
|
}
|
||||||
|
else if (type.IsArray)
|
||||||
|
{
|
||||||
|
var array = (Array)value;
|
||||||
|
writer.Write(array.Length); // Write array length
|
||||||
|
foreach (var item in array)
|
||||||
|
{
|
||||||
|
SerializeValue(item, writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (typeof(System.Collections.IList).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
var list = (System.Collections.IList)value;
|
||||||
|
writer.Write(list.Count); // Write list count
|
||||||
|
foreach (var item in list)
|
||||||
|
{
|
||||||
|
SerializeValue(item, writer); // Serialize each item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SerializeObject(value, writer); // Serialize nested objects
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object DeserializeValue(Type type, BinaryReader reader)
|
||||||
|
{
|
||||||
|
if (!reader.ReadBoolean()) // Null flag
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == typeof(int))
|
||||||
|
{
|
||||||
|
return reader.ReadInt32();
|
||||||
|
}
|
||||||
|
else if (type == typeof(string))
|
||||||
|
{
|
||||||
|
return reader.ReadString();
|
||||||
|
}
|
||||||
|
else if (type == typeof(bool))
|
||||||
|
{
|
||||||
|
return reader.ReadBoolean();
|
||||||
|
}
|
||||||
|
else if (type == typeof(double))
|
||||||
|
{
|
||||||
|
return reader.ReadDouble();
|
||||||
|
}
|
||||||
|
else if (type.IsArray)
|
||||||
|
{
|
||||||
|
int length = reader.ReadInt32();
|
||||||
|
var array = Array.CreateInstance(type.GetElementType(), length);
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
array.SetValue(DeserializeValue(type.GetElementType(), reader), i);
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
else if (typeof(System.Collections.IList).IsAssignableFrom(type))
|
||||||
|
{
|
||||||
|
int count = reader.ReadInt32();
|
||||||
|
var list = (System.Collections.IList)Activator.CreateInstance(type);
|
||||||
|
for (int i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
list.Add(DeserializeValue(type.GenericTypeArguments[0], reader));
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var method = typeof(BinarySerializationHelper)
|
||||||
|
.GetMethod("DeserializeObject")
|
||||||
|
.MakeGenericMethod(type);
|
||||||
|
|
||||||
|
return method.Invoke(null, new object[] { reader });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
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<ConnectorViewModel> Output { get; set; } =
|
||||||
|
new NodifyObservableCollection<ConnectorViewModel>();
|
||||||
|
|
||||||
|
public INodifyCommand AddOutputCommand { get; }
|
||||||
|
public INodifyCommand RemoveOutputCommand { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Examples/Nodify.Calculator/CalculatorOperationViewModel.cs
Normal file
51
Examples/Nodify.Calculator/CalculatorOperationViewModel.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
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);
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
184
Examples/Nodify.Calculator/CalculatorViewModel.cs
Normal file
184
Examples/Nodify.Calculator/CalculatorViewModel.cs
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class CalculatorViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public CalculatorViewModel()
|
||||||
|
{
|
||||||
|
CreateConnectionCommand = new DelegateCommand<ConnectorViewModel>(
|
||||||
|
_ => CreateConnection(PendingConnection.Source, PendingConnection.Target),
|
||||||
|
_ => CanCreateConnection(PendingConnection.Source, PendingConnection.Target));
|
||||||
|
StartConnectionCommand = new DelegateCommand<ConnectorViewModel>(_ => PendingConnection.IsVisible = true, (c) => c != null && !(c.IsConnected && c.IsInput));
|
||||||
|
DisconnectConnectorCommand = new DelegateCommand<ConnectorViewModel>(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);
|
||||||
|
x.NodeId = (Operations.Count + 1).ToString();
|
||||||
|
Debug.WriteLine($"Currently adding the node with node id : {x.NodeId} , Title : {x.Title}");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var item in x.Output)
|
||||||
|
{
|
||||||
|
DisconnectConnector(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
OperationsMenu = new OperationsMenuViewModel(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodifyObservableCollection<OperationViewModel> _operations = new NodifyObservableCollection<OperationViewModel>();
|
||||||
|
public NodifyObservableCollection<OperationViewModel> Operations
|
||||||
|
{
|
||||||
|
get => _operations;
|
||||||
|
set => SetProperty(ref _operations, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodifyObservableCollection<OperationViewModel> _selectedOperations = new NodifyObservableCollection<OperationViewModel>();
|
||||||
|
public NodifyObservableCollection<OperationViewModel> SelectedOperations
|
||||||
|
{
|
||||||
|
get => _selectedOperations;
|
||||||
|
set => SetProperty(ref _selectedOperations, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodifyObservableCollection<ConnectionViewModel> Connections { get; } = new NodifyObservableCollection<ConnectionViewModel>();
|
||||||
|
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.Shape == target.Shape &&
|
||||||
|
!source.IsConnected &&
|
||||||
|
!target.IsConnected &&
|
||||||
|
source.IsInput != target.IsInput);
|
||||||
|
|
||||||
|
internal void OpenGetSetVariable(Point TargetLocation, string className)
|
||||||
|
{
|
||||||
|
OperationsMenu.OpenOnlyGetSetVariable(TargetLocation, className);
|
||||||
|
OperationsMenu.Closed += OnOperationsMenuClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void OpenGetSetForVariable(Point targetLocation, OperationInfoViewModel variableInfo)
|
||||||
|
{
|
||||||
|
OperationsMenu.OpenGetSetForVariable(targetLocation, variableInfo);
|
||||||
|
OperationsMenu.Closed += OnOperationsMenuClosed;
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
InputNodeId = source.Operation.NodeId,
|
||||||
|
OutputNodeId = target.Operation.NodeId
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
Debug.WriteLine($"Creating a connection between input node id: {source.Operation.NodeId} and :{target.Operation.NodeId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
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.Location,
|
||||||
|
GroupSize = new Size(bounding.Width, bounding.Height)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
27
Examples/Nodify.Calculator/ColorToSolidBrushConverter.cs
Normal file
27
Examples/Nodify.Calculator/ColorToSolidBrushConverter.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
internal static class ColorToSolidBrushConverter
|
||||||
|
{
|
||||||
|
public static Brush Convert(Color value)
|
||||||
|
{
|
||||||
|
var brush = new SolidColorBrush(value);
|
||||||
|
return brush;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Color ConvertBack(Brush value)
|
||||||
|
{
|
||||||
|
if (value is SolidColorBrush brush)
|
||||||
|
return brush.Color;
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Examples/Nodify.Calculator/ConnectionViewModel.cs
Normal file
36
Examples/Nodify.Calculator/ConnectionViewModel.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _inputNodeId;
|
||||||
|
|
||||||
|
public string InputNodeId
|
||||||
|
{
|
||||||
|
get { return _inputNodeId; }
|
||||||
|
set { _inputNodeId = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private string _outputNodeId;
|
||||||
|
|
||||||
|
public string OutputNodeId
|
||||||
|
{
|
||||||
|
get { return _outputNodeId; }
|
||||||
|
set { _outputNodeId = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
100
Examples/Nodify.Calculator/ConnectorViewModel.cs
Normal file
100
Examples/Nodify.Calculator/ConnectorViewModel.cs
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public enum ConnectorShape
|
||||||
|
{
|
||||||
|
Circle,
|
||||||
|
Triangle,
|
||||||
|
Square,
|
||||||
|
}
|
||||||
|
|
||||||
|
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 ConnectorShape _shape;
|
||||||
|
public ConnectorShape Shape
|
||||||
|
{
|
||||||
|
get => _shape;
|
||||||
|
set => SetProperty(ref _shape, 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 System.Drawing.Color _color = System.Drawing.Color.DodgerBlue;
|
||||||
|
public System.Drawing.Color ConnectorColor
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
_color = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public Brush Color
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var mediacolor = System.Windows.Media.Color.FromArgb
|
||||||
|
(
|
||||||
|
_color.A,
|
||||||
|
_color.R,
|
||||||
|
_color.G,
|
||||||
|
_color.B
|
||||||
|
);
|
||||||
|
return ColorToSolidBrushConverter.Convert(mediacolor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Newtonsoft.Json.JsonIgnore]
|
||||||
|
[BsonIgnore]
|
||||||
|
private OperationViewModel _operation = default!;
|
||||||
|
[Newtonsoft.Json.JsonIgnore]
|
||||||
|
[BsonIgnore]
|
||||||
|
public OperationViewModel Operation
|
||||||
|
{
|
||||||
|
get => _operation;
|
||||||
|
set => SetProperty(ref _operation, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Newtonsoft.Json.JsonIgnore]
|
||||||
|
[BsonIgnore]
|
||||||
|
public List<ConnectorViewModel> ValueObservers { get; } = new List<ConnectorViewModel>();
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Examples/Nodify.Calculator/Converters/ItemToListConverter.cs
Normal file
31
Examples/Nodify.Calculator/Converters/ItemToListConverter.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
16
Examples/Nodify.Calculator/CreateOperationInfoViewModel.cs
Normal file
16
Examples/Nodify.Calculator/CreateOperationInfoViewModel.cs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
699
Examples/Nodify.Calculator/EditorView.xaml
Normal file
699
Examples/Nodify.Calculator/EditorView.xaml
Normal file
@@ -0,0 +1,699 @@
|
|||||||
|
<UserControl x:Class="Nodify.Calculator.EditorView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:Nodify.Calculator"
|
||||||
|
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||||
|
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||||
|
xmlns:sys="clr-namespace:System;assembly=System.Runtime"
|
||||||
|
d:DataContext="{d:DesignInstance Type=local:EditorViewModel}"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
d:DesignWidth="800">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<GeometryDrawing x:Key="SmallGridGeometry"
|
||||||
|
Geometry="M0,0 L0,1 0.03,1 0.03,0.03 1,0.03 1,0 Z"
|
||||||
|
Brush="{DynamicResource GridLinesBrush}" />
|
||||||
|
|
||||||
|
<GeometryDrawing x:Key="LargeGridGeometry"
|
||||||
|
Geometry="M0,0 L0,1 0.015,1 0.015,0.015 1,0.015 1,0 Z"
|
||||||
|
Brush="{DynamicResource GridLinesBrush}" />
|
||||||
|
|
||||||
|
<DrawingBrush x:Key="SmallGridLinesDrawingBrush"
|
||||||
|
TileMode="Tile"
|
||||||
|
ViewportUnits="Absolute"
|
||||||
|
Viewport="0 0 15 15"
|
||||||
|
Transform="{Binding ViewportTransform, ElementName=Editor}"
|
||||||
|
Drawing="{StaticResource SmallGridGeometry}" />
|
||||||
|
|
||||||
|
<DrawingBrush x:Key="LargeGridLinesDrawingBrush"
|
||||||
|
TileMode="Tile"
|
||||||
|
ViewportUnits="Absolute"
|
||||||
|
Opacity="0.5"
|
||||||
|
Viewport="0 0 150 150"
|
||||||
|
Transform="{Binding ViewportTransform, ElementName=Editor}"
|
||||||
|
Drawing="{StaticResource LargeGridGeometry}" />
|
||||||
|
|
||||||
|
<LinearGradientBrush x:Key="AnimatedBrush"
|
||||||
|
StartPoint="0 0"
|
||||||
|
EndPoint="1 0">
|
||||||
|
<GradientStop Color="#6366f1"
|
||||||
|
Offset="0" />
|
||||||
|
<GradientStop Color="#a855f7"
|
||||||
|
Offset="0.5" />
|
||||||
|
<GradientStop Color="#ec4899"
|
||||||
|
Offset="1" />
|
||||||
|
</LinearGradientBrush>
|
||||||
|
<Border x:Key="AnimatedBorderPlaceholder"
|
||||||
|
BorderBrush="{StaticResource AnimatedBrush}" />
|
||||||
|
|
||||||
|
<Storyboard x:Key="AnimateBorder"
|
||||||
|
RepeatBehavior="Forever">
|
||||||
|
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.StartPoint)"
|
||||||
|
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||||
|
Duration="0:0:2"
|
||||||
|
To="1 0" />
|
||||||
|
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.StartPoint)"
|
||||||
|
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||||
|
Duration="0:0:2"
|
||||||
|
To="1 1"
|
||||||
|
BeginTime="0:0:2" />
|
||||||
|
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.StartPoint)"
|
||||||
|
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||||
|
Duration="0:0:2"
|
||||||
|
To="0 1"
|
||||||
|
BeginTime="0:0:4" />
|
||||||
|
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.StartPoint)"
|
||||||
|
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||||
|
Duration="0:0:2"
|
||||||
|
To="0 0"
|
||||||
|
BeginTime="0:0:6" />
|
||||||
|
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.EndPoint)"
|
||||||
|
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||||
|
Duration="0:0:2"
|
||||||
|
To="1 1" />
|
||||||
|
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.EndPoint)"
|
||||||
|
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||||
|
Duration="0:0:2"
|
||||||
|
To="0 1"
|
||||||
|
BeginTime="0:0:2" />
|
||||||
|
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.EndPoint)"
|
||||||
|
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||||
|
Duration="0:0:2"
|
||||||
|
To="0 0"
|
||||||
|
BeginTime="0:0:4" />
|
||||||
|
<PointAnimation Storyboard.TargetProperty="BorderBrush.(LinearGradientBrush.EndPoint)"
|
||||||
|
Storyboard.Target="{StaticResource AnimatedBorderPlaceholder}"
|
||||||
|
Duration="0:0:2"
|
||||||
|
To="1 0"
|
||||||
|
BeginTime="0:0:6" />
|
||||||
|
</Storyboard>
|
||||||
|
|
||||||
|
<local:ItemToListConverter x:Key="ItemToListConverter" />
|
||||||
|
|
||||||
|
<DataTemplate x:Key="ConnectionTemplate"
|
||||||
|
DataType="{x:Type local:ConnectionViewModel}">
|
||||||
|
<nodify:CircuitConnection Source="{Binding Output.Anchor}"
|
||||||
|
Target="{Binding Input.Anchor}"
|
||||||
|
Foreground="{Binding Input.Color}"
|
||||||
|
Stroke="{Binding Input.Color}"
|
||||||
|
StrokeThickness="2"
|
||||||
|
/>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="PendingConnectionTemplate"
|
||||||
|
DataType="{x:Type local:PendingConnectionViewModel}">
|
||||||
|
<nodify:PendingConnection IsVisible="{Binding IsVisible}"
|
||||||
|
Source="{Binding Source, Mode=OneWayToSource}"
|
||||||
|
Target="{Binding Target, Mode=OneWayToSource}"
|
||||||
|
TargetAnchor="{Binding TargetLocation, Mode=OneWayToSource}"
|
||||||
|
StartedCommand="{Binding DataContext.StartConnectionCommand, RelativeSource={RelativeSource AncestorType={x:Type nodify:NodifyEditor}}}"
|
||||||
|
CompletedCommand="{Binding DataContext.CreateConnectionCommand, RelativeSource={RelativeSource AncestorType={x:Type nodify:NodifyEditor}}}" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<Style x:Key="ItemContainerStyle"
|
||||||
|
TargetType="{x:Type nodify:ItemContainer}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:ItemContainer}}">
|
||||||
|
<Setter Property="Location"
|
||||||
|
Value="{Binding Location}" />
|
||||||
|
<Setter Property="IsSelected"
|
||||||
|
Value="{Binding IsSelected}" />
|
||||||
|
<Setter Property="ActualSize"
|
||||||
|
Value="{Binding Size, Mode=OneWayToSource}" />
|
||||||
|
<Setter Property="BorderBrush"
|
||||||
|
Value="{Binding BorderBrush, Source={StaticResource AnimatedBorderPlaceholder}}" />
|
||||||
|
<Setter Property="BorderThickness"
|
||||||
|
Value="2" />
|
||||||
|
</Style>
|
||||||
|
<SolidColorBrush x:Key="SquareConnectorColor" Color="MediumSlateBlue"></SolidColorBrush>
|
||||||
|
<SolidColorBrush x:Key="TriangleConnectorColor" Color="White"></SolidColorBrush>
|
||||||
|
<Style x:Key="ConnectionStyle" TargetType="{x:Type nodify:BaseConnection}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Input.Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Square}">
|
||||||
|
<Setter Property="Stroke" Value="{StaticResource SquareConnectorColor}"></Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Input.Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||||
|
<Setter Property="Stroke" Value="{StaticResource TriangleConnectorColor}"></Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
<Setter Property="Stroke" Value="{DynamicResource Connection.StrokeBrush}"></Setter>
|
||||||
|
<Setter Property="Cursor" Value="Hand"></Setter>
|
||||||
|
<Setter Property="ToolTip" Value="Double click to split"></Setter>
|
||||||
|
</Style>
|
||||||
|
<ControlTemplate x:Key="SquareConnector" TargetType="Control">
|
||||||
|
<Rectangle Width="14"
|
||||||
|
Height="14"
|
||||||
|
StrokeDashCap="Round"
|
||||||
|
StrokeLineJoin="Round"
|
||||||
|
StrokeStartLineCap="Round"
|
||||||
|
StrokeEndLineCap="Round"
|
||||||
|
Stroke="{TemplateBinding BorderBrush}"
|
||||||
|
Fill="{TemplateBinding Background}"
|
||||||
|
StrokeThickness="2" />
|
||||||
|
</ControlTemplate>
|
||||||
|
|
||||||
|
<ControlTemplate x:Key="TriangleConnector" TargetType="Control">
|
||||||
|
<Polygon Width="14"
|
||||||
|
Height="14"
|
||||||
|
Points="2,2 4,2 12,7 4,12 2,12"
|
||||||
|
StrokeDashCap="Round"
|
||||||
|
StrokeLineJoin="Round"
|
||||||
|
StrokeStartLineCap="Round"
|
||||||
|
StrokeEndLineCap="Round"
|
||||||
|
Stroke="{TemplateBinding BorderBrush}"
|
||||||
|
Fill="{TemplateBinding Background}"
|
||||||
|
|
||||||
|
/>
|
||||||
|
</ControlTemplate>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
|
||||||
|
<nodify:NodifyEditor DataContext="{Binding Calculator}"
|
||||||
|
ItemsSource="{Binding Operations}"
|
||||||
|
Connections="{Binding Connections}"
|
||||||
|
SelectedItems="{Binding SelectedOperations}"
|
||||||
|
DisconnectConnectorCommand="{Binding DisconnectConnectorCommand}"
|
||||||
|
PendingConnection="{Binding PendingConnection}"
|
||||||
|
PendingConnectionTemplate="{StaticResource PendingConnectionTemplate}"
|
||||||
|
ConnectionTemplate="{StaticResource ConnectionTemplate}"
|
||||||
|
Background="{StaticResource SmallGridLinesDrawingBrush}"
|
||||||
|
ItemContainerStyle="{StaticResource ItemContainerStyle}"
|
||||||
|
HasCustomContextMenu="True"
|
||||||
|
GridCellSize="15"
|
||||||
|
AllowDrop="True"
|
||||||
|
Drop="OnDropNode"
|
||||||
|
x:Name="Editor">
|
||||||
|
<nodify:NodifyEditor.Resources>
|
||||||
|
<Style TargetType="{x:Type nodify:NodeInput}"
|
||||||
|
BasedOn="{StaticResource OriginalNodeInputStyle}">
|
||||||
|
<Setter Property="Header"
|
||||||
|
Value="{Binding}" />
|
||||||
|
<Setter Property="IsConnected"
|
||||||
|
Value="{Binding IsConnected}" />
|
||||||
|
<Setter Property="Anchor"
|
||||||
|
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||||
|
<Setter Property="ToolTip"
|
||||||
|
Value="{Binding Value}" />
|
||||||
|
<Setter Property="HeaderTemplate">
|
||||||
|
<Setter.Value>
|
||||||
|
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="{Binding Title}"
|
||||||
|
Margin="0 0 5 0" />
|
||||||
|
<TextBox Text="{Binding Value}"
|
||||||
|
Visibility="{Binding IsConnected, Converter={shared:BooleanToVisibilityConverter Negate=True}}" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Title}" Value="">
|
||||||
|
<Setter Property="HeaderTemplate">
|
||||||
|
<Setter.Value>
|
||||||
|
<DataTemplate/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Title}" Value="{x:Null}">
|
||||||
|
<Setter Property="HeaderTemplate">
|
||||||
|
<Setter.Value>
|
||||||
|
<DataTemplate/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Square}">
|
||||||
|
<Setter Property="ConnectorTemplate" Value="{StaticResource SquareConnector}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource SquareConnectorColor}"></Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||||
|
<Setter Property="ConnectorTemplate" Value="{StaticResource TriangleConnector}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource TriangleConnectorColor}"></Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Circle}">
|
||||||
|
<Setter Property="BorderBrush" Value="{Binding Color}"></Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type nodify:NodeOutput}"
|
||||||
|
BasedOn="{StaticResource OriginalNodeOutputStyle}">
|
||||||
|
<Setter Property="Header"
|
||||||
|
Value="{Binding}" />
|
||||||
|
<Setter Property="IsConnected"
|
||||||
|
Value="{Binding IsConnected}" />
|
||||||
|
<Setter Property="Anchor"
|
||||||
|
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||||
|
<Setter Property="HeaderTemplate">
|
||||||
|
<Setter.Value>
|
||||||
|
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||||
|
<TextBox Text="{Binding Title}"
|
||||||
|
IsEnabled="False" />
|
||||||
|
</DataTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Title}" Value="">
|
||||||
|
<Setter Property="HeaderTemplate">
|
||||||
|
<Setter.Value>
|
||||||
|
<DataTemplate/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Title}" Value="{x:Null}">
|
||||||
|
<Setter Property="HeaderTemplate">
|
||||||
|
<Setter.Value>
|
||||||
|
<DataTemplate/>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Square}">
|
||||||
|
<Setter Property="ConnectorTemplate" Value="{StaticResource SquareConnector}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource SquareConnectorColor}"></Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||||
|
<Setter Property="ConnectorTemplate" Value="{StaticResource TriangleConnector}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{StaticResource TriangleConnectorColor}"></Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Circle}">
|
||||||
|
<Setter Property="BorderBrush" Value="{Binding Color}"></Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:OperationGraphViewModel}">
|
||||||
|
<nodify:GroupingNode Header="{Binding}"
|
||||||
|
CanResize="{Binding IsExpanded}"
|
||||||
|
ActualSize="{Binding DesiredSize, Mode=TwoWay}"
|
||||||
|
MovementMode="Self">
|
||||||
|
<nodify:GroupingNode.HeaderTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type local:OperationGraphViewModel}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<TextBlock Text="{Binding Title}" />
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
Margin="5 0 0 0"
|
||||||
|
Grid.Column="1">
|
||||||
|
<TextBlock Text="Expand?"
|
||||||
|
Visibility="{Binding IsExpanded, Converter={shared:BooleanToVisibilityConverter}}"
|
||||||
|
Margin="0 0 5 0" />
|
||||||
|
<CheckBox IsChecked="{Binding IsExpanded}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</nodify:GroupingNode.HeaderTemplate>
|
||||||
|
<Grid>
|
||||||
|
<ScrollViewer CanContentScroll="True"
|
||||||
|
Visibility="{Binding IsExpanded, Converter={shared:BooleanToVisibilityConverter}}">
|
||||||
|
<nodify:NodifyEditor Tag="{Binding DataContext, RelativeSource={RelativeSource Self}}"
|
||||||
|
DataContext="{Binding InnerCalculator}"
|
||||||
|
ItemsSource="{Binding Operations}"
|
||||||
|
Connections="{Binding Connections}"
|
||||||
|
SelectedItems="{Binding SelectedOperations}"
|
||||||
|
DisconnectConnectorCommand="{Binding DisconnectConnectorCommand}"
|
||||||
|
PendingConnection="{Binding PendingConnection}"
|
||||||
|
PendingConnectionTemplate="{StaticResource PendingConnectionTemplate}"
|
||||||
|
ConnectionTemplate="{StaticResource ConnectionTemplate}"
|
||||||
|
ItemContainerStyle="{StaticResource ItemContainerStyle}"
|
||||||
|
HasCustomContextMenu="True"
|
||||||
|
Background="Transparent"
|
||||||
|
GridCellSize="15"
|
||||||
|
AllowDrop="True"
|
||||||
|
Drop="OnDropNode"
|
||||||
|
Visibility="{Binding DataContext.IsExpanded, RelativeSource={RelativeSource AncestorType=nodify:GroupingNode}, Converter={shared:BooleanToVisibilityConverter}}">
|
||||||
|
|
||||||
|
<nodify:NodifyEditor.InputBindings>
|
||||||
|
<KeyBinding Key="Delete"
|
||||||
|
Command="{Binding DeleteSelectionCommand}" />
|
||||||
|
<KeyBinding Key="G"
|
||||||
|
Modifiers="Ctrl"
|
||||||
|
Command="{Binding GroupSelectionCommand}" />
|
||||||
|
</nodify:NodifyEditor.InputBindings>
|
||||||
|
|
||||||
|
<nodify:NodifyEditor.CommandBindings>
|
||||||
|
<CommandBinding Command="{x:Static ApplicationCommands.ContextMenu}"
|
||||||
|
Executed="OpenContextMenu_Executed" />
|
||||||
|
</nodify:NodifyEditor.CommandBindings>
|
||||||
|
|
||||||
|
<CompositeCollection>
|
||||||
|
<nodify:DecoratorContainer DataContext="{Binding OperationsMenu}"
|
||||||
|
Location="{Binding Location}">
|
||||||
|
<local:OperationsMenuView />
|
||||||
|
</nodify:DecoratorContainer>
|
||||||
|
</CompositeCollection>
|
||||||
|
</nodify:NodifyEditor>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<ItemsControl ItemsSource="{Binding Input}"
|
||||||
|
Focusable="False">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<nodify:NodeInput />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<nodify:NodeOutput DataContext="{Binding Output}"
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
HorizontalAlignment="Right" />
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</nodify:GroupingNode>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:ExpandoOperationViewModel}">
|
||||||
|
<nodify:Node Header="{Binding Title}"
|
||||||
|
Content="{Binding}"
|
||||||
|
Input="{Binding Input}"
|
||||||
|
Output="{Binding Output}">
|
||||||
|
<nodify:Node.ContentTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type local:ExpandoOperationViewModel}">
|
||||||
|
<StackPanel>
|
||||||
|
<Button Style="{StaticResource IconButton}"
|
||||||
|
Content="{StaticResource PlusIcon}"
|
||||||
|
FocusVisualStyle="{StaticResource {x:Static SystemParameters.FocusVisualStyleKey}}"
|
||||||
|
Command="{Binding AddInputCommand}" />
|
||||||
|
<Button Style="{StaticResource IconButton}"
|
||||||
|
Content="{StaticResource RemoveKeyIcon}"
|
||||||
|
FocusVisualStyle="{StaticResource {x:Static SystemParameters.FocusVisualStyleKey}}"
|
||||||
|
Command="{Binding RemoveInputCommand}" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</nodify:Node.ContentTemplate>
|
||||||
|
</nodify:Node>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:APIOperationViewModel}">
|
||||||
|
<nodify:Node Header="{Binding Title}"
|
||||||
|
Content="{Binding}"
|
||||||
|
Input="{Binding Input}"
|
||||||
|
Output="{Binding Output}">
|
||||||
|
<nodify:Node.ContentTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type local:APIOperationViewModel}">
|
||||||
|
<StackPanel>
|
||||||
|
<TextBlock Text="{Binding OperationType}"></TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</nodify:Node.ContentTemplate>
|
||||||
|
</nodify:Node>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:ExpressionOperationViewModel}">
|
||||||
|
<nodify:Node Content="{Binding}"
|
||||||
|
Input="{Binding Input}"
|
||||||
|
Output="{Binding Output}">
|
||||||
|
<nodify:Node.ContentTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type local:ExpressionOperationViewModel}">
|
||||||
|
<TextBox Text="{Binding Expression}"
|
||||||
|
MinWidth="100"
|
||||||
|
Margin="5 0 0 0" />
|
||||||
|
</DataTemplate>
|
||||||
|
</nodify:Node.ContentTemplate>
|
||||||
|
</nodify:Node>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:CalculatorOperationViewModel}">
|
||||||
|
<nodify:Node Header="{Binding Title}"
|
||||||
|
Input="{Binding Input}"
|
||||||
|
Output="{Binding Output}"
|
||||||
|
ToolTip="Double click to expand">
|
||||||
|
<nodify:Node.InputBindings>
|
||||||
|
<MouseBinding Gesture="LeftDoubleClick"
|
||||||
|
Command="{Binding DataContext.OpenCalculatorCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
CommandParameter="{Binding InnerCalculator}" />
|
||||||
|
</nodify:Node.InputBindings>
|
||||||
|
</nodify:Node>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:CalculatorInputOperationViewModel}">
|
||||||
|
<DataTemplate.Resources>
|
||||||
|
<Style TargetType="{x:Type nodify:NodeOutput}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:NodeOutput}}">
|
||||||
|
<Setter Property="Header"
|
||||||
|
Value="{Binding}" />
|
||||||
|
<Setter Property="IsConnected"
|
||||||
|
Value="{Binding IsConnected}" />
|
||||||
|
<Setter Property="Anchor"
|
||||||
|
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||||
|
<Setter Property="HeaderTemplate">
|
||||||
|
<Setter.Value>
|
||||||
|
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBox Text="{Binding Value}"
|
||||||
|
IsEnabled="False" />
|
||||||
|
<TextBlock Text="{Binding Title}"
|
||||||
|
Margin="5 0 0 0" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</DataTemplate.Resources>
|
||||||
|
<nodify:Node Header="{Binding Title}"
|
||||||
|
Output="{Binding Output}">
|
||||||
|
<StackPanel>
|
||||||
|
<Button Style="{StaticResource IconButton}"
|
||||||
|
Content="{StaticResource PlusIcon}"
|
||||||
|
Command="{Binding AddOutputCommand}" />
|
||||||
|
<Button Style="{StaticResource IconButton}"
|
||||||
|
Content="{StaticResource RemoveKeyIcon}"
|
||||||
|
Command="{Binding RemoveOutputCommand}" />
|
||||||
|
</StackPanel>
|
||||||
|
</nodify:Node>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:OperationGroupViewModel}">
|
||||||
|
<nodify:GroupingNode ActualSize="{Binding GroupSize, Mode=TwoWay}">
|
||||||
|
<nodify:GroupingNode.Header>
|
||||||
|
<shared:EditableTextBlock Text="{Binding Title}"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
IsEditing="True" />
|
||||||
|
</nodify:GroupingNode.Header>
|
||||||
|
</nodify:GroupingNode>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:AuthOperationViewModel}">
|
||||||
|
<nodify:Node Header="🔐 Auth Configuration"
|
||||||
|
Content="{Binding}"
|
||||||
|
Input="{Binding Input}"
|
||||||
|
Output="{Binding Output}">
|
||||||
|
<nodify:Node.ContentTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type local:AuthOperationViewModel}">
|
||||||
|
<StackPanel Margin="4">
|
||||||
|
<TextBlock Text="Base URL" FontWeight="SemiBold" Margin="0 0 0 2" />
|
||||||
|
<TextBox Text="{Binding BaseUrl, UpdateSourceTrigger=PropertyChanged}"
|
||||||
|
MinWidth="180" Margin="0 0 0 6" />
|
||||||
|
<TextBlock Text="Auth Type" FontWeight="SemiBold" Margin="0 0 0 2" />
|
||||||
|
<ComboBox SelectedItem="{Binding AuthType}"
|
||||||
|
MinWidth="180" Margin="0 0 0 6">
|
||||||
|
<sys:String>Bearer Token</sys:String>
|
||||||
|
<sys:String>Basic Auth</sys:String>
|
||||||
|
<sys:String>API Key</sys:String>
|
||||||
|
<sys:String>None</sys:String>
|
||||||
|
</ComboBox>
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</nodify:Node.ContentTemplate>
|
||||||
|
</nodify:Node>
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:OperationViewModel}">
|
||||||
|
<nodify:Node Content="{Binding Title}"
|
||||||
|
Input="{Binding Input}"
|
||||||
|
Output="{Binding Output}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</nodify:NodifyEditor.Resources>
|
||||||
|
|
||||||
|
<nodify:NodifyEditor.InputBindings>
|
||||||
|
<KeyBinding Key="Delete"
|
||||||
|
Command="{Binding DeleteSelectionCommand}" />
|
||||||
|
<KeyBinding Key="G"
|
||||||
|
Modifiers="Ctrl"
|
||||||
|
Command="{Binding GroupSelectionCommand}" />
|
||||||
|
</nodify:NodifyEditor.InputBindings>
|
||||||
|
|
||||||
|
<nodify:NodifyEditor.CommandBindings>
|
||||||
|
<CommandBinding Command="{x:Static ApplicationCommands.ContextMenu}"
|
||||||
|
Executed="OpenContextMenu_Executed" />
|
||||||
|
</nodify:NodifyEditor.CommandBindings>
|
||||||
|
|
||||||
|
<nodify:NodifyEditor.Triggers>
|
||||||
|
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
|
||||||
|
<BeginStoryboard Name="AnimateBorder"
|
||||||
|
Storyboard="{StaticResource AnimateBorder}" />
|
||||||
|
</EventTrigger>
|
||||||
|
</nodify:NodifyEditor.Triggers>
|
||||||
|
|
||||||
|
<CompositeCollection>
|
||||||
|
<nodify:DecoratorContainer DataContext="{Binding OperationsMenu}"
|
||||||
|
Location="{Binding Location}">
|
||||||
|
<local:OperationsMenuView />
|
||||||
|
</nodify:DecoratorContainer>
|
||||||
|
</CompositeCollection>
|
||||||
|
</nodify:NodifyEditor>
|
||||||
|
|
||||||
|
<Grid Background="{StaticResource LargeGridLinesDrawingBrush}"
|
||||||
|
Panel.ZIndex="-2" />
|
||||||
|
|
||||||
|
<Border HorizontalAlignment="Right"
|
||||||
|
MinWidth="200"
|
||||||
|
MaxWidth="300"
|
||||||
|
MaxHeight="500"
|
||||||
|
Padding="7"
|
||||||
|
Margin="10"
|
||||||
|
CornerRadius="3"
|
||||||
|
BorderThickness="2">
|
||||||
|
<Border.Background>
|
||||||
|
<SolidColorBrush Color="{DynamicResource BackgroundColor}"
|
||||||
|
Opacity="0.7" />
|
||||||
|
</Border.Background>
|
||||||
|
<DockPanel>
|
||||||
|
<Button Content="📥 Import Swagger"
|
||||||
|
Command="{Binding Calculator.OperationsMenu.ImportSwaggerCommand}"
|
||||||
|
Margin="5"
|
||||||
|
Padding="8 4"
|
||||||
|
Cursor="Hand"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Background="{DynamicResource NodeInput.BorderBrush}"
|
||||||
|
Foreground="{DynamicResource ForegroundBrush}"
|
||||||
|
FontWeight="Bold"
|
||||||
|
DockPanel.Dock="Top" />
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
<ItemsControl ItemsSource="{Binding Calculator.OperationsMenu.AvailableOperations}"
|
||||||
|
Focusable="False">
|
||||||
|
<ItemsControl.ItemContainerStyle>
|
||||||
|
<Style>
|
||||||
|
<Setter Property="FrameworkElement.Margin"
|
||||||
|
Value="5" />
|
||||||
|
<Setter Property="FrameworkElement.HorizontalAlignment"
|
||||||
|
Value="Left" />
|
||||||
|
<Setter Property="FrameworkElement.Cursor"
|
||||||
|
Value="Hand" />
|
||||||
|
<Setter Property="FrameworkElement.ToolTip"
|
||||||
|
Value="Drag and drop into the editor" />
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.ItemContainerStyle>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type local:OperationViewModel}">
|
||||||
|
<nodify:Node Content="{Binding Title}"
|
||||||
|
Input="{Binding Input}"
|
||||||
|
Output="{Binding Output}"
|
||||||
|
BorderBrush="{StaticResource AnimatedBrush}"
|
||||||
|
BorderThickness="2"
|
||||||
|
MouseMove="OnNodeDrag"
|
||||||
|
Focusable="True"
|
||||||
|
KeyboardNavigation.TabNavigation="None">
|
||||||
|
</nodify:Node>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</ScrollViewer>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<Border HorizontalAlignment="Left"
|
||||||
|
MinWidth="200"
|
||||||
|
MaxWidth="250"
|
||||||
|
MaxHeight="500"
|
||||||
|
MinHeight="200"
|
||||||
|
Padding="7"
|
||||||
|
Margin="10"
|
||||||
|
CornerRadius="3"
|
||||||
|
BorderThickness="2">
|
||||||
|
<Border.Background>
|
||||||
|
<SolidColorBrush Color="{DynamicResource BackgroundColor}"
|
||||||
|
Opacity="0.7" />
|
||||||
|
</Border.Background>
|
||||||
|
<DockPanel>
|
||||||
|
<TextBlock Text="Variables" FontWeight="Bold" Margin="0 0 0 4" DockPanel.Dock="Top" />
|
||||||
|
<Button Content="➕ Add New Variable"
|
||||||
|
Command="{Binding Calculator.OperationsMenu.AddVariableCommand}"
|
||||||
|
Margin="0 0 0 6"
|
||||||
|
Padding="6 3"
|
||||||
|
Cursor="Hand"
|
||||||
|
DockPanel.Dock="Top"
|
||||||
|
Background="{DynamicResource NodeInput.BorderBrush}"
|
||||||
|
Foreground="{DynamicResource ForegroundBrush}" />
|
||||||
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel>
|
||||||
|
<!-- Simple Variables -->
|
||||||
|
<ItemsControl ItemsSource="{Binding Calculator.OperationsMenu.AvailableVariables}"
|
||||||
|
Focusable="False">
|
||||||
|
<ItemsControl.ItemContainerStyle>
|
||||||
|
<Style>
|
||||||
|
<Setter Property="FrameworkElement.Margin" Value="3" />
|
||||||
|
<Setter Property="FrameworkElement.HorizontalAlignment" Value="Left" />
|
||||||
|
<Setter Property="FrameworkElement.Cursor" Value="Hand" />
|
||||||
|
<Setter Property="FrameworkElement.ToolTip" Value="Drag and drop to GET or SET this variable" />
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.ItemContainerStyle>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type local:OperationInfoViewModel}">
|
||||||
|
<Border Background="#3E3E42" CornerRadius="3" Padding="6 4"
|
||||||
|
MouseMove="OnNodeDrag" Cursor="Hand">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="{Binding Title}" Foreground="LightGreen" FontWeight="SemiBold" />
|
||||||
|
<TextBlock Text=" : " Foreground="Gray" />
|
||||||
|
<TextBlock Text="{Binding VariableType}" Foreground="CornflowerBlue" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
|
||||||
|
<!-- Separator -->
|
||||||
|
<TextBlock Text="Models" FontWeight="Bold" Margin="0 8 0 4"
|
||||||
|
Visibility="{Binding Calculator.OperationsMenu.AvailableModels.Count, Converter={shared:BooleanToVisibilityConverter}}" />
|
||||||
|
|
||||||
|
<!-- Class Model Variables -->
|
||||||
|
<ItemsControl ItemsSource="{Binding Calculator.OperationsMenu.AvailableModels}"
|
||||||
|
Focusable="False">
|
||||||
|
<ItemsControl.ItemContainerStyle>
|
||||||
|
<Style>
|
||||||
|
<Setter Property="FrameworkElement.Margin" Value="5" />
|
||||||
|
<Setter Property="FrameworkElement.HorizontalAlignment" Value="Left" />
|
||||||
|
<Setter Property="FrameworkElement.Cursor" Value="Hand" />
|
||||||
|
<Setter Property="FrameworkElement.ToolTip" Value="Drag and drop into the editor" />
|
||||||
|
</Style>
|
||||||
|
</ItemsControl.ItemContainerStyle>
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type local:OperationViewModel}">
|
||||||
|
<nodify:Node Content="{Binding Title}"
|
||||||
|
Input="{Binding Input}"
|
||||||
|
Output="{Binding Output}"
|
||||||
|
BorderBrush="{StaticResource AnimatedBrush}"
|
||||||
|
BorderThickness="2"
|
||||||
|
MouseMove="OnNodeDrag"
|
||||||
|
Focusable="True"
|
||||||
|
KeyboardNavigation.TabNavigation="None">
|
||||||
|
</nodify:Node>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</DockPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
101
Examples/Nodify.Calculator/EditorView.xaml.cs
Normal file
101
Examples/Nodify.Calculator/EditorView.xaml.cs
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
using Nodify.Interactivity;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class OperationsMenuHandler : InputElementState<NodifyEditor>
|
||||||
|
{
|
||||||
|
private static InputGesture OpenGesture { get; } = new Interactivity.MouseGesture(MouseAction.RightClick);
|
||||||
|
private static InputGesture CloseGesture { get; } = new Interactivity.MouseGesture(MouseAction.LeftClick);
|
||||||
|
|
||||||
|
private OperationsMenuViewModel ViewModel => ((CalculatorViewModel)Element.DataContext).OperationsMenu;
|
||||||
|
|
||||||
|
public OperationsMenuHandler(NodifyEditor element) : base(element)
|
||||||
|
{
|
||||||
|
ProcessHandledEvents = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseUp(MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (!e.Handled && OpenGesture.Matches(e.Source, e))
|
||||||
|
{
|
||||||
|
ViewModel.OpenAt(Element.MouseLocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseDown(MouseButtonEventArgs e)
|
||||||
|
{
|
||||||
|
if (CloseGesture.Matches(e.Source, e))
|
||||||
|
{
|
||||||
|
ViewModel.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class EditorView : UserControl
|
||||||
|
{
|
||||||
|
public EditorView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
static EditorView()
|
||||||
|
{
|
||||||
|
InputProcessor.Shared<NodifyEditor>.RegisterHandlerFactory(editor => new OperationsMenuHandler(editor));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnDropNode(object sender, DragEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Source is NodifyEditor editor && editor.DataContext is CalculatorViewModel calculator
|
||||||
|
&& e.Data.GetData(typeof(OperationInfoViewModel)) is OperationInfoViewModel operation)
|
||||||
|
{
|
||||||
|
if (operation.IsModelNode)
|
||||||
|
{
|
||||||
|
var dc = editor.DataContext as CalculatorViewModel;
|
||||||
|
var lc = editor.GetLocationInsideEditor(e);
|
||||||
|
var orTitle = operation.Title;
|
||||||
|
dc.OpenGetSetVariable(lc, orTitle);
|
||||||
|
}
|
||||||
|
else if (operation.IsSimpleVariable)
|
||||||
|
{
|
||||||
|
var lc = editor.GetLocationInsideEditor(e);
|
||||||
|
calculator.OpenGetSetForVariable(lc, operation);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OperationViewModel op = OperationFactory.GetOperation(operation);
|
||||||
|
op.Location = editor.GetLocationInsideEditor(e);
|
||||||
|
calculator.Operations.Add(op);
|
||||||
|
}
|
||||||
|
|
||||||
|
e.Handled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnNodeDrag(object sender, MouseEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.LeftButton == MouseButtonState.Pressed && ((FrameworkElement)sender).DataContext is OperationInfoViewModel operation)
|
||||||
|
{
|
||||||
|
var data = new DataObject(typeof(OperationInfoViewModel), operation);
|
||||||
|
DragDrop.DoDragDrop(this, data, DragDropEffects.Copy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OpenContextMenu_Executed(object sender, ExecutedRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Source is NodifyEditor editor && editor.DataContext is CalculatorViewModel calculator)
|
||||||
|
{
|
||||||
|
if (calculator.OperationsMenu.IsVisible)
|
||||||
|
{
|
||||||
|
calculator.OperationsMenu.Close();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
calculator.OperationsMenu.OpenAt(editor.ViewportLocation + new Vector(editor.ViewportSize.Width / 3, editor.ViewportSize.Height / 3));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
40
Examples/Nodify.Calculator/EditorViewModel.cs
Normal file
40
Examples/Nodify.Calculator/EditorViewModel.cs
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class EditorViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public event Action<EditorViewModel, CalculatorViewModel>? OnOpenInnerCalculator;
|
||||||
|
|
||||||
|
public EditorViewModel? Parent { get; set; }
|
||||||
|
|
||||||
|
public EditorViewModel()
|
||||||
|
{
|
||||||
|
Calculator = new CalculatorViewModel();
|
||||||
|
OpenCalculatorCommand = new DelegateCommand<CalculatorViewModel>(calculator =>
|
||||||
|
{
|
||||||
|
OnOpenInnerCalculator?.Invoke(this, calculator);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public INodifyCommand OpenCalculatorCommand { get; }
|
||||||
|
|
||||||
|
public Guid Id { get; } = Guid.NewGuid();
|
||||||
|
|
||||||
|
private CalculatorViewModel _calculator = default!;
|
||||||
|
public CalculatorViewModel Calculator
|
||||||
|
{
|
||||||
|
get => _calculator;
|
||||||
|
set => SetProperty(ref _calculator, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? _name;
|
||||||
|
public string? Name
|
||||||
|
{
|
||||||
|
get => _name;
|
||||||
|
set => SetProperty(ref _name, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
677
Examples/Nodify.Calculator/Executor.cs
Normal file
677
Examples/Nodify.Calculator/Executor.cs
Normal file
@@ -0,0 +1,677 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
#if NET8_0_OR_GREATER
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Reflection.Emit;
|
||||||
|
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using System.IO;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public enum logType
|
||||||
|
{
|
||||||
|
Information,
|
||||||
|
Warning,
|
||||||
|
Error
|
||||||
|
}
|
||||||
|
public delegate void LogMe(string message, logType logtype = logType.Information);
|
||||||
|
|
||||||
|
public class Executor
|
||||||
|
{
|
||||||
|
private readonly EditorViewModel editorViewModel;
|
||||||
|
public event LogMe OnLogMe;
|
||||||
|
|
||||||
|
static bool isAlreadyLogged = false;
|
||||||
|
static Dictionary<string, string> outputs = new Dictionary<string, string>();
|
||||||
|
static Dictionary<string, dynamic> variables = new Dictionary<string, dynamic>();
|
||||||
|
|
||||||
|
// Auth configuration populated from the Auth node
|
||||||
|
private string _authBaseUrl = string.Empty;
|
||||||
|
private string _authType = string.Empty;
|
||||||
|
private string _authToken = string.Empty;
|
||||||
|
private string _authApiKey = string.Empty;
|
||||||
|
private string _authUsername = string.Empty;
|
||||||
|
private string _authPassword = string.Empty;
|
||||||
|
|
||||||
|
public Executor(EditorViewModel editorViewModel)
|
||||||
|
{
|
||||||
|
this.editorViewModel = editorViewModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string PerformPreCheck()
|
||||||
|
{
|
||||||
|
string errorString = string.Empty;
|
||||||
|
var calcModel = editorViewModel.Calculator;
|
||||||
|
var allnodes = calcModel.Operations;
|
||||||
|
errorString = ValidateRequiredNodes("end", allnodes);
|
||||||
|
if (!string.IsNullOrEmpty(errorString))
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke(errorString, logType.Error);
|
||||||
|
return errorString;
|
||||||
|
}
|
||||||
|
errorString = ValidateRequiredNodes("begin", allnodes);
|
||||||
|
OnLogMe?.Invoke(errorString, logType.Error);
|
||||||
|
return errorString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Execute()
|
||||||
|
{
|
||||||
|
string errorString = string.Empty;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke("Starting executing the flow.");
|
||||||
|
OnLogMe?.Invoke("Wish all the best to us :)");
|
||||||
|
OnLogMe?.Invoke("Perorming chain check, it may take some time depending upon the number of nodes.");
|
||||||
|
outputs.Clear();
|
||||||
|
var calcModel = editorViewModel.Calculator;
|
||||||
|
var allnodes = calcModel.Operations;
|
||||||
|
var allConnections = calcModel.Connections;
|
||||||
|
|
||||||
|
// Resolve Auth node configuration before execution
|
||||||
|
ResolveAuthNode(allnodes);
|
||||||
|
|
||||||
|
// Resolve GET variable nodes (they are not in the flow chain)
|
||||||
|
ResolveGetVariableNodes(allnodes, allConnections);
|
||||||
|
|
||||||
|
PerformChainCheck(allnodes, allConnections, false);
|
||||||
|
PerformChainCheck(allnodes, allConnections, true); //Execute operations
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke("either your or our bad luck :(", logType.Error);
|
||||||
|
OnLogMe?.Invoke("Error Details : ", logType.Error);
|
||||||
|
OnLogMe?.Invoke(e.Message, logType.Error);
|
||||||
|
OnLogMe?.Invoke(e.StackTrace, logType.Error);
|
||||||
|
errorString = "Error occured dueing execution of flow";
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string GenerateClassFromJson(string json, string className)
|
||||||
|
{
|
||||||
|
// Parse the JSON to understand its structure
|
||||||
|
JToken token = JToken.Parse(json);
|
||||||
|
|
||||||
|
if (token.Type == JTokenType.Array)
|
||||||
|
{
|
||||||
|
// If the JSON is an array, get the first object in the array for structure
|
||||||
|
JArray array = (JArray)token;
|
||||||
|
if (array.Count > 0 && array[0].Type == JTokenType.Object)
|
||||||
|
{
|
||||||
|
JObject firstObject = (JObject)array[0];
|
||||||
|
return $"{GenerateClassFromJObject(firstObject, className)}";
|
||||||
|
}
|
||||||
|
|
||||||
|
return $"The JSON array does not contain valid objects.";
|
||||||
|
}
|
||||||
|
else if (token.Type == JTokenType.Object)
|
||||||
|
{
|
||||||
|
// If the JSON is an object
|
||||||
|
JObject obj = (JObject)token;
|
||||||
|
return GenerateClassFromJObject(obj, className);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return "Unsupported JSON structure.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Generates a class definition string from a JObject.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jObject">The JObject representing the JSON object.</param>
|
||||||
|
/// <param name="className">The name of the class to generate.</param>
|
||||||
|
/// <returns>The class definition as a string.</returns>
|
||||||
|
public static string GenerateClassFromJObject(JObject jObject, string className)
|
||||||
|
{
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
|
||||||
|
// Start class definition
|
||||||
|
sb.AppendLine($"public class {className}");
|
||||||
|
sb.AppendLine("{");
|
||||||
|
|
||||||
|
// Loop through properties in the JObject and generate class fields
|
||||||
|
foreach (var property in jObject.Properties())
|
||||||
|
{
|
||||||
|
string propName = property.Name;
|
||||||
|
string propType = GetCSharpType(property.Value.Type);
|
||||||
|
|
||||||
|
// Generate the property definition
|
||||||
|
sb.AppendLine($"\tpublic {propType} {ToPascalCase(propName)} {{ get; set; }}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// End class definition
|
||||||
|
sb.AppendLine("}");
|
||||||
|
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Maps a JTokenType to a C# type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="jsonType">The JTokenType.</param>
|
||||||
|
/// <returns>The corresponding C# type as a string.</returns>
|
||||||
|
public static string GetCSharpType(JTokenType jsonType)
|
||||||
|
{
|
||||||
|
return jsonType switch
|
||||||
|
{
|
||||||
|
JTokenType.Integer => "int",
|
||||||
|
JTokenType.Float => "double",
|
||||||
|
JTokenType.String => "string",
|
||||||
|
JTokenType.Boolean => "bool",
|
||||||
|
JTokenType.Object => "object",
|
||||||
|
JTokenType.Array => "List<object>",
|
||||||
|
_ => "string", // Default to string for other types
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Converts a string to PascalCase for naming conventions.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="input">The string to convert.</param>
|
||||||
|
/// <returns>A PascalCase version of the input string.</returns>
|
||||||
|
public static string ToPascalCase(string input)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(input))
|
||||||
|
return input;
|
||||||
|
|
||||||
|
return char.ToUpper(input[0]) + input.Substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Type CreateTypeFromJson(JsonElement root, string typeName)
|
||||||
|
{
|
||||||
|
|
||||||
|
#if NET8_0_OR_GREATER
|
||||||
|
var assemblyName = new AssemblyName("DynamicAssembly");
|
||||||
|
var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
|
||||||
|
var moduleBuilder = assemblyBuilder.DefineDynamicModule("MainModule");
|
||||||
|
|
||||||
|
var typeBuilder = moduleBuilder.DefineType(typeName, TypeAttributes.Public);
|
||||||
|
|
||||||
|
foreach (var prop in root.EnumerateObject())
|
||||||
|
{
|
||||||
|
Type propType = prop.Value.ValueKind switch
|
||||||
|
{
|
||||||
|
JsonValueKind.String => typeof(string),
|
||||||
|
JsonValueKind.Number => typeof(int),
|
||||||
|
JsonValueKind.True or JsonValueKind.False => typeof(bool),
|
||||||
|
_ => typeof(object)
|
||||||
|
};
|
||||||
|
|
||||||
|
var fieldBuilder = typeBuilder.DefineField("_" + prop.Name, propType, FieldAttributes.Private);
|
||||||
|
var propertyBuilder = typeBuilder.DefineProperty(prop.Name, PropertyAttributes.HasDefault, propType, null);
|
||||||
|
|
||||||
|
// Getter
|
||||||
|
var getter = typeBuilder.DefineMethod(
|
||||||
|
"get_" + prop.Name,
|
||||||
|
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
|
||||||
|
propType,
|
||||||
|
Type.EmptyTypes
|
||||||
|
);
|
||||||
|
var getterIL = getter.GetILGenerator();
|
||||||
|
getterIL.Emit(OpCodes.Ldarg_0);
|
||||||
|
getterIL.Emit(OpCodes.Ldfld, fieldBuilder);
|
||||||
|
getterIL.Emit(OpCodes.Ret);
|
||||||
|
propertyBuilder.SetGetMethod(getter);
|
||||||
|
|
||||||
|
// Setter
|
||||||
|
var setter = typeBuilder.DefineMethod(
|
||||||
|
"set_" + prop.Name,
|
||||||
|
MethodAttributes.Public | MethodAttributes.SpecialName | MethodAttributes.HideBySig,
|
||||||
|
null,
|
||||||
|
new Type[] { propType }
|
||||||
|
);
|
||||||
|
var setterIL = setter.GetILGenerator();
|
||||||
|
setterIL.Emit(OpCodes.Ldarg_0);
|
||||||
|
setterIL.Emit(OpCodes.Ldarg_1);
|
||||||
|
setterIL.Emit(OpCodes.Stfld, fieldBuilder);
|
||||||
|
setterIL.Emit(OpCodes.Ret);
|
||||||
|
propertyBuilder.SetSetMethod(setter);
|
||||||
|
}
|
||||||
|
return typeBuilder.CreateType()!;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateModelsFromString(string jsonString)
|
||||||
|
{
|
||||||
|
#if NET8_0_OR_GREATER
|
||||||
|
|
||||||
|
var jsonDoc = JsonDocument.Parse(jsonString);
|
||||||
|
var root = jsonDoc.RootElement;
|
||||||
|
|
||||||
|
if (root.ValueKind == JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
ParseFromJsonelement(root);
|
||||||
|
}
|
||||||
|
else if(root.ValueKind == JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
foreach (var item in root.EnumerateArray())
|
||||||
|
{
|
||||||
|
if (item.ValueKind == JsonValueKind.Object)
|
||||||
|
{
|
||||||
|
ParseFromJsonelement(item);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ParseFromJsonelement(JsonElement jsonElem)
|
||||||
|
{
|
||||||
|
#if NET8_0_OR_GREATER
|
||||||
|
Type dynamicType = CreateTypeFromJson(jsonElem, "DynamicPerson");
|
||||||
|
|
||||||
|
object instance = Activator.CreateInstance(dynamicType);
|
||||||
|
|
||||||
|
foreach (var prop in jsonElem.EnumerateObject())
|
||||||
|
{
|
||||||
|
PropertyInfo pi = dynamicType.GetProperty(prop.Name);
|
||||||
|
object value = prop.Value.ValueKind switch
|
||||||
|
{
|
||||||
|
JsonValueKind.String => prop.Value.GetString(),
|
||||||
|
JsonValueKind.Number => prop.Value.GetDouble(),
|
||||||
|
JsonValueKind.True => true,
|
||||||
|
JsonValueKind.False => false,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
pi.SetValue(instance, value);
|
||||||
|
}
|
||||||
|
Console.WriteLine("Properties of runtime class:");
|
||||||
|
foreach (var prop in dynamicType.GetProperties())
|
||||||
|
{
|
||||||
|
Console.WriteLine($"{prop.Name} = {prop.GetValue(instance)}");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
private void StartExecution(OperationViewModel op, ICollection<ConnectionViewModel> connections)
|
||||||
|
{
|
||||||
|
var url = op.Title ?? "";
|
||||||
|
if (url.ToLower().Contains("create model"))
|
||||||
|
{
|
||||||
|
var conByTitle = connections.Where(c => c.Input.Operation.Title.ToLower().Contains("create model")).ToList();
|
||||||
|
if (conByTitle.Count <= 0)
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke("No input found", logType.Error);
|
||||||
|
throw new Exception("Input connection missig for : " + url);
|
||||||
|
}
|
||||||
|
var flowConnection = conByTitle.Where(c=>c.Input.Shape != ConnectorShape.Triangle).FirstOrDefault();
|
||||||
|
if (flowConnection == null)
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke("No input found", logType.Error);
|
||||||
|
throw new Exception("Input connection missig for : " + url);
|
||||||
|
}
|
||||||
|
|
||||||
|
var outputNodeId = flowConnection.InputNodeId;
|
||||||
|
|
||||||
|
string outputValue = string.Empty;
|
||||||
|
if (outputs.TryGetValue(outputNodeId, out outputValue))
|
||||||
|
{
|
||||||
|
//Convert model here
|
||||||
|
//CreateModelsFromString(outputValue);
|
||||||
|
var customModelDir = "CustomModels";
|
||||||
|
Directory.CreateDirectory(customModelDir);
|
||||||
|
string className = "Class 1";
|
||||||
|
className = className.Replace(" ", "");
|
||||||
|
var classStruct = GenerateClassFromJson(outputValue, className);
|
||||||
|
string flPath = Path.Join(customModelDir, $"{className}.cs");
|
||||||
|
File.WriteAllText(flPath, classStruct);
|
||||||
|
OperationInfoViewModel opv = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = className,
|
||||||
|
IsModelNode = true,
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.GET_SET,
|
||||||
|
ClassName = className
|
||||||
|
};
|
||||||
|
this.editorViewModel.Calculator.OperationsMenu.AddNewModel(opv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url.ToLower().Contains("set"))
|
||||||
|
{
|
||||||
|
// Handle simple variable SET (e.g., "SET myVar (string)")
|
||||||
|
if (op is SystemOperationViewModel sysVarOp && url.Contains("(") && url.Contains(")"))
|
||||||
|
{
|
||||||
|
var parts = url.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if (parts.Length >= 2)
|
||||||
|
{
|
||||||
|
var varName = parts[1];
|
||||||
|
// Find the data input connection (non-triangle)
|
||||||
|
var inputCons = connections.Where(c => c.Input.Operation == op && c.Input.Shape != ConnectorShape.Triangle).ToList();
|
||||||
|
if (inputCons.Any())
|
||||||
|
{
|
||||||
|
var sourceConn = inputCons.First();
|
||||||
|
var sourceNodeId = sourceConn.Output.Operation.NodeId;
|
||||||
|
if (outputs.TryGetValue(sourceNodeId, out var sourceVal))
|
||||||
|
{
|
||||||
|
variables[varName] = sourceVal;
|
||||||
|
OnLogMe?.Invoke($"Variable '{varName}' SET to: {sourceVal}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Use the connector value directly
|
||||||
|
variables[varName] = sourceConn.Output.Value.ToString();
|
||||||
|
OnLogMe?.Invoke($"Variable '{varName}' SET to connector value: {sourceConn.Output.Value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No connection — use default value from the Value input connector
|
||||||
|
var valueInput = op.Input.FirstOrDefault(i => i.Title == "Value");
|
||||||
|
if (valueInput != null)
|
||||||
|
{
|
||||||
|
variables[varName] = valueInput.Value.ToString();
|
||||||
|
OnLogMe?.Invoke($"Variable '{varName}' SET to default: {valueInput.Value}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputs[op.NodeId] = variables.ContainsKey(varName) ? variables[varName]?.ToString() ?? "" : "";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fullTitle = url;
|
||||||
|
var classNameToSet = fullTitle.Split(" ", StringSplitOptions.RemoveEmptyEntries)[1];
|
||||||
|
var inputConnectionList = connections.Where(c => c.Input.Operation.Title == url).ToList();
|
||||||
|
var inputConnection = inputConnectionList.Where(c => c.Input.Shape != ConnectorShape.Triangle).FirstOrDefault();
|
||||||
|
if (inputConnection == null)
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke("No input found", logType.Error);
|
||||||
|
throw new Exception("Input connection missig for : " + url);
|
||||||
|
}
|
||||||
|
var outPutNode = inputConnection.Output;
|
||||||
|
if (outPutNode == null)
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke("No output found", logType.Error);
|
||||||
|
throw new Exception("Output connection missig for : " + url);
|
||||||
|
}
|
||||||
|
if (outPutNode.Operation.Title.ToLower().Contains("parse json"))
|
||||||
|
{
|
||||||
|
var inputNodeForParseJson = connections.Where(c => c.Input.Operation.Title == outPutNode.Title).FirstOrDefault();
|
||||||
|
if (inputNodeForParseJson == null)
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke("No input found", logType.Error);
|
||||||
|
throw new Exception("Input connection missig for : " + url);
|
||||||
|
}
|
||||||
|
var inputNodeOutput = outputs[inputNodeForParseJson.InputNodeId];
|
||||||
|
if (inputNodeOutput != null)
|
||||||
|
{
|
||||||
|
var obj = JsonConvert.DeserializeObject(inputNodeOutput);
|
||||||
|
variables[op.NodeId] = obj;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
OnLogMe?.Invoke($"Execution started : {url}");
|
||||||
|
if (url.ToLower() == "begin" || url.ToLower() == "end")
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke($"Being or End node found. skipping it");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (url.ToLower() == "auth")
|
||||||
|
{
|
||||||
|
ResolveAuthNode(editorViewModel.Calculator.Operations);
|
||||||
|
OnLogMe?.Invoke($"Auth node resolved. Base URL: {_authBaseUrl}, Auth Type: {_authType}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
OnLogMe?.Invoke($"Starting Execution : {url}");
|
||||||
|
var res = GetResponse(url, "get");
|
||||||
|
if (!string.IsNullOrEmpty(res))
|
||||||
|
{
|
||||||
|
outputs.Add(op.NodeId, res);
|
||||||
|
}
|
||||||
|
OnLogMe?.Invoke($"Response Result : {res}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResolveAuthNode(ICollection<OperationViewModel> allNodes)
|
||||||
|
{
|
||||||
|
var authNode = allNodes.OfType<AuthOperationViewModel>().FirstOrDefault();
|
||||||
|
if (authNode == null)
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke("No Auth node found. Using defaults.", logType.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var inp in authNode.Input)
|
||||||
|
{
|
||||||
|
var title = inp.Title?.Trim() ?? string.Empty;
|
||||||
|
// For unconnected inputs the user types a value into the textbox, which is stored as Value (double).
|
||||||
|
// But the actual text is stored in the Title's textbox or the Value textbox.
|
||||||
|
// We read the connector's Value textbox string representation via the bound text.
|
||||||
|
var val = GetConnectorTextValue(inp);
|
||||||
|
|
||||||
|
switch (title)
|
||||||
|
{
|
||||||
|
case "Base URL": _authBaseUrl = val; break;
|
||||||
|
case "Auth Type": _authType = val; break;
|
||||||
|
case "Token": _authToken = val; break;
|
||||||
|
case "API Key": _authApiKey = val; break;
|
||||||
|
case "Username": _authUsername = val; break;
|
||||||
|
case "Password": _authPassword = val; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also read from the view-model properties which are bound to the text boxes in the node
|
||||||
|
if (!string.IsNullOrWhiteSpace(authNode.BaseUrl))
|
||||||
|
_authBaseUrl = authNode.BaseUrl;
|
||||||
|
if (!string.IsNullOrWhiteSpace(authNode.AuthType))
|
||||||
|
_authType = authNode.AuthType;
|
||||||
|
|
||||||
|
OnLogMe?.Invoke($"Auth configured — Base URL: {_authBaseUrl}, Auth Type: {_authType}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ResolveGetVariableNodes(ICollection<OperationViewModel> allNodes, ICollection<ConnectionViewModel> connections)
|
||||||
|
{
|
||||||
|
// Find all GET variable nodes (not in flow chain) and populate their output
|
||||||
|
foreach (var node in allNodes)
|
||||||
|
{
|
||||||
|
var title = node.Title ?? "";
|
||||||
|
if (!title.StartsWith("GET ") || !title.Contains("(") || !title.Contains(")"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var parts = title.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
if (parts.Length < 2) continue;
|
||||||
|
var varName = parts[1];
|
||||||
|
|
||||||
|
// Check if the variable already has a value
|
||||||
|
if (variables.TryGetValue(varName, out var existingVal))
|
||||||
|
{
|
||||||
|
outputs[node.NodeId] = existingVal?.ToString() ?? "";
|
||||||
|
// Set the output connector value for downstream nodes
|
||||||
|
var outConn = node.Output.FirstOrDefault(c => c.Shape != ConnectorShape.Triangle);
|
||||||
|
if (outConn != null)
|
||||||
|
{
|
||||||
|
if (double.TryParse(existingVal?.ToString(), out double dVal))
|
||||||
|
outConn.Value = dVal;
|
||||||
|
}
|
||||||
|
OnLogMe?.Invoke($"GET variable '{varName}' resolved to: {existingVal}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke($"GET variable '{varName}' has no value yet (will resolve during execution).", logType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetConnectorTextValue(ConnectorViewModel connector)
|
||||||
|
{
|
||||||
|
// When the connector is not connected, the user enters text in the Value text box.
|
||||||
|
// Value is a double, so we try to use it as a string representation.
|
||||||
|
// However, for string inputs (URL, token, etc.) the value won't be meaningful as a double.
|
||||||
|
// The user-typed string is actually stored in the Title field for unconnected inputs in some cases,
|
||||||
|
// but here we rely on the ConnectorViewModel.Value being 0 (default) and the actual string
|
||||||
|
// is not captured as double. So we return empty — the AuthOperationViewModel properties are the
|
||||||
|
// primary source set via the node's text boxes.
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetResponse(string url, string type)
|
||||||
|
{
|
||||||
|
string baseURL = !string.IsNullOrWhiteSpace(_authBaseUrl) ? _authBaseUrl : "https://localhost:7107";
|
||||||
|
string responseString = string.Empty;
|
||||||
|
|
||||||
|
#if NET8_0_OR_GREATER
|
||||||
|
using (HttpClient client = new HttpClient())
|
||||||
|
{
|
||||||
|
client.BaseAddress = new Uri(baseURL);
|
||||||
|
|
||||||
|
// Apply authentication headers
|
||||||
|
if (!string.IsNullOrWhiteSpace(_authType))
|
||||||
|
{
|
||||||
|
var authTypeLower = _authType.Trim().ToLower();
|
||||||
|
if (authTypeLower.Contains("bearer") && !string.IsNullOrWhiteSpace(_authToken))
|
||||||
|
{
|
||||||
|
client.DefaultRequestHeaders.Authorization =
|
||||||
|
new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", _authToken);
|
||||||
|
}
|
||||||
|
else if (authTypeLower.Contains("basic") &&
|
||||||
|
!string.IsNullOrWhiteSpace(_authUsername))
|
||||||
|
{
|
||||||
|
var credentials = Convert.ToBase64String(
|
||||||
|
Encoding.UTF8.GetBytes($"{_authUsername}:{_authPassword}"));
|
||||||
|
client.DefaultRequestHeaders.Authorization =
|
||||||
|
new System.Net.Http.Headers.AuthenticationHeaderValue("Basic", credentials);
|
||||||
|
}
|
||||||
|
else if (authTypeLower.Contains("api") && !string.IsNullOrWhiteSpace(_authApiKey))
|
||||||
|
{
|
||||||
|
client.DefaultRequestHeaders.Add("X-Api-Key", _authApiKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (type == "get")
|
||||||
|
{
|
||||||
|
HttpResponseMessage response = client.GetAsync(url).Result;
|
||||||
|
response.EnsureSuccessStatusCode();
|
||||||
|
responseString = response.Content.ReadAsStringAsync().Result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return responseString;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PerformChainCheck(ICollection<OperationViewModel> allNodes, ICollection<ConnectionViewModel> connections, bool isExecute)
|
||||||
|
{
|
||||||
|
string startNodeTitle = "begin";
|
||||||
|
string endNodeTitle = "end";
|
||||||
|
|
||||||
|
// Find the starting node
|
||||||
|
var startNode = allNodes.FirstOrDefault(node => node.Title?.ToLower() == startNodeTitle);
|
||||||
|
if (startNode == null)
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke("No Begin node found", logType.Error);
|
||||||
|
throw new Exception("Begin node not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track visited nodes to prevent infinite loops or revisits
|
||||||
|
HashSet<string> visitedNodes = new HashSet<string>();
|
||||||
|
|
||||||
|
// Perform the DFS Traversal
|
||||||
|
bool isValidChain = TraverseChain(startNode, endNodeTitle, connections, visitedNodes, isExecute);
|
||||||
|
|
||||||
|
if (isValidChain)
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke("Found complete chain...");
|
||||||
|
OnLogMe?.Invoke("You did it champ...");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke("Broken Chain found!!! Please fix the chain from begin to end.", logType.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TraverseChain(OperationViewModel currentNode, string endNodeTitle,
|
||||||
|
ICollection<ConnectionViewModel> connections,
|
||||||
|
HashSet<string> visitedNodes, bool isExecute)
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke($"Checking node: {currentNode.Title} , NodeId : {currentNode.NodeId}");
|
||||||
|
|
||||||
|
// If we've reached the "end" node, the chain is valid
|
||||||
|
if (currentNode.Title?.Equals(endNodeTitle, StringComparison.OrdinalIgnoreCase) == true)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect cycles and avoid infinite recursion
|
||||||
|
if (visitedNodes.Contains(currentNode.NodeId))
|
||||||
|
{
|
||||||
|
OnLogMe?.Invoke($"Cycle detected at node: {currentNode.Title}. Broken chain found!", logType.Warning);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
visitedNodes.Add(currentNode.NodeId);
|
||||||
|
|
||||||
|
// Find all outgoing connections from the current node
|
||||||
|
var outgoingConnections = connections.Where(conn => conn.Output?.Operation == currentNode && conn.Output.Shape == ConnectorShape.Triangle);
|
||||||
|
|
||||||
|
if (!outgoingConnections.Any())
|
||||||
|
{
|
||||||
|
// Only log the first "broken chain" warning, and stop further logging on backtrack
|
||||||
|
OnLogMe?.Invoke($"Broken chain detected at node: {currentNode.Title}", logType.Warning);
|
||||||
|
isAlreadyLogged = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check all outgoing connections
|
||||||
|
foreach (var connection in outgoingConnections)
|
||||||
|
{
|
||||||
|
var nextNode = connection.Input?.Operation;
|
||||||
|
if (nextNode == null)
|
||||||
|
{
|
||||||
|
// Handle null input in the connection
|
||||||
|
OnLogMe?.Invoke($"Broken chain detected due to null input connection from node: {currentNode.Title}", logType.Warning);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnLogMe?.Invoke($"Following connection from {currentNode.Title} to {nextNode.Title}");
|
||||||
|
|
||||||
|
if (isExecute)
|
||||||
|
{
|
||||||
|
StartExecution(currentNode, connections);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursively check the next node; stop on the first failure
|
||||||
|
if (TraverseChain(nextNode, endNodeTitle, connections, visitedNodes, isExecute))
|
||||||
|
{
|
||||||
|
return true; // If a valid path to the "end" is found, exit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isAlreadyLogged)
|
||||||
|
{
|
||||||
|
// If no connections lead to the end, this is the broken point => log here ONLY
|
||||||
|
OnLogMe?.Invoke($"Broken chain detected at node: {currentNode.Title}", logType.Warning);
|
||||||
|
isAlreadyLogged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string ValidateRequiredNodes(string nodeName, ICollection<OperationViewModel> allnodes)
|
||||||
|
{
|
||||||
|
var requireNode = allnodes.Where(c => c.Title.ToLower() == nodeName).ToList();
|
||||||
|
if (requireNode.Count > 1)
|
||||||
|
{
|
||||||
|
return $"One or more {nodeName} node found. Only one allowed at a time.";
|
||||||
|
}
|
||||||
|
else if (requireNode.Count == 0)
|
||||||
|
{
|
||||||
|
return $"No {nodeName} node found. At least one {nodeName} node required system to work";
|
||||||
|
}
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Examples/Nodify.Calculator/ExpandoOperationViewModel.cs
Normal file
33
Examples/Nodify.Calculator/ExpandoOperationViewModel.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class ExpandoOperationViewModel : OperationViewModel
|
||||||
|
{
|
||||||
|
public ExpandoOperationViewModel()
|
||||||
|
{
|
||||||
|
AddInputCommand = new RequeryCommand(
|
||||||
|
() => Input.Add(new ConnectorViewModel()),
|
||||||
|
() => Input.Count < MaxInput);
|
||||||
|
|
||||||
|
RemoveInputCommand = new RequeryCommand(
|
||||||
|
() => Input.RemoveAt(Input.Count - 1),
|
||||||
|
() => Input.Count > MinInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
public INodifyCommand AddInputCommand { get; }
|
||||||
|
public INodifyCommand RemoveInputCommand { get; }
|
||||||
|
|
||||||
|
private uint _minInput = 0;
|
||||||
|
public uint MinInput
|
||||||
|
{
|
||||||
|
get => _minInput;
|
||||||
|
set => SetProperty(ref _minInput, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint _maxInput = uint.MaxValue;
|
||||||
|
public uint MaxInput
|
||||||
|
{
|
||||||
|
get => _maxInput;
|
||||||
|
set => SetProperty(ref _maxInput, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
Examples/Nodify.Calculator/ExpressionOperationViewModel.cs
Normal file
59
Examples/Nodify.Calculator/ExpressionOperationViewModel.cs
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
using StringMath;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class ExpressionOperationViewModel : OperationViewModel
|
||||||
|
{
|
||||||
|
private MathExpr? _expr;
|
||||||
|
private string? _expression;
|
||||||
|
public string? Expression
|
||||||
|
{
|
||||||
|
get => _expression;
|
||||||
|
set => SetProperty(ref _expression, value)
|
||||||
|
.Then(GenerateInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GenerateInput()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_expr = Expression!.ToMathExpr();
|
||||||
|
ConnectorViewModel[]? toRemove = Input.Where(i => !_expr.LocalVariables.Contains(i.Title)).ToArray();
|
||||||
|
toRemove.ForEach(i => Input.Remove(i));
|
||||||
|
HashSet<string> existingVars = Input.Select(s => s.Title).Where(s => s != null).ToHashSet()!;
|
||||||
|
|
||||||
|
foreach (string variable in _expr.LocalVariables.Except(existingVars))
|
||||||
|
{
|
||||||
|
Input.Add(new ConnectorViewModel
|
||||||
|
{
|
||||||
|
Title = variable
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
OnInputValueChanged();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnInputValueChanged()
|
||||||
|
{
|
||||||
|
if (Output != null && _expr != null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Input.ForEach(i => _expr.Substitute(i.Title!, i.Value));
|
||||||
|
//Output.Value = _expr.Result;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
23
Examples/Nodify.Calculator/FlowRunner.xaml
Normal file
23
Examples/Nodify.Calculator/FlowRunner.xaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<Window x:Class="Nodify.Calculator.FlowRunner"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:Nodify.Calculator"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Loaded="Window_Loaded"
|
||||||
|
Title="FlowRunner" Height="450" Width="800" ResizeMode="NoResize" WindowStartupLocation="CenterOwner">
|
||||||
|
<Grid>
|
||||||
|
<RichTextBox x:Name="LogRichTextBox"
|
||||||
|
IsReadOnly="True"
|
||||||
|
VerticalScrollBarVisibility="Auto"
|
||||||
|
Background="#FF2D2D30"
|
||||||
|
Foreground="White"
|
||||||
|
FontFamily="Consolas"
|
||||||
|
FontSize="14"
|
||||||
|
BorderThickness="0">
|
||||||
|
<!-- Initialize with an empty document -->
|
||||||
|
<FlowDocument />
|
||||||
|
</RichTextBox>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
73
Examples/Nodify.Calculator/FlowRunner.xaml.cs
Normal file
73
Examples/Nodify.Calculator/FlowRunner.xaml.cs
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
using System;
|
||||||
|
using System.CodeDom.Compiler;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for FlowRunner.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class FlowRunner : Window
|
||||||
|
{
|
||||||
|
private readonly Executor _executor;
|
||||||
|
|
||||||
|
public FlowRunner(Executor executor)
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
_executor = executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void WriteLog(string message, logType logtype)
|
||||||
|
{
|
||||||
|
// IMPORTANT: UI updates must happen on the main UI thread.
|
||||||
|
// Dispatcher.Invoke ensures that, even if the event is fired from a background thread.
|
||||||
|
Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
// Choose a color based on the log type
|
||||||
|
Brush color = Brushes.White; // Default for Information
|
||||||
|
switch (logtype)
|
||||||
|
{
|
||||||
|
case logType.Warning:
|
||||||
|
color = Brushes.Yellow;
|
||||||
|
break;
|
||||||
|
case logType.Error:
|
||||||
|
color = Brushes.Red;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a text run with the specified color
|
||||||
|
var run = new Run($"{DateTime.Now:HH:mm:ss} [{logtype}]: {message}\n")
|
||||||
|
{
|
||||||
|
Foreground = color
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the text to a new paragraph and add the paragraph to the RichTextBox
|
||||||
|
var paragraph = new Paragraph();
|
||||||
|
paragraph.Inlines.Add(run);
|
||||||
|
LogRichTextBox.Document.Blocks.Add(paragraph);
|
||||||
|
|
||||||
|
// Auto-scroll to the bottom
|
||||||
|
LogRichTextBox.ScrollToEnd();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Window_Loaded(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
Task.Run(() => {
|
||||||
|
_executor.OnLogMe += WriteLog;
|
||||||
|
_executor.Execute();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
189
Examples/Nodify.Calculator/LiteDBHelper.cs
Normal file
189
Examples/Nodify.Calculator/LiteDBHelper.cs
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq.Expressions;
|
||||||
|
|
||||||
|
public class LiteDbHelper<T> : IDisposable where T : class
|
||||||
|
{
|
||||||
|
private string dbPath = @"MyData.db";
|
||||||
|
private readonly LiteDatabase _database;
|
||||||
|
private readonly ILiteCollection<T> _collection;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the LiteDbHelper class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="databasePath">The path to the LiteDB database file.</param>
|
||||||
|
/// <param name="collectionName">The name of the collection to work with.</param>
|
||||||
|
public LiteDbHelper(string collectionName)
|
||||||
|
{
|
||||||
|
_database = new LiteDatabase(dbPath);
|
||||||
|
_collection = _database.GetCollection<T>(collectionName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- CRUD Operations ---
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a new document into the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="document">The document to insert.</param>
|
||||||
|
/// <returns>The BsonValue of the inserted document's Id.</returns>
|
||||||
|
public BsonValue Insert(T document)
|
||||||
|
{
|
||||||
|
return _collection.Insert(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a collection of documents.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="documents">The documents to insert.</param>
|
||||||
|
/// <returns>The number of documents inserted.</returns>
|
||||||
|
public int BulkInsert(IEnumerable<T> documents)
|
||||||
|
{
|
||||||
|
return _collection.Insert(documents);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates a document in the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="document">The document to update.</param>
|
||||||
|
/// <returns>True if the document was updated, otherwise false.</returns>
|
||||||
|
public bool Update(T document)
|
||||||
|
{
|
||||||
|
return _collection.Update(document);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a document from the collection by its Id.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The Id of the document to delete.</param>
|
||||||
|
/// <returns>True if the document was deleted, otherwise false.</returns>
|
||||||
|
public bool Delete(BsonValue id)
|
||||||
|
{
|
||||||
|
return _collection.Delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes documents from the collection based on a predicate.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="predicate">The expression to filter documents to delete.</param>
|
||||||
|
/// <returns>The number of documents deleted.</returns>
|
||||||
|
public int DeleteMany(Expression<Func<T, bool>> predicate)
|
||||||
|
{
|
||||||
|
return _collection.DeleteMany(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Query Operations ---
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds a single document by its Id.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The Id of the document.</param>
|
||||||
|
/// <returns>The document if found, otherwise null.</returns>
|
||||||
|
public T FindById(BsonValue id)
|
||||||
|
{
|
||||||
|
return _collection.FindById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds the first document that matches the predicate.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="predicate">The expression to filter documents.</param>
|
||||||
|
/// <returns>The first matching document, or null if none are found.</returns>
|
||||||
|
public T FindOne(Expression<Func<T, bool>> predicate)
|
||||||
|
{
|
||||||
|
return _collection.FindOne(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds all documents in the collection.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>An enumerable of all documents.</returns>
|
||||||
|
public IEnumerable<T> FindAll()
|
||||||
|
{
|
||||||
|
return _collection.FindAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds documents based on a predicate.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="predicate">The expression to filter documents.</param>
|
||||||
|
/// <returns>An enumerable of matching documents.</returns>
|
||||||
|
public IEnumerable<T> Find(Expression<Func<T, bool>> predicate)
|
||||||
|
{
|
||||||
|
return _collection.Find(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if any document exists that matches the predicate.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="predicate">The expression to filter documents.</param>
|
||||||
|
/// <returns>True if a matching document exists, otherwise false.</returns>
|
||||||
|
public bool Exists(Expression<Func<T, bool>> predicate)
|
||||||
|
{
|
||||||
|
return _collection.Exists(predicate);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Indexing ---
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that an index is created for the specified field.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="field">The expression representing the field to be indexed.</param>
|
||||||
|
/// <param name="unique">Whether the index should enforce unique values.</param>
|
||||||
|
/// <returns>True if the index was created, otherwise false.</returns>
|
||||||
|
public bool EnsureIndex<K>(Expression<Func<T, K>> field, bool unique = false)
|
||||||
|
{
|
||||||
|
return _collection.EnsureIndex(field, unique);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- File Storage Operations ---
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uploads a file to the LiteDB file storage.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">A unique identifier for the file.</param>
|
||||||
|
/// <param name="filePath">The path to the file to upload.</param>
|
||||||
|
public void UploadFile(string id, string filePath)
|
||||||
|
{
|
||||||
|
_database.FileStorage.Upload(id, filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Downloads a file from the LiteDB file storage.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The unique identifier of the file.</param>
|
||||||
|
/// <param name="destinationPath">The path to save the downloaded file.</param>
|
||||||
|
public void DownloadFile(string id, Stream destinationPath)
|
||||||
|
{
|
||||||
|
_database.FileStorage.Download(id, destinationPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes a file from the LiteDB file storage.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The unique identifier of the file to delete.</param>
|
||||||
|
/// <returns>True if the file was deleted, otherwise false.</returns>
|
||||||
|
public bool DeleteFile(string id)
|
||||||
|
{
|
||||||
|
return _database.FileStorage.Delete(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Finds a file's metadata in the LiteDB file storage.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The unique identifier of the file.</param>
|
||||||
|
/// <returns>The LiteFileInfo object if found, otherwise null.</returns>
|
||||||
|
public LiteFileInfo<string> FindFileById(string id)
|
||||||
|
{
|
||||||
|
return _database.FileStorage.FindById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Disposes the LiteDatabase connection.
|
||||||
|
/// </summary>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_database?.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
164
Examples/Nodify.Calculator/MainWindow.xaml
Normal file
164
Examples/Nodify.Calculator/MainWindow.xaml
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<Window x:Class="Nodify.Calculator.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:Nodify.Calculator"
|
||||||
|
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||||
|
Background="{DynamicResource NodifyEditor.BackgroundBrush}"
|
||||||
|
Foreground="{DynamicResource ForegroundBrush}"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="MainWindow"
|
||||||
|
Height="650"
|
||||||
|
Width="1200">
|
||||||
|
<Window.DataContext>
|
||||||
|
<local:ApplicationViewModel />
|
||||||
|
</Window.DataContext>
|
||||||
|
|
||||||
|
<Window.InputBindings>
|
||||||
|
<KeyBinding Key="T"
|
||||||
|
Modifiers="Ctrl"
|
||||||
|
Command="{Binding Source={x:Static shared:ThemeManager.SetNextThemeCommand}}" />
|
||||||
|
<KeyBinding Key="N"
|
||||||
|
Modifiers="Ctrl"
|
||||||
|
Command="{Binding AddEditorCommand}" />
|
||||||
|
|
||||||
|
<KeyBinding Key="R"
|
||||||
|
Modifiers="Ctrl"
|
||||||
|
Command="{Binding RunFlowCommand}" />
|
||||||
|
|
||||||
|
<KeyBinding Key="S"
|
||||||
|
Modifiers="Ctrl"
|
||||||
|
Command="{Binding SaveFileCommand}" />
|
||||||
|
|
||||||
|
<KeyBinding Key="O"
|
||||||
|
Modifiers="Ctrl"
|
||||||
|
Command="{Binding OpenFileCommand}" />
|
||||||
|
|
||||||
|
<KeyBinding Key="W"
|
||||||
|
Modifiers="Ctrl"
|
||||||
|
Command="{Binding CloseEditorCommand}"
|
||||||
|
CommandParameter="{Binding SelectedEditor.Id}"/>
|
||||||
|
</Window.InputBindings>
|
||||||
|
|
||||||
|
<Window.Resources>
|
||||||
|
<shared:BindingProxy x:Key="Proxy"
|
||||||
|
DataContext="{Binding}"/>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:EditorViewModel}">
|
||||||
|
<local:EditorView/>
|
||||||
|
</DataTemplate>
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<shared:TabControlEx ItemsSource="{Binding Editors}"
|
||||||
|
SelectedItem="{Binding SelectedEditor}"
|
||||||
|
AddTabCommand="{Binding AddEditorCommand}"
|
||||||
|
AutoScrollToEnd="{Binding AutoSelectNewEditor}">
|
||||||
|
<shared:TabControlEx.ItemContainerStyle>
|
||||||
|
<Style TargetType="{x:Type shared:TabItemEx}"
|
||||||
|
BasedOn="{StaticResource {x:Type shared:TabItemEx}}">
|
||||||
|
<Setter Property="Header"
|
||||||
|
Value="{Binding Name}"/>
|
||||||
|
<Setter Property="CloseTabCommand"
|
||||||
|
Value="{Binding DataContext.CloseEditorCommand ,Source={StaticResource Proxy}}"/>
|
||||||
|
<Setter Property="CloseTabCommandParameter"
|
||||||
|
Value="{Binding Id}"/>
|
||||||
|
<Setter Property="ToolTip"
|
||||||
|
Value="Double click to edit" />
|
||||||
|
</Style>
|
||||||
|
</shared:TabControlEx.ItemContainerStyle>
|
||||||
|
</shared:TabControlEx>
|
||||||
|
|
||||||
|
<Expander Header="Click to hide/show"
|
||||||
|
IsExpanded="True"
|
||||||
|
Margin="10"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Bottom">
|
||||||
|
<Border MaxWidth="325"
|
||||||
|
MaxHeight="300"
|
||||||
|
CornerRadius="3">
|
||||||
|
<Border.Background>
|
||||||
|
<SolidColorBrush Color="{DynamicResource BackgroundColor}"
|
||||||
|
Opacity="0.7" />
|
||||||
|
</Border.Background>
|
||||||
|
<ScrollViewer HorizontalScrollBarVisibility="Disabled">
|
||||||
|
<StackPanel Margin="10"
|
||||||
|
IsHitTestVisible="False">
|
||||||
|
<StackPanel.Resources>
|
||||||
|
<Style TargetType="{x:Type TextBlock}"
|
||||||
|
BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||||
|
<Setter Property="Margin"
|
||||||
|
Value="0 0 0 5" />
|
||||||
|
</Style>
|
||||||
|
</StackPanel.Resources>
|
||||||
|
|
||||||
|
<StackPanel Margin="0 0 0 20">
|
||||||
|
<TextBlock Text="(New) Drag and drop nodes from the toolbox"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
Foreground="{DynamicResource NodeInput.BorderBrush}"
|
||||||
|
FontWeight="Bold"/>
|
||||||
|
</StackPanel>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
<Run Foreground="Red"
|
||||||
|
FontWeight="Bold">CTRL + N/W</Run>
|
||||||
|
<Run>: open/close editor</Run>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
<Run Foreground="Red"
|
||||||
|
FontWeight="Bold">CTRL + R</Run>
|
||||||
|
<Run>: Run Flow</Run>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
<Run Foreground="Red"
|
||||||
|
FontWeight="Bold">CTRL + S</Run>
|
||||||
|
<Run>: Save Flow</Run>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
<Run Foreground="Red"
|
||||||
|
FontWeight="Bold">CTRL + O</Run>
|
||||||
|
<Run>: Open Saved Flow</Run>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
<Run Foreground="Red"
|
||||||
|
FontWeight="Bold">ALT + Click</Run>
|
||||||
|
<Run>: disconnect connector</Run>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
<Run Foreground="Red"
|
||||||
|
FontWeight="Bold">Right Click</Run>
|
||||||
|
<Run>: show operations menu (create nodes)</Run>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
<Run Foreground="Red"
|
||||||
|
FontWeight="Bold">Delete</Run>
|
||||||
|
<Run>: delete selection</Run>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
<Run Foreground="Red"
|
||||||
|
FontWeight="Bold">CTRL + T</Run>
|
||||||
|
<Run>: change theme</Run>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap">
|
||||||
|
<Run Foreground="Red"
|
||||||
|
FontWeight="Bold">CTRL + G</Run>
|
||||||
|
<Run>: group selection (hold SHIFT and mouse drag the header to move the group node alone)</Run>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock Text="Drag a connection and drop it on the editor"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
FontWeight="Bold" />
|
||||||
|
<TextBlock Text="Hover over a connector to see its value"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
FontWeight="Bold" />
|
||||||
|
<TextBlock Text="Create a Calculator node and double click it to open"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
FontWeight="Bold" />
|
||||||
|
<TextBlock Text="Create an Operation Graph and add operations to it"
|
||||||
|
TextWrapping="Wrap"
|
||||||
|
FontWeight="Bold" />
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Border>
|
||||||
|
</Expander>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
26
Examples/Nodify.Calculator/MainWindow.xaml.cs
Normal file
26
Examples/Nodify.Calculator/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Nodify.Interactivity;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
EditorGestures.Mappings.Editor.Cutting.Unbind();
|
||||||
|
|
||||||
|
EventManager.RegisterClassHandler(
|
||||||
|
typeof(UIElement),
|
||||||
|
Keyboard.PreviewGotKeyboardFocusEvent,
|
||||||
|
(KeyboardFocusChangedEventHandler)OnPreviewGotKeyboardFocus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
||||||
|
{
|
||||||
|
Title = e.NewFocus.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
Examples/Nodify.Calculator/Models/SaveGraphModel.cs
Normal file
22
Examples/Nodify.Calculator/Models/SaveGraphModel.cs
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator.Models
|
||||||
|
{
|
||||||
|
public class SaveGraphModel
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
public List<SaveNodes> Nodes { get; set; } = new List<SaveNodes>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SaveNodes
|
||||||
|
{
|
||||||
|
public Point Location { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
13
Examples/Nodify.Calculator/Models/SwaggerNodeModel.cs
Normal file
13
Examples/Nodify.Calculator/Models/SwaggerNodeModel.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator.Models
|
||||||
|
{
|
||||||
|
public class SwaggerNodeModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Title { get; set; } = string.Empty;
|
||||||
|
public string OPType { get; set; } = string.Empty;
|
||||||
|
public List<string> InputNames { get; set; } = new List<string>();
|
||||||
|
public string SwaggerFileName { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Examples/Nodify.Calculator/Models/VariableModel.cs
Normal file
12
Examples/Nodify.Calculator/Models/VariableModel.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator.Models
|
||||||
|
{
|
||||||
|
public class VariableModel
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Name { get; set; } = string.Empty;
|
||||||
|
public string VariableType { get; set; } = "string";
|
||||||
|
public string DefaultValue { get; set; } = string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
Examples/Nodify.Calculator/Nodify.Calculator.csproj
Normal file
30
Examples/Nodify.Calculator/Nodify.Calculator.csproj
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFrameworks>net9-windows;</TargetFrameworks>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Remove="leet swagger.txt" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Include="leet swagger.txt">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="LiteDB" Version="5.0.21" />
|
||||||
|
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.14.0" />
|
||||||
|
<PackageReference Include="NSwag.CodeGeneration.CSharp" Version="14.5.0" />
|
||||||
|
<PackageReference Include="NSwag.Core" Version="14.5.0" />
|
||||||
|
<PackageReference Include="StringMath" Version="4.1.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Nodify\Nodify.csproj" />
|
||||||
|
<ProjectReference Include="..\Nodify.Shared\Nodify.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
44
Examples/Nodify.Calculator/OperationGraphViewModel.cs
Normal file
44
Examples/Nodify.Calculator/OperationGraphViewModel.cs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class OperationGraphViewModel : CalculatorOperationViewModel
|
||||||
|
{
|
||||||
|
private Size _size;
|
||||||
|
public Size DesiredSize
|
||||||
|
{
|
||||||
|
get => _size;
|
||||||
|
set => SetProperty(ref _size, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Size _prevSize;
|
||||||
|
|
||||||
|
private bool _isExpanded = true;
|
||||||
|
public bool IsExpanded
|
||||||
|
{
|
||||||
|
get => _isExpanded;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _isExpanded, value))
|
||||||
|
{
|
||||||
|
if (_isExpanded)
|
||||||
|
{
|
||||||
|
DesiredSize = _prevSize;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_prevSize = Size;
|
||||||
|
// Fit content
|
||||||
|
DesiredSize = new Size(double.NaN, double.NaN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public OperationGraphViewModel()
|
||||||
|
{
|
||||||
|
InnerCalculator.Operations[0].Location = new Point(50, 50);
|
||||||
|
InnerCalculator.Operations[1].Location = new Point(200, 50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Examples/Nodify.Calculator/OperationGroupViewModel.cs
Normal file
14
Examples/Nodify.Calculator/OperationGroupViewModel.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class OperationGroupViewModel : OperationViewModel
|
||||||
|
{
|
||||||
|
private Size _size;
|
||||||
|
public Size GroupSize
|
||||||
|
{
|
||||||
|
get => _size;
|
||||||
|
set => SetProperty(ref _size, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Examples/Nodify.Calculator/OperationInfoViewModel.cs
Normal file
36
Examples/Nodify.Calculator/OperationInfoViewModel.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public enum OperationType
|
||||||
|
{
|
||||||
|
Normal,
|
||||||
|
Expando,
|
||||||
|
Expression,
|
||||||
|
Calculator,
|
||||||
|
Group,
|
||||||
|
Graph,
|
||||||
|
API,
|
||||||
|
System
|
||||||
|
}
|
||||||
|
|
||||||
|
public class OperationInfoViewModel
|
||||||
|
{
|
||||||
|
public string? Title { get; set; }
|
||||||
|
public OperationType Type { get; set; }
|
||||||
|
public IOperation? Operation { get; set; }
|
||||||
|
public SystemOperations sysOp { get; set; }
|
||||||
|
public List<string?> Input { get; } = new List<string?>();
|
||||||
|
public List<string?> Output { get; } = new List<string?>();
|
||||||
|
public uint MinInput { get; set; }
|
||||||
|
public uint MaxInput { get; set; }
|
||||||
|
public string InputType { get; set; } = string.Empty;
|
||||||
|
public string OPType { get; set; }
|
||||||
|
public bool IsFlowNode { get; set; }
|
||||||
|
public bool IsModelNode { get; set; }
|
||||||
|
public string ClassName { get; set; }
|
||||||
|
public string VariableType { get; set; } = string.Empty;
|
||||||
|
public string DefaultValue { get; set; } = string.Empty;
|
||||||
|
public bool IsSimpleVariable { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
128
Examples/Nodify.Calculator/OperationViewModel.cs
Normal file
128
Examples/Nodify.Calculator/OperationViewModel.cs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using System;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class OperationViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public OperationViewModel()
|
||||||
|
{
|
||||||
|
Input.WhenAdded(x =>
|
||||||
|
{
|
||||||
|
x.Operation = this;
|
||||||
|
x.IsInput = true;
|
||||||
|
x.PropertyChanged += OnInputValueChanged;
|
||||||
|
})
|
||||||
|
.WhenRemoved(x =>
|
||||||
|
{
|
||||||
|
x.PropertyChanged -= OnInputValueChanged;
|
||||||
|
});
|
||||||
|
|
||||||
|
Output.WhenAdded(c =>
|
||||||
|
{
|
||||||
|
c.Operation = this;
|
||||||
|
c.IsInput = false;
|
||||||
|
c.Value = 0;
|
||||||
|
c.PropertyChanged += OnInputValueChanged;
|
||||||
|
})
|
||||||
|
.WhenRemoved(x =>
|
||||||
|
{
|
||||||
|
x.PropertyChanged -= OnInputValueChanged;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnInputValueChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(ConnectorViewModel.Value))
|
||||||
|
{
|
||||||
|
OnInputValueChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Point _location;
|
||||||
|
public Point Location
|
||||||
|
{
|
||||||
|
get => _location;
|
||||||
|
set => SetProperty(ref _location, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Size _size;
|
||||||
|
public Size Size
|
||||||
|
{
|
||||||
|
get => _size;
|
||||||
|
set => SetProperty(ref _size, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? _title;
|
||||||
|
public string? Title
|
||||||
|
{
|
||||||
|
get => _title;
|
||||||
|
set => SetProperty(ref _title, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isSelected;
|
||||||
|
public bool IsSelected
|
||||||
|
{
|
||||||
|
get => _isSelected;
|
||||||
|
set => SetProperty(ref _isSelected, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReadOnly { get; set; }
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
private IOperation? _operation;
|
||||||
|
[BsonIgnore]
|
||||||
|
public IOperation? Operation
|
||||||
|
{
|
||||||
|
get => _operation;
|
||||||
|
set => SetProperty(ref _operation, value)
|
||||||
|
.Then(OnInputValueChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string nodeId;
|
||||||
|
|
||||||
|
public string NodeId
|
||||||
|
{
|
||||||
|
get { return nodeId; }
|
||||||
|
set => SetProperty(ref nodeId, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public NodifyObservableCollection<ConnectorViewModel> Input { get; } = new NodifyObservableCollection<ConnectorViewModel>();
|
||||||
|
|
||||||
|
[BsonIgnore]
|
||||||
|
public NodifyObservableCollection<ConnectorViewModel> Output { get; } = new NodifyObservableCollection<ConnectorViewModel>();
|
||||||
|
|
||||||
|
//private ConnectorViewModel? _output;
|
||||||
|
//public ConnectorViewModel? Output
|
||||||
|
//{
|
||||||
|
// get => _output;
|
||||||
|
// set
|
||||||
|
// {
|
||||||
|
// if (SetProperty(ref _output, value) && _output != null)
|
||||||
|
// {
|
||||||
|
// _output.Operation = this;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
protected virtual void OnInputValueChanged()
|
||||||
|
{
|
||||||
|
//if (Output != null && Operation != null)
|
||||||
|
//{
|
||||||
|
// try
|
||||||
|
// {
|
||||||
|
// var input = Input.Select(i => i.Value).ToArray();
|
||||||
|
// Output.Value = Operation?.Execute(input) ?? 0;
|
||||||
|
// }
|
||||||
|
// catch
|
||||||
|
// {
|
||||||
|
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Examples/Nodify.Calculator/Operations/BinaryOperation.cs
Normal file
14
Examples/Nodify.Calculator/Operations/BinaryOperation.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class BinaryOperation : IOperation
|
||||||
|
{
|
||||||
|
private readonly Func<double, double, double> _func;
|
||||||
|
|
||||||
|
public BinaryOperation(Func<double, double, double> func) => _func = func;
|
||||||
|
|
||||||
|
public double Execute(params double[] operands)
|
||||||
|
=> _func.Invoke(operands[0], operands[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
7
Examples/Nodify.Calculator/Operations/IOperation.cs
Normal file
7
Examples/Nodify.Calculator/Operations/IOperation.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public interface IOperation
|
||||||
|
{
|
||||||
|
double Execute(params double[] operands);
|
||||||
|
}
|
||||||
|
}
|
||||||
469
Examples/Nodify.Calculator/Operations/OperationFactory.cs
Normal file
469
Examples/Nodify.Calculator/Operations/OperationFactory.cs
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
using Size = System.Windows.Size;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public static class OperationFactory
|
||||||
|
{
|
||||||
|
|
||||||
|
public static List<OperationInfoViewModel> GetSystemNodes()
|
||||||
|
{
|
||||||
|
List<OperationInfoViewModel> systemNodes = new List<OperationInfoViewModel>();
|
||||||
|
|
||||||
|
var copynode = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = "COPY",
|
||||||
|
Type = OperationType.System,
|
||||||
|
OPType = "copy",
|
||||||
|
IsFlowNode = false,
|
||||||
|
};
|
||||||
|
copynode.Input.Add("");
|
||||||
|
copynode.Output.Add("");
|
||||||
|
copynode.Output.Add("");
|
||||||
|
|
||||||
|
var begin = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = "Begin",
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.BEGIN,
|
||||||
|
IsFlowNode = true
|
||||||
|
};
|
||||||
|
begin.Output.Add("");
|
||||||
|
|
||||||
|
var ending = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = "End",
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.END,
|
||||||
|
IsFlowNode = true
|
||||||
|
};
|
||||||
|
ending.Input.Add("");
|
||||||
|
|
||||||
|
var debugAndCreateModels = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = "Debug & Create Model",
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.DEBUG_AND_CREATE_MODEL,
|
||||||
|
IsFlowNode = true
|
||||||
|
};
|
||||||
|
debugAndCreateModels.Input.Add("");
|
||||||
|
debugAndCreateModels.Output.Add("");
|
||||||
|
|
||||||
|
var jsonParseNode = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = "Parse Json",
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.PARSEJSON,
|
||||||
|
IsFlowNode = false
|
||||||
|
};
|
||||||
|
jsonParseNode.Input.Add("");
|
||||||
|
jsonParseNode.Output.Add("");
|
||||||
|
|
||||||
|
var splitNode = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = "Split",
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.SPLIT,
|
||||||
|
IsFlowNode = false
|
||||||
|
};
|
||||||
|
splitNode.Input.Add("");
|
||||||
|
splitNode.Output.Add("");
|
||||||
|
|
||||||
|
var takeNode = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = "TAKE",
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.TAKE,
|
||||||
|
IsFlowNode = false
|
||||||
|
};
|
||||||
|
takeNode.Input.Add("");
|
||||||
|
takeNode.Output.Add("");
|
||||||
|
|
||||||
|
var authNode = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = "Auth",
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.AUTH,
|
||||||
|
IsFlowNode = true
|
||||||
|
};
|
||||||
|
authNode.Input.Add("Base URL");
|
||||||
|
authNode.Input.Add("Auth Type");
|
||||||
|
authNode.Input.Add("Token");
|
||||||
|
authNode.Input.Add("API Key");
|
||||||
|
authNode.Input.Add("Username");
|
||||||
|
authNode.Input.Add("Password");
|
||||||
|
|
||||||
|
systemNodes.Add(authNode);
|
||||||
|
systemNodes.Add(copynode);
|
||||||
|
systemNodes.Add(begin);
|
||||||
|
systemNodes.Add(ending);
|
||||||
|
systemNodes.Add(debugAndCreateModels);
|
||||||
|
systemNodes.Add(jsonParseNode);
|
||||||
|
systemNodes.Add(splitNode);
|
||||||
|
systemNodes.Add(takeNode);
|
||||||
|
return systemNodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<OperationInfoViewModel> GetOperationsInfo(Type container)
|
||||||
|
{
|
||||||
|
List<OperationInfoViewModel> result = new List<OperationInfoViewModel>();
|
||||||
|
|
||||||
|
foreach (var method in container.GetMethods())
|
||||||
|
{
|
||||||
|
if (method.IsStatic)
|
||||||
|
{
|
||||||
|
OperationInfoViewModel op = new OperationInfoViewModel
|
||||||
|
{
|
||||||
|
Title = method.Name
|
||||||
|
};
|
||||||
|
|
||||||
|
var attr = method.GetCustomAttribute<OperationAttribute>();
|
||||||
|
var para = method.GetParameters();
|
||||||
|
|
||||||
|
bool generateInputNames = true;
|
||||||
|
|
||||||
|
op.Type = OperationType.Normal;
|
||||||
|
|
||||||
|
if (para.Length == 2)
|
||||||
|
{
|
||||||
|
var delType = typeof(Func<double, double, double>);
|
||||||
|
var del = (Func<double, double, double>)Delegate.CreateDelegate(delType, method);
|
||||||
|
|
||||||
|
op.Operation = new BinaryOperation(del);
|
||||||
|
}
|
||||||
|
else if (para.Length == 1)
|
||||||
|
{
|
||||||
|
if (para[0].ParameterType.IsArray)
|
||||||
|
{
|
||||||
|
op.Type = OperationType.Expando;
|
||||||
|
|
||||||
|
var delType = typeof(Func<double[], double>);
|
||||||
|
var del = (Func<double[], double>)Delegate.CreateDelegate(delType, method);
|
||||||
|
|
||||||
|
op.Operation = new ParamsOperation(del);
|
||||||
|
op.MaxInput = int.MaxValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var delType = typeof(Func<double, double>);
|
||||||
|
var del = (Func<double, double>)Delegate.CreateDelegate(delType, method);
|
||||||
|
|
||||||
|
op.Operation = new UnaryOperation(del);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (para.Length == 0)
|
||||||
|
{
|
||||||
|
var delType = typeof(Func<double>);
|
||||||
|
var del = (Func<double>)Delegate.CreateDelegate(delType, method);
|
||||||
|
|
||||||
|
op.Operation = new ValueOperation(del);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attr != null)
|
||||||
|
{
|
||||||
|
op.MinInput = attr.MinInput;
|
||||||
|
op.MaxInput = attr.MaxInput;
|
||||||
|
generateInputNames = attr.GenerateInputNames;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
op.MinInput = (uint)para.Length;
|
||||||
|
op.MaxInput = (uint)para.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var param in para)
|
||||||
|
{
|
||||||
|
op.Input.Add(generateInputNames ? param.Name : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = op.Input.Count; i < op.MinInput; i++)
|
||||||
|
{
|
||||||
|
op.Input.Add(null);
|
||||||
|
}
|
||||||
|
op.Output.Add("");
|
||||||
|
result.Add(op);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static OperationViewModel GetOperation(OperationInfoViewModel info)
|
||||||
|
{
|
||||||
|
var input = info.Input.Select(i => new ConnectorViewModel
|
||||||
|
{
|
||||||
|
Title = i
|
||||||
|
});
|
||||||
|
|
||||||
|
switch (info.Type)
|
||||||
|
{
|
||||||
|
case OperationType.Expression:
|
||||||
|
var eo = new ExpressionOperationViewModel
|
||||||
|
{
|
||||||
|
Title = info.Title,
|
||||||
|
Operation = info.Operation,
|
||||||
|
Expression = "1 + sin {a} + cos {b}"
|
||||||
|
};
|
||||||
|
eo.Output.Add(new ConnectorViewModel());
|
||||||
|
return eo;
|
||||||
|
case OperationType.Calculator:
|
||||||
|
return new CalculatorOperationViewModel
|
||||||
|
{
|
||||||
|
Title = info.Title,
|
||||||
|
Operation = info.Operation,
|
||||||
|
};
|
||||||
|
|
||||||
|
case OperationType.Expando:
|
||||||
|
var o = new ExpandoOperationViewModel
|
||||||
|
{
|
||||||
|
MaxInput = info.MaxInput,
|
||||||
|
MinInput = info.MinInput,
|
||||||
|
Title = info.Title,
|
||||||
|
Operation = info.Operation
|
||||||
|
};
|
||||||
|
o.Output.Add(new ConnectorViewModel());
|
||||||
|
o.Input.AddRange(input);
|
||||||
|
return o;
|
||||||
|
|
||||||
|
case OperationType.Group:
|
||||||
|
return new OperationGroupViewModel
|
||||||
|
{
|
||||||
|
Title = info.Title,
|
||||||
|
};
|
||||||
|
|
||||||
|
case OperationType.Graph:
|
||||||
|
return new OperationGraphViewModel
|
||||||
|
{
|
||||||
|
Title = info.Title,
|
||||||
|
DesiredSize = new Size(420, 250)
|
||||||
|
};
|
||||||
|
|
||||||
|
case OperationType.API:
|
||||||
|
var _o = new APIOperationViewModel
|
||||||
|
{
|
||||||
|
Title = info.Title,
|
||||||
|
OperationType = info.OPType.ToUpper()
|
||||||
|
};
|
||||||
|
var connectorViewModel = new ConnectorViewModel()
|
||||||
|
{
|
||||||
|
Title = "",
|
||||||
|
Shape = ConnectorShape.Triangle
|
||||||
|
};
|
||||||
|
var connectorViewModel2 = new ConnectorViewModel()
|
||||||
|
{
|
||||||
|
Title = "",
|
||||||
|
Shape = ConnectorShape.Triangle,
|
||||||
|
IsInput = false
|
||||||
|
};
|
||||||
|
_o.Output.Add(connectorViewModel2);
|
||||||
|
_o.Output.Add(new ConnectorViewModel());
|
||||||
|
_o.Input.Add(connectorViewModel);
|
||||||
|
foreach (var item in input)
|
||||||
|
{
|
||||||
|
item.ConnectorColor = Color.GreenYellow;
|
||||||
|
_o.Input.Add(item);
|
||||||
|
}
|
||||||
|
//_o.Input.AddRange(input);
|
||||||
|
return _o;
|
||||||
|
case OperationType.System:
|
||||||
|
if (info.sysOp == SystemOperations.AUTH)
|
||||||
|
{
|
||||||
|
var authOp = new AuthOperationViewModel
|
||||||
|
{
|
||||||
|
Title = info.Title,
|
||||||
|
SystemOperationType = SystemOperations.AUTH
|
||||||
|
};
|
||||||
|
// Add flow connectors (triangle)
|
||||||
|
var flowIn = new ConnectorViewModel()
|
||||||
|
{
|
||||||
|
Title = "",
|
||||||
|
Shape = ConnectorShape.Triangle
|
||||||
|
};
|
||||||
|
var flowOut = new ConnectorViewModel()
|
||||||
|
{
|
||||||
|
Title = "",
|
||||||
|
Shape = ConnectorShape.Triangle,
|
||||||
|
IsInput = false
|
||||||
|
};
|
||||||
|
authOp.Input.Add(flowIn);
|
||||||
|
authOp.Output.Add(flowOut);
|
||||||
|
// Add data input connectors
|
||||||
|
foreach (var inp in input)
|
||||||
|
{
|
||||||
|
inp.ConnectorColor = System.Drawing.Color.Orange;
|
||||||
|
authOp.Input.Add(inp);
|
||||||
|
}
|
||||||
|
return authOp;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sysOp = new SystemOperationViewModel
|
||||||
|
{
|
||||||
|
Title = info.Title,
|
||||||
|
SystemOperationType = info.sysOp
|
||||||
|
};
|
||||||
|
|
||||||
|
if (info.sysOp == SystemOperations.GET_SET && info.IsModelNode)
|
||||||
|
{
|
||||||
|
if (info.Title == "GET")
|
||||||
|
{
|
||||||
|
info.Output.Add("");
|
||||||
|
info.IsFlowNode = false;
|
||||||
|
}
|
||||||
|
else if (info.Title == "SET")
|
||||||
|
{
|
||||||
|
info.Input.Add("");
|
||||||
|
var customModelDir = "CustomModels";
|
||||||
|
Directory.CreateDirectory(customModelDir);
|
||||||
|
var flpath = System.IO.Path.Combine(customModelDir, info.ClassName + ".cs");
|
||||||
|
if (File.Exists(flpath))
|
||||||
|
{
|
||||||
|
//Read all the properties from the file
|
||||||
|
var fileContent = File.ReadAllText(flpath);
|
||||||
|
|
||||||
|
// Parse and analyze properties
|
||||||
|
var properties = GetPropertiesFromClass(fileContent);
|
||||||
|
|
||||||
|
// Print out the extracted properties and their types
|
||||||
|
Console.WriteLine("Properties found:");
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Property Name: {property.Name}, Type: {property.Type}");
|
||||||
|
info.Output.Add(property.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
info.IsFlowNode = true;
|
||||||
|
}
|
||||||
|
var flTitle = $"{info.Title} {info.ClassName}";
|
||||||
|
sysOp.Title = flTitle;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.sysOp == SystemOperations.GET_SET && info.IsSimpleVariable)
|
||||||
|
{
|
||||||
|
var varLabel = $"{info.Title} {info.ClassName} ({info.VariableType})";
|
||||||
|
sysOp.Title = varLabel;
|
||||||
|
|
||||||
|
if (info.Title == "GET")
|
||||||
|
{
|
||||||
|
// GET variable: output the value, no flow needed
|
||||||
|
info.Output.Add("Value");
|
||||||
|
info.IsFlowNode = false;
|
||||||
|
}
|
||||||
|
else if (info.Title == "SET")
|
||||||
|
{
|
||||||
|
// SET variable: input connector for the value, flow node
|
||||||
|
info.Input.Add("Value");
|
||||||
|
info.IsFlowNode = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.sysOp != SystemOperations.BEGIN &&
|
||||||
|
info.sysOp != SystemOperations.END &&
|
||||||
|
info.IsFlowNode)
|
||||||
|
{
|
||||||
|
var flowinputnode = new ConnectorViewModel()
|
||||||
|
{
|
||||||
|
Title = "",
|
||||||
|
Shape = ConnectorShape.Triangle
|
||||||
|
};
|
||||||
|
var flowoutputnode = new ConnectorViewModel()
|
||||||
|
{
|
||||||
|
Title = "",
|
||||||
|
Shape = ConnectorShape.Triangle,
|
||||||
|
IsInput = false
|
||||||
|
};
|
||||||
|
sysOp.Input.Add(flowinputnode);
|
||||||
|
sysOp.Output.Add(flowoutputnode);
|
||||||
|
}
|
||||||
|
foreach (var item in info.Output)
|
||||||
|
{
|
||||||
|
var out1 = new ConnectorViewModel()
|
||||||
|
{
|
||||||
|
Title = string.IsNullOrEmpty(item) ? "" : item,
|
||||||
|
IsInput = false,
|
||||||
|
ConnectorColor = Color.DarkRed,
|
||||||
|
Shape = (info.sysOp == SystemOperations.BEGIN || info.sysOp == SystemOperations.END) ? ConnectorShape.Triangle : ConnectorShape.Circle,
|
||||||
|
};
|
||||||
|
if (out1.Shape != ConnectorShape.Triangle)
|
||||||
|
{
|
||||||
|
out1.ConnectorColor = Color.DeepPink;
|
||||||
|
}
|
||||||
|
sysOp.Output.Add(out1);
|
||||||
|
}
|
||||||
|
foreach (var item in input)
|
||||||
|
{
|
||||||
|
item.Shape = (info.sysOp == SystemOperations.BEGIN || info.sysOp == SystemOperations.END) ? ConnectorShape.Triangle : ConnectorShape.Circle;
|
||||||
|
sysOp.Input.Add(item);
|
||||||
|
}
|
||||||
|
return sysOp;
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
var op = new OperationViewModel
|
||||||
|
{
|
||||||
|
Title = info.Title,
|
||||||
|
Operation = info.Operation,
|
||||||
|
};
|
||||||
|
var ccv = new ConnectorViewModel()
|
||||||
|
{
|
||||||
|
IsInput = false,
|
||||||
|
Shape = ConnectorShape.Circle,
|
||||||
|
Title = ""
|
||||||
|
};
|
||||||
|
op.Output.Add(ccv);
|
||||||
|
|
||||||
|
op.Input.AddRange(input);
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<CustomProperty> GetPropertiesFromClass(string classContent)
|
||||||
|
{
|
||||||
|
// Parse the C# class content using Roslyn
|
||||||
|
var syntaxTree = CSharpSyntaxTree.ParseText(classContent);
|
||||||
|
var root = syntaxTree.GetRoot();
|
||||||
|
|
||||||
|
// Find the first class declaration (you can refine this if there are multiple classes)
|
||||||
|
var classDeclaration = root.DescendantNodes().OfType<ClassDeclarationSyntax>().FirstOrDefault();
|
||||||
|
|
||||||
|
// List to store extracted properties
|
||||||
|
var properties = new List<CustomProperty>();
|
||||||
|
|
||||||
|
if (classDeclaration != null)
|
||||||
|
{
|
||||||
|
// Look for property declarations inside the class
|
||||||
|
var propertyDeclarations = classDeclaration.Members.OfType<PropertyDeclarationSyntax>();
|
||||||
|
foreach (var property in propertyDeclarations)
|
||||||
|
{
|
||||||
|
var propertyName = property.Identifier.Text; // Property name
|
||||||
|
var propertyType = property.Type.ToString(); // Property type
|
||||||
|
|
||||||
|
// Add the property and its type to the list
|
||||||
|
properties.Add(new CustomProperty
|
||||||
|
{
|
||||||
|
Name = propertyName,
|
||||||
|
Type = propertyType
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return properties;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomProperty // <- Renamed to avoid conflicts with System.Reflection.PropertyInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
35
Examples/Nodify.Calculator/Operations/OperationsContainer.cs
Normal file
35
Examples/Nodify.Calculator/Operations/OperationsContainer.cs
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public static class OperationsContainer
|
||||||
|
{
|
||||||
|
[Operation(MinInput = 2, MaxInput = 10, GenerateInputNames = false)]
|
||||||
|
public static double Add(params double[] operands)
|
||||||
|
=> operands.Sum();
|
||||||
|
|
||||||
|
[Operation(MinInput = 2, MaxInput = 10, GenerateInputNames = false)]
|
||||||
|
public static double Multiply(params double[] operands)
|
||||||
|
=> operands.Aggregate((x, y) => x * y);
|
||||||
|
|
||||||
|
public static double Divide(double a, double b)
|
||||||
|
=> a / b;
|
||||||
|
|
||||||
|
public static double Subtract(double a, double b)
|
||||||
|
=> a - b;
|
||||||
|
|
||||||
|
public static double Pow(double value, double exp)
|
||||||
|
=> (double)Math.Pow((double)value, (double)exp);
|
||||||
|
|
||||||
|
public static double PI()
|
||||||
|
=> (double)Math.PI;
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class OperationAttribute : Attribute
|
||||||
|
{
|
||||||
|
public uint MaxInput { get; set; }
|
||||||
|
public uint MinInput { get; set; }
|
||||||
|
public bool GenerateInputNames { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Examples/Nodify.Calculator/Operations/ParamsOperation.cs
Normal file
14
Examples/Nodify.Calculator/Operations/ParamsOperation.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class ParamsOperation : IOperation
|
||||||
|
{
|
||||||
|
private readonly Func<double[], double> _func;
|
||||||
|
|
||||||
|
public ParamsOperation(Func<double[], double> func) => _func = func;
|
||||||
|
|
||||||
|
public double Execute(params double[] operands)
|
||||||
|
=> _func.Invoke(operands);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Examples/Nodify.Calculator/Operations/UnaryOperation.cs
Normal file
14
Examples/Nodify.Calculator/Operations/UnaryOperation.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class UnaryOperation : IOperation
|
||||||
|
{
|
||||||
|
private readonly Func<double, double> _func;
|
||||||
|
|
||||||
|
public UnaryOperation(Func<double, double> func) => _func = func;
|
||||||
|
|
||||||
|
public double Execute(params double[] operands)
|
||||||
|
=> _func.Invoke(operands[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
14
Examples/Nodify.Calculator/Operations/ValueOperation.cs
Normal file
14
Examples/Nodify.Calculator/Operations/ValueOperation.cs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public class ValueOperation : IOperation
|
||||||
|
{
|
||||||
|
private readonly Func<double> _func;
|
||||||
|
|
||||||
|
public ValueOperation(Func<double> func) => _func = func;
|
||||||
|
|
||||||
|
public double Execute(params double[] operands)
|
||||||
|
=> _func();
|
||||||
|
}
|
||||||
|
}
|
||||||
50
Examples/Nodify.Calculator/OperationsExtensions.cs
Normal file
50
Examples/Nodify.Calculator/OperationsExtensions.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public static class OperationsExtensions
|
||||||
|
{
|
||||||
|
public static Rect GetBoundingBox(this IEnumerable<OperationViewModel> nodes, double padding = 0, int gridCellSize = 15)
|
||||||
|
{
|
||||||
|
var minX = double.MaxValue;
|
||||||
|
var minY = double.MaxValue;
|
||||||
|
|
||||||
|
var maxX = double.MinValue;
|
||||||
|
var maxY = double.MinValue;
|
||||||
|
|
||||||
|
const int width = 200; //node.Width
|
||||||
|
const int height = 100; //node.Height
|
||||||
|
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
if (node.Location.X < minX)
|
||||||
|
{
|
||||||
|
minX = node.Location.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Location.Y < minY)
|
||||||
|
{
|
||||||
|
minY = node.Location.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sizeX = node.Location.X + width;
|
||||||
|
if (sizeX > maxX)
|
||||||
|
{
|
||||||
|
maxX = sizeX;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sizeY = node.Location.Y + height;
|
||||||
|
if (sizeY > maxY)
|
||||||
|
{
|
||||||
|
maxY = sizeY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new Rect(minX - padding, minY - padding, maxX - minX + padding * 2, maxY - minY + padding * 2);
|
||||||
|
result.X = (int)result.X / gridCellSize * gridCellSize;
|
||||||
|
result.Y = (int)result.Y / gridCellSize * gridCellSize;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
81
Examples/Nodify.Calculator/OperationsMenuView.xaml
Normal file
81
Examples/Nodify.Calculator/OperationsMenuView.xaml
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
<UserControl x:Class="Nodify.Calculator.OperationsMenuView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:Nodify.Calculator"
|
||||||
|
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
MinWidth="250"
|
||||||
|
d:DesignHeight="400"
|
||||||
|
d:DesignWidth="250"
|
||||||
|
d:DataContext="{d:DesignInstance local:OperationsMenuViewModel}"
|
||||||
|
Visibility="{Binding IsVisible, Converter={shared:BooleanToVisibilityConverter}}">
|
||||||
|
<UserControl.Resources>
|
||||||
|
<Style TargetType="{x:Type TextBlock}"
|
||||||
|
BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||||
|
<Setter Property="Foreground"
|
||||||
|
Value="{DynamicResource ForegroundBrush}" />
|
||||||
|
</Style>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<Border Padding="7"
|
||||||
|
CornerRadius="3"
|
||||||
|
Background="{DynamicResource Node.BackgroundBrush}"
|
||||||
|
BorderBrush="{StaticResource NodifyEditor.SelectionRectangleStrokeBrush}"
|
||||||
|
BorderThickness="2">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<ItemsControl Grid.Row="1"
|
||||||
|
x:Name="OperationsList"
|
||||||
|
Focusable="True"
|
||||||
|
KeyboardNavigation.TabNavigation="Cycle"
|
||||||
|
ItemsSource="{Binding MenuAvailableOperations}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type local:OperationInfoViewModel}">
|
||||||
|
<Button Content="{Binding Title}"
|
||||||
|
Command="{Binding DataContext.CreateOperationCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||||||
|
CommandParameter="{Binding}"
|
||||||
|
ClickMode="Press"
|
||||||
|
Background="Transparent"
|
||||||
|
BorderBrush="Transparent"
|
||||||
|
Foreground="{DynamicResource ForegroundBrush}"
|
||||||
|
Padding="3"
|
||||||
|
Cursor="Hand"
|
||||||
|
HorizontalContentAlignment="Left">
|
||||||
|
<Button.Style>
|
||||||
|
<Style TargetType="{x:Type Button}">
|
||||||
|
<Setter Property="FocusVisualStyle"
|
||||||
|
Value="{StaticResource {x:Static SystemParameters.FocusVisualStyleKey}}" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type Button}">
|
||||||
|
<Border Name="Border"
|
||||||
|
Background="{TemplateBinding Background}"
|
||||||
|
Padding="{TemplateBinding Padding}">
|
||||||
|
<ContentPresenter />
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver"
|
||||||
|
Value="True">
|
||||||
|
<Setter Property="Background"
|
||||||
|
TargetName="Border"
|
||||||
|
Value="{DynamicResource NodeInput.BorderBrush}" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</Button.Style>
|
||||||
|
</Button>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</UserControl>
|
||||||
51
Examples/Nodify.Calculator/OperationsMenuView.xaml.cs
Normal file
51
Examples/Nodify.Calculator/OperationsMenuView.xaml.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public partial class OperationsMenuView : UserControl
|
||||||
|
{
|
||||||
|
private readonly WeakReference<UIElement?> _focusToRestore = new WeakReference<UIElement?>(null!);
|
||||||
|
|
||||||
|
public OperationsMenuView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
IsVisibleChanged += OperationsMenuView_IsVisibleChanged;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OperationsMenuView_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (!IsLoaded)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (e.NewValue is true)
|
||||||
|
{
|
||||||
|
_focusToRestore.SetTarget(Keyboard.FocusedElement as UIElement);
|
||||||
|
Dispatcher.BeginInvoke(new Action(() => OperationsList.Focus()), System.Windows.Threading.DispatcherPriority.Input);
|
||||||
|
}
|
||||||
|
else if (e.NewValue is false)
|
||||||
|
{
|
||||||
|
if (_focusToRestore.TryGetTarget(out var elementToFocus))
|
||||||
|
{
|
||||||
|
Dispatcher.BeginInvoke(new Action(() =>
|
||||||
|
{
|
||||||
|
elementToFocus!.Focus();
|
||||||
|
}), System.Windows.Threading.DispatcherPriority.Input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnKeyDown(KeyEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.Key == Key.Escape)
|
||||||
|
{
|
||||||
|
SetCurrentValue(VisibilityProperty, Visibility.Collapsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
380
Examples/Nodify.Calculator/OperationsMenuViewModel.cs
Normal file
380
Examples/Nodify.Calculator/OperationsMenuViewModel.cs
Normal file
@@ -0,0 +1,380 @@
|
|||||||
|
using LiteDB;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
using NSwag;
|
||||||
|
using NSwag.CodeGeneration.CSharp;
|
||||||
|
using Nodify.Calculator.Models;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection.Metadata;
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
MenuAvailableOperations.Clear();
|
||||||
|
MenuAvailableOperations.AddRange(AvailableOperations);
|
||||||
|
Close();
|
||||||
|
Location = targetLocation;
|
||||||
|
IsVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenOnlyGetSetVariable(Point targetLocation, string className)
|
||||||
|
{
|
||||||
|
var getVM = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = "GET",
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.GET_SET,
|
||||||
|
IsModelNode = true,
|
||||||
|
ClassName = className,
|
||||||
|
};
|
||||||
|
var setVM = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = "SET",
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.GET_SET,
|
||||||
|
IsModelNode = true,
|
||||||
|
ClassName = className
|
||||||
|
};
|
||||||
|
MenuAvailableOperations.Clear();
|
||||||
|
MenuAvailableOperations.Add(getVM);
|
||||||
|
MenuAvailableOperations.Add(setVM);
|
||||||
|
Close();
|
||||||
|
Location = targetLocation;
|
||||||
|
IsVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OpenGetSetForVariable(Point targetLocation, OperationInfoViewModel variableInfo)
|
||||||
|
{
|
||||||
|
var getVM = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = "GET",
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.GET_SET,
|
||||||
|
IsSimpleVariable = true,
|
||||||
|
VariableType = variableInfo.VariableType,
|
||||||
|
DefaultValue = variableInfo.DefaultValue,
|
||||||
|
ClassName = variableInfo.Title ?? string.Empty
|
||||||
|
};
|
||||||
|
var setVM = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = "SET",
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.GET_SET,
|
||||||
|
IsSimpleVariable = true,
|
||||||
|
VariableType = variableInfo.VariableType,
|
||||||
|
DefaultValue = variableInfo.DefaultValue,
|
||||||
|
ClassName = variableInfo.Title ?? string.Empty
|
||||||
|
};
|
||||||
|
MenuAvailableOperations.Clear();
|
||||||
|
MenuAvailableOperations.Add(getVM);
|
||||||
|
MenuAvailableOperations.Add(setVM);
|
||||||
|
Close();
|
||||||
|
Location = targetLocation;
|
||||||
|
IsVisible = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
IsVisible = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NodifyObservableCollection<OperationInfoViewModel> MenuAvailableOperations { get; }
|
||||||
|
public NodifyObservableCollection<OperationInfoViewModel> AvailableOperations { get; }
|
||||||
|
public NodifyObservableCollection<OperationInfoViewModel> SwaggerOperations { get; }
|
||||||
|
public NodifyObservableCollection<OperationInfoViewModel> AvailableModels { get; }
|
||||||
|
public NodifyObservableCollection<OperationInfoViewModel> AvailableVariables { get; }
|
||||||
|
public INodifyCommand CreateOperationCommand { get; }
|
||||||
|
|
||||||
|
[Newtonsoft.Json.JsonIgnore]
|
||||||
|
[BsonIgnore]
|
||||||
|
private readonly CalculatorViewModel _calculator;
|
||||||
|
|
||||||
|
public OperationsMenuViewModel(CalculatorViewModel calculator)
|
||||||
|
{
|
||||||
|
_calculator = calculator;
|
||||||
|
List<OperationInfoViewModel> operations = new List<OperationInfoViewModel>();
|
||||||
|
AvailableModels = new NodifyObservableCollection<OperationInfoViewModel>();
|
||||||
|
AvailableVariables = new NodifyObservableCollection<OperationInfoViewModel>();
|
||||||
|
LoadVariablesFromDb();
|
||||||
|
|
||||||
|
var customModelDir = "CustomModels";
|
||||||
|
Directory.CreateDirectory(customModelDir);
|
||||||
|
var dirInfo = new DirectoryInfo(customModelDir);
|
||||||
|
var allFiles = dirInfo.GetFiles("*.cs");
|
||||||
|
foreach (var item in allFiles)
|
||||||
|
{
|
||||||
|
var flName = Path.GetFileNameWithoutExtension(item.Name);
|
||||||
|
var opVInfo = new OperationInfoViewModel()
|
||||||
|
{
|
||||||
|
Title = flName,
|
||||||
|
IsModelNode = true,
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.GET_SET,
|
||||||
|
ClassName = flName
|
||||||
|
};
|
||||||
|
AvailableModels.Add(opVInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
operations.AddRange(OperationFactory.GetSystemNodes());
|
||||||
|
operations.AddRange(OperationFactory.GetOperationsInfo(typeof(OperationsContainer)));
|
||||||
|
|
||||||
|
SwaggerOperations = new NodifyObservableCollection<OperationInfoViewModel>();
|
||||||
|
LoadSwaggerNodesFromDb();
|
||||||
|
|
||||||
|
operations.AddRange(SwaggerOperations);
|
||||||
|
|
||||||
|
AvailableOperations = new NodifyObservableCollection<OperationInfoViewModel>(operations);
|
||||||
|
MenuAvailableOperations = new NodifyObservableCollection<OperationInfoViewModel>(operations);
|
||||||
|
CreateOperationCommand = new DelegateCommand<OperationInfoViewModel>(CreateOperation);
|
||||||
|
ImportSwaggerCommand = new DelegateCommand(ImportSwagger);
|
||||||
|
AddVariableCommand = new DelegateCommand(AddVariable);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void AddNewModel(OperationInfoViewModel opModel)
|
||||||
|
{
|
||||||
|
if (System.Windows.Application.Current.Dispatcher.CheckAccess())
|
||||||
|
{
|
||||||
|
// Add directly if on UI thread
|
||||||
|
AvailableModels.Add(opModel);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Otherwise, marshal the call to the UI thread
|
||||||
|
System.Windows.Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
AvailableModels.Add(opModel);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public INodifyCommand ImportSwaggerCommand { get; }
|
||||||
|
public INodifyCommand AddVariableCommand { get; }
|
||||||
|
|
||||||
|
private void AddVariable()
|
||||||
|
{
|
||||||
|
var dialog = new AddVariableDialog();
|
||||||
|
dialog.Owner = System.Windows.Application.Current.MainWindow;
|
||||||
|
if (dialog.ShowDialog() != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var varName = dialog.VariableName;
|
||||||
|
var varType = dialog.VariableType;
|
||||||
|
var defaultVal = dialog.DefaultValue;
|
||||||
|
|
||||||
|
// Check for duplicate name
|
||||||
|
if (AvailableVariables.Any(v => v.Title == varName))
|
||||||
|
{
|
||||||
|
MessageBox.Show($"A variable named '{varName}' already exists.", "Duplicate Variable", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var varInfo = CreateVariableInfo(varName, varType, defaultVal);
|
||||||
|
AvailableVariables.Add(varInfo);
|
||||||
|
SaveVariableToDb(varName, varType, defaultVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private OperationInfoViewModel CreateVariableInfo(string name, string varType, string defaultValue)
|
||||||
|
{
|
||||||
|
return new OperationInfoViewModel
|
||||||
|
{
|
||||||
|
Title = name,
|
||||||
|
Type = OperationType.System,
|
||||||
|
sysOp = SystemOperations.GET_SET,
|
||||||
|
IsSimpleVariable = true,
|
||||||
|
VariableType = varType,
|
||||||
|
DefaultValue = defaultValue,
|
||||||
|
IsModelNode = false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveVariableToDb(string name, string varType, string defaultValue)
|
||||||
|
{
|
||||||
|
using var db = new LiteDbHelper<VariableModel>("Variables");
|
||||||
|
db.Insert(new VariableModel
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
VariableType = varType,
|
||||||
|
DefaultValue = defaultValue
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadVariablesFromDb()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var db = new LiteDbHelper<VariableModel>("Variables");
|
||||||
|
foreach (var v in db.FindAll())
|
||||||
|
{
|
||||||
|
AvailableVariables.Add(CreateVariableInfo(v.Name, v.VariableType, v.DefaultValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// DB may not exist yet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ImportSwagger()
|
||||||
|
{
|
||||||
|
var openFileDialog = new OpenFileDialog
|
||||||
|
{
|
||||||
|
Filter = "JSON files (*.json)|*.json|Text files (*.txt)|*.txt|All files (*.*)|*.*",
|
||||||
|
Title = "Import Swagger JSON File"
|
||||||
|
};
|
||||||
|
|
||||||
|
if (openFileDialog.ShowDialog() != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var nodes = ParseSwaggerFile(openFileDialog.FileName);
|
||||||
|
var fileName = Path.GetFileName(openFileDialog.FileName);
|
||||||
|
SaveSwaggerNodesToDb(nodes, fileName);
|
||||||
|
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
SwaggerOperations.Add(node);
|
||||||
|
AvailableOperations.Add(node);
|
||||||
|
MenuAvailableOperations.Add(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
MessageBox.Show($"Successfully imported {nodes.Count} API endpoints from Swagger.", "Import Swagger", MessageBoxButton.OK, MessageBoxImage.Information);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"Failed to parse Swagger file: {ex.Message}", "Import Error", MessageBoxButton.OK, MessageBoxImage.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<OperationInfoViewModel> ParseSwaggerFile(string jsonFilePath)
|
||||||
|
{
|
||||||
|
var operations = new List<OperationInfoViewModel>();
|
||||||
|
var openApiDocument = OpenApiDocument.FromFileAsync(jsonFilePath).Result;
|
||||||
|
|
||||||
|
foreach (var path in openApiDocument.Paths)
|
||||||
|
{
|
||||||
|
foreach (var method in path.Value)
|
||||||
|
{
|
||||||
|
var ovmodel = new OperationInfoViewModel
|
||||||
|
{
|
||||||
|
Title = path.Key,
|
||||||
|
OPType = method.Key,
|
||||||
|
Type = OperationType.API
|
||||||
|
};
|
||||||
|
|
||||||
|
var addedParams = new HashSet<string>();
|
||||||
|
foreach (var parameter in method.Value.Parameters)
|
||||||
|
{
|
||||||
|
if (addedParams.Add(parameter.Name))
|
||||||
|
{
|
||||||
|
ovmodel.Input.Add(parameter.Name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ovmodel.Output.Add("");
|
||||||
|
operations.Add(ovmodel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return operations;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveSwaggerNodesToDb(List<OperationInfoViewModel> nodes, string swaggerFileName)
|
||||||
|
{
|
||||||
|
using var db = new LiteDbHelper<SwaggerNodeModel>("SwaggerNodes");
|
||||||
|
db.DeleteMany(n => n.SwaggerFileName == swaggerFileName);
|
||||||
|
|
||||||
|
foreach (var node in nodes)
|
||||||
|
{
|
||||||
|
db.Insert(new SwaggerNodeModel
|
||||||
|
{
|
||||||
|
Title = node.Title ?? string.Empty,
|
||||||
|
OPType = node.OPType ?? string.Empty,
|
||||||
|
InputNames = new List<string>(node.Input),
|
||||||
|
SwaggerFileName = swaggerFileName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LoadSwaggerNodesFromDb()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var db = new LiteDbHelper<SwaggerNodeModel>("SwaggerNodes");
|
||||||
|
var savedNodes = db.FindAll();
|
||||||
|
|
||||||
|
foreach (var saved in savedNodes)
|
||||||
|
{
|
||||||
|
var ovmodel = new OperationInfoViewModel
|
||||||
|
{
|
||||||
|
Title = saved.Title,
|
||||||
|
OPType = saved.OPType,
|
||||||
|
Type = OperationType.API
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var inputName in saved.InputNames)
|
||||||
|
{
|
||||||
|
ovmodel.Input.Add(inputName);
|
||||||
|
}
|
||||||
|
|
||||||
|
ovmodel.Output.Add("");
|
||||||
|
SwaggerOperations.Add(ovmodel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// DB may not exist yet on first run
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.FirstOrDefault() : op.Input.FirstOrDefault();
|
||||||
|
if (connector != null && _calculator.CanCreateConnection(pending.Source, connector))
|
||||||
|
{
|
||||||
|
_calculator.CreateConnection(pending.Source, connector);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
36
Examples/Nodify.Calculator/PendingConnectionViewModel.cs
Normal file
36
Examples/Nodify.Calculator/PendingConnectionViewModel.cs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
Examples/Nodify.Calculator/SystemOperationViewModel.cs
Normal file
33
Examples/Nodify.Calculator/SystemOperationViewModel.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace Nodify.Calculator
|
||||||
|
{
|
||||||
|
public enum SystemOperations
|
||||||
|
{
|
||||||
|
COPY,
|
||||||
|
IF,
|
||||||
|
TAKE,
|
||||||
|
BEGIN,
|
||||||
|
END,
|
||||||
|
DEBUG_AND_CREATE_MODEL,
|
||||||
|
GET_SET,
|
||||||
|
PARSEJSON,
|
||||||
|
SPLIT,
|
||||||
|
AUTH
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SystemOperationViewModel : OperationViewModel
|
||||||
|
{
|
||||||
|
private SystemOperations _systemOperation;
|
||||||
|
|
||||||
|
public SystemOperations SystemOperationType
|
||||||
|
{
|
||||||
|
get => _systemOperation;
|
||||||
|
set => SetProperty(ref _systemOperation, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
189
Examples/Nodify.Calculator/leet swagger.txt
Normal file
189
Examples/Nodify.Calculator/leet swagger.txt
Normal file
@@ -0,0 +1,189 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.1",
|
||||||
|
"info": {
|
||||||
|
"title": "LeetU",
|
||||||
|
"version": "1.0"
|
||||||
|
},
|
||||||
|
"paths": {
|
||||||
|
"/course": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Course"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Course"
|
||||||
|
],
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Course"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"text/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Course"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"application/*+json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Course"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/course/{courseId}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Course"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "courseId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/student/{studentId}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Student"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "studentId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/student": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Student"
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/student/{studentId}/course": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Student"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "studentId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/student/{studentId}/course/{courseId}": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Student"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "studentId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "courseId",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Success"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"components": {
|
||||||
|
"schemas": {
|
||||||
|
"Course": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
"startDate": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"additionalProperties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
53
Examples/Nodify.Playground/App.xaml
Normal file
53
Examples/Nodify.Playground/App.xaml
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<Application x:Class="Nodify.Playground.App"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||||
|
StartupUri="MainWindow.xaml">
|
||||||
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<Style x:Key="{x:Static SystemParameters.FocusVisualStyleKey}">
|
||||||
|
<Setter Property="Control.Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate>
|
||||||
|
<Rectangle StrokeThickness="1"
|
||||||
|
StrokeDashArray="2"
|
||||||
|
Margin="-2"
|
||||||
|
RadiusX="3"
|
||||||
|
RadiusY="3"
|
||||||
|
Stroke="DodgerBlue" />
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Nodify;component/Themes/Nodify.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Nodify;component/Themes/FocusVisual.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Nodify.Shared;component/Themes/Icons.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Nodify.Shared;component/Themes/Nodify.xaml" />
|
||||||
|
<ResourceDictionary Source="pack://application:,,,/Nodify.Playground;component/Themes/Nodify.xaml" />
|
||||||
|
<ResourceDictionary>
|
||||||
|
<Color x:Key="NodifyEditor.FocusVisualColor">DodgerBlue</Color>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type nodify:HotKeyControl}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:HotKeyControl}}">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type nodify:HotKeyControl}">
|
||||||
|
<Border CornerRadius="3"
|
||||||
|
Background="OrangeRed">
|
||||||
|
<TextBlock Text="{Binding Number, RelativeSource={RelativeSource TemplatedParent}}"
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="White" />
|
||||||
|
</Border>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
|
</Application.Resources>
|
||||||
|
</Application>
|
||||||
17
Examples/Nodify.Playground/App.xaml.cs
Normal file
17
Examples/Nodify.Playground/App.xaml.cs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for App.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class App : Application
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
10
Examples/Nodify.Playground/AssemblyInfo.cs
Normal file
10
Examples/Nodify.Playground/AssemblyInfo.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
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)
|
||||||
|
)]
|
||||||
41
Examples/Nodify.Playground/BaseSettingViewModel.cs
Normal file
41
Examples/Nodify.Playground/BaseSettingViewModel.cs
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Nodify.Playground
|
||||||
|
{
|
||||||
|
public class BaseSettingViewModel<T> : 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)}.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
27
Examples/Nodify.Playground/Converters/UIntToRectConverter.cs
Normal file
27
Examples/Nodify.Playground/Converters/UIntToRectConverter.cs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
21
Examples/Nodify.Playground/Editor/CommentNodeViewModel.cs
Normal file
21
Examples/Nodify.Playground/Editor/CommentNodeViewModel.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
51
Examples/Nodify.Playground/Editor/ConnectionViewModel.cs
Normal file
51
Examples/Nodify.Playground/Editor/ConnectionViewModel.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
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<Point>(Split);
|
||||||
|
DisconnectCommand = new DelegateCommand(Remove);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Split(Point point)
|
||||||
|
=> Graph.Schema.SplitConnection(this, point);
|
||||||
|
|
||||||
|
public void Remove()
|
||||||
|
=> Graph.Connections.Remove(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
109
Examples/Nodify.Playground/Editor/ConnectorViewModel.cs
Normal file
109
Examples/Nodify.Playground/Editor/ConnectorViewModel.cs
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
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<ConnectionViewModel> Connections { get; } = new NodifyObservableCollection<ConnectionViewModel>();
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Examples/Nodify.Playground/Editor/FlowNodeViewModel.cs
Normal file
34
Examples/Nodify.Playground/Editor/FlowNodeViewModel.cs
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
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<ConnectorViewModel> Input { get; } = new NodifyObservableCollection<ConnectorViewModel>();
|
||||||
|
public NodifyObservableCollection<ConnectorViewModel> Output { get; } = new NodifyObservableCollection<ConnectorViewModel>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
133
Examples/Nodify.Playground/Editor/GraphSchema.cs
Normal file
133
Examples/Nodify.Playground/Editor/GraphSchema.cs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
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<NodeViewModel> nodes, string? text = default)
|
||||||
|
{
|
||||||
|
var rect = nodes.GetBoundingBox(50);
|
||||||
|
var comment = new CommentNodeViewModel
|
||||||
|
{
|
||||||
|
Location = rect.Location,
|
||||||
|
Size = rect.Size,
|
||||||
|
Title = text ?? "New comment"
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes[0].Graph.Nodes.Add(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rewires all connections from the source connector to the target connector if possible.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>The source must be an input connector.</remarks>
|
||||||
|
public void Rewire(ConnectorViewModel source, ConnectorViewModel target)
|
||||||
|
{
|
||||||
|
if (source == target || source.Flow != ConnectorFlow.Input)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var connectionsToRewire = source.Connections.ToList();
|
||||||
|
foreach (var connection in connectionsToRewire)
|
||||||
|
{
|
||||||
|
if (CanAddConnection(connection.Output, target))
|
||||||
|
{
|
||||||
|
source.Node.Graph.Connections.Remove(connection);
|
||||||
|
AddConnection(connection.Output, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
Examples/Nodify.Playground/Editor/KnotNodeViewModel.cs
Normal file
31
Examples/Nodify.Playground/Editor/KnotNodeViewModel.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
32
Examples/Nodify.Playground/Editor/NodeViewModel.cs
Normal file
32
Examples/Nodify.Playground/Editor/NodeViewModel.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
735
Examples/Nodify.Playground/Editor/NodifyEditorView.xaml
Normal file
735
Examples/Nodify.Playground/Editor/NodifyEditorView.xaml
Normal file
@@ -0,0 +1,735 @@
|
|||||||
|
<UserControl x:Class="Nodify.Playground.NodifyEditorView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:Nodify.Playground"
|
||||||
|
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||||
|
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Background="{DynamicResource NodifyEditor.BackgroundBrush}"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
d:DesignWidth="800">
|
||||||
|
|
||||||
|
<UserControl.DataContext>
|
||||||
|
<local:NodifyEditorViewModel />
|
||||||
|
</UserControl.DataContext>
|
||||||
|
|
||||||
|
<UserControl.Resources>
|
||||||
|
<shared:RandomBrushConverter x:Key="RandomBrushConverter" />
|
||||||
|
<local:FlowToDirectionConverter x:Key="FlowToDirectionConverter" />
|
||||||
|
<local:FlowToConnectorPositionConverter x:Key="FlowToConnectorPositionConverter" />
|
||||||
|
|
||||||
|
<GeometryDrawing x:Key="SmallGridGeometry"
|
||||||
|
Geometry="M0,0 L0,1 0.03,1 0.03,0.03 1,0.03 1,0 Z"
|
||||||
|
Brush="{DynamicResource GridLinesBrush}" />
|
||||||
|
|
||||||
|
<GeometryDrawing x:Key="LargeGridGeometry"
|
||||||
|
Geometry="M0,0 L0,1 0.015,1 0.015,0.015 1,0.015 1,0 Z"
|
||||||
|
Brush="{DynamicResource GridLinesBrush}" />
|
||||||
|
|
||||||
|
<DrawingBrush x:Key="SmallGridLinesDrawingBrush"
|
||||||
|
TileMode="Tile"
|
||||||
|
ViewportUnits="Absolute"
|
||||||
|
Viewport="{Binding GridSpacing, Source={x:Static local:EditorSettings.Instance}, Converter={local:UIntToRectConverter}}"
|
||||||
|
Transform="{Binding ViewportTransform, ElementName=Editor}"
|
||||||
|
Drawing="{StaticResource SmallGridGeometry}" />
|
||||||
|
|
||||||
|
<DrawingBrush x:Key="LargeGridLinesDrawingBrush"
|
||||||
|
TileMode="Tile"
|
||||||
|
ViewportUnits="Absolute"
|
||||||
|
Opacity="0.5"
|
||||||
|
Viewport="{Binding GridSpacing, Source={x:Static local:EditorSettings.Instance}, Converter={local:UIntToRectConverter Multiplier=10}}"
|
||||||
|
Transform="{Binding ViewportTransform, ElementName=Editor}"
|
||||||
|
Drawing="{StaticResource LargeGridGeometry}" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="SquareConnectorColor"
|
||||||
|
Color="MediumSlateBlue" />
|
||||||
|
<SolidColorBrush x:Key="TriangleConnectorColor"
|
||||||
|
Color="MediumVioletRed" />
|
||||||
|
<SolidColorBrush x:Key="SquareConnectorOutline"
|
||||||
|
Color="MediumSlateBlue"
|
||||||
|
Opacity="0.15" />
|
||||||
|
<SolidColorBrush x:Key="TriangleConnectorOutline"
|
||||||
|
Color="MediumVioletRed"
|
||||||
|
Opacity="0.15" />
|
||||||
|
|
||||||
|
<UIElement x:Key="ConnectionAnimationPlaceholder"
|
||||||
|
Opacity="1" />
|
||||||
|
|
||||||
|
<Storyboard x:Key="HighlightConnection">
|
||||||
|
<DoubleAnimation Storyboard.Target="{StaticResource ConnectionAnimationPlaceholder}"
|
||||||
|
Storyboard.TargetProperty="(UIElement.Opacity)"
|
||||||
|
Duration="0:0:0.3"
|
||||||
|
From="1"
|
||||||
|
To="0.3" />
|
||||||
|
</Storyboard>
|
||||||
|
|
||||||
|
<Style x:Key="ConnectionStyle"
|
||||||
|
TargetType="{x:Type nodify:BaseConnection}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:BaseConnection}}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Input.Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Square}">
|
||||||
|
<Setter Property="Stroke"
|
||||||
|
Value="{StaticResource SquareConnectorColor}" />
|
||||||
|
<Setter Property="Fill"
|
||||||
|
Value="{StaticResource SquareConnectorColor}" />
|
||||||
|
<Setter Property="OutlineBrush"
|
||||||
|
Value="{StaticResource SquareConnectorOutline}" />
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Input.Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||||
|
<Setter Property="Stroke"
|
||||||
|
Value="{StaticResource TriangleConnectorColor}" />
|
||||||
|
<Setter Property="Fill"
|
||||||
|
Value="{StaticResource TriangleConnectorColor}" />
|
||||||
|
<Setter Property="OutlineBrush"
|
||||||
|
Value="{StaticResource TriangleConnectorOutline}" />
|
||||||
|
</DataTrigger>
|
||||||
|
<Trigger Property="IsMouseDirectlyOver"
|
||||||
|
Value="True">
|
||||||
|
<Trigger.EnterActions>
|
||||||
|
<BeginStoryboard Name="HighlightConnection"
|
||||||
|
Storyboard="{StaticResource HighlightConnection}" />
|
||||||
|
</Trigger.EnterActions>
|
||||||
|
<Trigger.ExitActions>
|
||||||
|
<RemoveStoryboard BeginStoryboardName="HighlightConnection" />
|
||||||
|
</Trigger.ExitActions>
|
||||||
|
<Setter Property="Opacity"
|
||||||
|
Value="1" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsSelectable"
|
||||||
|
Value="True">
|
||||||
|
<Setter Property="Cursor"
|
||||||
|
Value="Hand" />
|
||||||
|
</Trigger>
|
||||||
|
<MultiTrigger>
|
||||||
|
<MultiTrigger.Conditions>
|
||||||
|
<Condition Property="IsMouseDirectlyOver"
|
||||||
|
Value="False" />
|
||||||
|
<Condition Property="IsSelected"
|
||||||
|
Value="False" />
|
||||||
|
</MultiTrigger.Conditions>
|
||||||
|
<MultiTrigger.Setters>
|
||||||
|
<Setter Property="OutlineBrush"
|
||||||
|
Value="Transparent" />
|
||||||
|
</MultiTrigger.Setters>
|
||||||
|
</MultiTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
<Setter Property="Opacity"
|
||||||
|
Value="{Binding Source={StaticResource ConnectionAnimationPlaceholder}, Path=Opacity}" />
|
||||||
|
<Setter Property="Stroke"
|
||||||
|
Value="{DynamicResource Connection.StrokeBrush}" />
|
||||||
|
<Setter Property="Fill"
|
||||||
|
Value="{DynamicResource Connection.StrokeBrush}" />
|
||||||
|
<Setter Property="OutlineBrush">
|
||||||
|
<Setter.Value>
|
||||||
|
<SolidColorBrush Color="{DynamicResource Connection.StrokeColor}"
|
||||||
|
Opacity="0.15" />
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="ToolTip"
|
||||||
|
Value="Double click to split" />
|
||||||
|
<Setter Property="Source"
|
||||||
|
Value="{Binding Output.Anchor}" />
|
||||||
|
<Setter Property="Target"
|
||||||
|
Value="{Binding Input.Anchor}" />
|
||||||
|
<Setter Property="SplitCommand"
|
||||||
|
Value="{Binding SplitCommand}" />
|
||||||
|
<Setter Property="DisconnectCommand"
|
||||||
|
Value="{Binding DisconnectCommand}" />
|
||||||
|
<Setter Property="SourceOffsetMode"
|
||||||
|
Value="{Binding ConnectionSourceOffsetMode, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="TargetOffsetMode"
|
||||||
|
Value="{Binding ConnectionTargetOffsetMode, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="SourceOffset"
|
||||||
|
Value="{Binding ConnectionSourceOffset.Size, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="TargetOffset"
|
||||||
|
Value="{Binding ConnectionTargetOffset.Size, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="ArrowSize"
|
||||||
|
Value="{Binding ConnectionArrowSize.Size, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="ArrowEnds"
|
||||||
|
Value="{Binding ArrowHeadEnds, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="ArrowShape"
|
||||||
|
Value="{Binding ArrowHeadShape, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="Spacing"
|
||||||
|
Value="{Binding ConnectionSpacing, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="Direction"
|
||||||
|
Value="{Binding Output.Flow, Converter={StaticResource FlowToDirectionConverter}}" />
|
||||||
|
<Setter Property="SourceOrientation"
|
||||||
|
Value="{Binding Output.Node.Orientation}" />
|
||||||
|
<Setter Property="TargetOrientation"
|
||||||
|
Value="{Binding Input.Node.Orientation}" />
|
||||||
|
<Setter Property="DirectionalArrowsCount"
|
||||||
|
Value="{Binding DirectionalArrowsCount, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="DirectionalArrowsOffset"
|
||||||
|
Value="{Binding DirectionalArrowsOffset, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="IsAnimatingDirectionalArrows"
|
||||||
|
Value="{Binding IsAnimatingConnections, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="DirectionalArrowsAnimationDuration"
|
||||||
|
Value="{Binding DirectionalArrowsAnimationDuration, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="Text"
|
||||||
|
Value="{Binding ConnectionText, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="IsSelectable"
|
||||||
|
Value="{Binding SelectableConnections, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="IsSelected"
|
||||||
|
Value="{Binding IsSelected}" />
|
||||||
|
<Setter Property="StrokeThickness"
|
||||||
|
Value="{Binding ConnectionStrokeThickness, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="OutlineThickness"
|
||||||
|
Value="{Binding ConnectionOutlineThickness, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="FocusVisualPadding"
|
||||||
|
Value="{Binding ConnectionFocusVisualPadding, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="CircuitConnectionTemplate">
|
||||||
|
<nodify:CircuitConnection Style="{StaticResource ConnectionStyle}"
|
||||||
|
Angle="{Binding CircuitConnectionAngle, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
CornerRadius="{Binding ConnectionCornerRadius, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="StepConnectionTemplate">
|
||||||
|
<nodify:StepConnection Style="{StaticResource ConnectionStyle}"
|
||||||
|
CornerRadius="{Binding ConnectionCornerRadius, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
SourcePosition="{Binding ., Converter={StaticResource FlowToConnectorPositionConverter}, ConverterParameter=Output}"
|
||||||
|
TargetPosition="{Binding ., Converter={StaticResource FlowToConnectorPositionConverter}, ConverterParameter=Input}" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="LineConnectionTemplate">
|
||||||
|
<nodify:LineConnection Style="{StaticResource ConnectionStyle}"
|
||||||
|
CornerRadius="{Binding ConnectionCornerRadius, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate x:Key="ConnectionTemplate">
|
||||||
|
<nodify:Connection Style="{StaticResource ConnectionStyle}" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<ControlTemplate x:Key="SquareConnector"
|
||||||
|
TargetType="Control">
|
||||||
|
<Rectangle Width="14"
|
||||||
|
Height="14"
|
||||||
|
StrokeDashCap="Round"
|
||||||
|
StrokeLineJoin="Round"
|
||||||
|
StrokeStartLineCap="Round"
|
||||||
|
StrokeEndLineCap="Round"
|
||||||
|
Stroke="{TemplateBinding BorderBrush}"
|
||||||
|
Fill="{TemplateBinding Background}"
|
||||||
|
StrokeThickness="2" />
|
||||||
|
</ControlTemplate>
|
||||||
|
|
||||||
|
<ControlTemplate x:Key="TriangleConnector"
|
||||||
|
TargetType="Control">
|
||||||
|
<Polygon Width="14"
|
||||||
|
Height="14"
|
||||||
|
Points="1,13 13,13 7,1"
|
||||||
|
StrokeDashCap="Round"
|
||||||
|
StrokeLineJoin="Round"
|
||||||
|
StrokeStartLineCap="Round"
|
||||||
|
StrokeEndLineCap="Round"
|
||||||
|
Stroke="{TemplateBinding BorderBrush}"
|
||||||
|
Fill="{TemplateBinding Background}"
|
||||||
|
StrokeThickness="2" />
|
||||||
|
</ControlTemplate>
|
||||||
|
|
||||||
|
<Storyboard x:Key="MarchingAnts">
|
||||||
|
<DoubleAnimation RepeatBehavior="Forever"
|
||||||
|
Storyboard.TargetProperty="StrokeDashOffset"
|
||||||
|
BeginTime="00:00:00"
|
||||||
|
Duration="0:3:0"
|
||||||
|
From="1000"
|
||||||
|
To="0" />
|
||||||
|
</Storyboard>
|
||||||
|
|
||||||
|
<Style x:Key="SelectionRectangleStyle"
|
||||||
|
TargetType="Rectangle"
|
||||||
|
BasedOn="{StaticResource NodifyEditor.SelectionRectangleStyle}">
|
||||||
|
<Setter Property="StrokeDashArray"
|
||||||
|
Value="4 4" />
|
||||||
|
<Setter Property="StrokeThickness"
|
||||||
|
Value="2" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
|
||||||
|
<BeginStoryboard Storyboard="{StaticResource MarchingAnts}" />
|
||||||
|
</EventTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="CuttingLineStyle"
|
||||||
|
TargetType="{x:Type nodify:CuttingLine}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:CuttingLine}}">
|
||||||
|
<Setter Property="StrokeDashArray"
|
||||||
|
Value="1 1" />
|
||||||
|
<Setter Property="StrokeThickness"
|
||||||
|
Value="2" />
|
||||||
|
</Style>
|
||||||
|
</UserControl.Resources>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<nodify:NodifyEditor x:Name="Editor"
|
||||||
|
ItemsSource="{Binding Nodes}"
|
||||||
|
SelectedItem="{Binding SelectedNode}"
|
||||||
|
SelectedItems="{Binding SelectedNodes}"
|
||||||
|
CanSelectMultipleItems="{Binding CanSelectMultipleNodes, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
Connections="{Binding Connections}"
|
||||||
|
SelectedConnection="{Binding SelectedConnection}"
|
||||||
|
SelectedConnections="{Binding SelectedConnections}"
|
||||||
|
CanSelectMultipleConnections="{Binding CanSelectMultipleConnections, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
PendingConnection="{Binding PendingConnection}"
|
||||||
|
DisconnectConnectorCommand="{Binding DisconnectConnectorCommand}"
|
||||||
|
ViewportLocation="{Binding Location.Value, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
ViewportSize="{Binding ViewportSize, Mode=OneWayToSource}"
|
||||||
|
ViewportZoom="{Binding Zoom, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
MinViewportZoom="{Binding MinZoom, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
MaxViewportZoom="{Binding MaxZoom, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
AutoPanSpeed="{Binding AutoPanningSpeed, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
AutoPanEdgeDistance="{Binding AutoPanningEdgeDistance, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
GridCellSize="{Binding GridSpacing, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
EnableRealtimeSelection="{Binding EnableRealtimeSelection, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
DisableAutoPanning="{Binding DisableAutoPanning, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
DisablePanning="{Binding DisablePanning, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
DisableZooming="{Binding DisableZooming, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
DisplayConnectionsOnTop="{Binding DisplayConnectionsOnTop, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
BringIntoViewSpeed="{Binding BringIntoViewSpeed, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
BringIntoViewMaxDuration="{Binding BringIntoViewMaxDuration, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
SelectionRectangleStyle="{StaticResource SelectionRectangleStyle}"
|
||||||
|
CuttingLineStyle="{StaticResource CuttingLineStyle}">
|
||||||
|
<nodify:NodifyEditor.Style>
|
||||||
|
<Style TargetType="{x:Type nodify:NodifyEditor}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:NodifyEditor}}">
|
||||||
|
<Setter Property="ConnectionTemplate"
|
||||||
|
Value="{StaticResource ConnectionTemplate}" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding ShowGridLines, Source={x:Static local:PlaygroundSettings.Instance}}"
|
||||||
|
Value="True">
|
||||||
|
<Setter Property="Background"
|
||||||
|
Value="{StaticResource SmallGridLinesDrawingBrush}" />
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding ConnectionStyle, Mode=TwoWay, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
Value="Line">
|
||||||
|
<Setter Property="ConnectionTemplate"
|
||||||
|
Value="{StaticResource LineConnectionTemplate}" />
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding ConnectionStyle, Mode=TwoWay, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
Value="Circuit">
|
||||||
|
<Setter Property="ConnectionTemplate"
|
||||||
|
Value="{StaticResource CircuitConnectionTemplate}" />
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding ConnectionStyle, Mode=TwoWay, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
Value="Step">
|
||||||
|
<Setter Property="ConnectionTemplate"
|
||||||
|
Value="{StaticResource StepConnectionTemplate}" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</nodify:NodifyEditor.Style>
|
||||||
|
|
||||||
|
<nodify:NodifyEditor.InputBindings>
|
||||||
|
<KeyBinding Key="Delete"
|
||||||
|
Command="{Binding DeleteSelectionCommand}" />
|
||||||
|
<KeyBinding Key="C"
|
||||||
|
Command="{Binding CommentSelectionCommand}" />
|
||||||
|
</nodify:NodifyEditor.InputBindings>
|
||||||
|
|
||||||
|
<nodify:NodifyEditor.Resources>
|
||||||
|
<Style TargetType="{x:Type nodify:PendingConnection}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:PendingConnection}}">
|
||||||
|
<Setter Property="CompletedCommand"
|
||||||
|
Value="{Binding Graph.CreateConnectionCommand}" />
|
||||||
|
<Setter Property="Source"
|
||||||
|
Value="{Binding Source, Mode=OneWayToSource}" />
|
||||||
|
<Setter Property="Target"
|
||||||
|
Value="{Binding PreviewTarget, Mode=OneWayToSource}" />
|
||||||
|
<Setter Property="PreviewTarget"
|
||||||
|
Value="{Binding PreviewTarget, Mode=OneWayToSource}" />
|
||||||
|
<Setter Property="Content"
|
||||||
|
Value="{Binding PreviewText}" />
|
||||||
|
<Setter Property="EnablePreview"
|
||||||
|
Value="{Binding EnablePendingConnectionPreview, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="EnableSnapping"
|
||||||
|
Value="{Binding EnablePendingConnectionSnapping, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="AllowOnlyConnectors"
|
||||||
|
Value="{Binding AllowConnectingToConnectorsOnly, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="Direction"
|
||||||
|
Value="{Binding Source.Flow, Converter={StaticResource FlowToDirectionConverter}}" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="{x:Type nodify:PendingConnection}">
|
||||||
|
<Canvas>
|
||||||
|
<nodify:Connection Source="{TemplateBinding SourceAnchor}"
|
||||||
|
Target="{TemplateBinding TargetAnchor}"
|
||||||
|
Direction="{TemplateBinding Direction}"
|
||||||
|
SourceOrientation="{Binding Source.Node.Orientation}"
|
||||||
|
TargetOrientation="{Binding TargetOrientation}"
|
||||||
|
DirectionalArrowsCount="{Binding DirectionalArrowsCount, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
StrokeThickness="{TemplateBinding StrokeThickness}"
|
||||||
|
SourceOffset="{Binding ConnectionSourceOffset.Size, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
TargetOffset="{Binding ConnectionTargetOffset.Size, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
SourceOffsetMode="{Binding ConnectionSourceOffsetMode, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
TargetOffsetMode="None"
|
||||||
|
ArrowSize="{Binding ConnectionArrowSize.Size, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
ArrowEnds="{Binding ArrowHeadEnds, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
ArrowShape="{Binding ArrowHeadShape, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
Spacing="{Binding ConnectionSpacing, Source={x:Static local:EditorSettings.Instance}}">
|
||||||
|
<nodify:Connection.Style>
|
||||||
|
<Style TargetType="nodify:Connection"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:Connection}}">
|
||||||
|
<Setter Property="Stroke"
|
||||||
|
Value="{DynamicResource Connection.StrokeBrush}" />
|
||||||
|
<Setter Property="Fill"
|
||||||
|
Value="{DynamicResource Connection.StrokeBrush}" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Source.Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Square}">
|
||||||
|
<Setter Property="Stroke"
|
||||||
|
Value="{StaticResource SquareConnectorColor}" />
|
||||||
|
<Setter Property="Fill"
|
||||||
|
Value="{StaticResource SquareConnectorColor}" />
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Source.Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||||
|
<Setter Property="Stroke"
|
||||||
|
Value="{StaticResource TriangleConnectorColor}" />
|
||||||
|
<Setter Property="Fill"
|
||||||
|
Value="{StaticResource TriangleConnectorColor}" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</nodify:Connection.Style>
|
||||||
|
</nodify:Connection>
|
||||||
|
<Border Background="{TemplateBinding Background}"
|
||||||
|
Canvas.Left="{Binding TargetAnchor.X, RelativeSource={RelativeSource TemplatedParent}}"
|
||||||
|
Canvas.Top="{Binding TargetAnchor.Y, RelativeSource={RelativeSource TemplatedParent}}"
|
||||||
|
Visibility="{Binding PreviewText, Converter={shared:StringToVisibilityConverter}}"
|
||||||
|
Padding="{TemplateBinding Padding}"
|
||||||
|
BorderThickness="{TemplateBinding BorderThickness}"
|
||||||
|
BorderBrush="{TemplateBinding BorderBrush}"
|
||||||
|
CornerRadius="3"
|
||||||
|
Margin="15">
|
||||||
|
<ContentPresenter />
|
||||||
|
</Border>
|
||||||
|
</Canvas>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type nodify:Connector}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:Connector}}">
|
||||||
|
<Setter Property="Anchor"
|
||||||
|
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||||
|
<Setter Property="IsConnected"
|
||||||
|
Value="{Binding IsConnected}" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type nodify:NodeInput}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:NodeInput}}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Square}">
|
||||||
|
<Setter Property="ConnectorTemplate"
|
||||||
|
Value="{StaticResource SquareConnector}" />
|
||||||
|
<Setter Property="BorderBrush"
|
||||||
|
Value="{StaticResource SquareConnectorColor}" />
|
||||||
|
<Setter Property="HeaderTemplate">
|
||||||
|
<Setter.Value>
|
||||||
|
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="{Binding Title}"
|
||||||
|
Margin="0 0 5 0" />
|
||||||
|
<TextBox Text="{Binding MaxConnections}"
|
||||||
|
MinWidth="30" />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||||
|
<Setter Property="ConnectorTemplate"
|
||||||
|
Value="{StaticResource TriangleConnector}" />
|
||||||
|
<Setter Property="BorderBrush"
|
||||||
|
Value="{StaticResource TriangleConnectorColor}" />
|
||||||
|
<Setter Property="HeaderTemplate">
|
||||||
|
<Setter.Value>
|
||||||
|
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock Text="{Binding Title}"
|
||||||
|
Margin="0 0 5 0"
|
||||||
|
VerticalAlignment="Center" />
|
||||||
|
<CheckBox />
|
||||||
|
</StackPanel>
|
||||||
|
</DataTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
<Setter Property="HeaderTemplate">
|
||||||
|
<Setter.Value>
|
||||||
|
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||||
|
<TextBlock Text="{Binding Title}" />
|
||||||
|
</DataTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="Header"
|
||||||
|
Value="{Binding}" />
|
||||||
|
<Setter Property="Anchor"
|
||||||
|
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||||
|
<Setter Property="IsConnected"
|
||||||
|
Value="{Binding IsConnected}" />
|
||||||
|
<Setter Property="Background"
|
||||||
|
Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="{x:Type nodify:NodeOutput}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:NodeOutput}}">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Square}">
|
||||||
|
<Setter Property="ConnectorTemplate"
|
||||||
|
Value="{StaticResource SquareConnector}" />
|
||||||
|
<Setter Property="BorderBrush"
|
||||||
|
Value="{StaticResource SquareConnectorColor}" />
|
||||||
|
</DataTrigger>
|
||||||
|
<DataTrigger Binding="{Binding Shape}"
|
||||||
|
Value="{x:Static local:ConnectorShape.Triangle}">
|
||||||
|
<Setter Property="ConnectorTemplate"
|
||||||
|
Value="{StaticResource TriangleConnector}" />
|
||||||
|
<Setter Property="BorderBrush"
|
||||||
|
Value="{StaticResource TriangleConnectorColor}" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
<Setter Property="Header"
|
||||||
|
Value="{Binding Title}" />
|
||||||
|
<Setter Property="Anchor"
|
||||||
|
Value="{Binding Anchor, Mode=OneWayToSource}" />
|
||||||
|
<Setter Property="IsConnected"
|
||||||
|
Value="{Binding IsConnected}" />
|
||||||
|
<Setter Property="Background"
|
||||||
|
Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:KnotNodeViewModel}">
|
||||||
|
<nodify:KnotNode Content="{Binding Connector}" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:CommentNodeViewModel}">
|
||||||
|
<nodify:GroupingNode ActualSize="{Binding Size}"
|
||||||
|
Header="{Binding Title}"
|
||||||
|
MovementMode="{Binding GroupingNodeMovement, Mode=TwoWay, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:FlowNodeViewModel}">
|
||||||
|
<nodify:Node Input="{Binding Input}"
|
||||||
|
Output="{Binding Output}"
|
||||||
|
Header="{Binding Title}" />
|
||||||
|
</DataTemplate>
|
||||||
|
|
||||||
|
<DataTemplate DataType="{x:Type local:VerticalNodeViewModel}">
|
||||||
|
<nodify:Node Header="{Binding Input}"
|
||||||
|
Footer="{Binding Output}"
|
||||||
|
Content="{Binding Title}">
|
||||||
|
<nodify:Node.ContentTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<TextBlock Text="{Binding}"
|
||||||
|
Margin="5" />
|
||||||
|
</DataTemplate>
|
||||||
|
</nodify:Node.ContentTemplate>
|
||||||
|
<nodify:Node.HeaderTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ItemsControl ItemsSource="{Binding}"
|
||||||
|
Focusable="False">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||||
|
<nodify:NodeInput Orientation="Vertical" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Center" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
</DataTemplate>
|
||||||
|
</nodify:Node.HeaderTemplate>
|
||||||
|
<nodify:Node.FooterTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<ItemsControl ItemsSource="{Binding}"
|
||||||
|
Focusable="False">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type local:ConnectorViewModel}">
|
||||||
|
<nodify:NodeOutput Orientation="Vertical" />
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
<ItemsControl.ItemsPanel>
|
||||||
|
<ItemsPanelTemplate>
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Center" />
|
||||||
|
</ItemsPanelTemplate>
|
||||||
|
</ItemsControl.ItemsPanel>
|
||||||
|
</ItemsControl>
|
||||||
|
</DataTemplate>
|
||||||
|
</nodify:Node.FooterTemplate>
|
||||||
|
</nodify:Node>
|
||||||
|
</DataTemplate>
|
||||||
|
</nodify:NodifyEditor.Resources>
|
||||||
|
|
||||||
|
<nodify:NodifyEditor.ItemContainerStyle>
|
||||||
|
<Style TargetType="{x:Type nodify:ItemContainer}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:ItemContainer}}">
|
||||||
|
<Setter Property="BorderThickness"
|
||||||
|
Value="2" />
|
||||||
|
<Setter Property="SelectedBorderThickness"
|
||||||
|
Value="4" />
|
||||||
|
<Setter Property="IsSelectable"
|
||||||
|
Value="{Binding SelectableNodes, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="IsDraggable"
|
||||||
|
Value="{Binding DraggableNodes, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
<Setter Property="CacheMode">
|
||||||
|
<Setter.Value>
|
||||||
|
<BitmapCache RenderAtScale="{Binding MaxZoom, Source={x:Static local:EditorSettings.Instance}}"
|
||||||
|
EnableClearType="True" />
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Setter Property="Location"
|
||||||
|
Value="{Binding Location}" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsSelected"
|
||||||
|
Value="True">
|
||||||
|
<Setter Property="Panel.ZIndex"
|
||||||
|
Value="1" />
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</nodify:NodifyEditor.ItemContainerStyle>
|
||||||
|
</nodify:NodifyEditor>
|
||||||
|
|
||||||
|
<Grid Background="{StaticResource LargeGridLinesDrawingBrush}"
|
||||||
|
Visibility="{Binding ShowGridLines, Source={x:Static local:PlaygroundSettings.Instance}, Converter={shared:BooleanToVisibilityConverter}}"
|
||||||
|
Panel.ZIndex="-2" />
|
||||||
|
|
||||||
|
<nodify:Minimap ItemsSource="{Binding ItemsSource, ElementName=Editor}"
|
||||||
|
ViewportSize="{Binding ViewportSize, ElementName=Editor}"
|
||||||
|
ViewportLocation="{Binding ViewportLocation, ElementName=Editor}"
|
||||||
|
Visibility="{Binding ShowMinimap, Source={x:Static local:PlaygroundSettings.Instance}, Converter={shared:BooleanToVisibilityConverter}}"
|
||||||
|
IsReadOnly="{Binding DisableMinimapControls, Source={x:Static local:PlaygroundSettings.Instance}}"
|
||||||
|
ResizeToViewport="{Binding ResizeToViewport, Source={x:Static local:PlaygroundSettings.Instance}}"
|
||||||
|
MaxViewportOffset="{Binding MinimapMaxViewportOffset.Size, Source={x:Static local:PlaygroundSettings.Instance}}"
|
||||||
|
Zoom="Minimap_Zoom"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Width="300"
|
||||||
|
Height="200"
|
||||||
|
Margin="5 40">
|
||||||
|
<nodify:Minimap.ItemTemplate>
|
||||||
|
<DataTemplate DataType="{x:Type local:NodeViewModel}">
|
||||||
|
<Grid />
|
||||||
|
</DataTemplate>
|
||||||
|
</nodify:Minimap.ItemTemplate>
|
||||||
|
<nodify:Minimap.ItemContainerStyle>
|
||||||
|
<Style TargetType="{x:Type nodify:MinimapItem}"
|
||||||
|
BasedOn="{StaticResource {x:Type nodify:MinimapItem}}">
|
||||||
|
<Setter Property="Location"
|
||||||
|
Value="{Binding Location}" />
|
||||||
|
<Setter Property="Width"
|
||||||
|
Value="150" />
|
||||||
|
<Setter Property="Height"
|
||||||
|
Value="130" />
|
||||||
|
</Style>
|
||||||
|
</nodify:Minimap.ItemContainerStyle>
|
||||||
|
</nodify:Minimap>
|
||||||
|
|
||||||
|
<StackPanel HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Top"
|
||||||
|
Margin="5 60"
|
||||||
|
Width="250">
|
||||||
|
<Border CornerRadius="3"
|
||||||
|
Visibility="{Binding SelectedConnection, Converter={shared:BooleanToVisibilityConverter}}"
|
||||||
|
Background="{DynamicResource PanelBackgroundBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="{DynamicResource BorderBrush}"
|
||||||
|
Margin="0 0 0 10">
|
||||||
|
<StackPanel Margin="10">
|
||||||
|
<StackPanel.Resources>
|
||||||
|
<Style TargetType="{x:Type TextBlock}"
|
||||||
|
BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||||
|
<Setter Property="Margin"
|
||||||
|
Value="0 0 0 5" />
|
||||||
|
</Style>
|
||||||
|
</StackPanel.Resources>
|
||||||
|
|
||||||
|
<StackPanel Margin="0 0 0 14">
|
||||||
|
<TextBlock Text="Selected connection"
|
||||||
|
Foreground="{DynamicResource Node.ForegroundBrush}"
|
||||||
|
FontWeight="Bold" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<TextBlock TextWrapping="Wrap"
|
||||||
|
Margin="0 0 0 14"
|
||||||
|
Foreground="{DynamicResource Node.ForegroundBrush}">
|
||||||
|
<Run>From</Run>
|
||||||
|
<Run Text="{Binding SelectedConnection.Output.Node.Title}"
|
||||||
|
Foreground="Red" />
|
||||||
|
<Run> - </Run>
|
||||||
|
<Run Text="{Binding SelectedConnection.Output.Title}"
|
||||||
|
Foreground="Red" />
|
||||||
|
<Run>to</Run>
|
||||||
|
<Run Text="{Binding SelectedConnection.Input.Node.Title}"
|
||||||
|
Foreground="Red" />
|
||||||
|
<Run> - </Run>
|
||||||
|
<Run Text="{Binding SelectedConnection.Input.Title}"
|
||||||
|
Foreground="Red" />
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<Button Command="{Binding SelectedConnection.DisconnectCommand}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Style="{StaticResource HollowButton}"
|
||||||
|
Content="Delete" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
<Border CornerRadius="3"
|
||||||
|
Visibility="{Binding SelectedNode, Converter={shared:BooleanToVisibilityConverter}}"
|
||||||
|
Background="{DynamicResource PanelBackgroundBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="{DynamicResource BorderBrush}">
|
||||||
|
<StackPanel Margin="10">
|
||||||
|
<StackPanel.Resources>
|
||||||
|
<Style TargetType="{x:Type TextBlock}"
|
||||||
|
BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||||
|
<Setter Property="Margin"
|
||||||
|
Value="0 0 0 5" />
|
||||||
|
</Style>
|
||||||
|
</StackPanel.Resources>
|
||||||
|
|
||||||
|
<StackPanel Margin="0 0 0 14">
|
||||||
|
<TextBlock Text="Selected node"
|
||||||
|
Foreground="{DynamicResource Node.ForegroundBrush}"
|
||||||
|
FontWeight="Bold" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<TextBlock TextWrapping="Wrap"
|
||||||
|
Margin="0 0 0 14"
|
||||||
|
Foreground="{DynamicResource Node.ForegroundBrush}">
|
||||||
|
<Run>Title: </Run>
|
||||||
|
<Run Text="{Binding SelectedNode.Title}"
|
||||||
|
Foreground="Red" />
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock TextWrapping="Wrap"
|
||||||
|
Margin="0 0 0 14"
|
||||||
|
Foreground="{DynamicResource Node.ForegroundBrush}">
|
||||||
|
<Run>Location: </Run>
|
||||||
|
<Run Text="{Binding SelectedNode.Location}"
|
||||||
|
Foreground="Red" />
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<Button Command="{Binding SelectedNode.DeleteCommand}"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Style="{StaticResource HollowButton}"
|
||||||
|
Content="Delete" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
51
Examples/Nodify.Playground/Editor/NodifyEditorView.xaml.cs
Normal file
51
Examples/Nodify.Playground/Editor/NodifyEditorView.xaml.cs
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
using Nodify.Events;
|
||||||
|
using Nodify.Interactivity;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Nodify.Playground
|
||||||
|
{
|
||||||
|
public partial class NodifyEditorView : UserControl
|
||||||
|
{
|
||||||
|
public NodifyEditor EditorInstance => Editor;
|
||||||
|
|
||||||
|
public NodifyEditorView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
EditorInstance.ActiveNavigationLayerChanged += DisplayActiveNavigationLayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static NodifyEditorView()
|
||||||
|
{
|
||||||
|
InputProcessor.Shared<Connector>.ReplaceHandlerFactory<ConnectorState.Connecting>(elem => new CustomConnecting(elem));
|
||||||
|
InputProcessor.Shared<Connector>.RegisterHandlerFactory(elem => new RetargetConnections(elem));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Minimap_Zoom(object sender, ZoomEventArgs e)
|
||||||
|
{
|
||||||
|
EditorInstance.ZoomAtPosition(e.Zoom, e.Location);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DisplayActiveNavigationLayer(KeyboardNavigationLayerId layerId)
|
||||||
|
{
|
||||||
|
var editorVm = (NodifyEditorViewModel)EditorInstance.DataContext;
|
||||||
|
|
||||||
|
if (layerId == KeyboardNavigationLayerId.Nodes)
|
||||||
|
{
|
||||||
|
editorVm.KeyboardNavigationLayer = nameof(KeyboardNavigationLayerId.Nodes);
|
||||||
|
}
|
||||||
|
else if (layerId == KeyboardNavigationLayerId.Connections)
|
||||||
|
{
|
||||||
|
editorVm.KeyboardNavigationLayer = nameof(KeyboardNavigationLayerId.Connections);
|
||||||
|
}
|
||||||
|
else if (layerId == KeyboardNavigationLayerId.Decorators)
|
||||||
|
{
|
||||||
|
editorVm.KeyboardNavigationLayer = nameof(KeyboardNavigationLayerId.Decorators);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
editorVm.KeyboardNavigationLayer = "Custom";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
133
Examples/Nodify.Playground/Editor/NodifyEditorViewModel.cs
Normal file
133
Examples/Nodify.Playground/Editor/NodifyEditorViewModel.cs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Nodify.Playground
|
||||||
|
{
|
||||||
|
public class NodifyEditorViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
public NodifyEditorViewModel()
|
||||||
|
{
|
||||||
|
Schema = new GraphSchema();
|
||||||
|
|
||||||
|
PendingConnection = new PendingConnectionViewModel
|
||||||
|
{
|
||||||
|
Graph = this
|
||||||
|
};
|
||||||
|
|
||||||
|
DeleteSelectionCommand = new DelegateCommand(DeleteSelection, () => SelectedNodes.Count > 0 || SelectedConnections.Count > 0);
|
||||||
|
CommentSelectionCommand = new RequeryCommand(() => Schema.AddCommentAroundNodes(SelectedNodes, "New comment"), () => SelectedNodes.Count > 0);
|
||||||
|
DisconnectConnectorCommand = new DelegateCommand<ConnectorViewModel>(c => c.Disconnect());
|
||||||
|
CreateConnectionCommand = new DelegateCommand<object>(target => Schema.TryAddConnection(PendingConnection.Source!, target),
|
||||||
|
target => PendingConnection.Source != null && target != null && Schema.CanAddConnection(PendingConnection.Source, target));
|
||||||
|
|
||||||
|
Connections.WhenAdded(c =>
|
||||||
|
{
|
||||||
|
c.Graph = this;
|
||||||
|
c.Input.Connections.Add(c);
|
||||||
|
c.Output.Connections.Add(c);
|
||||||
|
})
|
||||||
|
// Called when the collection is cleared
|
||||||
|
.WhenRemoved(c =>
|
||||||
|
{
|
||||||
|
c.Input.Connections.Remove(c);
|
||||||
|
c.Output.Connections.Remove(c);
|
||||||
|
});
|
||||||
|
|
||||||
|
Nodes.WhenAdded(x => x.Graph = this)
|
||||||
|
// Not called when the collection is cleared
|
||||||
|
.WhenRemoved(x =>
|
||||||
|
{
|
||||||
|
if (x is FlowNodeViewModel flow)
|
||||||
|
{
|
||||||
|
flow.Disconnect();
|
||||||
|
}
|
||||||
|
else if (x is KnotNodeViewModel knot)
|
||||||
|
{
|
||||||
|
knot.Connector.Disconnect();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.WhenCleared(x => Connections.Clear());
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodifyObservableCollection<NodeViewModel> _nodes = new NodifyObservableCollection<NodeViewModel>();
|
||||||
|
public NodifyObservableCollection<NodeViewModel> Nodes
|
||||||
|
{
|
||||||
|
get => _nodes;
|
||||||
|
set => SetProperty(ref _nodes, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodifyObservableCollection<NodeViewModel> _selectedNodes = new NodifyObservableCollection<NodeViewModel>();
|
||||||
|
public NodifyObservableCollection<NodeViewModel> SelectedNodes
|
||||||
|
{
|
||||||
|
get => _selectedNodes;
|
||||||
|
set => SetProperty(ref _selectedNodes, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodifyObservableCollection<ConnectionViewModel> _selectedConnections = new NodifyObservableCollection<ConnectionViewModel>();
|
||||||
|
public NodifyObservableCollection<ConnectionViewModel> SelectedConnections
|
||||||
|
{
|
||||||
|
get => _selectedConnections;
|
||||||
|
set => SetProperty(ref _selectedConnections, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodifyObservableCollection<ConnectionViewModel> _connections = new NodifyObservableCollection<ConnectionViewModel>();
|
||||||
|
public NodifyObservableCollection<ConnectionViewModel> Connections
|
||||||
|
{
|
||||||
|
get => _connections;
|
||||||
|
set => SetProperty(ref _connections, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Size _viewportSize;
|
||||||
|
public Size ViewportSize
|
||||||
|
{
|
||||||
|
get => _viewportSize;
|
||||||
|
set => SetProperty(ref _viewportSize, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PendingConnectionViewModel PendingConnection { get; }
|
||||||
|
|
||||||
|
private ConnectionViewModel? _selectedConnection;
|
||||||
|
public ConnectionViewModel? SelectedConnection
|
||||||
|
{
|
||||||
|
get => _selectedConnection;
|
||||||
|
set => SetProperty(ref _selectedConnection, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodeViewModel? _selectedNode;
|
||||||
|
public NodeViewModel? SelectedNode
|
||||||
|
{
|
||||||
|
get => _selectedNode;
|
||||||
|
set => SetProperty(ref _selectedNode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public GraphSchema Schema { get; }
|
||||||
|
|
||||||
|
private string? _keyboardNavigationLayer;
|
||||||
|
public string? KeyboardNavigationLayer
|
||||||
|
{
|
||||||
|
get => _keyboardNavigationLayer;
|
||||||
|
set => SetProperty(ref _keyboardNavigationLayer, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ICommand DeleteSelectionCommand { get; }
|
||||||
|
public ICommand DisconnectConnectorCommand { get; }
|
||||||
|
public ICommand CreateConnectionCommand { get; }
|
||||||
|
public ICommand CommentSelectionCommand { get; }
|
||||||
|
|
||||||
|
private void DeleteSelection()
|
||||||
|
{
|
||||||
|
foreach (var connection in SelectedConnections.ToList())
|
||||||
|
{
|
||||||
|
connection.Remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
var selected = SelectedNodes.ToList();
|
||||||
|
|
||||||
|
for (int i = 0; i < selected.Count; i++)
|
||||||
|
{
|
||||||
|
Nodes.Remove(selected[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Nodify.Playground
|
||||||
|
{
|
||||||
|
public class PendingConnectionViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private NodifyEditorViewModel _graph = default!;
|
||||||
|
public NodifyEditorViewModel Graph
|
||||||
|
{
|
||||||
|
get => _graph;
|
||||||
|
internal set => SetProperty(ref _graph, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectorViewModel? _source;
|
||||||
|
public ConnectorViewModel? Source
|
||||||
|
{
|
||||||
|
get => _source;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if(SetProperty(ref _source, value))
|
||||||
|
{
|
||||||
|
SetTargetOrientation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private object? _previewTarget;
|
||||||
|
public object? PreviewTarget
|
||||||
|
{
|
||||||
|
get => _previewTarget;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (SetProperty(ref _previewTarget, value))
|
||||||
|
{
|
||||||
|
OnPreviewTargetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? _previewText;
|
||||||
|
public string? PreviewText
|
||||||
|
{
|
||||||
|
get => _previewText;
|
||||||
|
set => SetProperty(ref _previewText, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Orientation _targetOrientation;
|
||||||
|
public Orientation TargetOrientation
|
||||||
|
{
|
||||||
|
get => _targetOrientation;
|
||||||
|
set => SetProperty(ref _targetOrientation, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnPreviewTargetChanged()
|
||||||
|
{
|
||||||
|
bool canConnect = PreviewTarget != null && Graph.Schema.CanAddConnection(Source!, PreviewTarget);
|
||||||
|
PreviewText = PreviewTarget switch
|
||||||
|
{
|
||||||
|
ConnectorViewModel con when con == Source => $"Can't connect to self",
|
||||||
|
ConnectorViewModel con => $"{(canConnect ? "Connect" : "Can't connect")} to {con.Title ?? "pin"}",
|
||||||
|
FlowNodeViewModel flow => $"{(canConnect ? "Connect" : "Can't connect")} to {flow.Title ?? "node"}",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
SetTargetOrientation();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetTargetOrientation()
|
||||||
|
{
|
||||||
|
TargetOrientation = PreviewTarget switch
|
||||||
|
{
|
||||||
|
ConnectorViewModel con when con.Node is FlowNodeViewModel flow => flow.Orientation,
|
||||||
|
FlowNodeViewModel flow => flow.Orientation,
|
||||||
|
NodifyEditorViewModel editor when Source?.Node is FlowNodeViewModel flow => flow.Orientation,
|
||||||
|
_ => Orientation.Horizontal,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
132
Examples/Nodify.Playground/Editor/RetargetConnections.cs
Normal file
132
Examples/Nodify.Playground/Editor/RetargetConnections.cs
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
using Nodify.Interactivity;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Nodify.Playground
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Connecting state that prevents connecting when <see cref="RetargetConnections"/> is in progress.
|
||||||
|
/// </summary>
|
||||||
|
public class CustomConnecting : ConnectorState.Connecting
|
||||||
|
{
|
||||||
|
protected override bool CanBegin => !RetargetConnections.InProgress;
|
||||||
|
|
||||||
|
public CustomConnecting(Connector connector) : base(connector)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Hold CTRL+LeftClick on a connector to start reconnecting it.
|
||||||
|
/// </summary>
|
||||||
|
public class RetargetConnections : DragState<Connector>
|
||||||
|
{
|
||||||
|
public static InputGestureRef Reconnect { get; } = new Interactivity.MouseGesture(MouseAction.LeftClick, ModifierKeys.Control)
|
||||||
|
{
|
||||||
|
IgnoreModifierKeysOnRelease = true
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Used to prevent connecting when <see cref="EditorSettings.EnableStickyConnectors"/> is enabled.
|
||||||
|
/// </summary>
|
||||||
|
public static bool InProgress { get; private set; }
|
||||||
|
|
||||||
|
protected override bool CanBegin => ViewModel.IsConnected && ViewModel.Flow == ConnectorFlow.Input;
|
||||||
|
protected override bool IsToggle => EditorSettings.Instance.EnableStickyConnectors;
|
||||||
|
|
||||||
|
private ConnectorViewModel ViewModel => (ConnectorViewModel)Element.DataContext;
|
||||||
|
private Vector _connectorOffset;
|
||||||
|
private Connector? _targetConnector;
|
||||||
|
|
||||||
|
public RetargetConnections(Connector element) : base(element, Reconnect, EditorGestures.Mappings.Connector.CancelAction)
|
||||||
|
{
|
||||||
|
PositionElement = Element.Editor ?? (IInputElement)Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnBegin(InputEventArgs e)
|
||||||
|
{
|
||||||
|
_connectorOffset = ViewModel.Node.Orientation == Orientation.Horizontal
|
||||||
|
? (Vector)EditorSettings.Instance.ConnectionTargetOffset.Value
|
||||||
|
: new Vector(EditorSettings.Instance.ConnectionTargetOffset.Value.Y, EditorSettings.Instance.ConnectionTargetOffset.Value.X);
|
||||||
|
|
||||||
|
InProgress = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnMouseMove(MouseEventArgs e)
|
||||||
|
{
|
||||||
|
var position = Element.Editor!.MouseLocation;
|
||||||
|
|
||||||
|
if (EditorSettings.Instance.EnablePendingConnectionHitTesting)
|
||||||
|
{
|
||||||
|
var connector = Element.FindTargetConnector(position);
|
||||||
|
connector?.UpdateAnchor();
|
||||||
|
|
||||||
|
SetTargetConnector(connector);
|
||||||
|
UpdateConnections(connector != null ? connector.Anchor : position + _connectorOffset);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UpdateConnections(position + _connectorOffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateConnections(Point position)
|
||||||
|
{
|
||||||
|
foreach (var connection in ViewModel.Connections)
|
||||||
|
{
|
||||||
|
connection.Input.Anchor = position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnEnd(InputEventArgs e)
|
||||||
|
{
|
||||||
|
var position = Element.Editor!.MouseLocation;
|
||||||
|
var target = Element.FindTargetConnector(position);
|
||||||
|
target?.UpdateAnchor();
|
||||||
|
|
||||||
|
if (target?.DataContext is ConnectorViewModel targetVM && ViewModel != targetVM)
|
||||||
|
{
|
||||||
|
ViewModel.Node.Graph.Schema.Rewire(ViewModel, targetVM);
|
||||||
|
}
|
||||||
|
|
||||||
|
SetTargetConnector(null);
|
||||||
|
|
||||||
|
// Reset the position of connections that were not rewired
|
||||||
|
Element.UpdateAnchor();
|
||||||
|
InProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnCancel(InputEventArgs e)
|
||||||
|
{
|
||||||
|
SetTargetConnector(null);
|
||||||
|
|
||||||
|
// Reset the position of connections that were not rewired
|
||||||
|
Element.UpdateAnchor();
|
||||||
|
InProgress = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets the connection target and updates the visual state of the target element.
|
||||||
|
/// </summary>
|
||||||
|
private void SetTargetConnector(Connector? target)
|
||||||
|
{
|
||||||
|
if (target == _targetConnector)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_targetConnector != null)
|
||||||
|
{
|
||||||
|
PendingConnection.SetIsOverElement(_targetConnector, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target != null)
|
||||||
|
{
|
||||||
|
PendingConnection.SetIsOverElement(target, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
_targetConnector = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
Examples/Nodify.Playground/Editor/VerticalNodeViewModel.cs
Normal file
12
Examples/Nodify.Playground/Editor/VerticalNodeViewModel.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
using System.Windows.Controls;
|
||||||
|
|
||||||
|
namespace Nodify.Playground
|
||||||
|
{
|
||||||
|
public class VerticalNodeViewModel : FlowNodeViewModel
|
||||||
|
{
|
||||||
|
public VerticalNodeViewModel()
|
||||||
|
{
|
||||||
|
Orientation = Orientation.Vertical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
82
Examples/Nodify.Playground/EditorInputMode.cs
Normal file
82
Examples/Nodify.Playground/EditorInputMode.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
using Nodify.Interactivity;
|
||||||
|
using System.Windows.Input;
|
||||||
|
|
||||||
|
namespace Nodify.Playground
|
||||||
|
{
|
||||||
|
public enum EditorInputMode
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
PanOnly,
|
||||||
|
SelectOnly,
|
||||||
|
CutOnly
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum EditorGesturesMappings
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
Custom
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EditorInputModeExtensions
|
||||||
|
{
|
||||||
|
public static void Apply(this EditorGestures mappings, EditorInputMode inputMode)
|
||||||
|
{
|
||||||
|
mappings.Apply(PlaygroundSettings.Instance.EditorGesturesMappings.ToGesturesMappings());
|
||||||
|
|
||||||
|
switch (inputMode)
|
||||||
|
{
|
||||||
|
case EditorInputMode.PanOnly:
|
||||||
|
mappings.Editor.Selection.Unbind();
|
||||||
|
mappings.Editor.Cutting.Unbind();
|
||||||
|
mappings.ItemContainer.Selection.Unbind();
|
||||||
|
mappings.ItemContainer.Drag.Unbind();
|
||||||
|
mappings.Connector.Connect.Unbind();
|
||||||
|
break;
|
||||||
|
case EditorInputMode.SelectOnly:
|
||||||
|
mappings.Editor.Pan.Unbind();
|
||||||
|
mappings.Editor.Cutting.Unbind();
|
||||||
|
mappings.ItemContainer.Drag.Unbind();
|
||||||
|
mappings.Connector.Connect.Unbind();
|
||||||
|
break;
|
||||||
|
case EditorInputMode.CutOnly:
|
||||||
|
mappings.Editor.Cutting.Value = new Interactivity.MouseGesture(MouseAction.LeftClick);
|
||||||
|
mappings.Editor.Selection.Unbind();
|
||||||
|
mappings.Editor.Pan.Unbind();
|
||||||
|
mappings.ItemContainer.Selection.Unbind();
|
||||||
|
mappings.ItemContainer.Drag.Unbind();
|
||||||
|
mappings.Connector.Connect.Unbind();
|
||||||
|
break;
|
||||||
|
case EditorInputMode.Default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void Apply(this EditorGestures value, EditorGesturesMappings mappings)
|
||||||
|
{
|
||||||
|
var newMappings = mappings.ToGesturesMappings();
|
||||||
|
value.Apply(newMappings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EditorGestures ToGesturesMappings(this EditorGesturesMappings mappings)
|
||||||
|
{
|
||||||
|
return mappings switch
|
||||||
|
{
|
||||||
|
EditorGesturesMappings.Custom => new CustomGesturesMappings(),
|
||||||
|
_ => new EditorGestures()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomGesturesMappings : EditorGestures
|
||||||
|
{
|
||||||
|
public CustomGesturesMappings()
|
||||||
|
{
|
||||||
|
Editor.Pan.Value = new AnyGesture(new Interactivity.MouseGesture(MouseAction.LeftClick), new Interactivity.MouseGesture(MouseAction.MiddleClick));
|
||||||
|
Editor.ZoomModifierKey = ModifierKeys.Control;
|
||||||
|
Editor.Selection.Apply(new SelectionGestures(MouseAction.RightClick));
|
||||||
|
// comment to drag with right click - we copy the default gestures of the item container which uses left click for selection
|
||||||
|
ItemContainer.Drag.Value = new AnyGesture(ItemContainer.Selection.Replace.Value, ItemContainer.Selection.Remove.Value, ItemContainer.Selection.Append.Value, ItemContainer.Selection.Invert.Value);
|
||||||
|
ItemContainer.Selection.Apply(Editor.Selection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
951
Examples/Nodify.Playground/EditorSettings.cs
Normal file
951
Examples/Nodify.Playground/EditorSettings.cs
Normal file
@@ -0,0 +1,951 @@
|
|||||||
|
using Nodify.Interactivity;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Nodify.Playground
|
||||||
|
{
|
||||||
|
public enum ConnectionStyle
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
Line,
|
||||||
|
Circuit,
|
||||||
|
Step
|
||||||
|
}
|
||||||
|
|
||||||
|
public class EditorSettings : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly IReadOnlyCollection<ISettingViewModel> _settings;
|
||||||
|
public IEnumerable<ISettingViewModel> Settings => PlaygroundSettings.Instance.FilterAndSort(_settings);
|
||||||
|
|
||||||
|
private readonly IReadOnlyCollection<ISettingViewModel> _advancedSettings;
|
||||||
|
public IEnumerable<ISettingViewModel> AdvancedSettings => PlaygroundSettings.Instance.FilterAndSort(_advancedSettings);
|
||||||
|
|
||||||
|
private EditorSettings()
|
||||||
|
{
|
||||||
|
PlaygroundSettings.Instance.PropertyChanged += OnSearchTextChanged;
|
||||||
|
|
||||||
|
_settings = new List<ISettingViewModel>()
|
||||||
|
{
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnableRealtimeSelection,
|
||||||
|
val => Instance.EnableRealtimeSelection = val,
|
||||||
|
"Realtime selection: ",
|
||||||
|
"Selects items when finished if disabled."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.SelectableNodes,
|
||||||
|
val => Instance.SelectableNodes = val,
|
||||||
|
"Selectable nodes: ",
|
||||||
|
"Whether nodes can be selected."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.DraggableNodes,
|
||||||
|
val => Instance.DraggableNodes= val,
|
||||||
|
"Draggable nodes: ",
|
||||||
|
"Whether nodes can be dragged."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.CanSelectMultipleNodes,
|
||||||
|
val => Instance.CanSelectMultipleNodes = val,
|
||||||
|
"Can select multiple nodes: "),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnablePendingConnectionSnapping,
|
||||||
|
val => Instance.EnablePendingConnectionSnapping = val,
|
||||||
|
"Pending connection snapping: ",
|
||||||
|
"Whether to snap the pending connection to connectors"),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnablePendingConnectionPreview,
|
||||||
|
val => Instance.EnablePendingConnectionPreview = val,
|
||||||
|
"Pending connection preview: ",
|
||||||
|
"Show information about the pending connection."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowConnectingToConnectorsOnly,
|
||||||
|
val => Instance.AllowConnectingToConnectorsOnly = val,
|
||||||
|
"Disable drop connection on node: ",
|
||||||
|
"Can connect directly to nodes if enabled"),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.DisableAutoPanning,
|
||||||
|
val => Instance.DisableAutoPanning = val,
|
||||||
|
"Disable auto panning: "),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.DisablePanning,
|
||||||
|
val => Instance.DisablePanning = val,
|
||||||
|
"Disable panning: "),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.DisableZooming,
|
||||||
|
val => Instance.DisableZooming = val,
|
||||||
|
"Disable zooming: "),
|
||||||
|
new ProxySettingViewModel<uint>(
|
||||||
|
() => Instance.GridSpacing,
|
||||||
|
val => Instance.GridSpacing = val,
|
||||||
|
"Grid spacing: ",
|
||||||
|
"Snapping value in pixels"),
|
||||||
|
new ProxySettingViewModel<PointEditor>(
|
||||||
|
() => Instance.Location,
|
||||||
|
val => Instance.Location = val,
|
||||||
|
"Location: ",
|
||||||
|
"The viewport's location."),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.Zoom,
|
||||||
|
val => Instance.Zoom = val,
|
||||||
|
"Zoom: ",
|
||||||
|
"The viewport's zoom. Not accurate when trying to zoom outside the MinViewportZoom and MaxViewportZoom because of dependency property coercion not updating the binding with the final result."),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.MinZoom,
|
||||||
|
val => Instance.MinZoom = val,
|
||||||
|
"Min zoom: "),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.MaxZoom,
|
||||||
|
val => Instance.MaxZoom = val,
|
||||||
|
"Max zoom: "),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.AutoPanningSpeed,
|
||||||
|
val => Instance.AutoPanningSpeed = val,
|
||||||
|
"Auto panning speed: ",
|
||||||
|
"Speed value in pixels per tick"),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.AutoPanningEdgeDistance,
|
||||||
|
val => Instance.AutoPanningEdgeDistance = val,
|
||||||
|
"Auto panning edge distance: ",
|
||||||
|
"Distance from edge to trigger auto panning"),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnableStickyConnectors,
|
||||||
|
val => Instance.EnableStickyConnectors = val,
|
||||||
|
"Enable sticky connectors: ",
|
||||||
|
"The connection can be completed in two steps (e.g. click to create pending connection, click to connect)"),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.SelectableConnections,
|
||||||
|
val => Instance.SelectableConnections = val,
|
||||||
|
"Selectable connections: ",
|
||||||
|
"Whether connections can be selected."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.CanSelectMultipleConnections,
|
||||||
|
val => Instance.CanSelectMultipleConnections = val,
|
||||||
|
"Can select multiple connections: "),
|
||||||
|
new ProxySettingViewModel<ConnectionStyle>(
|
||||||
|
() => Instance.ConnectionStyle,
|
||||||
|
val => Instance.ConnectionStyle = val,
|
||||||
|
"Connection style: "),
|
||||||
|
new ProxySettingViewModel<string?>(
|
||||||
|
() => Instance.ConnectionText,
|
||||||
|
val => Instance.ConnectionText = val,
|
||||||
|
"Connection text: "),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.CircuitConnectionAngle,
|
||||||
|
val => Instance.CircuitConnectionAngle = val,
|
||||||
|
"Connection angle: ",
|
||||||
|
"Applies to circuit connection style"),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.ConnectionCornerRadius,
|
||||||
|
val => Instance.ConnectionCornerRadius = val,
|
||||||
|
"Connection corner radius: ",
|
||||||
|
"The corner radius between the line segments."),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.ConnectionSpacing,
|
||||||
|
val => Instance.ConnectionSpacing = val,
|
||||||
|
"Connection spacing: ",
|
||||||
|
"The distance between the start point and the where the angle breaks"),
|
||||||
|
new ProxySettingViewModel<PointEditor>(
|
||||||
|
() => Instance.ConnectionArrowSize,
|
||||||
|
val => Instance.ConnectionArrowSize = val,
|
||||||
|
"Connection arrowhead size: ",
|
||||||
|
"The size of the arrowhead."),
|
||||||
|
new ProxySettingViewModel<uint>(
|
||||||
|
() => Instance.DirectionalArrowsCount,
|
||||||
|
val => Instance.DirectionalArrowsCount = val,
|
||||||
|
"Directional arrows count: ",
|
||||||
|
"The number of arrowheads to draw on the line flowing in the direction of the connection."),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.DirectionalArrowsOffset,
|
||||||
|
val => Instance.DirectionalArrowsOffset = val,
|
||||||
|
"Directional arrows offset: ",
|
||||||
|
"Used to animate the directional arrowheads flowing in the direction of the connection (value is between 0 and 1)."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.IsAnimatingConnections,
|
||||||
|
val => Instance.IsAnimatingConnections = val,
|
||||||
|
"Animate directional arrows: ",
|
||||||
|
"Used to animate the directional arrowheads by animating the DirectionalArrowsOffset value"),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.DirectionalArrowsAnimationDuration,
|
||||||
|
val => Instance.DirectionalArrowsAnimationDuration = val,
|
||||||
|
"Arrows animation duration: ",
|
||||||
|
"The duration in seconds of a directional arrowhead flowing from start to end."),
|
||||||
|
new ProxySettingViewModel<ArrowHeadEnds>(
|
||||||
|
() => Instance.ArrowHeadEnds,
|
||||||
|
val => Instance.ArrowHeadEnds = val,
|
||||||
|
"Connection arrowhead end: ",
|
||||||
|
"The location of the arrowhead."),
|
||||||
|
new ProxySettingViewModel<ArrowHeadShape>(
|
||||||
|
() => Instance.ArrowHeadShape,
|
||||||
|
val => Instance.ArrowHeadShape = val,
|
||||||
|
"Connection arrowhead shape: ",
|
||||||
|
"The shape of the arrow head."),
|
||||||
|
new ProxySettingViewModel<ConnectionOffsetMode>(
|
||||||
|
() => Instance.ConnectionSourceOffsetMode,
|
||||||
|
val => Instance.ConnectionSourceOffsetMode = val,
|
||||||
|
"Connection source offset mode: "),
|
||||||
|
new ProxySettingViewModel<ConnectionOffsetMode>(
|
||||||
|
() => Instance.ConnectionTargetOffsetMode,
|
||||||
|
val => Instance.ConnectionTargetOffsetMode = val,
|
||||||
|
"Connection target offset mode: "),
|
||||||
|
new ProxySettingViewModel<PointEditor>(
|
||||||
|
() => Instance.ConnectionSourceOffset,
|
||||||
|
val => Instance.ConnectionSourceOffset = val,
|
||||||
|
"Connection source offset: "),
|
||||||
|
new ProxySettingViewModel<PointEditor>(
|
||||||
|
() => Instance.ConnectionTargetOffset,
|
||||||
|
val => Instance.ConnectionTargetOffset = val,
|
||||||
|
"Connection target offset: "),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.ConnectionStrokeThickness,
|
||||||
|
val => Instance.ConnectionStrokeThickness = val,
|
||||||
|
"Connection stroke thickness: "),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.ConnectionOutlineThickness,
|
||||||
|
val => Instance.ConnectionOutlineThickness = val,
|
||||||
|
"Connection outline thickness: "),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.ConnectionFocusVisualPadding,
|
||||||
|
val => Instance.ConnectionFocusVisualPadding = val,
|
||||||
|
"Connection focus visual padding: "),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.DisplayConnectionsOnTop,
|
||||||
|
val => Instance.DisplayConnectionsOnTop = val,
|
||||||
|
"Display connections on top: "),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.BringIntoViewSpeed,
|
||||||
|
val => Instance.BringIntoViewSpeed = val,
|
||||||
|
"Bring into view speed: ",
|
||||||
|
"Bring location into view animation speed in pixels per second"),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.BringIntoViewMaxDuration,
|
||||||
|
val => Instance.BringIntoViewMaxDuration = val,
|
||||||
|
"Bring into view max duration: ",
|
||||||
|
"Bring location into view max animation duration"),
|
||||||
|
new ProxySettingViewModel<GroupingMovementMode>(
|
||||||
|
() => Instance.GroupingNodeMovement,
|
||||||
|
val => Instance.GroupingNodeMovement = val,
|
||||||
|
"Grouping node movement: ",
|
||||||
|
"Whether the grouping node is sticky or not"),
|
||||||
|
};
|
||||||
|
|
||||||
|
_advancedSettings = new List<ISettingViewModel>()
|
||||||
|
{
|
||||||
|
new ProxySettingViewModel<uint>(
|
||||||
|
() => Instance.MaxHotKeys,
|
||||||
|
val => Instance.MaxHotKeys = val,
|
||||||
|
"Max hot keys: ",
|
||||||
|
"The maximum number of generated hot keys"),
|
||||||
|
new ProxySettingViewModel<HotKeysDisplayMode>(
|
||||||
|
() => Instance.HotKeysDisplayMode,
|
||||||
|
val => Instance.HotKeysDisplayMode = val,
|
||||||
|
"Hot keys display mode: ",
|
||||||
|
"Specifies how hotkeys are displayed for a pending connection."),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.MouseActionSuppressionThreshold,
|
||||||
|
val => Instance.MouseActionSuppressionThreshold = val,
|
||||||
|
"Context menu suppression threshold: ",
|
||||||
|
"Disable context menu after mouse moved this far."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.PreserveSelectionOnRightClick,
|
||||||
|
val => Instance.PreserveSelectionOnRightClick = val,
|
||||||
|
"Preserve selection on right click: ",
|
||||||
|
"Whether right-click on the container should preserve the current selection."),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.AutoPanningTickRate,
|
||||||
|
val => Instance.AutoPanningTickRate = val,
|
||||||
|
"Auto panning tick rate: ",
|
||||||
|
"How often is the new position calculated in milliseconds. Disable and enable auto panning for this to have effect."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnableSnappingCorrection,
|
||||||
|
val => Instance.EnableSnappingCorrection = val,
|
||||||
|
"Enable snapping correction: ",
|
||||||
|
"Correct the final position when moving a selection."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnableCuttingLinePreview,
|
||||||
|
val => Instance.EnableCuttingLinePreview = val,
|
||||||
|
"Enable cutting line preview: ",
|
||||||
|
"Applies custom connection style on intersection (hurts performance due to hit testing)."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnablePendingConnectionHitTesting,
|
||||||
|
val => Instance.EnablePendingConnectionHitTesting = val,
|
||||||
|
"Enable pending connection hit testing: ",
|
||||||
|
"Disable to improve performance."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnableConnectorOptimizations,
|
||||||
|
val => Instance.EnableConnectorOptimizations = val,
|
||||||
|
"Enable connector optimizations: ",
|
||||||
|
"Enables optimizations for connectors based on viewport distance and minimum selected nodes."),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.OptimizeSafeZone,
|
||||||
|
val => Instance.OptimizeSafeZone = val,
|
||||||
|
"Optimize connectors safe zone: ",
|
||||||
|
"The minimum distance from the viewport where connectors will start optimizing"),
|
||||||
|
new ProxySettingViewModel<uint>(
|
||||||
|
() => Instance.OptimizeMinimumSelectedItems,
|
||||||
|
val => Instance.OptimizeMinimumSelectedItems = val,
|
||||||
|
"Optimize connectors minimum selection: ",
|
||||||
|
"The minimum selected items needed to start optimizing when outside the safe zone."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnableRenderingOptimizations,
|
||||||
|
val => Instance.EnableRenderingOptimizations = val,
|
||||||
|
"Enable nodes rendering optimization: ",
|
||||||
|
"Enables rendering optimizations for nodes based on zoom out percent and nodes count. (zoom in/out to apply optimization)"),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.OptimizeRenderingZoomOutPercent,
|
||||||
|
val => Instance.OptimizeRenderingZoomOutPercent = val,
|
||||||
|
"Rendering optimization zoom out percent: ",
|
||||||
|
"The zoom out percent that triggers the optimization. (percent of x = 1 - MinViewportZoom)"),
|
||||||
|
new ProxySettingViewModel<uint>(
|
||||||
|
() => Instance.OptimizeRenderingMinimumNodes,
|
||||||
|
val => Instance.OptimizeRenderingMinimumNodes = val,
|
||||||
|
"Rendering optimization minimum nodes: ",
|
||||||
|
"The minimum nodes needed to start optimizing when zoom out percent is met."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnableDraggingOptimizations,
|
||||||
|
val => Instance.EnableDraggingOptimizations = val,
|
||||||
|
"Enable nodes dragging optimizations: ",
|
||||||
|
"Simulates dragging visually but only commits the changes at the end."),
|
||||||
|
new ProxySettingViewModel<double>(
|
||||||
|
() => Instance.FitToScreenExtentMargin,
|
||||||
|
val => Instance.FitToScreenExtentMargin = val,
|
||||||
|
"Fit to screen extent margin: ",
|
||||||
|
"Adds some margin to the nodes extent when fit to screen"),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowMinimapPanningCancellation,
|
||||||
|
val => Instance.AllowMinimapPanningCancellation = val,
|
||||||
|
"Allow minimap panning cancellation: ",
|
||||||
|
"Right click or escape to cancel panning."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowCuttingCancellation,
|
||||||
|
val => Instance.AllowCuttingCancellation = val,
|
||||||
|
"Allow cutting cancellation: ",
|
||||||
|
"Right click to cancel cutting."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowPushItemsCancellation,
|
||||||
|
val => Instance.AllowPushItemsCancellation = val,
|
||||||
|
"Allow push nodes cancellation: ",
|
||||||
|
"Right click to cancel pushing nodes."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowPanningCancellation,
|
||||||
|
val => Instance.AllowPanningCancellation= val,
|
||||||
|
"Allow panning cancellation: ",
|
||||||
|
"Press Escape to cancel panning."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowSelectionCancellation,
|
||||||
|
val => Instance.AllowSelectionCancellation = val,
|
||||||
|
"Allow selection cancellation: ",
|
||||||
|
"Press Escape to cancel selecting."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowDraggingCancellation,
|
||||||
|
val => Instance.AllowDraggingCancellation = val,
|
||||||
|
"Allow dragging cancellation: ",
|
||||||
|
"Right click to cancel dragging."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowPendingConnectionCancellation,
|
||||||
|
val => Instance.AllowPendingConnectionCancellation = val,
|
||||||
|
"Allow pending connection cancellation: ",
|
||||||
|
"Right click to cancel pending connection."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnableToggledCutting,
|
||||||
|
val => Instance.EnableToggledCutting = val,
|
||||||
|
"Enable toggled cutting mode: ",
|
||||||
|
"The interaction will be completed in two steps using the same gesture to start and end."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnableToggledPushingItems,
|
||||||
|
val => Instance.EnableToggledPushingItems = val,
|
||||||
|
"Enable toggled pushing items mode: ",
|
||||||
|
"The interaction will be completed in two steps using the same gesture to start and end."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnableToggledPanning,
|
||||||
|
val => Instance.EnableToggledPanning = val,
|
||||||
|
"Enable toggled panning mode: ",
|
||||||
|
"The interaction will be completed in two steps using the same gesture to start and end."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnableToggledSelecting,
|
||||||
|
val => Instance.EnableToggledSelecting = val,
|
||||||
|
"Enable toggled selecting mode: ",
|
||||||
|
"The interaction will be completed in two steps using the same gesture to start and end."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnableToggledDragging,
|
||||||
|
val => Instance.EnableToggledDragging = val,
|
||||||
|
"Enable toggled dragging mode: ",
|
||||||
|
"The interaction will be completed in two steps using the same gesture to start and end."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.EnableMinimapToggledPanning,
|
||||||
|
val => Instance.EnableMinimapToggledPanning = val,
|
||||||
|
"Enable minimap toggled panning mode: ",
|
||||||
|
"The interaction will be completed in two steps using the same gesture to start and end."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowPanningWhileSelecting,
|
||||||
|
val => Instance.AllowPanningWhileSelecting = val,
|
||||||
|
"Allow panning while selecting: ",
|
||||||
|
"Whether panning is allowed while selecting items in the editor."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowPanningWhileCutting,
|
||||||
|
val => Instance.AllowPanningWhileCutting = val,
|
||||||
|
"Allow panning while cutting: ",
|
||||||
|
"Whether panning is allowed while cutting connections in the editor."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowPanningWhilePushingItems,
|
||||||
|
val => Instance.AllowPanningWhilePushingItems = val,
|
||||||
|
"Allow panning while pushing items: ",
|
||||||
|
"Whether panning is allowed while pushing items items in the editor."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowZoomingWhileSelecting,
|
||||||
|
val => Instance.AllowZoomingWhileSelecting = val,
|
||||||
|
"Allow zooming while selecting: ",
|
||||||
|
"Whether zooming is allowed while selecting items in the editor."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowZoomingWhileCutting,
|
||||||
|
val => Instance.AllowZoomingWhileCutting = val,
|
||||||
|
"Allow zooming while cutting: ",
|
||||||
|
"Whether zooming is allowed while cutting connections in the editor."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowZoomingWhilePushingItems,
|
||||||
|
val => Instance.AllowZoomingWhilePushingItems = val,
|
||||||
|
"Allow zooming while pushing items: ",
|
||||||
|
"Whether zooming is allowed while pushing items connections in the editor."),
|
||||||
|
new ProxySettingViewModel<bool>(
|
||||||
|
() => Instance.AllowZoomingWhilePanning,
|
||||||
|
val => Instance.AllowZoomingWhilePanning = val,
|
||||||
|
"Allow zooming while panning: ",
|
||||||
|
"Whether zooming is allowed while panning connections in the editor."),
|
||||||
|
};
|
||||||
|
|
||||||
|
EnableCuttingLinePreview = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSearchTextChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(PlaygroundSettings.SearchText))
|
||||||
|
{
|
||||||
|
OnPropertyChanged(nameof(Settings));
|
||||||
|
OnPropertyChanged(nameof(AdvancedSettings));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EditorSettings Instance { get; } = new EditorSettings();
|
||||||
|
|
||||||
|
#region Default settings
|
||||||
|
|
||||||
|
private bool _enablePendingConnectionSnapping = true;
|
||||||
|
public bool EnablePendingConnectionSnapping
|
||||||
|
{
|
||||||
|
get => _enablePendingConnectionSnapping;
|
||||||
|
set => SetProperty(ref _enablePendingConnectionSnapping, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _enablePendingConnectionPreview = true;
|
||||||
|
public bool EnablePendingConnectionPreview
|
||||||
|
{
|
||||||
|
get => _enablePendingConnectionPreview;
|
||||||
|
set => SetProperty(ref _enablePendingConnectionPreview, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _allowConnectingToConnectorsOnly;
|
||||||
|
public bool AllowConnectingToConnectorsOnly
|
||||||
|
{
|
||||||
|
get => _allowConnectingToConnectorsOnly;
|
||||||
|
set => SetProperty(ref _allowConnectingToConnectorsOnly, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _realtimeSelection = true;
|
||||||
|
public bool EnableRealtimeSelection
|
||||||
|
{
|
||||||
|
get => _realtimeSelection;
|
||||||
|
set => SetProperty(ref _realtimeSelection, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _disableAutoPanning = false;
|
||||||
|
public bool DisableAutoPanning
|
||||||
|
{
|
||||||
|
get => _disableAutoPanning;
|
||||||
|
set => SetProperty(ref _disableAutoPanning, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _autoPanningSpeed = 15d;
|
||||||
|
public double AutoPanningSpeed
|
||||||
|
{
|
||||||
|
get => _autoPanningSpeed;
|
||||||
|
set => SetProperty(ref _autoPanningSpeed, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _autoPanningEdgeDistance = 15d;
|
||||||
|
public double AutoPanningEdgeDistance
|
||||||
|
{
|
||||||
|
get => _autoPanningEdgeDistance;
|
||||||
|
set => SetProperty(ref _autoPanningEdgeDistance, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _disablePanning = false;
|
||||||
|
public bool DisablePanning
|
||||||
|
{
|
||||||
|
get => _disablePanning;
|
||||||
|
set => SetProperty(ref _disablePanning, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _disableZooming = false;
|
||||||
|
public bool DisableZooming
|
||||||
|
{
|
||||||
|
get => _disableZooming;
|
||||||
|
set => SetProperty(ref _disableZooming, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint _gridSpacing = 15u;
|
||||||
|
public uint GridSpacing
|
||||||
|
{
|
||||||
|
get => _gridSpacing;
|
||||||
|
set => SetProperty(ref _gridSpacing, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _minZoom = 0.1;
|
||||||
|
public double MinZoom
|
||||||
|
{
|
||||||
|
get => _minZoom;
|
||||||
|
set => SetProperty(ref _minZoom, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _maxZoom = 2;
|
||||||
|
public double MaxZoom
|
||||||
|
{
|
||||||
|
get => _maxZoom;
|
||||||
|
set => SetProperty(ref _maxZoom, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _zoom = 1;
|
||||||
|
public double Zoom
|
||||||
|
{
|
||||||
|
get => _zoom;
|
||||||
|
set => SetProperty(ref _zoom, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PointEditor _location = new PointEditor();
|
||||||
|
public PointEditor Location
|
||||||
|
{
|
||||||
|
get => _location;
|
||||||
|
set => SetProperty(ref _location, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _selectableConnections = true;
|
||||||
|
public bool SelectableConnections
|
||||||
|
{
|
||||||
|
get => _selectableConnections;
|
||||||
|
set => SetProperty(ref _selectableConnections, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _canSelectMultipleConnections = true;
|
||||||
|
public bool CanSelectMultipleConnections
|
||||||
|
{
|
||||||
|
get => _canSelectMultipleConnections;
|
||||||
|
set => SetProperty(ref _canSelectMultipleConnections, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _draggableNodes = true;
|
||||||
|
public bool DraggableNodes
|
||||||
|
{
|
||||||
|
get => _draggableNodes;
|
||||||
|
set => SetProperty(ref _draggableNodes, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _selectableNodes = true;
|
||||||
|
public bool SelectableNodes
|
||||||
|
{
|
||||||
|
get => _selectableNodes;
|
||||||
|
set => SetProperty(ref _selectableNodes, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _canSelectMultipleNodes = true;
|
||||||
|
public bool CanSelectMultipleNodes
|
||||||
|
{
|
||||||
|
get => _canSelectMultipleNodes;
|
||||||
|
set => SetProperty(ref _canSelectMultipleNodes, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectionStyle _connectionStyle;
|
||||||
|
public ConnectionStyle ConnectionStyle
|
||||||
|
{
|
||||||
|
get => _connectionStyle;
|
||||||
|
set => SetProperty(ref _connectionStyle, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? _connectionText;
|
||||||
|
public string? ConnectionText
|
||||||
|
{
|
||||||
|
get => _connectionText;
|
||||||
|
set => SetProperty(ref _connectionText, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _circuitConnectionAngle = 45;
|
||||||
|
public double CircuitConnectionAngle
|
||||||
|
{
|
||||||
|
get => _circuitConnectionAngle;
|
||||||
|
set => SetProperty(ref _circuitConnectionAngle, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _connectionCornerRadius = 10;
|
||||||
|
public double ConnectionCornerRadius
|
||||||
|
{
|
||||||
|
get => _connectionCornerRadius;
|
||||||
|
set => SetProperty(ref _connectionCornerRadius, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _connectionSpacing = 20;
|
||||||
|
public double ConnectionSpacing
|
||||||
|
{
|
||||||
|
get => _connectionSpacing;
|
||||||
|
set => SetProperty(ref _connectionSpacing, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectionOffsetMode _srcConnectionOffsetMode = ConnectionOffsetMode.Static;
|
||||||
|
public ConnectionOffsetMode ConnectionSourceOffsetMode
|
||||||
|
{
|
||||||
|
get => _srcConnectionOffsetMode;
|
||||||
|
set => SetProperty(ref _srcConnectionOffsetMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectionOffsetMode _targetConnectionOffsetMode = ConnectionOffsetMode.Static;
|
||||||
|
public ConnectionOffsetMode ConnectionTargetOffsetMode
|
||||||
|
{
|
||||||
|
get => _targetConnectionOffsetMode;
|
||||||
|
set => SetProperty(ref _targetConnectionOffsetMode, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrowHeadEnds _arrowHeadEnds = ArrowHeadEnds.End;
|
||||||
|
public ArrowHeadEnds ArrowHeadEnds
|
||||||
|
{
|
||||||
|
get => _arrowHeadEnds;
|
||||||
|
set => SetProperty(ref _arrowHeadEnds, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ArrowHeadShape _arrowHeadShape = ArrowHeadShape.Arrowhead;
|
||||||
|
public ArrowHeadShape ArrowHeadShape
|
||||||
|
{
|
||||||
|
get => _arrowHeadShape;
|
||||||
|
set => SetProperty(ref _arrowHeadShape, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PointEditor _connectionSourceOffset = new Size(14, 0);
|
||||||
|
public PointEditor ConnectionSourceOffset
|
||||||
|
{
|
||||||
|
get => _connectionSourceOffset;
|
||||||
|
set => SetProperty(ref _connectionSourceOffset, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PointEditor _connectionTargetOffset = new Size(14, 0);
|
||||||
|
public PointEditor ConnectionTargetOffset
|
||||||
|
{
|
||||||
|
get => _connectionTargetOffset;
|
||||||
|
set => SetProperty(ref _connectionTargetOffset, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _connectionStrokeThickness = 3;
|
||||||
|
public double ConnectionStrokeThickness
|
||||||
|
{
|
||||||
|
get => _connectionStrokeThickness;
|
||||||
|
set => SetProperty(ref _connectionStrokeThickness, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _connectionOutlineThickness = 5;
|
||||||
|
public double ConnectionOutlineThickness
|
||||||
|
{
|
||||||
|
get => _connectionOutlineThickness;
|
||||||
|
set => SetProperty(ref _connectionOutlineThickness, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _connectionFocusVisualPadding = 1;
|
||||||
|
public double ConnectionFocusVisualPadding
|
||||||
|
{
|
||||||
|
get => _connectionFocusVisualPadding;
|
||||||
|
set => SetProperty(ref _connectionFocusVisualPadding, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private uint _directionalArrowsCount = 3;
|
||||||
|
public uint DirectionalArrowsCount
|
||||||
|
{
|
||||||
|
get => _directionalArrowsCount;
|
||||||
|
set => SetProperty(ref _directionalArrowsCount, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _directionalArrowsOffset;
|
||||||
|
public double DirectionalArrowsOffset
|
||||||
|
{
|
||||||
|
get => _directionalArrowsOffset;
|
||||||
|
set => SetProperty(ref _directionalArrowsOffset, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _isAnimatingConnections;
|
||||||
|
public bool IsAnimatingConnections
|
||||||
|
{
|
||||||
|
get => _isAnimatingConnections;
|
||||||
|
set => SetProperty(ref _isAnimatingConnections, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _directionalArrowsAnimationDuration = 2.0;
|
||||||
|
public double DirectionalArrowsAnimationDuration
|
||||||
|
{
|
||||||
|
get => _directionalArrowsAnimationDuration;
|
||||||
|
set => SetProperty(ref _directionalArrowsAnimationDuration, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private PointEditor _connectionArrowSize = new Size(8, 8);
|
||||||
|
public PointEditor ConnectionArrowSize
|
||||||
|
{
|
||||||
|
get => _connectionArrowSize;
|
||||||
|
set => SetProperty(ref _connectionArrowSize, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool _displayConnectionsOnTop;
|
||||||
|
public bool DisplayConnectionsOnTop
|
||||||
|
{
|
||||||
|
get => _displayConnectionsOnTop;
|
||||||
|
set => SetProperty(ref _displayConnectionsOnTop, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _bringIntoViewSpeed = 1000;
|
||||||
|
public double BringIntoViewSpeed
|
||||||
|
{
|
||||||
|
get => _bringIntoViewSpeed;
|
||||||
|
set => SetProperty(ref _bringIntoViewSpeed, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private double _bringIntoViewMaxDuration = 1;
|
||||||
|
public double BringIntoViewMaxDuration
|
||||||
|
{
|
||||||
|
get => _bringIntoViewMaxDuration;
|
||||||
|
set => SetProperty(ref _bringIntoViewMaxDuration, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupingMovementMode _groupingNodeMovement;
|
||||||
|
public GroupingMovementMode GroupingNodeMovement
|
||||||
|
{
|
||||||
|
get => _groupingNodeMovement;
|
||||||
|
set => SetProperty(ref _groupingNodeMovement, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Advanced settings
|
||||||
|
|
||||||
|
public uint MaxHotKeys
|
||||||
|
{
|
||||||
|
get => PendingConnection.MaxHotKeys;
|
||||||
|
set => PendingConnection.MaxHotKeys = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public HotKeysDisplayMode HotKeysDisplayMode
|
||||||
|
{
|
||||||
|
get => PendingConnection.HotKeysDisplayMode;
|
||||||
|
set => PendingConnection.HotKeysDisplayMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool PreserveSelectionOnRightClick
|
||||||
|
{
|
||||||
|
get => ItemContainer.PreserveSelectionOnRightClick;
|
||||||
|
set => ItemContainer.PreserveSelectionOnRightClick = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double MouseActionSuppressionThreshold
|
||||||
|
{
|
||||||
|
get => NodifyEditor.MouseActionSuppressionThreshold;
|
||||||
|
set => NodifyEditor.MouseActionSuppressionThreshold = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double AutoPanningTickRate
|
||||||
|
{
|
||||||
|
get => NodifyEditor.AutoPanningTickRate;
|
||||||
|
set => NodifyEditor.AutoPanningTickRate = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowMinimapPanningCancellation
|
||||||
|
{
|
||||||
|
get => Minimap.AllowPanningCancellation;
|
||||||
|
set => Minimap.AllowPanningCancellation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowCuttingCancellation
|
||||||
|
{
|
||||||
|
get => NodifyEditor.AllowCuttingCancellation;
|
||||||
|
set => NodifyEditor.AllowCuttingCancellation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowPushItemsCancellation
|
||||||
|
{
|
||||||
|
get => NodifyEditor.AllowPushItemsCancellation;
|
||||||
|
set => NodifyEditor.AllowPushItemsCancellation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowPanningCancellation
|
||||||
|
{
|
||||||
|
get => NodifyEditor.AllowPanningCancellation;
|
||||||
|
set => NodifyEditor.AllowPanningCancellation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowSelectionCancellation
|
||||||
|
{
|
||||||
|
get => NodifyEditor.AllowSelectionCancellation;
|
||||||
|
set => NodifyEditor.AllowSelectionCancellation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowDraggingCancellation
|
||||||
|
{
|
||||||
|
get => NodifyEditor.AllowDraggingCancellation;
|
||||||
|
set => NodifyEditor.AllowDraggingCancellation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowPendingConnectionCancellation
|
||||||
|
{
|
||||||
|
get => Connector.AllowPendingConnectionCancellation;
|
||||||
|
set => Connector.AllowPendingConnectionCancellation = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableSnappingCorrection
|
||||||
|
{
|
||||||
|
get => NodifyEditor.EnableSnappingCorrection;
|
||||||
|
set => NodifyEditor.EnableSnappingCorrection = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableCuttingLinePreview
|
||||||
|
{
|
||||||
|
get => NodifyEditor.EnableCuttingLinePreview;
|
||||||
|
set => NodifyEditor.EnableCuttingLinePreview = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnablePendingConnectionHitTesting
|
||||||
|
{
|
||||||
|
get => PendingConnection.EnableHitTesting;
|
||||||
|
set => PendingConnection.EnableHitTesting = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableConnectorOptimizations
|
||||||
|
{
|
||||||
|
get => Connector.EnableOptimizations;
|
||||||
|
set => Connector.EnableOptimizations = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double OptimizeSafeZone
|
||||||
|
{
|
||||||
|
get => Connector.OptimizeSafeZone;
|
||||||
|
set => Connector.OptimizeSafeZone = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint OptimizeMinimumSelectedItems
|
||||||
|
{
|
||||||
|
get => Connector.OptimizeMinimumSelectedItems;
|
||||||
|
set => Connector.OptimizeMinimumSelectedItems = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableRenderingOptimizations
|
||||||
|
{
|
||||||
|
get => NodifyEditor.EnableRenderingContainersOptimizations;
|
||||||
|
set => NodifyEditor.EnableRenderingContainersOptimizations = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint OptimizeRenderingMinimumNodes
|
||||||
|
{
|
||||||
|
get => NodifyEditor.OptimizeRenderingMinimumContainers;
|
||||||
|
set => NodifyEditor.OptimizeRenderingMinimumContainers = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double OptimizeRenderingZoomOutPercent
|
||||||
|
{
|
||||||
|
get => NodifyEditor.OptimizeRenderingZoomOutPercent;
|
||||||
|
set => NodifyEditor.OptimizeRenderingZoomOutPercent = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double FitToScreenExtentMargin
|
||||||
|
{
|
||||||
|
get => NodifyEditor.FitToScreenExtentMargin;
|
||||||
|
set => NodifyEditor.FitToScreenExtentMargin = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableDraggingOptimizations
|
||||||
|
{
|
||||||
|
get => NodifyEditor.EnableDraggingContainersOptimizations;
|
||||||
|
set => NodifyEditor.EnableDraggingContainersOptimizations = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableStickyConnectors
|
||||||
|
{
|
||||||
|
get => ConnectorState.EnableToggledConnectingMode;
|
||||||
|
set => ConnectorState.EnableToggledConnectingMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableToggledPanning
|
||||||
|
{
|
||||||
|
get => EditorState.EnableToggledPanningMode;
|
||||||
|
set => EditorState.EnableToggledPanningMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableToggledCutting
|
||||||
|
{
|
||||||
|
get => EditorState.EnableToggledCuttingMode;
|
||||||
|
set => EditorState.EnableToggledCuttingMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableToggledPushingItems
|
||||||
|
{
|
||||||
|
get => EditorState.EnableToggledPushingItemsMode;
|
||||||
|
set => EditorState.EnableToggledPushingItemsMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableToggledSelecting
|
||||||
|
{
|
||||||
|
get => EditorState.EnableToggledSelectingMode;
|
||||||
|
set => EditorState.EnableToggledSelectingMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableToggledDragging
|
||||||
|
{
|
||||||
|
get => ContainerState.EnableToggledDraggingMode;
|
||||||
|
set => ContainerState.EnableToggledDraggingMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EnableMinimapToggledPanning
|
||||||
|
{
|
||||||
|
get => MinimapState.EnableToggledPanningMode;
|
||||||
|
set => MinimapState.EnableToggledPanningMode = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowPanningWhileSelecting
|
||||||
|
{
|
||||||
|
get => EditorState.AllowPanningWhileSelecting;
|
||||||
|
set => EditorState.AllowPanningWhileSelecting = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowPanningWhileCutting
|
||||||
|
{
|
||||||
|
get => EditorState.AllowPanningWhileCutting;
|
||||||
|
set => EditorState.AllowPanningWhileCutting = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowPanningWhilePushingItems
|
||||||
|
{
|
||||||
|
get => EditorState.AllowPanningWhilePushingItems;
|
||||||
|
set => EditorState.AllowPanningWhilePushingItems = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowZoomingWhileSelecting
|
||||||
|
{
|
||||||
|
get => EditorState.AllowZoomingWhileSelecting;
|
||||||
|
set => EditorState.AllowZoomingWhileSelecting = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowZoomingWhileCutting
|
||||||
|
{
|
||||||
|
get => EditorState.AllowZoomingWhileCutting;
|
||||||
|
set => EditorState.AllowZoomingWhileCutting = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowZoomingWhilePushingItems
|
||||||
|
{
|
||||||
|
get => EditorState.AllowZoomingWhilePushingItems;
|
||||||
|
set => EditorState.AllowZoomingWhilePushingItems = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowZoomingWhilePanning
|
||||||
|
{
|
||||||
|
get => EditorState.AllowZoomingWhilePanning;
|
||||||
|
set => EditorState.AllowZoomingWhilePanning = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
47
Examples/Nodify.Playground/EditorSettingsView.xaml
Normal file
47
Examples/Nodify.Playground/EditorSettingsView.xaml
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
<UserControl x:Class="Nodify.Playground.EditorSettingsView"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="clr-namespace:Nodify.Playground"
|
||||||
|
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||||
|
d:Foreground="{DynamicResource ForegroundBrush}"
|
||||||
|
d:Background="{DynamicResource PanelBackgroundBrush}"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
|
||||||
|
<StackPanel>
|
||||||
|
<Border BorderThickness="1"
|
||||||
|
Padding="10"
|
||||||
|
HorizontalAlignment="Stretch">
|
||||||
|
<local:SettingsView Items="{Binding Source={x:Static local:EditorSettings.Instance}, Path=Settings}"/>
|
||||||
|
</Border>
|
||||||
|
<Expander
|
||||||
|
Header="Advanced"
|
||||||
|
Padding="0 5 0 0"
|
||||||
|
BorderThickness="0 0 0 1"
|
||||||
|
IsExpanded="True"
|
||||||
|
BorderBrush="{DynamicResource BackgroundBrush}">
|
||||||
|
<Expander.Style>
|
||||||
|
<Style TargetType="{x:Type Expander}"
|
||||||
|
BasedOn="{StaticResource {x:Type Expander}}">
|
||||||
|
<Setter Property="Tag"
|
||||||
|
Value="{StaticResource ExpandRightIcon}" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsExpanded"
|
||||||
|
Value="True">
|
||||||
|
<Setter Property="Tag"
|
||||||
|
Value="{StaticResource ExpandDownIcon}" />
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Expander.Style>
|
||||||
|
|
||||||
|
<Border BorderThickness="1"
|
||||||
|
Padding="10"
|
||||||
|
HorizontalAlignment="Stretch">
|
||||||
|
<local:SettingsView Items="{Binding Source={x:Static local:EditorSettings.Instance}, Path=AdvancedSettings}"/>
|
||||||
|
</Border>
|
||||||
|
</Expander>
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
</UserControl>
|
||||||
26
Examples/Nodify.Playground/EditorSettingsView.xaml.cs
Normal file
26
Examples/Nodify.Playground/EditorSettingsView.xaml.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Data;
|
||||||
|
using System.Windows.Documents;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
using System.Windows.Media.Imaging;
|
||||||
|
using System.Windows.Navigation;
|
||||||
|
using System.Windows.Shapes;
|
||||||
|
|
||||||
|
namespace Nodify.Playground
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for EditorSettingsView.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class EditorSettingsView : UserControl
|
||||||
|
{
|
||||||
|
public EditorSettingsView()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Nodify.Playground
|
||||||
|
{
|
||||||
|
public static class NodeViewModelExtensions
|
||||||
|
{
|
||||||
|
public static Rect GetBoundingBox(this IList<NodeViewModel> nodes, double padding = 0, int gridCellSize = 15)
|
||||||
|
{
|
||||||
|
double minX = double.MaxValue;
|
||||||
|
double minY = double.MaxValue;
|
||||||
|
|
||||||
|
double maxX = double.MinValue;
|
||||||
|
double maxY = double.MinValue;
|
||||||
|
|
||||||
|
for (int i = 0; i < nodes.Count; i++)
|
||||||
|
{
|
||||||
|
var node = nodes[i];
|
||||||
|
var width = 200; //node.Width
|
||||||
|
var height = 200; //node.Height
|
||||||
|
|
||||||
|
if (node.Location.X < minX)
|
||||||
|
{
|
||||||
|
minX = node.Location.X;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.Location.Y < minY)
|
||||||
|
{
|
||||||
|
minY = node.Location.Y;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sizeX = node.Location.X + width;
|
||||||
|
if (sizeX > maxX)
|
||||||
|
{
|
||||||
|
maxX = sizeX;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sizeY = node.Location.Y + height;
|
||||||
|
if (sizeY > maxY)
|
||||||
|
{
|
||||||
|
maxY = sizeY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new Rect(minX - padding, minY - padding, maxX - minX + padding * 2, maxY - minY + padding * 2);
|
||||||
|
result.X = (int)result.X / gridCellSize * gridCellSize;
|
||||||
|
result.Y = (int)result.Y / gridCellSize * gridCellSize;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddRange<T>(this ICollection<T> col, IEnumerable<T> items)
|
||||||
|
{
|
||||||
|
if (items is IList<T> itemsCol)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < itemsCol.Count; i++)
|
||||||
|
{
|
||||||
|
col.Add(itemsCol[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (items is T[] itemsArr)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < itemsArr.Length; i++)
|
||||||
|
{
|
||||||
|
col.Add(itemsArr[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
col.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
186
Examples/Nodify.Playground/Helpers/RandomNodesGenerator.cs
Normal file
186
Examples/Nodify.Playground/Helpers/RandomNodesGenerator.cs
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows;
|
||||||
|
|
||||||
|
namespace Nodify.Playground
|
||||||
|
{
|
||||||
|
public struct NodesGeneratorSettings
|
||||||
|
{
|
||||||
|
private static readonly Random _rand = new Random();
|
||||||
|
|
||||||
|
public NodesGeneratorSettings(uint count)
|
||||||
|
{
|
||||||
|
GridSnap = 15;
|
||||||
|
MinNodesCount = MaxNodesCount = count;
|
||||||
|
MinInputCount = MinOutputCount = 0;
|
||||||
|
MaxInputCount = MaxOutputCount = 7;
|
||||||
|
|
||||||
|
ConnectorNameGenerator = (s, i) => $"{new string('C', (int)i % 5)} {i}";
|
||||||
|
NodeNameGenerator = (s, i) => $"Node {i}";
|
||||||
|
NodeLocationGenerator = (s, i) =>
|
||||||
|
{
|
||||||
|
static double EaseOut(double percent, double increment, double start, double end, double total)
|
||||||
|
=> -end * (increment /= total) * (increment - 2) + start;
|
||||||
|
|
||||||
|
var xDistanceBetweenNodes = _rand.Next(150, 350);
|
||||||
|
var yDistanceBetweenNodes = _rand.Next(200, 350);
|
||||||
|
var randSignX = _rand.Next(0, 100) > 50 ? 1 : -1;
|
||||||
|
var randSignY = _rand.Next(0, 100) > 50 ? 1 : -1;
|
||||||
|
var gridOffsetX = i * xDistanceBetweenNodes;
|
||||||
|
var gridOffsetY = i * yDistanceBetweenNodes;
|
||||||
|
|
||||||
|
var x = gridOffsetX * Math.Sin(xDistanceBetweenNodes * randSignX / (i + 1));
|
||||||
|
var y = gridOffsetY * Math.Sin(yDistanceBetweenNodes * randSignY / (i + 1));
|
||||||
|
var easeX = x * EaseOut(i / count, i, 1, 0.01, count);
|
||||||
|
var easeY = y * EaseOut(i / count, i, 1, 0.01, count);
|
||||||
|
|
||||||
|
x = s.Snap((int)easeX);
|
||||||
|
y = s.Snap((int)easeY);
|
||||||
|
|
||||||
|
return new Point(x, y);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public uint GridSnap;
|
||||||
|
public uint MinNodesCount;
|
||||||
|
public uint MaxNodesCount;
|
||||||
|
public uint MinInputCount;
|
||||||
|
public uint MaxInputCount;
|
||||||
|
public uint MinOutputCount;
|
||||||
|
public uint MaxOutputCount;
|
||||||
|
|
||||||
|
public Func<NodesGeneratorSettings, uint, string?> ConnectorNameGenerator;
|
||||||
|
public Func<NodesGeneratorSettings, uint, string?> NodeNameGenerator;
|
||||||
|
public Func<NodesGeneratorSettings, uint, Point> NodeLocationGenerator;
|
||||||
|
|
||||||
|
public int Snap(int x)
|
||||||
|
=> x / (int)GridSnap * (int)GridSnap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RandomNodesGenerator
|
||||||
|
{
|
||||||
|
private static readonly Random _rand = new Random();
|
||||||
|
|
||||||
|
public static List<T> GenerateNodes<T>(NodesGeneratorSettings settings)
|
||||||
|
where T : FlowNodeViewModel, new()
|
||||||
|
{
|
||||||
|
var nodes = new List<T>();
|
||||||
|
var count = _rand.Next((int)settings.MinNodesCount, (int)settings.MaxNodesCount + 1);
|
||||||
|
|
||||||
|
for (uint i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
var node = new T
|
||||||
|
{
|
||||||
|
Title = settings.NodeNameGenerator(settings, i),
|
||||||
|
Location = settings.NodeLocationGenerator(settings, i)
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes.Add(node);
|
||||||
|
node.Input.AddRange(GenerateConnectors(settings, _rand.Next((int)settings.MinInputCount, (int)settings.MaxInputCount + 1)));
|
||||||
|
node.Output.AddRange(GenerateConnectors(settings, _rand.Next((int)settings.MinOutputCount, (int)settings.MaxOutputCount + 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ConnectionViewModel> GenerateConnections(IList<NodeViewModel> nodes)
|
||||||
|
{
|
||||||
|
HashSet<NodeViewModel> visited = new HashSet<NodeViewModel>(nodes.Count);
|
||||||
|
List<ConnectionViewModel> connections = new List<ConnectionViewModel>(nodes.Count);
|
||||||
|
|
||||||
|
for (uint i = 0; i < nodes.Count; i++)
|
||||||
|
{
|
||||||
|
var n1 = nodes[_rand.Next(0, nodes.Count)];
|
||||||
|
var n2 = nodes[_rand.Next(0, nodes.Count)];
|
||||||
|
|
||||||
|
if (n1 == n2 && !(visited.Add(n1) && visited.Add(n2)))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ConnectorViewModel> input = n1 is FlowNodeViewModel flow ? flow.Input.ToList() :
|
||||||
|
n1 is KnotNodeViewModel knot ? new List<ConnectorViewModel> { knot.Connector } : new List<ConnectorViewModel>();
|
||||||
|
|
||||||
|
List<ConnectorViewModel> output = n2 is FlowNodeViewModel flow2 ? flow2.Output.ToList() :
|
||||||
|
n2 is KnotNodeViewModel knot2 ? new List<ConnectorViewModel> { knot2.Connector } : new List<ConnectorViewModel>();
|
||||||
|
|
||||||
|
connections.AddRange(ConnectPins(input, output));
|
||||||
|
}
|
||||||
|
|
||||||
|
return connections;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ConnectionViewModel> ConnectPins(IList<ConnectorViewModel> source, IList<ConnectorViewModel> target)
|
||||||
|
{
|
||||||
|
Dictionary<ConnectorViewModel, List<ConnectorViewModel>> connections = new Dictionary<ConnectorViewModel, List<ConnectorViewModel>>();
|
||||||
|
List<ConnectionViewModel> result = new List<ConnectionViewModel>();
|
||||||
|
|
||||||
|
for (int di = 0; di < target.Count; di++)
|
||||||
|
{
|
||||||
|
if (source.Count > 1 && target.Count > 1 && _rand.Next() % 2 == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var outP = target[di];
|
||||||
|
|
||||||
|
if (!connections.TryGetValue(outP, out var outConns))
|
||||||
|
{
|
||||||
|
var newList = new List<ConnectorViewModel>();
|
||||||
|
connections.Add(outP, newList);
|
||||||
|
outConns = newList;
|
||||||
|
}
|
||||||
|
|
||||||
|
var conNum = _rand.Next(0, source.Count + 1);
|
||||||
|
for (uint ci = 0; ci < conNum; ci++)
|
||||||
|
{
|
||||||
|
var inP = source[_rand.Next(0, conNum)];
|
||||||
|
|
||||||
|
if (!connections.TryGetValue(inP, out var inConns))
|
||||||
|
{
|
||||||
|
var newList = new List<ConnectorViewModel>();
|
||||||
|
connections.Add(inP, newList);
|
||||||
|
inConns = newList;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!connections[inP].Contains(outP) && !connections[outP].Contains(inP))
|
||||||
|
{
|
||||||
|
var isInput = inP.Flow == ConnectorFlow.Input;
|
||||||
|
|
||||||
|
var connection = new ConnectionViewModel
|
||||||
|
{
|
||||||
|
Input = isInput ? inP : outP,
|
||||||
|
Output = isInput ? outP : inP
|
||||||
|
};
|
||||||
|
result.Add(connection);
|
||||||
|
|
||||||
|
inConns.Add(outP);
|
||||||
|
outConns.Add(inP);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ConnectorViewModel> GenerateConnectors(NodesGeneratorSettings settings, int count)
|
||||||
|
{
|
||||||
|
var list = new List<ConnectorViewModel>(count);
|
||||||
|
|
||||||
|
for (uint i = 0; i < count; i++)
|
||||||
|
{
|
||||||
|
int shapeVal = _rand.Next() % 3;
|
||||||
|
var connector = new ConnectorViewModel
|
||||||
|
{
|
||||||
|
Title = settings.ConnectorNameGenerator(settings, i),
|
||||||
|
Shape = PlaygroundSettings.Instance.UseCustomConnectors ? (ConnectorShape)shapeVal : ConnectorShape.Circle
|
||||||
|
};
|
||||||
|
|
||||||
|
list.Add(connector);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
24
Examples/Nodify.Playground/ISettingViewModel.cs
Normal file
24
Examples/Nodify.Playground/ISettingViewModel.cs
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
namespace Nodify.Playground
|
||||||
|
{
|
||||||
|
public enum SettingsType
|
||||||
|
{
|
||||||
|
Boolean,
|
||||||
|
Number,
|
||||||
|
Option,
|
||||||
|
Point,
|
||||||
|
Text
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ISettingViewModel
|
||||||
|
{
|
||||||
|
string Name { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the content within the tooltip.
|
||||||
|
/// </summary>
|
||||||
|
string? Description { get; }
|
||||||
|
object? Value { get; set; }
|
||||||
|
|
||||||
|
SettingsType Type { get;}
|
||||||
|
}
|
||||||
|
}
|
||||||
275
Examples/Nodify.Playground/MainWindow.xaml
Normal file
275
Examples/Nodify.Playground/MainWindow.xaml
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
<Window x:Class="Nodify.Playground.MainWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:local="clr-namespace:Nodify.Playground"
|
||||||
|
xmlns:shared="clr-namespace:Nodify;assembly=Nodify.Shared"
|
||||||
|
xmlns:nodify="https://miroiu.github.io/nodify"
|
||||||
|
mc:Ignorable="d"
|
||||||
|
Title="MainWindow"
|
||||||
|
Height="700"
|
||||||
|
Width="1300">
|
||||||
|
|
||||||
|
<Window.Resources>
|
||||||
|
<shared:DebugConverter x:Key="DebugConverter" />
|
||||||
|
<shared:ToStringConverter x:Key="ToStringConverter" />
|
||||||
|
<shared:StringToVisibilityConverter x:Key="StringToVisibilityConverter" />
|
||||||
|
</Window.Resources>
|
||||||
|
|
||||||
|
<Window.DataContext>
|
||||||
|
<local:PlaygroundViewModel />
|
||||||
|
</Window.DataContext>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<local:NodifyEditorView x:Name="EditorView"
|
||||||
|
DataContext="{Binding GraphViewModel}"
|
||||||
|
Grid.RowSpan="3" />
|
||||||
|
|
||||||
|
<!--ACTIONS-->
|
||||||
|
<Border VerticalAlignment="Top"
|
||||||
|
Background="{DynamicResource PanelBackgroundBrush}"
|
||||||
|
Padding="10">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<Button Command="{Binding GenerateRandomNodesCommand}"
|
||||||
|
Content="GENERATE NODES"
|
||||||
|
ToolTip="Generate nodes using the specified settings."
|
||||||
|
Style="{StaticResource HollowButton}" />
|
||||||
|
<Button Command="{Binding ToggleConnectionsCommand}"
|
||||||
|
Content="{Binding ConnectNodesText}"
|
||||||
|
ToolTip="Will add new connections if Connect Nodes is checked, otherwise it will disconnect nodes."
|
||||||
|
Style="{StaticResource HollowButton}" />
|
||||||
|
<Button Command="{Binding PerformanceTestCommand}"
|
||||||
|
Content="PERFORMANCE TEST"
|
||||||
|
ToolTip="You will encounter rendering performance issues. Try disabling the connections to see the difference."
|
||||||
|
Style="{StaticResource HollowButton}" />
|
||||||
|
<Button Command="{Binding ResetCommand}"
|
||||||
|
Content="RESET PLAYGROUND"
|
||||||
|
ToolTip="Reset the Location, Zoom, Nodes and Connections."
|
||||||
|
Style="{StaticResource HollowButton}" />
|
||||||
|
<Button Click="BringIntoView_Click"
|
||||||
|
Content="BRING INTO VIEW"
|
||||||
|
ToolTip="Bring a random node into view."
|
||||||
|
Style="{StaticResource HollowButton}" />
|
||||||
|
<Button Command="{x:Static nodify:EditorCommands.FitToScreen}"
|
||||||
|
Content="FIT TO SCREEN"
|
||||||
|
ToolTip="Scales the viewport to fit all nodes if that's possible."
|
||||||
|
CommandTarget="{Binding EditorInstance, ElementName=EditorView}"
|
||||||
|
Style="{StaticResource HollowButton}" />
|
||||||
|
<Button Command="{Binding GraphViewModel.CommentSelectionCommand}"
|
||||||
|
Content="COMMENT SELECTION"
|
||||||
|
ToolTip="Creates a comment node containing the selected nodes."
|
||||||
|
Style="{StaticResource HollowButton}" />
|
||||||
|
<Button Click="AnimateConnections_Click"
|
||||||
|
Content="TOGGLE CONNECTIONS ANIMATION"
|
||||||
|
ToolTip="Starts or stops animating the directional arrows on all connections (see DirectionalArrowsCount)"
|
||||||
|
Style="{StaticResource HollowButton}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<Button Style="{StaticResource IconButton}"
|
||||||
|
Content="{StaticResource ThemeIcon}"
|
||||||
|
Command="{Binding Source={x:Static shared:ThemeManager.SetNextThemeCommand}}"
|
||||||
|
ToolTip="Change theme"
|
||||||
|
Grid.Column="1" />
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!--SETTINGS-->
|
||||||
|
<Expander Grid.Row="1"
|
||||||
|
HorizontalContentAlignment="Left"
|
||||||
|
VerticalContentAlignment="Center"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
Background="{DynamicResource PanelBackgroundBrush}"
|
||||||
|
IsExpanded="True"
|
||||||
|
ExpandDirection="Left"
|
||||||
|
Padding="0 1 4 3">
|
||||||
|
<Expander.Style>
|
||||||
|
<Style TargetType="{x:Type Expander}"
|
||||||
|
BasedOn="{StaticResource {x:Type Expander}}">
|
||||||
|
<Setter Property="Tag"
|
||||||
|
Value="{StaticResource ExpandRightIcon}" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsExpanded"
|
||||||
|
Value="True">
|
||||||
|
<Setter Property="Tag"
|
||||||
|
Value="{StaticResource ExpandLeftIcon}" />
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Expander.Style>
|
||||||
|
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Margin="0 0 5 10">
|
||||||
|
<TextBlock Margin="0 0 0 3">Search:</TextBlock>
|
||||||
|
<TextBox Text="{Binding Source={x:Static local:PlaygroundSettings.Instance}, Path=SearchText, UpdateSourceTrigger=PropertyChanged, Mode=TwoWay}"
|
||||||
|
Padding="4" />
|
||||||
|
</StackPanel>
|
||||||
|
<ScrollViewer Grid.Row="1">
|
||||||
|
<Grid IsSharedSizeScope="True"
|
||||||
|
Width="330">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Expander Header="Playground Settings"
|
||||||
|
Padding="0 5 0 0"
|
||||||
|
BorderThickness="0 0 0 1"
|
||||||
|
IsExpanded="True"
|
||||||
|
BorderBrush="{DynamicResource BackgroundBrush}">
|
||||||
|
<Expander.Style>
|
||||||
|
<Style TargetType="{x:Type Expander}"
|
||||||
|
BasedOn="{StaticResource {x:Type Expander}}">
|
||||||
|
<Setter Property="Tag"
|
||||||
|
Value="{StaticResource ExpandRightIcon}" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsExpanded"
|
||||||
|
Value="True">
|
||||||
|
<Setter Property="Tag"
|
||||||
|
Value="{StaticResource ExpandDownIcon}" />
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Expander.Style>
|
||||||
|
<Border BorderThickness="1"
|
||||||
|
Padding="10"
|
||||||
|
HorizontalAlignment="Stretch">
|
||||||
|
<local:SettingsView Items="{Binding Source={x:Static local:PlaygroundSettings.Instance}, Path=Settings}" />
|
||||||
|
</Border>
|
||||||
|
</Expander>
|
||||||
|
|
||||||
|
<Expander Header="Editor Settings"
|
||||||
|
Padding="0 5 0 0"
|
||||||
|
BorderThickness="0 0 0 1"
|
||||||
|
IsExpanded="True"
|
||||||
|
BorderBrush="{DynamicResource BackgroundBrush}"
|
||||||
|
Grid.Row="1">
|
||||||
|
<Expander.Style>
|
||||||
|
<Style TargetType="{x:Type Expander}"
|
||||||
|
BasedOn="{StaticResource {x:Type Expander}}">
|
||||||
|
<Setter Property="Tag"
|
||||||
|
Value="{StaticResource ExpandRightIcon}" />
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsExpanded"
|
||||||
|
Value="True">
|
||||||
|
<Setter Property="Tag"
|
||||||
|
Value="{StaticResource ExpandDownIcon}" />
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Expander.Style>
|
||||||
|
|
||||||
|
<local:EditorSettingsView />
|
||||||
|
</Expander>
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
</Grid>
|
||||||
|
</Expander>
|
||||||
|
|
||||||
|
<!--INFORMATION-->
|
||||||
|
<Border Grid.Row="2"
|
||||||
|
Background="{DynamicResource PanelBackgroundBrush}"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Padding="10">
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.Resources>
|
||||||
|
<Style TargetType="{x:Type TextBlock}"
|
||||||
|
BasedOn="{StaticResource {x:Type TextBlock}}">
|
||||||
|
<Setter Property="Foreground"
|
||||||
|
Value="{DynamicResource ForegroundBrush}" />
|
||||||
|
<Setter Property="Margin"
|
||||||
|
Value="0 0 15 0" />
|
||||||
|
</Style>
|
||||||
|
</Grid.Resources>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal">
|
||||||
|
<TextBlock ToolTip="The number of selected items.">
|
||||||
|
<TextBlock.Inlines>
|
||||||
|
<Run Text="Selected nodes: " />
|
||||||
|
<Run Foreground="YellowGreen"
|
||||||
|
Text="{Binding GraphViewModel.SelectedNodes.Count, Mode=OneWay}" />
|
||||||
|
<Run Text="/" />
|
||||||
|
<Run Text="{Binding GraphViewModel.Nodes.Count, Mode=OneWay}" />
|
||||||
|
</TextBlock.Inlines>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock ToolTip="The number of selected connections.">
|
||||||
|
<TextBlock.Inlines>
|
||||||
|
<Run Text="Selected connections: " />
|
||||||
|
<Run Foreground="YellowGreen"
|
||||||
|
Text="{Binding GraphViewModel.SelectedConnections.Count, Mode=OneWay}" />
|
||||||
|
<Run Text="/" />
|
||||||
|
<Run Text="{Binding GraphViewModel.Connections.Count, Mode=OneWay}" />
|
||||||
|
</TextBlock.Inlines>
|
||||||
|
</TextBlock>
|
||||||
|
|
||||||
|
<Border Visibility="{Binding GraphViewModel.KeyboardNavigationLayer, Converter={StaticResource StringToVisibilityConverter}}"
|
||||||
|
ToolTip="Press CTRL+[ or CTRL+] in the editor to change the keyboard navigation layer."
|
||||||
|
Padding="14 0 0 0"
|
||||||
|
Height="16"
|
||||||
|
CornerRadius="3"
|
||||||
|
Background="{DynamicResource PanelBackgroundBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
BorderBrush="{DynamicResource BorderBrush}">
|
||||||
|
<TextBlock>
|
||||||
|
<Run Text="Navigating: " />
|
||||||
|
<Run Foreground="{DynamicResource ForegroundBrush}"
|
||||||
|
Text="{Binding GraphViewModel.KeyboardNavigationLayer, Mode=OneWay}" />
|
||||||
|
</TextBlock>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<StackPanel Orientation="Horizontal"
|
||||||
|
HorizontalAlignment="Right">
|
||||||
|
<TextBlock ToolTip="The viewport's location.">
|
||||||
|
<TextBlock.Inlines>
|
||||||
|
<Run Text="Location: " />
|
||||||
|
<Run Foreground="Orange"
|
||||||
|
Text="{Binding Location.Value, Mode=OneWay, Converter={StaticResource ToStringConverter}, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
</TextBlock.Inlines>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock ToolTip="The viewport's size.">
|
||||||
|
<TextBlock.Inlines>
|
||||||
|
<Run Text="Size: " />
|
||||||
|
<Run Foreground="YellowGreen"
|
||||||
|
Text="{Binding GraphViewModel.ViewportSize, Mode=OneWay, Converter={StaticResource ToStringConverter}}" />
|
||||||
|
</TextBlock.Inlines>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock ToolTip="The viewport's zoom. Not accurate when trying to zoom outside the MinViewportZoom and MaxViewportZoom because of dependency property coercion not updating the binding with the final result.">
|
||||||
|
<TextBlock.Inlines>
|
||||||
|
<Run Text="Zoom: " />
|
||||||
|
<Run Foreground="DodgerBlue"
|
||||||
|
Text="{Binding Zoom, Mode=OneWay, Converter={StaticResource ToStringConverter}, Source={x:Static local:EditorSettings.Instance}}" />
|
||||||
|
</TextBlock.Inlines>
|
||||||
|
</TextBlock>
|
||||||
|
<TextBlock ToolTip="The estimated frame rate. (my be buggy)">
|
||||||
|
<TextBlock.Inlines>
|
||||||
|
<Run Text="FPS: " />
|
||||||
|
<Run Foreground="LawnGreen"
|
||||||
|
Name="FPSText" />
|
||||||
|
</TextBlock.Inlines>
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
95
Examples/Nodify.Playground/MainWindow.xaml.cs
Normal file
95
Examples/Nodify.Playground/MainWindow.xaml.cs
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
using System;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using System.Windows.Media;
|
||||||
|
|
||||||
|
namespace Nodify.Playground
|
||||||
|
{
|
||||||
|
public static class CompositionTargetEx
|
||||||
|
{
|
||||||
|
private static TimeSpan _last = TimeSpan.Zero;
|
||||||
|
private static event Action<double>? FrameUpdating;
|
||||||
|
|
||||||
|
public static event Action<double> Rendering
|
||||||
|
{
|
||||||
|
add
|
||||||
|
{
|
||||||
|
if (FrameUpdating == null)
|
||||||
|
{
|
||||||
|
CompositionTarget.Rendering += OnRendering;
|
||||||
|
}
|
||||||
|
FrameUpdating += value;
|
||||||
|
}
|
||||||
|
remove
|
||||||
|
{
|
||||||
|
FrameUpdating -= value;
|
||||||
|
if (FrameUpdating == null)
|
||||||
|
{
|
||||||
|
CompositionTarget.Rendering -= OnRendering;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnRendering(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
RenderingEventArgs args = (RenderingEventArgs)e;
|
||||||
|
var renderingTime = args.RenderingTime;
|
||||||
|
if (renderingTime == _last)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double fps = 1000 / (renderingTime - _last).TotalMilliseconds;
|
||||||
|
_last = renderingTime;
|
||||||
|
FrameUpdating?.Invoke(fps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Interaction logic for MainWindow.xaml
|
||||||
|
/// </summary>
|
||||||
|
public partial class MainWindow : Window
|
||||||
|
{
|
||||||
|
private readonly Random _rand = new Random();
|
||||||
|
|
||||||
|
public MainWindow()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
|
||||||
|
CompositionTargetEx.Rendering += OnRendering;
|
||||||
|
|
||||||
|
EventManager.RegisterClassHandler(
|
||||||
|
typeof(UIElement),
|
||||||
|
Keyboard.PreviewGotKeyboardFocusEvent,
|
||||||
|
(KeyboardFocusChangedEventHandler)OnPreviewGotKeyboardFocus);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnPreviewGotKeyboardFocus(object sender, KeyboardFocusChangedEventArgs e)
|
||||||
|
{
|
||||||
|
Title = e.NewFocus.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnRendering(double fps)
|
||||||
|
{
|
||||||
|
FPSText.Text = fps.ToString("0");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BringIntoView_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (DataContext is PlaygroundViewModel model)
|
||||||
|
{
|
||||||
|
NodifyObservableCollection<NodeViewModel> nodes = model.GraphViewModel.Nodes;
|
||||||
|
int index = _rand.Next(nodes.Count);
|
||||||
|
|
||||||
|
if (nodes.Count > index)
|
||||||
|
{
|
||||||
|
NodeViewModel node = nodes[index];
|
||||||
|
EditorCommands.BringIntoView.Execute(node.Location, EditorView.Editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AnimateConnections_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
EditorSettings.Instance.IsAnimatingConnections = !EditorSettings.Instance.IsAnimatingConnections;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
Examples/Nodify.Playground/Nodify.Playground.csproj
Normal file
17
Examples/Nodify.Playground/Nodify.Playground.csproj
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>WinExe</OutputType>
|
||||||
|
<TargetFrameworks>net9-windows;net8-windows;net6-windows;net5-windows;netcoreapp3.1;net48;net472</TargetFrameworks>
|
||||||
|
<UseWPF>true</UseWPF>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)'=='net472' OR '$(TargetFramework)'=='net48'">
|
||||||
|
<LangVersion>8.0</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Nodify\Nodify.csproj" />
|
||||||
|
<ProjectReference Include="..\Nodify.Shared\Nodify.Shared.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user