1
0
mirror of https://github.com/Siccity/xNode.git synced 2026-02-04 14:24:54 +08:00

refactor project

convert init on load method to wait until editor is done updating before attempting to reconnect loose nodes
This commit is contained in:
Stephen Hodgson 2023-07-27 13:04:45 -04:00
parent 82f7887931
commit bad4cc5c17
No known key found for this signature in database
GPG Key ID: 9A5CBE13747461CA
129 changed files with 6996 additions and 3581 deletions

View File

@ -1,8 +0,0 @@
root = true
[*.cs]
indent_style = space
indent_size = 4
end_of_line = crlf
insert_final_newline = false
trim_trailing_whitespace = true

38
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: build
on:
pull_request:
branches:
- '*'
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
include:
- os: windows
build-target: Android
steps:
- uses: actions/checkout@v3
with:
clean: false
lfs: true
# Installs the Unity Editor based on your project version text file
# sets -> env.UNITY_EDITOR_PATH
# sets -> env.UNITY_PROJECT_PATH
# https://github.com/XRTK/unity-setup
- uses: xrtk/unity-setup@v7.2
with:
build-targets: ${{ matrix.build-target }}
- name: Unity Build (${{ matrix.build-target }})
uses: RageAgainstThePixel/unity-build@v5
with:
build-target: ${{ matrix.build-target }}
publish-artifacts: false

35
.github/workflows/upm-subtree-split.yml vendored Normal file
View File

@ -0,0 +1,35 @@
name: upm-subtree-split
on:
push:
branches:
- main
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
upm-subtree-split:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
token: ${{ secrets.CI_TOKEN }}
fetch-depth: 0
- name: upm subtree split
run: |
# upm subtree split
git config user.name github-actions
git config user.email github-actions@github.com
git fetch --all --tags
$packageDir = Get-Item -Path "**/Packages/com.*" | Select-Object -ExpandProperty FullName
$packageDir = $packageDir.replace('${{ github.workspace }}/','')
Write-Host $packageDir
git subtree split --prefix="$packageDir" -b upm
git checkout upm
git fetch origin upm
git rebase origin/upm --reapply-cherry-picks
git push origin upm --force-with-lease --tags --set-upstream --verbose
working-directory: ${{ github.workspace }}
shell: pwsh

112
.gitignore vendored
View File

@ -1,30 +1,84 @@
/[Ll]ibrary/ # ============ #
/[Tt]emp/ # System Files #
/[Oo]bj/ # ============ #
/[Bb]uild/
# Autogenerated VS/MD solution and project files
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
# Unity3D generated meta files
*.pidb.meta
# Unity3D Generated File On Crash Reports
sysinfo.txt
/Examples/
.git.meta
.gitignore.meta
.gitattributes.meta
# OS X only:
.DS_Store .DS_Store
._*
# =============== #
# Unity generated #
# =============== #
[Aa]pp/
[Aa]pp.meta
[Bb]in/
[Bb]uilds/
[Bb]uild/
[Ll]ibrary/
[Ll]ogs/
[Oo]bj/
[Tt]emp/
UserSettings/
UWP/
WindowsStoreApp/
UnityGenerated/
UnityPackageManager/
.out/
.gradle/
project.json
project.lock.json
*.package
TextMesh Pro.meta
TextMesh Pro/
UIElementsSchema/
*packages-lock.json
# ============ #
# Certificates #
# ============ #
*.cert
*.privkey
*.pfx
*.pfx.meta
# ===================================== #
# Visual Studio / MonoDevelop generated #
# ===================================== #
.vs/
ExportedObj/
obj/
*.svd
*.userprefs
/*.csproj
*.csproj
*.pidb
*.suo
/*.sln
*.sln
*.user
*.unityproj
*.ipch
*.opensdf
*.sdf
*.tlog
*.log
*.idb
*.opendb
*.vsconfig
# ============================ #
# Visual Studio Code Generated #
# ============================ #
.vscode/
# ========================= #
# Jetbrains Rider Generated #
# ========================= #
.idea/
_ReSharper.Caches
# ===================== #
# Project Specific List #
# ===================== #
--Version/
artifacts/
StreamingAssets/
StreamingAssets.meta

View File

@ -1,40 +0,0 @@
## Contributing to xNode
💙Thank you for taking the time to contribute💙
If you haven't already, join our [Discord channel](https://discord.gg/qgPrHv4)!
## Pull Requests
Try to keep your pull requests relevant, neat, and manageable. If you are adding multiple features, split them into separate PRs.
These are the main points to follow:
1) Use formatting which is consistent with the rest of xNode base (see below)
2) Keep _one feature_ per PR (see below)
3) xNode aims to be compatible with C# 4.x, do not use new language features
4) Avoid including irellevant whitespace or formatting changes
5) Comment your code
6) Spell check your code / comments
7) Use concrete types, not *var*
8) Use english language
## New features
xNode aims to be simple and extendible, not trying to fix all of Unity's shortcomings.
Approved changes might be rejected if bundled with rejected changes, so keep PRs as separate as possible.
If your feature aims to cover something not related to editing nodes, it generally won't be accepted. If in doubt, ask on the Discord channel.
## Coding conventions
Using consistent formatting is key to having a clean git history. Skim through the code and you'll get the hang of it quickly.
* Methods, Types and properties PascalCase
* Variables camelCase
* Public methods XML commented. Params described if not obvious
* Explicit usage of brackets when doing multiple math operations on the same line
## Formatting
I use VSCode with the C# FixFormat extension and the following setting overrides:
```json
"csharpfixformat.style.spaces.beforeParenthesis": false,
"csharpfixformat.style.indent.regionIgnored": true
```
* Open braces on same line as condition
* 4 spaces for indentation.

View File

@ -1,66 +0,0 @@
using UnityEditor;
using UnityEngine;
using System.IO;
namespace XNodeEditor {
/// <summary> Deals with modified assets </summary>
class NodeEditorAssetModProcessor : UnityEditor.AssetModificationProcessor {
/// <summary> Automatically delete Node sub-assets before deleting their script.
/// This is important to do, because you can't delete null sub assets.
/// <para/> For another workaround, see: https://gitlab.com/RotaryHeart-UnityShare/subassetmissingscriptdelete </summary>
private static AssetDeleteResult OnWillDeleteAsset (string path, RemoveAssetOptions options) {
// Skip processing anything without the .cs extension
if (Path.GetExtension(path) != ".cs") return AssetDeleteResult.DidNotDelete;
// Get the object that is requested for deletion
UnityEngine.Object obj = AssetDatabase.LoadAssetAtPath<UnityEngine.Object> (path);
// If we aren't deleting a script, return
if (!(obj is UnityEditor.MonoScript)) return AssetDeleteResult.DidNotDelete;
// Check script type. Return if deleting a non-node script
UnityEditor.MonoScript script = obj as UnityEditor.MonoScript;
System.Type scriptType = script.GetClass ();
if (scriptType == null || (scriptType != typeof (XNode.Node) && !scriptType.IsSubclassOf (typeof (XNode.Node)))) return AssetDeleteResult.DidNotDelete;
// Find all ScriptableObjects using this script
string[] guids = AssetDatabase.FindAssets ("t:" + scriptType);
for (int i = 0; i < guids.Length; i++) {
string assetpath = AssetDatabase.GUIDToAssetPath (guids[i]);
Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath);
for (int k = 0; k < objs.Length; k++) {
XNode.Node node = objs[k] as XNode.Node;
if (node.GetType () == scriptType) {
if (node != null && node.graph != null) {
// Delete the node and notify the user
Debug.LogWarning (node.name + " of " + node.graph + " depended on deleted script and has been removed automatically.", node.graph);
node.graph.RemoveNode (node);
}
}
}
}
// We didn't actually delete the script. Tell the internal system to carry on with normal deletion procedure
return AssetDeleteResult.DidNotDelete;
}
/// <summary> Automatically re-add loose node assets to the Graph node list </summary>
[InitializeOnLoadMethod]
private static void OnReloadEditor () {
// Find all NodeGraph assets
string[] guids = AssetDatabase.FindAssets ("t:" + typeof (XNode.NodeGraph));
for (int i = 0; i < guids.Length; i++) {
string assetpath = AssetDatabase.GUIDToAssetPath (guids[i]);
XNode.NodeGraph graph = AssetDatabase.LoadAssetAtPath (assetpath, typeof (XNode.NodeGraph)) as XNode.NodeGraph;
graph.nodes.RemoveAll(x => x == null); //Remove null items
Object[] objs = AssetDatabase.LoadAllAssetRepresentationsAtPath (assetpath);
// Ensure that all sub node assets are present in the graph node list
for (int u = 0; u < objs.Length; u++) {
// Ignore null sub assets
if (objs[u] == null) continue;
if (!graph.nodes.Contains (objs[u] as XNode.Node)) graph.nodes.Add(objs[u] as XNode.Node);
}
}
}
}
}

View File

@ -1,45 +0,0 @@
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Experimental.AssetImporters;
using UnityEngine;
using XNode;
namespace XNodeEditor {
/// <summary> Deals with modified assets </summary>
class NodeGraphImporter : AssetPostprocessor {
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) {
foreach (string path in importedAssets) {
// Skip processing anything without the .asset extension
if (Path.GetExtension(path) != ".asset") continue;
// Get the object that is requested for deletion
NodeGraph graph = AssetDatabase.LoadAssetAtPath<NodeGraph>(path);
if (graph == null) continue;
// Get attributes
Type graphType = graph.GetType();
NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll(
graphType.GetCustomAttributes(typeof(NodeGraph.RequireNodeAttribute), true), x => x as NodeGraph.RequireNodeAttribute);
Vector2 position = Vector2.zero;
foreach (NodeGraph.RequireNodeAttribute attrib in attribs) {
if (attrib.type0 != null) AddRequired(graph, attrib.type0, ref position);
if (attrib.type1 != null) AddRequired(graph, attrib.type1, ref position);
if (attrib.type2 != null) AddRequired(graph, attrib.type2, ref position);
}
}
}
private static void AddRequired(NodeGraph graph, Type type, ref Vector2 position) {
if (!graph.nodes.Any(x => x.GetType() == type)) {
XNode.Node node = graph.AddNode(type);
node.position = position;
position.x += 200;
if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type);
if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(graph))) AssetDatabase.AddObjectToAsset(node, graph);
}
}
}
}

View File

@ -1,7 +0,0 @@
{
"name": "com.github.siccity.xnode",
"description": "xNode provides a set of APIs and an editor interface for creating and editing custom node graphs.",
"version": "1.8.0",
"unity": "2018.1",
"displayName": "xNode"
}

View File

@ -1,9 +1,8 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 657b15cb3ec32a24ca80faebf094d0f4 guid: 999581c3922a8f942959bcb0a0fc7d18
folderAsset: yes folderAsset: yes
timeCreated: 1505418321
licenseType: Free
DefaultImporter: DefaultImporter:
externalObjects: {}
userData: userData:
assetBundleName: assetBundleName:
assetBundleVariant: assetBundleVariant:

View File

@ -0,0 +1,267 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
m_ObjectHideFlags: 0
serializedVersion: 2
m_OcclusionBakeSettings:
smallestOccluder: 5
smallestHole: 0.25
backfaceThreshold: 100
m_SceneGUID: 00000000000000000000000000000000
m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
m_ObjectHideFlags: 0
serializedVersion: 9
m_Fog: 0
m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1}
m_FogMode: 3
m_FogDensity: 0.01
m_LinearFogStart: 0
m_LinearFogEnd: 300
m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1}
m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1}
m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1}
m_AmbientIntensity: 1
m_AmbientMode: 0
m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1}
m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0}
m_HaloStrength: 0.5
m_FlareStrength: 1
m_FlareFadeSpeed: 3
m_HaloTexture: {fileID: 0}
m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0}
m_DefaultReflectionMode: 0
m_DefaultReflectionResolution: 128
m_ReflectionBounces: 1
m_ReflectionIntensity: 1
m_CustomReflection: {fileID: 0}
m_Sun: {fileID: 705507994}
m_IndirectSpecularColor: {r: 0, g: 0, b: 0, a: 1}
m_UseRadianceAmbientProbe: 0
--- !u!157 &3
LightmapSettings:
m_ObjectHideFlags: 0
serializedVersion: 12
m_GIWorkflowMode: 1
m_GISettings:
serializedVersion: 2
m_BounceScale: 1
m_IndirectOutputScale: 1
m_AlbedoBoost: 1
m_EnvironmentLightingMode: 0
m_EnableBakedLightmaps: 1
m_EnableRealtimeLightmaps: 0
m_LightmapEditorSettings:
serializedVersion: 12
m_Resolution: 2
m_BakeResolution: 40
m_AtlasSize: 1024
m_AO: 0
m_AOMaxDistance: 1
m_CompAOExponent: 1
m_CompAOExponentDirect: 0
m_ExtractAmbientOcclusion: 0
m_Padding: 2
m_LightmapParameters: {fileID: 0}
m_LightmapsBakeMode: 1
m_TextureCompression: 1
m_FinalGather: 0
m_FinalGatherFiltering: 1
m_FinalGatherRayCount: 256
m_ReflectionCompression: 2
m_MixedBakeMode: 2
m_BakeBackend: 1
m_PVRSampling: 1
m_PVRDirectSampleCount: 32
m_PVRSampleCount: 500
m_PVRBounces: 2
m_PVREnvironmentSampleCount: 500
m_PVREnvironmentReferencePointCount: 2048
m_PVRFilteringMode: 2
m_PVRDenoiserTypeDirect: 0
m_PVRDenoiserTypeIndirect: 0
m_PVRDenoiserTypeAO: 0
m_PVRFilterTypeDirect: 0
m_PVRFilterTypeIndirect: 0
m_PVRFilterTypeAO: 0
m_PVREnvironmentMIS: 0
m_PVRCulling: 1
m_PVRFilteringGaussRadiusDirect: 1
m_PVRFilteringGaussRadiusIndirect: 5
m_PVRFilteringGaussRadiusAO: 2
m_PVRFilteringAtrousPositionSigmaDirect: 0.5
m_PVRFilteringAtrousPositionSigmaIndirect: 2
m_PVRFilteringAtrousPositionSigmaAO: 1
m_ExportTrainingData: 0
m_TrainingDataDestination: TrainingData
m_LightProbeSampleCountMultiplier: 4
m_LightingDataAsset: {fileID: 0}
m_LightingSettings: {fileID: 0}
--- !u!196 &4
NavMeshSettings:
serializedVersion: 2
m_ObjectHideFlags: 0
m_BuildSettings:
serializedVersion: 2
agentTypeID: 0
agentRadius: 0.5
agentHeight: 2
agentSlope: 45
agentClimb: 0.4
ledgeDropHeight: 0
maxJumpAcrossDistance: 0
minRegionArea: 2
manualCellSize: 0
cellSize: 0.16666667
manualTileSize: 0
tileSize: 256
accuratePlacement: 0
debug:
m_Flags: 0
m_NavMeshData: {fileID: 0}
--- !u!1 &705507993
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 705507995}
- component: {fileID: 705507994}
m_Layer: 0
m_Name: Directional Light
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!108 &705507994
Light:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 705507993}
m_Enabled: 1
serializedVersion: 8
m_Type: 1
m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1}
m_Intensity: 1
m_Range: 10
m_SpotAngle: 30
m_CookieSize: 10
m_Shadows:
m_Type: 2
m_Resolution: -1
m_CustomResolution: -1
m_Strength: 1
m_Bias: 0.05
m_NormalBias: 0.4
m_NearPlane: 0.2
m_Cookie: {fileID: 0}
m_DrawHalo: 0
m_Flare: {fileID: 0}
m_RenderMode: 0
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_Lightmapping: 1
m_LightShadowCasterMode: 0
m_AreaSize: {x: 1, y: 1}
m_BounceIntensity: 1
m_ColorTemperature: 6570
m_UseColorTemperature: 0
m_ShadowRadius: 0
m_ShadowAngle: 0
--- !u!4 &705507995
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 705507993}
m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261}
m_LocalPosition: {x: 0, y: 3, z: 0}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 1
m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0}
--- !u!1 &963194225
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 963194228}
- component: {fileID: 963194227}
- component: {fileID: 963194226}
m_Layer: 0
m_Name: Main Camera
m_TagString: MainCamera
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!81 &963194226
AudioListener:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 963194225}
m_Enabled: 1
--- !u!20 &963194227
Camera:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 963194225}
m_Enabled: 1
serializedVersion: 2
m_ClearFlags: 1
m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0}
m_projectionMatrixMode: 1
m_SensorSize: {x: 36, y: 24}
m_LensShift: {x: 0, y: 0}
m_GateFitMode: 2
m_FocalLength: 50
m_NormalizedViewPortRect:
serializedVersion: 2
x: 0
y: 0
width: 1
height: 1
near clip plane: 0.3
far clip plane: 1000
field of view: 60
orthographic: 0
orthographic size: 5
m_Depth: -1
m_CullingMask:
serializedVersion: 2
m_Bits: 4294967295
m_RenderingPath: -1
m_TargetTexture: {fileID: 0}
m_TargetDisplay: 0
m_TargetEye: 3
m_HDR: 1
m_AllowMSAA: 1
m_AllowDynamicResolution: 0
m_ForceIntoRT: 0
m_OcclusionCulling: 1
m_StereoConvergence: 10
m_StereoSeparation: 0.022
--- !u!4 &963194228
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInternal: {fileID: 0}
m_GameObject: {fileID: 963194225}
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 1, z: -10}
m_LocalScale: {x: 1, y: 1, z: 1}
m_Children: []
m_Father: {fileID: 0}
m_RootOrder: 0
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

View File

@ -1,6 +1,6 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: bc1db8b29c76d44648c9c86c2dfade6d guid: 9fc0d4010bbf28b4594072e72b8655ab
TextScriptImporter: DefaultImporter:
externalObjects: {} externalObjects: {}
userData: userData:
assetBundleName: assetBundleName:

View File

@ -0,0 +1,75 @@
# https://docs.microsoft.com/en-us/dotnet/fundamentals/code-analysis/code-style-rule-options
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
indent_style = space
tab_width = 4
# New line preferences
end_of_line = crlf
insert_final_newline = true
trim_trailing_whitespace = true
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_open_brace = all
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members:error
# Code-block preferences
csharp_prefer_braces = true:error
# Use language keywords for types
dotnet_style_predefined_type_for_member_access = true
dotnet_style_predefined_type_for_locals_parameters_members = true
# Code Style
csharp_style_var_when_type_is_apparent = true
#### Resharper/Rider Rules ####
# https://www.jetbrains.com/help/resharper/EditorConfig_Properties.html
resharper_csharp_force_attribute_style=separate
csharp_place_field_attribute_on_same_line=false
csharp_place_accessorholder_attribute_on_same_line=false
csharp_trailing_comma_in_multiline_lists=false
csharp_trailing_comma_in_singleline_lists=false
csharp_keep_existing_attribute_arrangement=false
csharp_blank_lines_around_region=1
csharp_blank_lines_inside_region=1
csharp_keep_blank_lines_in_code=false
csharp_remove_blank_lines_near_braces_in_code=true
csharp_blank_lines_before_control_transfer_statements=1
csharp_blank_lines_after_control_transfer_statements=1
csharp_blank_lines_before_block_statements=1
csharp_blank_lines_after_block_statements=1
csharp_blank_lines_before_multiline_statements=1
csharp_blank_lines_after_multiline_statements=1
csharp_blank_lines_around_block_case_section=0
csharp_blank_lines_around_multiline_case_section=0
csharp_blank_lines_before_case=0
csharp_blank_lines_after_case=0
resharper_unity_duplicate_event_function_highlighting=error
resharper_unity_duplicate_shortcut_highlighting=error
resharper_unity_expected_component_highlighting=error
resharper_unity_explicit_tag_comparison_highlighting=error
resharper_unity_incorrect_mono_behaviour_instantiation_highlighting=error
resharper_unity_incorrect_scriptable_object_instantiation_highlighting=error
resharper_unity_no_null_coalescing_highlighting=error
resharper_unity_performance_critical_code_camera_main_highlighting=error
resharper_unity_performance_critical_code_invocation_highlighting=warning
resharper_unity_performance_critical_code_null_comparison_highlighting=warning
resharper_unity_possible_misapplication_of_attribute_to_multiple_fields_highlighting=error
resharper_unity_redundant_attribute_on_target_highlighting=error
resharper_unity_redundant_formerly_serialized_as_attribute_highlighting=error
resharper_unity_unresolved_component_or_scriptable_object_highlighting=error
resharper_use_name_of_instead_of_type_of_highlighting=error
resharper_wrong_public_modifier_specification_highlighting=error

View File

@ -0,0 +1,31 @@
name: publish
on:
push:
branches:
- upm
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
token: ${{ secrets.CI_TOKEN }}
ref: upm
clean: true
lfs: true
- uses: xrtk/upm-release@development
with:
upm-username: 'pillow-build-bot'
upm-email: 'hello@pillow.social'
upm-server-address: 'http://upm.pillow.social:4873'
upm-auth-token: '${{ secrets.UPM_AUTH_TOKEN }}'
github-username: 'TogetherXR'
github-pat: '${{ secrets.CI_TOKEN }}'
github-token: '${{ secrets.GITHUB_TOKEN }}'
package-root: '${{ github.workspace }}'

View File

@ -0,0 +1,2 @@
.npmrc
.github/

View File

@ -0,0 +1 @@
strict-ssl=false

View File

@ -1,6 +1,7 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: 243efae3a6b7941ad8f8e54dcf38ce8c guid: 809e2193de010ae4b9fa476efbe41cd4
TextScriptImporter: folderAsset: yes
DefaultImporter:
externalObjects: {} externalObjects: {}
userData: userData:
assetBundleName: assetBundleName:

View File

@ -51,44 +51,56 @@ namespace XNodeEditor
public void Run() public void Run()
{ {
if ( func2 != null ) if ( func2 != null )
{
func2( userData ); func2( userData );
}
else if ( func != null ) else if ( func != null )
{
func(); func();
} }
} }
}
private List<AdvancedGenericMenuItem> items = new List<AdvancedGenericMenuItem>(); private List<AdvancedGenericMenuItem> items = new List<AdvancedGenericMenuItem>();
private AdvancedGenericMenuItem FindOrCreateItem( string name, AdvancedGenericMenuItem currentRoot = null ) private AdvancedGenericMenuItem FindOrCreateItem( string name, AdvancedGenericMenuItem currentRoot = null )
{ {
if ( string.IsNullOrWhiteSpace( name ) ) if ( string.IsNullOrWhiteSpace( name ) )
{
return null; return null;
}
AdvancedGenericMenuItem item = null; AdvancedGenericMenuItem item = null;
string[] paths = name.Split( '/' ); var paths = name.Split( '/' );
if ( currentRoot == null ) if ( currentRoot == null )
{ {
item = items.FirstOrDefault( x => x != null && x.name == paths[0] ); item = items.FirstOrDefault( x => x != null && x.name == paths[0] );
if ( item == null ) if ( item == null )
{
items.Add( item = new AdvancedGenericMenuItem( paths[0] ) ); items.Add( item = new AdvancedGenericMenuItem( paths[0] ) );
} }
}
else else
{ {
item = currentRoot.children.OfType<AdvancedGenericMenuItem>().FirstOrDefault( x => x.name == paths[0] ); item = currentRoot.children.OfType<AdvancedGenericMenuItem>().FirstOrDefault( x => x.name == paths[0] );
if ( item == null ) if ( item == null )
{
currentRoot.AddChild( item = new AdvancedGenericMenuItem( paths[0] ) ); currentRoot.AddChild( item = new AdvancedGenericMenuItem( paths[0] ) );
} }
}
if ( paths.Length > 1 ) if ( paths.Length > 1 )
{
return FindOrCreateItem( string.Join( "/", paths, 1, paths.Length - 1 ), item ); return FindOrCreateItem( string.Join( "/", paths, 1, paths.Length - 1 ), item );
}
return item; return item;
} }
private AdvancedGenericMenuItem FindParent( string name ) private AdvancedGenericMenuItem FindParent( string name )
{ {
string[] paths = name.Split( '/' ); var paths = name.Split( '/' );
return FindOrCreateItem( string.Join( "/", paths, 0, paths.Length - 1 ) ); return FindOrCreateItem( string.Join( "/", paths, 0, paths.Length - 1 ) );
} }
@ -169,10 +181,14 @@ namespace XNodeEditor
{ {
var parent = string.IsNullOrWhiteSpace( path ) ? null : FindParent( path ); var parent = string.IsNullOrWhiteSpace( path ) ? null : FindParent( path );
if ( parent == null ) if ( parent == null )
{
items.Add( null ); items.Add( null );
}
else else
{
parent.AddSeparator(); parent.AddSeparator();
} }
}
// //
// Summary: // Summary:
@ -195,10 +211,14 @@ namespace XNodeEditor
foreach ( var m in items ) foreach ( var m in items )
{ {
if ( m == null ) if ( m == null )
{
root.AddSeparator(); root.AddSeparator();
}
else else
{
root.AddChild( m ); root.AddChild( m );
} }
}
return root; return root;
} }
@ -206,8 +226,10 @@ namespace XNodeEditor
protected override void ItemSelected( AdvancedDropdownItem item ) protected override void ItemSelected( AdvancedDropdownItem item )
{ {
if ( item is AdvancedGenericMenuItem gmItem ) if ( item is AdvancedGenericMenuItem gmItem )
{
gmItem.Run(); gmItem.Run();
} }
} }
} }
}
#endif #endif

View File

@ -27,8 +27,11 @@ namespace XNodeEditor {
position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label); position = EditorGUI.PrefixLabel(position, GUIUtility.GetControlID(FocusType.Passive), label);
// Get current enum name // Get current enum name
string enumName = ""; var enumName = "";
if (property.enumValueIndex >= 0 && property.enumValueIndex < property.enumDisplayNames.Length) enumName = property.enumDisplayNames[property.enumValueIndex]; if (property.enumValueIndex >= 0 && property.enumValueIndex < property.enumDisplayNames.Length)
{
enumName = property.enumDisplayNames[property.enumValueIndex];
}
#if UNITY_2017_1_OR_NEWER #if UNITY_2017_1_OR_NEWER
// Display dropdown // Display dropdown
@ -49,16 +52,16 @@ namespace XNodeEditor {
public static void ShowContextMenuAtMouse(SerializedProperty property) { public static void ShowContextMenuAtMouse(SerializedProperty property) {
// Initialize menu // Initialize menu
GenericMenu menu = new GenericMenu(); var menu = new GenericMenu();
// Add all enum display names to menu // Add all enum display names to menu
for (int i = 0; i < property.enumDisplayNames.Length; i++) { for (var i = 0; i < property.enumDisplayNames.Length; i++) {
int index = i; var index = i;
menu.AddItem(new GUIContent(property.enumDisplayNames[i]), false, () => SetEnum(property, index)); menu.AddItem(new GUIContent(property.enumDisplayNames[i]), false, () => SetEnum(property, index));
} }
// Display at cursor position // Display at cursor position
Rect r = new Rect(Event.current.mousePosition, new Vector2(0, 0)); var r = new Rect(Event.current.mousePosition, new Vector2(0, 0));
menu.DropDown(r); menu.DropDown(r);
} }

View File

@ -57,8 +57,8 @@ namespace XNodeEditor {
serializedObject.Update(); serializedObject.Update();
if (GUILayout.Button("Edit graph", GUILayout.Height(40))) { if (GUILayout.Button("Edit graph", GUILayout.Height(40))) {
SerializedProperty graphProp = serializedObject.FindProperty("graph"); var graphProp = serializedObject.FindProperty("graph");
NodeEditorWindow w = NodeEditorWindow.Open(graphProp.objectReferenceValue as XNode.NodeGraph); var w = NodeEditorWindow.Open(graphProp.objectReferenceValue as XNode.NodeGraph);
w.Home(); // Focus selected node w.Home(); // Focus selected node
} }

View File

@ -16,8 +16,8 @@ namespace XNodeEditor {
string[] deletedAssets, string[] deletedAssets,
string[] movedAssets, string[] movedAssets,
string[] movedFromAssetPaths) { string[] movedFromAssetPaths) {
for (int i = 0; i < movedAssets.Length; i++) { for (var i = 0; i < movedAssets.Length; i++) {
Node nodeAsset = AssetDatabase.LoadMainAssetAtPath(movedAssets[i]) as Node; var nodeAsset = AssetDatabase.LoadMainAssetAtPath(movedAssets[i]) as Node;
// If the renamed asset is a node graph, but the v2 AssetDatabase has swapped a sub-asset node to be its // If the renamed asset is a node graph, but the v2 AssetDatabase has swapped a sub-asset node to be its
// main asset, reset the node graph to be the main asset and rename the node asset back to its default // main asset, reset the node graph to be the main asset and rename the node asset back to its default

View File

@ -72,18 +72,26 @@ namespace XNodeEditor {
#else #else
// Iterate through serialized properties and draw them like the Inspector (But with ports) // Iterate through serialized properties and draw them like the Inspector (But with ports)
SerializedProperty iterator = serializedObject.GetIterator(); var iterator = serializedObject.GetIterator();
bool enterChildren = true; var enterChildren = true;
while (iterator.NextVisible(enterChildren)) { while (iterator.NextVisible(enterChildren)) {
enterChildren = false; enterChildren = false;
if (excludes.Contains(iterator.name)) continue; if (excludes.Contains(iterator.name))
{
continue;
}
NodeEditorGUILayout.PropertyField(iterator, true); NodeEditorGUILayout.PropertyField(iterator, true);
} }
#endif #endif
// Iterate through dynamic ports and draw them in the order in which they are serialized // Iterate through dynamic ports and draw them in the order in which they are serialized
foreach (XNode.NodePort dynamicPort in target.DynamicPorts) { foreach (var dynamicPort in target.DynamicPorts) {
if (NodeEditorGUILayout.IsDynamicPortListPort(dynamicPort)) continue; if (NodeEditorGUILayout.IsDynamicPortListPort(dynamicPort))
{
continue;
}
NodeEditorGUILayout.PortField(dynamicPort); NodeEditorGUILayout.PortField(dynamicPort);
} }
@ -103,20 +111,32 @@ namespace XNodeEditor {
} }
public virtual int GetWidth() { public virtual int GetWidth() {
Type type = target.GetType(); var type = target.GetType();
int width; int width;
if (type.TryGetAttributeWidth(out width)) return width; if (type.TryGetAttributeWidth(out width))
else return 208; {
return width;
}
else
{
return 208;
}
} }
/// <summary> Returns color for target node </summary> /// <summary> Returns color for target node </summary>
public virtual Color GetTint() { public virtual Color GetTint() {
// Try get color from [NodeTint] attribute // Try get color from [NodeTint] attribute
Type type = target.GetType(); var type = target.GetType();
Color color; Color color;
if (type.TryGetAttributeTint(out color)) return color; if (type.TryGetAttributeTint(out color))
{
return color;
}
// Return default color (grey) // Return default color (grey)
else return NodeEditorPreferences.GetSettings().tintColor; else
{
return NodeEditorPreferences.GetSettings().tintColor;
}
} }
public virtual GUIStyle GetBodyStyle() { public virtual GUIStyle GetBodyStyle() {
@ -134,10 +154,10 @@ namespace XNodeEditor {
/// <summary> Add items for the context menu when right-clicking this node. Override to add custom menu items. </summary> /// <summary> Add items for the context menu when right-clicking this node. Override to add custom menu items. </summary>
public virtual void AddContextMenuItems(GenericMenu menu) { public virtual void AddContextMenuItems(GenericMenu menu) {
bool canRemove = true; var canRemove = true;
// Actions if only one node is selected // Actions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
XNode.Node node = Selection.activeObject as XNode.Node; var node = Selection.activeObject as XNode.Node;
menu.AddItem(new GUIContent("Move To Top"), false, () => NodeEditorWindow.current.MoveNodeToTop(node)); menu.AddItem(new GUIContent("Move To Top"), false, () => NodeEditorWindow.current.MoveNodeToTop(node));
menu.AddItem(new GUIContent("Rename"), false, NodeEditorWindow.current.RenameSelectedNode); menu.AddItem(new GUIContent("Rename"), false, NodeEditorWindow.current.RenameSelectedNode);
@ -148,19 +168,29 @@ namespace XNodeEditor {
menu.AddItem(new GUIContent("Copy"), false, NodeEditorWindow.current.CopySelectedNodes); menu.AddItem(new GUIContent("Copy"), false, NodeEditorWindow.current.CopySelectedNodes);
menu.AddItem(new GUIContent("Duplicate"), false, NodeEditorWindow.current.DuplicateSelectedNodes); menu.AddItem(new GUIContent("Duplicate"), false, NodeEditorWindow.current.DuplicateSelectedNodes);
if (canRemove) menu.AddItem(new GUIContent("Remove"), false, NodeEditorWindow.current.RemoveSelectedNodes); if (canRemove)
else menu.AddItem(new GUIContent("Remove"), false, null); {
menu.AddItem(new GUIContent("Remove"), false, NodeEditorWindow.current.RemoveSelectedNodes);
}
else
{
menu.AddItem(new GUIContent("Remove"), false, null);
}
// Custom sctions if only one node is selected // Custom sctions if only one node is selected
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
XNode.Node node = Selection.activeObject as XNode.Node; var node = Selection.activeObject as XNode.Node;
menu.AddCustomContextMenuItems(node); menu.AddCustomContextMenuItems(node);
} }
} }
/// <summary> Rename the node asset. This will trigger a reimport of the node. </summary> /// <summary> Rename the node asset. This will trigger a reimport of the node. </summary>
public void Rename(string newName) { public void Rename(string newName) {
if (newName == null || newName.Trim() == "") newName = NodeEditorUtilities.NodeDefaultName(target.GetType()); if (newName == null || newName.Trim() == "")
{
newName = NodeEditorUtilities.NodeDefaultName(target.GetType());
}
target.name = newName; target.name = newName;
OnRename(); OnRename();
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target)); AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(target));

View File

@ -23,11 +23,11 @@ namespace XNodeEditor {
public bool IsHoveringReroute { get { return hoveredReroute.port != null; } } public bool IsHoveringReroute { get { return hoveredReroute.port != null; } }
/// <summary> Return the dragged port or null if not exist </summary> /// <summary> Return the dragged port or null if not exist </summary>
public XNode.NodePort DraggedOutputPort { get { XNode.NodePort result = draggedOutput; return result; } } public XNode.NodePort DraggedOutputPort { get { var result = draggedOutput; return result; } }
/// <summary> Return the Hovered port or null if not exist </summary> /// <summary> Return the Hovered port or null if not exist </summary>
public XNode.NodePort HoveredPort { get { XNode.NodePort result = hoveredPort; return result; } } public XNode.NodePort HoveredPort { get { var result = hoveredPort; return result; } }
/// <summary> Return the Hovered node or null if not exist </summary> /// <summary> Return the Hovered node or null if not exist </summary>
public XNode.Node HoveredNode { get { XNode.Node result = hoveredNode; return result; } } public XNode.Node HoveredNode { get { var result = hoveredNode; return result; } }
private XNode.Node hoveredNode = null; private XNode.Node hoveredNode = null;
[NonSerialized] public XNode.NodePort hoveredPort = null; [NonSerialized] public XNode.NodePort hoveredPort = null;
@ -48,7 +48,7 @@ namespace XNodeEditor {
public void Controls() { public void Controls() {
wantsMouseMove = true; wantsMouseMove = true;
Event e = Event.current; var e = Event.current;
switch (e.type) { switch (e.type) {
case EventType.DragUpdated: case EventType.DragUpdated:
case EventType.DragPerform: case EventType.DragPerform:
@ -63,10 +63,21 @@ namespace XNodeEditor {
lastMousePosition = e.mousePosition; lastMousePosition = e.mousePosition;
break; break;
case EventType.ScrollWheel: case EventType.ScrollWheel:
float oldZoom = zoom; var oldZoom = zoom;
if (e.delta.y > 0) zoom += 0.1f * zoom; if (e.delta.y > 0)
else zoom -= 0.1f * zoom; {
if (NodeEditorPreferences.GetSettings().zoomToMouse) panOffset += (1 - oldZoom / zoom) * (WindowToGridPosition(e.mousePosition) + panOffset); zoom += 0.1f * zoom;
}
else
{
zoom -= 0.1f * zoom;
}
if (NodeEditorPreferences.GetSettings().zoomToMouse)
{
panOffset += (1 - oldZoom / zoom) * (WindowToGridPosition(e.mousePosition) + panOffset);
}
break; break;
case EventType.MouseDrag: case EventType.MouseDrag:
if (e.button == 0) { if (e.button == 0) {
@ -85,16 +96,19 @@ namespace XNodeEditor {
} }
if (currentActivity == NodeActivity.DragNode) { if (currentActivity == NodeActivity.DragNode) {
// Holding ctrl inverts grid snap // Holding ctrl inverts grid snap
bool gridSnap = NodeEditorPreferences.GetSettings().gridSnap; var gridSnap = NodeEditorPreferences.GetSettings().gridSnap;
if (e.control) gridSnap = !gridSnap; if (e.control)
{
gridSnap = !gridSnap;
}
Vector2 mousePos = WindowToGridPosition(e.mousePosition); var mousePos = WindowToGridPosition(e.mousePosition);
// Move selected nodes with offset // Move selected nodes with offset
for (int i = 0; i < Selection.objects.Length; i++) { for (var i = 0; i < Selection.objects.Length; i++) {
if (Selection.objects[i] is XNode.Node) { if (Selection.objects[i] is XNode.Node) {
XNode.Node node = Selection.objects[i] as XNode.Node; var node = Selection.objects[i] as XNode.Node;
Undo.RecordObject(node, "Moved Node"); Undo.RecordObject(node, "Moved Node");
Vector2 initial = node.position; var initial = node.position;
node.position = mousePos + dragOffset[i]; node.position = mousePos + dragOffset[i];
if (gridSnap) { if (gridSnap) {
node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8; node.position.x = (Mathf.Round((node.position.x + 8) / 16) * 16) - 8;
@ -102,9 +116,9 @@ namespace XNodeEditor {
} }
// Offset portConnectionPoints instantly if a node is dragged so they aren't delayed by a frame. // Offset portConnectionPoints instantly if a node is dragged so they aren't delayed by a frame.
Vector2 offset = node.position - initial; var offset = node.position - initial;
if (offset.sqrMagnitude > 0) { if (offset.sqrMagnitude > 0) {
foreach (XNode.NodePort output in node.Outputs) { foreach (var output in node.Outputs) {
Rect rect; Rect rect;
if (portConnectionPoints.TryGetValue(output, out rect)) { if (portConnectionPoints.TryGetValue(output, out rect)) {
rect.position += offset; rect.position += offset;
@ -112,7 +126,7 @@ namespace XNodeEditor {
} }
} }
foreach (XNode.NodePort input in node.Inputs) { foreach (var input in node.Inputs) {
Rect rect; Rect rect;
if (portConnectionPoints.TryGetValue(input, out rect)) { if (portConnectionPoints.TryGetValue(input, out rect)) {
rect.position += offset; rect.position += offset;
@ -123,8 +137,8 @@ namespace XNodeEditor {
} }
} }
// Move selected reroutes with offset // Move selected reroutes with offset
for (int i = 0; i < selectedReroutes.Count; i++) { for (var i = 0; i < selectedReroutes.Count; i++) {
Vector2 pos = mousePos + dragOffset[Selection.objects.Length + i]; var pos = mousePos + dragOffset[Selection.objects.Length + i];
if (gridSnap) { if (gridSnap) {
pos.x = (Mathf.Round(pos.x / 16) * 16); pos.x = (Mathf.Round(pos.x / 16) * 16);
pos.y = (Mathf.Round(pos.y / 16) * 16); pos.y = (Mathf.Round(pos.y / 16) * 16);
@ -139,8 +153,8 @@ namespace XNodeEditor {
dragBoxStart = WindowToGridPosition(e.mousePosition); dragBoxStart = WindowToGridPosition(e.mousePosition);
Repaint(); Repaint();
} else if (currentActivity == NodeActivity.DragGrid) { } else if (currentActivity == NodeActivity.DragGrid) {
Vector2 boxStartPos = GridToWindowPosition(dragBoxStart); var boxStartPos = GridToWindowPosition(dragBoxStart);
Vector2 boxSize = e.mousePosition - boxStartPos; var boxSize = e.mousePosition - boxStartPos;
if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); } if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); }
if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); } if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); }
selectionBox = new Rect(boxStartPos, boxSize); selectionBox = new Rect(boxStartPos, boxSize);
@ -167,22 +181,31 @@ namespace XNodeEditor {
hoveredPort.VerifyConnections(); hoveredPort.VerifyConnections();
autoConnectOutput = null; autoConnectOutput = null;
if (hoveredPort.IsConnected) { if (hoveredPort.IsConnected) {
XNode.Node node = hoveredPort.node; var node = hoveredPort.node;
XNode.NodePort output = hoveredPort.Connection; var output = hoveredPort.Connection;
int outputConnectionIndex = output.GetConnectionIndex(hoveredPort); var outputConnectionIndex = output.GetConnectionIndex(hoveredPort);
draggedOutputReroutes = output.GetReroutePoints(outputConnectionIndex); draggedOutputReroutes = output.GetReroutePoints(outputConnectionIndex);
hoveredPort.Disconnect(output); hoveredPort.Disconnect(output);
draggedOutput = output; draggedOutput = output;
draggedOutputTarget = hoveredPort; draggedOutputTarget = hoveredPort;
if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); if (NodeEditor.onUpdateNode != null)
{
NodeEditor.onUpdateNode(node);
}
} }
} }
} else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
// If mousedown on node header, select or deselect // If mousedown on node header, select or deselect
if (!Selection.Contains(hoveredNode)) { if (!Selection.Contains(hoveredNode)) {
SelectNode(hoveredNode, e.control || e.shift); SelectNode(hoveredNode, e.control || e.shift);
if (!e.control && !e.shift) selectedReroutes.Clear(); if (!e.control && !e.shift)
} else if (e.control || e.shift) DeselectNode(hoveredNode); {
selectedReroutes.Clear();
}
} else if (e.control || e.shift)
{
DeselectNode(hoveredNode);
}
// Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown. // Cache double click state, but only act on it in MouseUp - Except ClickCount only works in mouseDown.
isDoubleClick = (e.clickCount == 2); isDoubleClick = (e.clickCount == 2);
@ -193,7 +216,10 @@ namespace XNodeEditor {
// If reroute isn't selected // If reroute isn't selected
if (!selectedReroutes.Contains(hoveredReroute)) { if (!selectedReroutes.Contains(hoveredReroute)) {
// Add it // Add it
if (e.control || e.shift) selectedReroutes.Add(hoveredReroute); if (e.control || e.shift)
{
selectedReroutes.Add(hoveredReroute);
}
// Select it // Select it
else { else {
selectedReroutes = new List<RerouteReference>() { hoveredReroute }; selectedReroutes = new List<RerouteReference>() { hoveredReroute };
@ -202,7 +228,11 @@ namespace XNodeEditor {
} }
// Deselect // Deselect
else if (e.control || e.shift) selectedReroutes.Remove(hoveredReroute); else if (e.control || e.shift)
{
selectedReroutes.Remove(hoveredReroute);
}
e.Use(); e.Use();
currentActivity = NodeActivity.HoldNode; currentActivity = NodeActivity.HoldNode;
} }
@ -222,20 +252,27 @@ namespace XNodeEditor {
if (IsDraggingPort) { if (IsDraggingPort) {
// If connection is valid, save it // If connection is valid, save it
if (draggedOutputTarget != null && graphEditor.CanConnect(draggedOutput, draggedOutputTarget)) { if (draggedOutputTarget != null && graphEditor.CanConnect(draggedOutput, draggedOutputTarget)) {
XNode.Node node = draggedOutputTarget.node; var node = draggedOutputTarget.node;
if (graph.nodes.Count != 0) draggedOutput.Connect(draggedOutputTarget); if (graph.nodes.Count != 0)
{
draggedOutput.Connect(draggedOutputTarget);
}
// ConnectionIndex can be -1 if the connection is removed instantly after creation // ConnectionIndex can be -1 if the connection is removed instantly after creation
int connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget); var connectionIndex = draggedOutput.GetConnectionIndex(draggedOutputTarget);
if (connectionIndex != -1) { if (connectionIndex != -1) {
draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes); draggedOutput.GetReroutePoints(connectionIndex).AddRange(draggedOutputReroutes);
if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); if (NodeEditor.onUpdateNode != null)
{
NodeEditor.onUpdateNode(node);
}
EditorUtility.SetDirty(graph); EditorUtility.SetDirty(graph);
} }
} }
// Open context menu for auto-connection if there is no target node // Open context menu for auto-connection if there is no target node
else if (draggedOutputTarget == null && NodeEditorPreferences.GetSettings().dragToCreate && autoConnectOutput != null) { else if (draggedOutputTarget == null && NodeEditorPreferences.GetSettings().dragToCreate && autoConnectOutput != null) {
GenericMenu menu = new GenericMenu(); var menu = new GenericMenu();
graphEditor.AddContextMenuItems(menu, draggedOutput.ValueType); graphEditor.AddContextMenuItems(menu, draggedOutput.ValueType);
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
} }
@ -243,18 +280,31 @@ namespace XNodeEditor {
draggedOutput = null; draggedOutput = null;
draggedOutputTarget = null; draggedOutputTarget = null;
EditorUtility.SetDirty(graph); EditorUtility.SetDirty(graph);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave)
{
AssetDatabase.SaveAssets();
}
} else if (currentActivity == NodeActivity.DragNode) { } else if (currentActivity == NodeActivity.DragNode) {
IEnumerable<XNode.Node> nodes = Selection.objects.Where(x => x is XNode.Node).Select(x => x as XNode.Node); var nodes = Selection.objects.Where(x => x is XNode.Node).Select(x => x as XNode.Node);
foreach (XNode.Node node in nodes) EditorUtility.SetDirty(node); foreach (var node in nodes)
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); {
EditorUtility.SetDirty(node);
}
if (NodeEditorPreferences.GetSettings().autoSave)
{
AssetDatabase.SaveAssets();
}
} else if (!IsHoveringNode) { } else if (!IsHoveringNode) {
// If click outside node, release field focus // If click outside node, release field focus
if (!isPanning) { if (!isPanning) {
EditorGUI.FocusTextInControl(null); EditorGUI.FocusTextInControl(null);
EditorGUIUtility.editingTextField = false; EditorGUIUtility.editingTextField = false;
} }
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave)
{
AssetDatabase.SaveAssets();
}
} }
// If click node header, select it. // If click node header, select it.
@ -264,7 +314,7 @@ namespace XNodeEditor {
// Double click to center node // Double click to center node
if (isDoubleClick) { if (isDoubleClick) {
Vector2 nodeDimension = nodeSizes.ContainsKey(hoveredNode) ? nodeSizes[hoveredNode] / 2 : Vector2.zero; var nodeDimension = nodeSizes.ContainsKey(hoveredNode) ? nodeSizes[hoveredNode] / 2 : Vector2.zero;
panOffset = -hoveredNode.position - nodeDimension; panOffset = -hoveredNode.position - nodeDimension;
} }
} }
@ -289,15 +339,19 @@ namespace XNodeEditor {
} else if (IsHoveringPort) { } else if (IsHoveringPort) {
ShowPortContextMenu(hoveredPort); ShowPortContextMenu(hoveredPort);
} else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) { } else if (IsHoveringNode && IsHoveringTitle(hoveredNode)) {
if (!Selection.Contains(hoveredNode)) SelectNode(hoveredNode, false); if (!Selection.Contains(hoveredNode))
{
SelectNode(hoveredNode, false);
}
autoConnectOutput = null; autoConnectOutput = null;
GenericMenu menu = new GenericMenu(); var menu = new GenericMenu();
NodeEditor.GetEditor(hoveredNode, this).AddContextMenuItems(menu); NodeEditor.GetEditor(hoveredNode, this).AddContextMenuItems(menu);
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
e.Use(); // Fixes copy/paste context menu appearing in Unity 5.6.6f2 - doesn't occur in 2018.3.2f1 Probably needs to be used in other places. e.Use(); // Fixes copy/paste context menu appearing in Unity 5.6.6f2 - doesn't occur in 2018.3.2f1 Probably needs to be used in other places.
} else if (!IsHoveringNode) { } else if (!IsHoveringNode) {
autoConnectOutput = null; autoConnectOutput = null;
GenericMenu menu = new GenericMenu(); var menu = new GenericMenu();
graphEditor.AddContextMenuItems(menu); graphEditor.AddContextMenuItems(menu);
menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); menu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
} }
@ -308,20 +362,33 @@ namespace XNodeEditor {
isDoubleClick = false; isDoubleClick = false;
break; break;
case EventType.KeyDown: case EventType.KeyDown:
if (EditorGUIUtility.editingTextField || GUIUtility.keyboardControl != 0) break; if (EditorGUIUtility.editingTextField || GUIUtility.keyboardControl != 0)
else if (e.keyCode == KeyCode.F) Home(); {
break;
}
else if (e.keyCode == KeyCode.F)
{
Home();
}
if (NodeEditorUtilities.IsMac()) { if (NodeEditorUtilities.IsMac()) {
if (e.keyCode == KeyCode.Return) RenameSelectedNode(); if (e.keyCode == KeyCode.Return)
{
RenameSelectedNode();
}
} else { } else {
if (e.keyCode == KeyCode.F2) RenameSelectedNode(); if (e.keyCode == KeyCode.F2)
{
RenameSelectedNode();
}
} }
if (e.keyCode == KeyCode.A) { if (e.keyCode == KeyCode.A) {
if (Selection.objects.Any(x => graph.nodes.Contains(x as XNode.Node))) { if (Selection.objects.Any(x => graph.nodes.Contains(x as XNode.Node))) {
foreach (XNode.Node node in graph.nodes) { foreach (var node in graph.nodes) {
DeselectNode(node); DeselectNode(node);
} }
} else { } else {
foreach (XNode.Node node in graph.nodes) { foreach (var node in graph.nodes) {
SelectNode(node, true); SelectNode(node, true);
} }
} }
@ -331,22 +398,42 @@ namespace XNodeEditor {
case EventType.ValidateCommand: case EventType.ValidateCommand:
case EventType.ExecuteCommand: case EventType.ExecuteCommand:
if (e.commandName == "SoftDelete") { if (e.commandName == "SoftDelete") {
if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes(); if (e.type == EventType.ExecuteCommand)
{
RemoveSelectedNodes();
}
e.Use(); e.Use();
} else if (NodeEditorUtilities.IsMac() && e.commandName == "Delete") { } else if (NodeEditorUtilities.IsMac() && e.commandName == "Delete") {
if (e.type == EventType.ExecuteCommand) RemoveSelectedNodes(); if (e.type == EventType.ExecuteCommand)
{
RemoveSelectedNodes();
}
e.Use(); e.Use();
} else if (e.commandName == "Duplicate") { } else if (e.commandName == "Duplicate") {
if (e.type == EventType.ExecuteCommand) DuplicateSelectedNodes(); if (e.type == EventType.ExecuteCommand)
{
DuplicateSelectedNodes();
}
e.Use(); e.Use();
} else if (e.commandName == "Copy") { } else if (e.commandName == "Copy") {
if (!EditorGUIUtility.editingTextField) { if (!EditorGUIUtility.editingTextField) {
if (e.type == EventType.ExecuteCommand) CopySelectedNodes(); if (e.type == EventType.ExecuteCommand)
{
CopySelectedNodes();
}
e.Use(); e.Use();
} }
} else if (e.commandName == "Paste") { } else if (e.commandName == "Paste") {
if (!EditorGUIUtility.editingTextField) { if (!EditorGUIUtility.editingTextField) {
if (e.type == EventType.ExecuteCommand) PasteNodes(WindowToGridPosition(lastMousePosition)); if (e.type == EventType.ExecuteCommand)
{
PasteNodes(WindowToGridPosition(lastMousePosition));
}
e.Use(); e.Use();
} }
} }
@ -365,15 +452,15 @@ namespace XNodeEditor {
private void RecalculateDragOffsets(Event current) { private void RecalculateDragOffsets(Event current) {
dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count]; dragOffset = new Vector2[Selection.objects.Length + selectedReroutes.Count];
// Selected nodes // Selected nodes
for (int i = 0; i < Selection.objects.Length; i++) { for (var i = 0; i < Selection.objects.Length; i++) {
if (Selection.objects[i] is XNode.Node) { if (Selection.objects[i] is XNode.Node) {
XNode.Node node = Selection.objects[i] as XNode.Node; var node = Selection.objects[i] as XNode.Node;
dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition); dragOffset[i] = node.position - WindowToGridPosition(current.mousePosition);
} }
} }
// Selected reroutes // Selected reroutes
for (int i = 0; i < selectedReroutes.Count; i++) { for (var i = 0; i < selectedReroutes.Count; i++) {
dragOffset[Selection.objects.Length + i] = selectedReroutes[i].GetPoint() - WindowToGridPosition(current.mousePosition); dragOffset[Selection.objects.Length + i] = selectedReroutes[i].GetPoint() - WindowToGridPosition(current.mousePosition);
} }
} }
@ -382,8 +469,8 @@ namespace XNodeEditor {
public void Home() { public void Home() {
var nodes = Selection.objects.Where(o => o is XNode.Node).Cast<XNode.Node>().ToList(); var nodes = Selection.objects.Where(o => o is XNode.Node).Cast<XNode.Node>().ToList();
if (nodes.Count > 0) { if (nodes.Count > 0) {
Vector2 minPos = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); var minPos = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
Vector2 maxPos = nodes.Select(x => x.position + (nodeSizes.ContainsKey(x) ? nodeSizes[x] : Vector2.zero)).Aggregate((x, y) => new Vector2(Mathf.Max(x.x, y.x), Mathf.Max(x.y, y.y))); var maxPos = nodes.Select(x => x.position + (nodeSizes.ContainsKey(x) ? nodeSizes[x] : Vector2.zero)).Aggregate((x, y) => new Vector2(Mathf.Max(x.x, y.x), Mathf.Max(x.y, y.y)));
panOffset = -(minPos + (maxPos - minPos) / 2f); panOffset = -(minPos + (maxPos - minPos) / 2f);
} else { } else {
zoom = 2; zoom = 2;
@ -395,13 +482,13 @@ namespace XNodeEditor {
public void RemoveSelectedNodes() { public void RemoveSelectedNodes() {
// We need to delete reroutes starting at the highest point index to avoid shifting indices // We need to delete reroutes starting at the highest point index to avoid shifting indices
selectedReroutes = selectedReroutes.OrderByDescending(x => x.pointIndex).ToList(); selectedReroutes = selectedReroutes.OrderByDescending(x => x.pointIndex).ToList();
for (int i = 0; i < selectedReroutes.Count; i++) { for (var i = 0; i < selectedReroutes.Count; i++) {
selectedReroutes[i].RemovePoint(); selectedReroutes[i].RemovePoint();
} }
selectedReroutes.Clear(); selectedReroutes.Clear();
foreach (UnityEngine.Object item in Selection.objects) { foreach (var item in Selection.objects) {
if (item is XNode.Node) { if (item is XNode.Node) {
XNode.Node node = item as XNode.Node; var node = item as XNode.Node;
graphEditor.RemoveNode(node); graphEditor.RemoveNode(node);
} }
} }
@ -410,7 +497,7 @@ namespace XNodeEditor {
/// <summary> Initiate a rename on the currently selected node </summary> /// <summary> Initiate a rename on the currently selected node </summary>
public void RenameSelectedNode() { public void RenameSelectedNode() {
if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) { if (Selection.objects.Length == 1 && Selection.activeObject is XNode.Node) {
XNode.Node node = Selection.activeObject as XNode.Node; var node = Selection.activeObject as XNode.Node;
Vector2 size; Vector2 size;
if (nodeSizes.TryGetValue(node, out size)) { if (nodeSizes.TryGetValue(node, out size)) {
RenamePopup.Show(Selection.activeObject, size.x); RenamePopup.Show(Selection.activeObject, size.x);
@ -432,10 +519,14 @@ namespace XNodeEditor {
/// <summary> Duplicate selected nodes and select the duplicates </summary> /// <summary> Duplicate selected nodes and select the duplicates </summary>
public void DuplicateSelectedNodes() { public void DuplicateSelectedNodes() {
// Get selected nodes which are part of this graph // Get selected nodes which are part of this graph
XNode.Node[] selectedNodes = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray(); var selectedNodes = Selection.objects.Select(x => x as XNode.Node).Where(x => x != null && x.graph == graph).ToArray();
if (selectedNodes == null || selectedNodes.Length == 0) return; if (selectedNodes == null || selectedNodes.Length == 0)
{
return;
}
// Get top left node position // Get top left node position
Vector2 topLeftNode = selectedNodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); var topLeftNode = selectedNodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
InsertDuplicateNodes(selectedNodes, topLeftNode + new Vector2(30, 30)); InsertDuplicateNodes(selectedNodes, topLeftNode + new Vector2(30, 30));
} }
@ -448,40 +539,53 @@ namespace XNodeEditor {
} }
private void InsertDuplicateNodes(XNode.Node[] nodes, Vector2 topLeft) { private void InsertDuplicateNodes(XNode.Node[] nodes, Vector2 topLeft) {
if (nodes == null || nodes.Length == 0) return; if (nodes == null || nodes.Length == 0)
{
return;
}
// Get top-left node // Get top-left node
Vector2 topLeftNode = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y))); var topLeftNode = nodes.Select(x => x.position).Aggregate((x, y) => new Vector2(Mathf.Min(x.x, y.x), Mathf.Min(x.y, y.y)));
Vector2 offset = topLeft - topLeftNode; var offset = topLeft - topLeftNode;
UnityEngine.Object[] newNodes = new UnityEngine.Object[nodes.Length]; var newNodes = new UnityEngine.Object[nodes.Length];
Dictionary<XNode.Node, XNode.Node> substitutes = new Dictionary<XNode.Node, XNode.Node>(); var substitutes = new Dictionary<XNode.Node, XNode.Node>();
for (int i = 0; i < nodes.Length; i++) { for (var i = 0; i < nodes.Length; i++) {
XNode.Node srcNode = nodes[i]; var srcNode = nodes[i];
if (srcNode == null) continue; if (srcNode == null)
{
continue;
}
// Check if user is allowed to add more of given node type // Check if user is allowed to add more of given node type
XNode.Node.DisallowMultipleNodesAttribute disallowAttrib; XNode.Node.DisallowMultipleNodesAttribute disallowAttrib;
Type nodeType = srcNode.GetType(); var nodeType = srcNode.GetType();
if (NodeEditorUtilities.GetAttrib(nodeType, out disallowAttrib)) { if (NodeEditorUtilities.GetAttrib(nodeType, out disallowAttrib)) {
int typeCount = graph.nodes.Count(x => x.GetType() == nodeType); var typeCount = graph.nodes.Count(x => x.GetType() == nodeType);
if (typeCount >= disallowAttrib.max) continue; if (typeCount >= disallowAttrib.max)
{
continue;
}
} }
XNode.Node newNode = graphEditor.CopyNode(srcNode); var newNode = graphEditor.CopyNode(srcNode);
substitutes.Add(srcNode, newNode); substitutes.Add(srcNode, newNode);
newNode.position = srcNode.position + offset; newNode.position = srcNode.position + offset;
newNodes[i] = newNode; newNodes[i] = newNode;
} }
// Walk through the selected nodes again, recreate connections, using the new nodes // Walk through the selected nodes again, recreate connections, using the new nodes
for (int i = 0; i < nodes.Length; i++) { for (var i = 0; i < nodes.Length; i++) {
XNode.Node srcNode = nodes[i]; var srcNode = nodes[i];
if (srcNode == null) continue; if (srcNode == null)
foreach (XNode.NodePort port in srcNode.Ports) { {
for (int c = 0; c < port.ConnectionCount; c++) { continue;
XNode.NodePort inputPort = port.direction == XNode.NodePort.IO.Input ? port : port.GetConnection(c); }
XNode.NodePort outputPort = port.direction == XNode.NodePort.IO.Output ? port : port.GetConnection(c);
foreach (var port in srcNode.Ports) {
for (var c = 0; c < port.ConnectionCount; c++) {
var inputPort = port.direction == XNode.NodePort.IO.Input ? port : port.GetConnection(c);
var outputPort = port.direction == XNode.NodePort.IO.Output ? port : port.GetConnection(c);
XNode.Node newNodeIn, newNodeOut; XNode.Node newNodeIn, newNodeOut;
if (substitutes.TryGetValue(inputPort.node, out newNodeIn) && substitutes.TryGetValue(outputPort.node, out newNodeOut)) { if (substitutes.TryGetValue(inputPort.node, out newNodeIn) && substitutes.TryGetValue(outputPort.node, out newNodeOut)) {
@ -490,7 +594,10 @@ namespace XNodeEditor {
inputPort = newNodeIn.GetInputPort(inputPort.fieldName); inputPort = newNodeIn.GetInputPort(inputPort.fieldName);
outputPort = newNodeOut.GetOutputPort(outputPort.fieldName); outputPort = newNodeOut.GetOutputPort(outputPort.fieldName);
} }
if (!inputPort.IsConnectedTo(outputPort)) inputPort.Connect(outputPort); if (!inputPort.IsConnectedTo(outputPort))
{
inputPort.Connect(outputPort);
}
} }
} }
} }
@ -502,33 +609,43 @@ namespace XNodeEditor {
/// <summary> Draw a connection as we are dragging it </summary> /// <summary> Draw a connection as we are dragging it </summary>
public void DrawDraggedConnection() { public void DrawDraggedConnection() {
if (IsDraggingPort) { if (IsDraggingPort) {
Gradient gradient = graphEditor.GetNoodleGradient(draggedOutput, null); var gradient = graphEditor.GetNoodleGradient(draggedOutput, null);
float thickness = graphEditor.GetNoodleThickness(draggedOutput, null); var thickness = graphEditor.GetNoodleThickness(draggedOutput, null);
NoodlePath path = graphEditor.GetNoodlePath(draggedOutput, null); var path = graphEditor.GetNoodlePath(draggedOutput, null);
NoodleStroke stroke = graphEditor.GetNoodleStroke(draggedOutput, null); var stroke = graphEditor.GetNoodleStroke(draggedOutput, null);
Rect fromRect; Rect fromRect;
if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect)) return; if (!_portConnectionPoints.TryGetValue(draggedOutput, out fromRect))
List<Vector2> gridPoints = new List<Vector2>(); {
return;
}
var gridPoints = new List<Vector2>();
gridPoints.Add(fromRect.center); gridPoints.Add(fromRect.center);
for (int i = 0; i < draggedOutputReroutes.Count; i++) { for (var i = 0; i < draggedOutputReroutes.Count; i++) {
gridPoints.Add(draggedOutputReroutes[i]); gridPoints.Add(draggedOutputReroutes[i]);
} }
if (draggedOutputTarget != null) gridPoints.Add(portConnectionPoints[draggedOutputTarget].center); if (draggedOutputTarget != null)
else gridPoints.Add(WindowToGridPosition(Event.current.mousePosition)); {
gridPoints.Add(portConnectionPoints[draggedOutputTarget].center);
}
else
{
gridPoints.Add(WindowToGridPosition(Event.current.mousePosition));
}
DrawNoodle(gradient, path, stroke, thickness, gridPoints); DrawNoodle(gradient, path, stroke, thickness, gridPoints);
GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(draggedOutput); var portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(draggedOutput);
Color bgcol = Color.black; var bgcol = Color.black;
Color frcol = gradient.colorKeys[0].color; var frcol = gradient.colorKeys[0].color;
bgcol.a = 0.6f; bgcol.a = 0.6f;
frcol.a = 0.6f; frcol.a = 0.6f;
// Loop through reroute points again and draw the points // Loop through reroute points again and draw the points
for (int i = 0; i < draggedOutputReroutes.Count; i++) { for (var i = 0; i < draggedOutputReroutes.Count; i++) {
// Draw reroute point at position // Draw reroute point at position
Rect rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16)); var rect = new Rect(draggedOutputReroutes[i], new Vector2(16, 16));
rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8); rect.position = new Vector2(rect.position.x - 8, rect.position.y - 8);
rect = GridToWindowRect(rect); rect = GridToWindowRect(rect);
@ -537,29 +654,46 @@ namespace XNodeEditor {
} }
} }
bool IsHoveringTitle(XNode.Node node) { private bool IsHoveringTitle(XNode.Node node) {
Vector2 mousePos = Event.current.mousePosition; var mousePos = Event.current.mousePosition;
//Get node position //Get node position
Vector2 nodePos = GridToWindowPosition(node.position); var nodePos = GridToWindowPosition(node.position);
float width; float width;
Vector2 size; Vector2 size;
if (nodeSizes.TryGetValue(node, out size)) width = size.x; if (nodeSizes.TryGetValue(node, out size))
else width = 200; {
Rect windowRect = new Rect(nodePos, new Vector2(width / zoom, 30 / zoom)); width = size.x;
}
else
{
width = 200;
}
var windowRect = new Rect(nodePos, new Vector2(width / zoom, 30 / zoom));
return windowRect.Contains(mousePos); return windowRect.Contains(mousePos);
} }
/// <summary> Attempt to connect dragged output to target node </summary> /// <summary> Attempt to connect dragged output to target node </summary>
public void AutoConnect(XNode.Node node) { public void AutoConnect(XNode.Node node) {
if (autoConnectOutput == null) return; if (autoConnectOutput == null)
{
return;
}
// Find compatible input port // Find compatible input port
XNode.NodePort inputPort = node.Ports.FirstOrDefault(x => x.IsInput && graphEditor.CanConnect(autoConnectOutput, x)); var inputPort = node.Ports.FirstOrDefault(x => x.IsInput && graphEditor.CanConnect(autoConnectOutput, x));
if (inputPort != null) autoConnectOutput.Connect(inputPort); if (inputPort != null)
{
autoConnectOutput.Connect(inputPort);
}
// Save changes // Save changes
EditorUtility.SetDirty(graph); EditorUtility.SetDirty(graph);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave)
{
AssetDatabase.SaveAssets();
}
autoConnectOutput = null; autoConnectOutput = null;
} }
} }

View File

@ -0,0 +1,103 @@
using UnityEditor;
using UnityEngine;
using System.IO;
namespace XNodeEditor
{
/// <summary> Deals with modified assets </summary>
internal class NodeEditorAssetModProcessor : AssetModificationProcessor
{
/// <summary> Automatically delete Node sub-assets before deleting their script.
/// This is important to do, because you can't delete null sub assets.
/// <para/> For another workaround, see: https://gitlab.com/RotaryHeart-UnityShare/subassetmissingscriptdelete </summary>
private static AssetDeleteResult OnWillDeleteAsset(string path, RemoveAssetOptions options)
{
// Skip processing anything without the .cs extension
if (Path.GetExtension(path) != ".cs")
{
return AssetDeleteResult.DidNotDelete;
}
// Get the object that is requested for deletion
var obj = AssetDatabase.LoadAssetAtPath<Object>(path);
// If we aren't deleting a script, return
if (obj is MonoScript script)
{
var scriptType = script.GetClass();
if (scriptType == null ||
(scriptType != typeof(XNode.Node) && !scriptType.IsSubclassOf(typeof(XNode.Node))))
{
return AssetDeleteResult.DidNotDelete;
}
// Find all ScriptableObjects using this script
var guids = AssetDatabase.FindAssets($"t:{scriptType}");
for (var i = 0; i < guids.Length; i++)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
var objects = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetPath);
for (var k = 0; k < objects.Length; k++)
{
if (objects[k] is XNode.Node node &&
node.GetType() == scriptType &&
node != null && node.graph != null)
{
// Delete the node and notify the user
Debug.LogWarning($"{node.name} of {node.graph} depended on deleted script and has been removed automatically.", node.graph);
node.graph.RemoveNode(node);
}
}
}
// We didn't actually delete the script. Tell the internal system to carry on with normal deletion procedure
return AssetDeleteResult.DidNotDelete;
}
// Check script type. Return if deleting a non-node script
return AssetDeleteResult.DidNotDelete;
}
[InitializeOnLoadMethod]
private static void Init() => OnReloadEditor();
/// <summary> Automatically re-add loose node assets to the Graph node list </summary>
private static void OnReloadEditor() => EditorApplication.delayCall += () =>
{
if (EditorApplication.isUpdating)
{
OnReloadEditor();
return;
}
// Find all NodeGraph assets
var guids = AssetDatabase.FindAssets($"t:{typeof(XNode.NodeGraph)}");
for (var i = 0; i < guids.Length; i++)
{
var assetPath = AssetDatabase.GUIDToAssetPath(guids[i]);
if (AssetDatabase.LoadAssetAtPath(assetPath, typeof(XNode.NodeGraph)) is XNode.NodeGraph graph)
{
graph.nodes.RemoveAll(x => x == null); //Remove null items
var objects = AssetDatabase.LoadAllAssetRepresentationsAtPath(assetPath);
// Ensure that all sub node assets are present in the graph node list
for (var u = 0; u < objects.Length; u++)
{
// Ignore null sub assets
if (objects[u] == null) { continue; }
if (!graph.nodes.Contains(objects[u] as XNode.Node))
{
graph.nodes.Add(objects[u] as XNode.Node);
}
}
}
}
};
}
}

View File

@ -40,11 +40,15 @@ namespace XNodeEditor.Internal {
#endif #endif
public static T GetEditor(K target, NodeEditorWindow window) { public static T GetEditor(K target, NodeEditorWindow window) {
if (target == null) return null; if (target == null)
{
return null;
}
T editor; T editor;
if (!editors.TryGetValue(target, out editor)) { if (!editors.TryGetValue(target, out editor)) {
Type type = target.GetType(); var type = target.GetType();
Type editorType = GetEditorType(type); var editorType = GetEditorType(type);
editor = Activator.CreateInstance(editorType) as T; editor = Activator.CreateInstance(editorType) as T;
editor.target = target; editor.target = target;
editor.serializedObject = new SerializedObject(target); editor.serializedObject = new SerializedObject(target);
@ -52,15 +56,31 @@ namespace XNodeEditor.Internal {
editor.OnCreate(); editor.OnCreate();
editors.Add(target, editor); editors.Add(target, editor);
} }
if (editor.target == null) editor.target = target; if (editor.target == null)
if (editor.window != window) editor.window = window; {
if (editor.serializedObject == null) editor.serializedObject = new SerializedObject(target); editor.target = target;
}
if (editor.window != window)
{
editor.window = window;
}
if (editor.serializedObject == null)
{
editor.serializedObject = new SerializedObject(target);
}
return editor; return editor;
} }
public static void DestroyEditor( K target ) public static void DestroyEditor( K target )
{ {
if ( target == null ) return; if ( target == null )
{
return;
}
T editor; T editor;
if ( editors.TryGetValue( target, out editor ) ) if ( editors.TryGetValue( target, out editor ) )
{ {
@ -69,10 +89,22 @@ namespace XNodeEditor.Internal {
} }
private static Type GetEditorType(Type type) { private static Type GetEditorType(Type type) {
if (type == null) return null; if (type == null)
if (editorTypes == null) CacheCustomEditors(); {
return null;
}
if (editorTypes == null)
{
CacheCustomEditors();
}
Type result; Type result;
if (editorTypes.TryGetValue(type, out result)) return result; if (editorTypes.TryGetValue(type, out result))
{
return result;
}
//If type isn't found, try base type //If type isn't found, try base type
return GetEditorType(type.BaseType); return GetEditorType(type.BaseType);
} }
@ -81,12 +113,20 @@ namespace XNodeEditor.Internal {
editorTypes = new Dictionary<Type, Type>(); editorTypes = new Dictionary<Type, Type>();
//Get all classes deriving from NodeEditor via reflection //Get all classes deriving from NodeEditor via reflection
Type[] nodeEditors = typeof(T).GetDerivedTypes(); var nodeEditors = typeof(T).GetDerivedTypes();
for (int i = 0; i < nodeEditors.Length; i++) { for (var i = 0; i < nodeEditors.Length; i++) {
if (nodeEditors[i].IsAbstract) continue; if (nodeEditors[i].IsAbstract)
{
continue;
}
var attribs = nodeEditors[i].GetCustomAttributes(typeof(A), false); var attribs = nodeEditors[i].GetCustomAttributes(typeof(A), false);
if (attribs == null || attribs.Length == 0) continue; if (attribs == null || attribs.Length == 0)
A attrib = attribs[0] as A; {
continue;
}
var attrib = attribs[0] as A;
editorTypes.Add(attrib.GetInspectedType(), nodeEditors[i]); editorTypes.Add(attrib.GetInspectedType(), nodeEditors[i]);
} }
} }

View File

@ -21,9 +21,13 @@ namespace XNodeEditor {
private static readonly Vector3[] polyLineTempArray = new Vector3[2]; private static readonly Vector3[] polyLineTempArray = new Vector3[2];
protected virtual void OnGUI() { protected virtual void OnGUI() {
Event e = Event.current; var e = Event.current;
Matrix4x4 m = GUI.matrix; var m = GUI.matrix;
if (graph == null) return; if (graph == null)
{
return;
}
ValidateGraphEditor(); ValidateGraphEditor();
Controls(); Controls();
@ -48,7 +52,7 @@ namespace XNodeEditor {
GUI.EndClip(); GUI.EndClip();
GUIUtility.ScaleAroundPivot(Vector2.one / zoom, rect.size * 0.5f); GUIUtility.ScaleAroundPivot(Vector2.one / zoom, rect.size * 0.5f);
Vector4 padding = new Vector4(0, topPadding, 0, 0); var padding = new Vector4(0, topPadding, 0, 0);
padding *= zoom; padding *= zoom;
GUI.BeginClip(new Rect(-((rect.width * zoom) - rect.width) * 0.5f, -(((rect.height * zoom) - rect.height) * 0.5f) + (topPadding * zoom), GUI.BeginClip(new Rect(-((rect.width * zoom) - rect.width) * 0.5f, -(((rect.height * zoom) - rect.height) * 0.5f) + (topPadding * zoom),
rect.width * zoom, rect.width * zoom,
@ -57,7 +61,7 @@ namespace XNodeEditor {
public static void EndZoomed(Rect rect, float zoom, float topPadding) { public static void EndZoomed(Rect rect, float zoom, float topPadding) {
GUIUtility.ScaleAroundPivot(Vector2.one * zoom, rect.size * 0.5f); GUIUtility.ScaleAroundPivot(Vector2.one * zoom, rect.size * 0.5f);
Vector3 offset = new Vector3( var offset = new Vector3(
(((rect.width * zoom) - rect.width) * 0.5f), (((rect.width * zoom) - rect.width) * 0.5f),
(((rect.height * zoom) - rect.height) * 0.5f) + (-topPadding * zoom) + topPadding, (((rect.height * zoom) - rect.height) * 0.5f) + (-topPadding * zoom) + topPadding,
0); 0);
@ -68,21 +72,21 @@ namespace XNodeEditor {
rect.position = Vector2.zero; rect.position = Vector2.zero;
Vector2 center = rect.size / 2f; var center = rect.size / 2f;
Texture2D gridTex = graphEditor.GetGridTexture(); var gridTex = graphEditor.GetGridTexture();
Texture2D crossTex = graphEditor.GetSecondaryGridTexture(); var crossTex = graphEditor.GetSecondaryGridTexture();
// Offset from origin in tile units // Offset from origin in tile units
float xOffset = -(center.x * zoom + panOffset.x) / gridTex.width; var xOffset = -(center.x * zoom + panOffset.x) / gridTex.width;
float yOffset = ((center.y - rect.size.y) * zoom + panOffset.y) / gridTex.height; var yOffset = ((center.y - rect.size.y) * zoom + panOffset.y) / gridTex.height;
Vector2 tileOffset = new Vector2(xOffset, yOffset); var tileOffset = new Vector2(xOffset, yOffset);
// Amount of tiles // Amount of tiles
float tileAmountX = Mathf.Round(rect.size.x * zoom) / gridTex.width; var tileAmountX = Mathf.Round(rect.size.x * zoom) / gridTex.width;
float tileAmountY = Mathf.Round(rect.size.y * zoom) / gridTex.height; var tileAmountY = Mathf.Round(rect.size.y * zoom) / gridTex.height;
Vector2 tileAmount = new Vector2(tileAmountX, tileAmountY); var tileAmount = new Vector2(tileAmountX, tileAmountY);
// Draw tiled background // Draw tiled background
GUI.DrawTextureWithTexCoords(rect, gridTex, new Rect(tileOffset, tileAmount)); GUI.DrawTextureWithTexCoords(rect, gridTex, new Rect(tileOffset, tileAmount));
@ -91,9 +95,9 @@ namespace XNodeEditor {
public void DrawSelectionBox() { public void DrawSelectionBox() {
if (currentActivity == NodeActivity.DragGrid) { if (currentActivity == NodeActivity.DragGrid) {
Vector2 curPos = WindowToGridPosition(Event.current.mousePosition); var curPos = WindowToGridPosition(Event.current.mousePosition);
Vector2 size = curPos - dragBoxStart; var size = curPos - dragBoxStart;
Rect r = new Rect(dragBoxStart, size); var r = new Rect(dragBoxStart, size);
r.position = GridToWindowPosition(r.position); r.position = GridToWindowPosition(r.position);
r.size /= zoom; r.size /= zoom;
Handles.DrawSolidRectangleWithOutline(r, new Color(0, 0, 0, 0.1f), new Color(1, 1, 1, 0.6f)); Handles.DrawSolidRectangleWithOutline(r, new Color(0, 0, 0, 0.1f), new Color(1, 1, 1, 0.6f));
@ -105,16 +109,19 @@ namespace XNodeEditor {
} }
/// <summary> Show right-click context menu for hovered reroute </summary> /// <summary> Show right-click context menu for hovered reroute </summary>
void ShowRerouteContextMenu(RerouteReference reroute) { private void ShowRerouteContextMenu(RerouteReference reroute) {
GenericMenu contextMenu = new GenericMenu(); var contextMenu = new GenericMenu();
contextMenu.AddItem(new GUIContent("Remove"), false, () => reroute.RemovePoint()); contextMenu.AddItem(new GUIContent("Remove"), false, () => reroute.RemovePoint());
contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave)
{
AssetDatabase.SaveAssets();
}
} }
/// <summary> Show right-click context menu for hovered port </summary> /// <summary> Show right-click context menu for hovered port </summary>
void ShowPortContextMenu(XNode.NodePort hoveredPort) { private void ShowPortContextMenu(XNode.NodePort hoveredPort) {
GenericMenu contextMenu = new GenericMenu(); var contextMenu = new GenericMenu();
foreach (var port in hoveredPort.GetConnections()) { foreach (var port in hoveredPort.GetConnections()) {
var name = port.node.name; var name = port.node.name;
var index = hoveredPort.GetConnectionIndex(port); var index = hoveredPort.GetConnectionIndex(port);
@ -126,16 +133,23 @@ namespace XNodeEditor {
contextMenu.AddSeparator(""); contextMenu.AddSeparator("");
if (hoveredPort.direction == XNode.NodePort.IO.Input) if (hoveredPort.direction == XNode.NodePort.IO.Input)
{
graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, XNode.NodePort.IO.Output); graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, XNode.NodePort.IO.Output);
}
else else
{
graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, XNode.NodePort.IO.Input); graphEditor.AddContextMenuItems(contextMenu, hoveredPort.ValueType, XNode.NodePort.IO.Input);
} }
}
contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero)); contextMenu.DropDown(new Rect(Event.current.mousePosition, Vector2.zero));
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave)
{
AssetDatabase.SaveAssets();
}
} }
static Vector2 CalculateBezierPoint(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t) { private static Vector2 CalculateBezierPoint(Vector2 p0, Vector2 p1, Vector2 p2, Vector2 p3, float t) {
float u = 1 - t; var u = 1 - t;
float tt = t * t, uu = u * u; float tt = t * t, uu = u * u;
float uuu = uu * u, ttt = tt * t; float uuu = uu * u, ttt = tt * t;
return new Vector2( return new Vector2(
@ -145,7 +159,7 @@ namespace XNodeEditor {
} }
/// <summary> Draws a line segment without allocating temporary arrays </summary> /// <summary> Draws a line segment without allocating temporary arrays </summary>
static void DrawAAPolyLineNonAlloc(float thickness, Vector2 p0, Vector2 p1) { private static void DrawAAPolyLineNonAlloc(float thickness, Vector2 p0, Vector2 p1) {
polyLineTempArray[0].x = p0.x; polyLineTempArray[0].x = p0.x;
polyLineTempArray[0].y = p0.y; polyLineTempArray[0].y = p0.y;
polyLineTempArray[1].x = p1.x; polyLineTempArray[1].x = p1.x;
@ -156,30 +170,36 @@ namespace XNodeEditor {
/// <summary> Draw a bezier from output to input in grid coordinates </summary> /// <summary> Draw a bezier from output to input in grid coordinates </summary>
public void DrawNoodle(Gradient gradient, NoodlePath path, NoodleStroke stroke, float thickness, List<Vector2> gridPoints) { public void DrawNoodle(Gradient gradient, NoodlePath path, NoodleStroke stroke, float thickness, List<Vector2> gridPoints) {
// convert grid points to window points // convert grid points to window points
for (int i = 0; i < gridPoints.Count; ++i) for (var i = 0; i < gridPoints.Count; ++i)
{
gridPoints[i] = GridToWindowPosition(gridPoints[i]); gridPoints[i] = GridToWindowPosition(gridPoints[i]);
}
Color originalHandlesColor = Handles.color; var originalHandlesColor = Handles.color;
Handles.color = gradient.Evaluate(0f); Handles.color = gradient.Evaluate(0f);
int length = gridPoints.Count; var length = gridPoints.Count;
switch (path) { switch (path) {
case NoodlePath.Curvy: case NoodlePath.Curvy:
Vector2 outputTangent = Vector2.right; var outputTangent = Vector2.right;
for (int i = 0; i < length - 1; i++) { for (var i = 0; i < length - 1; i++) {
Vector2 inputTangent; Vector2 inputTangent;
// Cached most variables that repeat themselves here to avoid so many indexer calls :p // Cached most variables that repeat themselves here to avoid so many indexer calls :p
Vector2 point_a = gridPoints[i]; var point_a = gridPoints[i];
Vector2 point_b = gridPoints[i + 1]; var point_b = gridPoints[i + 1];
float dist_ab = Vector2.Distance(point_a, point_b); var dist_ab = Vector2.Distance(point_a, point_b);
if (i == 0) outputTangent = zoom * dist_ab * 0.01f * Vector2.right; if (i == 0)
{
outputTangent = zoom * dist_ab * 0.01f * Vector2.right;
}
if (i < length - 2) { if (i < length - 2) {
Vector2 point_c = gridPoints[i + 2]; var point_c = gridPoints[i + 2];
Vector2 ab = (point_b - point_a).normalized; var ab = (point_b - point_a).normalized;
Vector2 cb = (point_b - point_c).normalized; var cb = (point_b - point_c).normalized;
Vector2 ac = (point_c - point_a).normalized; var ac = (point_c - point_a).normalized;
Vector2 p = (ab + cb) * 0.5f; var p = (ab + cb) * 0.5f;
float tangentLength = (dist_ab + Vector2.Distance(point_b, point_c)) * 0.005f * zoom; var tangentLength = (dist_ab + Vector2.Distance(point_b, point_c)) * 0.005f * zoom;
float side = ((ac.x * (point_b.y - point_a.y)) - (ac.y * (point_b.x - point_a.x))); var side = ((ac.x * (point_b.y - point_a.y)) - (ac.y * (point_b.x - point_a.x)));
p = tangentLength * Mathf.Sign(side) * new Vector2(-p.y, p.x); p = tangentLength * Mathf.Sign(side) * new Vector2(-p.y, p.x);
inputTangent = p; inputTangent = p;
@ -188,24 +208,38 @@ namespace XNodeEditor {
} }
// Calculates the tangents for the bezier's curves. // Calculates the tangents for the bezier's curves.
float zoomCoef = 50 / zoom; var zoomCoef = 50 / zoom;
Vector2 tangent_a = point_a + outputTangent * zoomCoef; var tangent_a = point_a + outputTangent * zoomCoef;
Vector2 tangent_b = point_b + inputTangent * zoomCoef; var tangent_b = point_b + inputTangent * zoomCoef;
// Hover effect. // Hover effect.
int division = Mathf.RoundToInt(.2f * dist_ab) + 3; var division = Mathf.RoundToInt(.2f * dist_ab) + 3;
// Coloring and bezier drawing. // Coloring and bezier drawing.
int draw = 0; var draw = 0;
Vector2 bezierPrevious = point_a; var bezierPrevious = point_a;
for (int j = 1; j <= division; ++j) { for (var j = 1; j <= division; ++j) {
if (stroke == NoodleStroke.Dashed) { if (stroke == NoodleStroke.Dashed) {
draw++; draw++;
if (draw >= 2) draw = -2; if (draw >= 2)
if (draw < 0) continue; {
if (draw == 0) bezierPrevious = CalculateBezierPoint(point_a, tangent_a, tangent_b, point_b, (j - 1f) / (float) division); draw = -2;
}
if (draw < 0)
{
continue;
}
if (draw == 0)
{
bezierPrevious = CalculateBezierPoint(point_a, tangent_a, tangent_b, point_b, (j - 1f) / (float) division);
}
} }
if (i == length - 2) if (i == length - 2)
{
Handles.color = gradient.Evaluate((j + 1f) / division); Handles.color = gradient.Evaluate((j + 1f) / division);
Vector2 bezierNext = CalculateBezierPoint(point_a, tangent_a, tangent_b, point_b, j / (float) division); }
var bezierNext = CalculateBezierPoint(point_a, tangent_a, tangent_b, point_b, j / (float) division);
DrawAAPolyLineNonAlloc(thickness, bezierPrevious, bezierNext); DrawAAPolyLineNonAlloc(thickness, bezierPrevious, bezierNext);
bezierPrevious = bezierNext; bezierPrevious = bezierNext;
} }
@ -213,36 +247,47 @@ namespace XNodeEditor {
} }
break; break;
case NoodlePath.Straight: case NoodlePath.Straight:
for (int i = 0; i < length - 1; i++) { for (var i = 0; i < length - 1; i++) {
Vector2 point_a = gridPoints[i]; var point_a = gridPoints[i];
Vector2 point_b = gridPoints[i + 1]; var point_b = gridPoints[i + 1];
// Draws the line with the coloring. // Draws the line with the coloring.
Vector2 prev_point = point_a; var prev_point = point_a;
// Approximately one segment per 5 pixels // Approximately one segment per 5 pixels
int segments = (int) Vector2.Distance(point_a, point_b) / 5; var segments = (int) Vector2.Distance(point_a, point_b) / 5;
segments = Math.Max(segments, 1); segments = Math.Max(segments, 1);
int draw = 0; var draw = 0;
for (int j = 0; j <= segments; j++) { for (var j = 0; j <= segments; j++) {
draw++; draw++;
float t = j / (float) segments; var t = j / (float) segments;
Vector2 lerp = Vector2.Lerp(point_a, point_b, t); var lerp = Vector2.Lerp(point_a, point_b, t);
if (draw > 0) { if (draw > 0) {
if (i == length - 2) Handles.color = gradient.Evaluate(t); if (i == length - 2)
{
Handles.color = gradient.Evaluate(t);
}
DrawAAPolyLineNonAlloc(thickness, prev_point, lerp); DrawAAPolyLineNonAlloc(thickness, prev_point, lerp);
} }
prev_point = lerp; prev_point = lerp;
if (stroke == NoodleStroke.Dashed && draw >= 2) draw = -2; if (stroke == NoodleStroke.Dashed && draw >= 2)
{
draw = -2;
}
} }
} }
break; break;
case NoodlePath.Angled: case NoodlePath.Angled:
for (int i = 0; i < length - 1; i++) { for (var i = 0; i < length - 1; i++) {
if (i == length - 1) continue; // Skip last index if (i == length - 1)
{
continue; // Skip last index
}
if (gridPoints[i].x <= gridPoints[i + 1].x - (50 / zoom)) { if (gridPoints[i].x <= gridPoints[i + 1].x - (50 / zoom)) {
float midpoint = (gridPoints[i].x + gridPoints[i + 1].x) * 0.5f; var midpoint = (gridPoints[i].x + gridPoints[i + 1].x) * 0.5f;
Vector2 start_1 = gridPoints[i]; var start_1 = gridPoints[i];
Vector2 end_1 = gridPoints[i + 1]; var end_1 = gridPoints[i + 1];
start_1.x = midpoint; start_1.x = midpoint;
end_1.x = midpoint; end_1.x = midpoint;
if (i == length - 2) { if (i == length - 2) {
@ -257,13 +302,13 @@ namespace XNodeEditor {
DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]); DrawAAPolyLineNonAlloc(thickness, end_1, gridPoints[i + 1]);
} }
} else { } else {
float midpoint = (gridPoints[i].y + gridPoints[i + 1].y) * 0.5f; var midpoint = (gridPoints[i].y + gridPoints[i + 1].y) * 0.5f;
Vector2 start_1 = gridPoints[i]; var start_1 = gridPoints[i];
Vector2 end_1 = gridPoints[i + 1]; var end_1 = gridPoints[i + 1];
start_1.x += 25 / zoom; start_1.x += 25 / zoom;
end_1.x -= 25 / zoom; end_1.x -= 25 / zoom;
Vector2 start_2 = start_1; var start_2 = start_1;
Vector2 end_2 = end_1; var end_2 = end_1;
start_2.y = midpoint; start_2.y = midpoint;
end_2.y = midpoint; end_2.y = midpoint;
if (i == length - 2) { if (i == length - 2) {
@ -287,8 +332,8 @@ namespace XNodeEditor {
} }
break; break;
case NoodlePath.ShaderLab: case NoodlePath.ShaderLab:
Vector2 start = gridPoints[0]; var start = gridPoints[0];
Vector2 end = gridPoints[length - 1]; var end = gridPoints[length - 1];
//Modify first and last point in array so we can loop trough them nicely. //Modify first and last point in array so we can loop trough them nicely.
gridPoints[0] = gridPoints[0] + Vector2.right * (20 / zoom); gridPoints[0] = gridPoints[0] + Vector2.right * (20 / zoom);
gridPoints[length - 1] = gridPoints[length - 1] + Vector2.left * (20 / zoom); gridPoints[length - 1] = gridPoints[length - 1] + Vector2.left * (20 / zoom);
@ -297,26 +342,33 @@ namespace XNodeEditor {
DrawAAPolyLineNonAlloc(thickness, start, gridPoints[0]); DrawAAPolyLineNonAlloc(thickness, start, gridPoints[0]);
Handles.color = gradient.Evaluate(1f); Handles.color = gradient.Evaluate(1f);
DrawAAPolyLineNonAlloc(thickness, end, gridPoints[length - 1]); DrawAAPolyLineNonAlloc(thickness, end, gridPoints[length - 1]);
for (int i = 0; i < length - 1; i++) { for (var i = 0; i < length - 1; i++) {
Vector2 point_a = gridPoints[i]; var point_a = gridPoints[i];
Vector2 point_b = gridPoints[i + 1]; var point_b = gridPoints[i + 1];
// Draws the line with the coloring. // Draws the line with the coloring.
Vector2 prev_point = point_a; var prev_point = point_a;
// Approximately one segment per 5 pixels // Approximately one segment per 5 pixels
int segments = (int) Vector2.Distance(point_a, point_b) / 5; var segments = (int) Vector2.Distance(point_a, point_b) / 5;
segments = Math.Max(segments, 1); segments = Math.Max(segments, 1);
int draw = 0; var draw = 0;
for (int j = 0; j <= segments; j++) { for (var j = 0; j <= segments; j++) {
draw++; draw++;
float t = j / (float) segments; var t = j / (float) segments;
Vector2 lerp = Vector2.Lerp(point_a, point_b, t); var lerp = Vector2.Lerp(point_a, point_b, t);
if (draw > 0) { if (draw > 0) {
if (i == length - 2) Handles.color = gradient.Evaluate(t); if (i == length - 2)
{
Handles.color = gradient.Evaluate(t);
}
DrawAAPolyLineNonAlloc(thickness, prev_point, lerp); DrawAAPolyLineNonAlloc(thickness, prev_point, lerp);
} }
prev_point = lerp; prev_point = lerp;
if (stroke == NoodleStroke.Dashed && draw >= 2) draw = -2; if (stroke == NoodleStroke.Dashed && draw >= 2)
{
draw = -2;
}
} }
} }
gridPoints[0] = start; gridPoints[0] = start;
@ -328,41 +380,58 @@ namespace XNodeEditor {
/// <summary> Draws all connections </summary> /// <summary> Draws all connections </summary>
public void DrawConnections() { public void DrawConnections() {
Vector2 mousePos = Event.current.mousePosition; var mousePos = Event.current.mousePosition;
List<RerouteReference> selection = preBoxSelectionReroute != null ? new List<RerouteReference>(preBoxSelectionReroute) : new List<RerouteReference>(); var selection = preBoxSelectionReroute != null ? new List<RerouteReference>(preBoxSelectionReroute) : new List<RerouteReference>();
hoveredReroute = new RerouteReference(); hoveredReroute = new RerouteReference();
List<Vector2> gridPoints = new List<Vector2>(2); var gridPoints = new List<Vector2>(2);
Color col = GUI.color; var col = GUI.color;
foreach (XNode.Node node in graph.nodes) { foreach (var node in graph.nodes) {
//If a null node is found, return. This can happen if the nodes associated script is deleted. It is currently not possible in Unity to delete a null asset. //If a null node is found, return. This can happen if the nodes associated script is deleted. It is currently not possible in Unity to delete a null asset.
if (node == null) continue; if (node == null)
{
continue;
}
// Draw full connections and output > reroute // Draw full connections and output > reroute
foreach (XNode.NodePort output in node.Outputs) { foreach (var output in node.Outputs) {
//Needs cleanup. Null checks are ugly //Needs cleanup. Null checks are ugly
Rect fromRect; Rect fromRect;
if (!_portConnectionPoints.TryGetValue(output, out fromRect)) continue; if (!_portConnectionPoints.TryGetValue(output, out fromRect))
{
continue;
}
Color portColor = graphEditor.GetPortColor(output); var portColor = graphEditor.GetPortColor(output);
GUIStyle portStyle = graphEditor.GetPortStyle(output); var portStyle = graphEditor.GetPortStyle(output);
for (int k = 0; k < output.ConnectionCount; k++) { for (var k = 0; k < output.ConnectionCount; k++) {
XNode.NodePort input = output.GetConnection(k); var input = output.GetConnection(k);
Gradient noodleGradient = graphEditor.GetNoodleGradient(output, input); var noodleGradient = graphEditor.GetNoodleGradient(output, input);
float noodleThickness = graphEditor.GetNoodleThickness(output, input); var noodleThickness = graphEditor.GetNoodleThickness(output, input);
NoodlePath noodlePath = graphEditor.GetNoodlePath(output, input); var noodlePath = graphEditor.GetNoodlePath(output, input);
NoodleStroke noodleStroke = graphEditor.GetNoodleStroke(output, input); var noodleStroke = graphEditor.GetNoodleStroke(output, input);
// Error handling // Error handling
if (input == null) continue; //If a script has been updated and the port doesn't exist, it is removed and null is returned. If this happens, return. if (input == null)
if (!input.IsConnectedTo(output)) input.Connect(output); {
Rect toRect; continue; //If a script has been updated and the port doesn't exist, it is removed and null is returned. If this happens, return.
if (!_portConnectionPoints.TryGetValue(input, out toRect)) continue; }
List<Vector2> reroutePoints = output.GetReroutePoints(k); if (!input.IsConnectedTo(output))
{
input.Connect(output);
}
Rect toRect;
if (!_portConnectionPoints.TryGetValue(input, out toRect))
{
continue;
}
var reroutePoints = output.GetReroutePoints(k);
gridPoints.Clear(); gridPoints.Clear();
gridPoints.Add(fromRect.center); gridPoints.Add(fromRect.center);
@ -371,10 +440,10 @@ namespace XNodeEditor {
DrawNoodle(noodleGradient, noodlePath, noodleStroke, noodleThickness, gridPoints); DrawNoodle(noodleGradient, noodlePath, noodleStroke, noodleThickness, gridPoints);
// Loop through reroute points again and draw the points // Loop through reroute points again and draw the points
for (int i = 0; i < reroutePoints.Count; i++) { for (var i = 0; i < reroutePoints.Count; i++) {
RerouteReference rerouteRef = new RerouteReference(output, k, i); var rerouteRef = new RerouteReference(output, k, i);
// Draw reroute point at position // Draw reroute point at position
Rect rect = new Rect(reroutePoints[i], new Vector2(12, 12)); var rect = new Rect(reroutePoints[i], new Vector2(12, 12));
rect.position = new Vector2(rect.position.x - 6, rect.position.y - 6); rect.position = new Vector2(rect.position.x - 6, rect.position.y - 6);
rect = GridToWindowRect(rect); rect = GridToWindowRect(rect);
@ -386,19 +455,28 @@ namespace XNodeEditor {
GUI.color = portColor; GUI.color = portColor;
GUI.DrawTexture(rect, portStyle.active.background); GUI.DrawTexture(rect, portStyle.active.background);
if (rect.Overlaps(selectionBox)) selection.Add(rerouteRef); if (rect.Overlaps(selectionBox))
if (rect.Contains(mousePos)) hoveredReroute = rerouteRef; {
selection.Add(rerouteRef);
}
if (rect.Contains(mousePos))
{
hoveredReroute = rerouteRef;
}
} }
} }
} }
} }
GUI.color = col; GUI.color = col;
if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) selectedReroutes = selection; if (Event.current.type != EventType.Layout && currentActivity == NodeActivity.DragGrid)
{
selectedReroutes = selection;
}
} }
private void DrawNodes() { private void DrawNodes() {
Event e = Event.current; var e = Event.current;
if (e.type == EventType.Layout) { if (e.type == EventType.Layout) {
selectionCache = new List<UnityEngine.Object>(Selection.objects); selectionCache = new List<UnityEngine.Object>(Selection.objects);
} }
@ -406,38 +484,53 @@ namespace XNodeEditor {
System.Reflection.MethodInfo onValidate = null; System.Reflection.MethodInfo onValidate = null;
if (Selection.activeObject != null && Selection.activeObject is XNode.Node) { if (Selection.activeObject != null && Selection.activeObject is XNode.Node) {
onValidate = Selection.activeObject.GetType().GetMethod("OnValidate"); onValidate = Selection.activeObject.GetType().GetMethod("OnValidate");
if (onValidate != null) EditorGUI.BeginChangeCheck(); if (onValidate != null)
{
EditorGUI.BeginChangeCheck();
}
} }
BeginZoomed(position, zoom, topPadding); BeginZoomed(position, zoom, topPadding);
Vector2 mousePos = Event.current.mousePosition; var mousePos = Event.current.mousePosition;
if (e.type != EventType.Layout) { if (e.type != EventType.Layout) {
hoveredNode = null; hoveredNode = null;
hoveredPort = null; hoveredPort = null;
} }
List<UnityEngine.Object> preSelection = preBoxSelection != null ? new List<UnityEngine.Object>(preBoxSelection) : new List<UnityEngine.Object>(); var preSelection = preBoxSelection != null ? new List<UnityEngine.Object>(preBoxSelection) : new List<UnityEngine.Object>();
// Selection box stuff // Selection box stuff
Vector2 boxStartPos = GridToWindowPositionNoClipped(dragBoxStart); var boxStartPos = GridToWindowPositionNoClipped(dragBoxStart);
Vector2 boxSize = mousePos - boxStartPos; var boxSize = mousePos - boxStartPos;
if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); } if (boxSize.x < 0) { boxStartPos.x += boxSize.x; boxSize.x = Mathf.Abs(boxSize.x); }
if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); } if (boxSize.y < 0) { boxStartPos.y += boxSize.y; boxSize.y = Mathf.Abs(boxSize.y); }
Rect selectionBox = new Rect(boxStartPos, boxSize); var selectionBox = new Rect(boxStartPos, boxSize);
//Save guiColor so we can revert it //Save guiColor so we can revert it
Color guiColor = GUI.color; var guiColor = GUI.color;
List<XNode.NodePort> removeEntries = new List<XNode.NodePort>(); var removeEntries = new List<XNode.NodePort>();
if (e.type == EventType.Layout) culledNodes = new List<XNode.Node>(); if (e.type == EventType.Layout)
for (int n = 0; n < graph.nodes.Count; n++) { {
culledNodes = new List<XNode.Node>();
}
for (var n = 0; n < graph.nodes.Count; n++) {
// Skip null nodes. The user could be in the process of renaming scripts, so removing them at this point is not advisable. // Skip null nodes. The user could be in the process of renaming scripts, so removing them at this point is not advisable.
if (graph.nodes[n] == null) continue; if (graph.nodes[n] == null)
if (n >= graph.nodes.Count) return; {
XNode.Node node = graph.nodes[n]; continue;
}
if (n >= graph.nodes.Count)
{
return;
}
var node = graph.nodes[n];
// Culling // Culling
if (e.type == EventType.Layout) { if (e.type == EventType.Layout) {
@ -446,16 +539,28 @@ namespace XNodeEditor {
culledNodes.Add(node); culledNodes.Add(node);
continue; continue;
} }
} else if (culledNodes.Contains(node)) continue; } else if (culledNodes.Contains(node))
{
continue;
}
if (e.type == EventType.Repaint) { if (e.type == EventType.Repaint) {
removeEntries.Clear(); removeEntries.Clear();
foreach (var kvp in _portConnectionPoints) foreach (var kvp in _portConnectionPoints)
if (kvp.Key.node == node) removeEntries.Add(kvp.Key); {
foreach (var k in removeEntries) _portConnectionPoints.Remove(k); if (kvp.Key.node == node)
{
removeEntries.Add(kvp.Key);
}
} }
NodeEditor nodeEditor = NodeEditor.GetEditor(node, this); foreach (var k in removeEntries)
{
_portConnectionPoints.Remove(k);
}
}
var nodeEditor = NodeEditor.GetEditor(node, this);
NodeEditor.portPositions.Clear(); NodeEditor.portPositions.Clear();
@ -463,15 +568,15 @@ namespace XNodeEditor {
EditorGUIUtility.labelWidth = 84; EditorGUIUtility.labelWidth = 84;
//Get node position //Get node position
Vector2 nodePos = GridToWindowPositionNoClipped(node.position); var nodePos = GridToWindowPositionNoClipped(node.position);
GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000))); GUILayout.BeginArea(new Rect(nodePos, new Vector2(nodeEditor.GetWidth(), 4000)));
bool selected = selectionCache.Contains(graph.nodes[n]); var selected = selectionCache.Contains(graph.nodes[n]);
if (selected) { if (selected) {
GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle()); var style = new GUIStyle(nodeEditor.GetBodyStyle());
GUIStyle highlightStyle = new GUIStyle(nodeEditor.GetBodyHighlightStyle()); var highlightStyle = new GUIStyle(nodeEditor.GetBodyHighlightStyle());
highlightStyle.padding = style.padding; highlightStyle.padding = style.padding;
style.padding = new RectOffset(); style.padding = new RectOffset();
GUI.color = nodeEditor.GetTint(); GUI.color = nodeEditor.GetTint();
@ -479,7 +584,7 @@ namespace XNodeEditor {
GUI.color = NodeEditorPreferences.GetSettings().highlightColor; GUI.color = NodeEditorPreferences.GetSettings().highlightColor;
GUILayout.BeginVertical(new GUIStyle(highlightStyle)); GUILayout.BeginVertical(new GUIStyle(highlightStyle));
} else { } else {
GUIStyle style = new GUIStyle(nodeEditor.GetBodyStyle()); var style = new GUIStyle(nodeEditor.GetBodyStyle());
GUI.color = nodeEditor.GetTint(); GUI.color = nodeEditor.GetTint();
GUILayout.BeginVertical(style); GUILayout.BeginVertical(style);
} }
@ -493,7 +598,11 @@ namespace XNodeEditor {
//If user changed a value, notify other scripts through onUpdateNode //If user changed a value, notify other scripts through onUpdateNode
if (EditorGUI.EndChangeCheck()) { if (EditorGUI.EndChangeCheck()) {
if (NodeEditor.onUpdateNode != null) NodeEditor.onUpdateNode(node); if (NodeEditor.onUpdateNode != null)
{
NodeEditor.onUpdateNode(node);
}
EditorUtility.SetDirty(node); EditorUtility.SetDirty(node);
nodeEditor.serializedObject.ApplyModifiedProperties(); nodeEditor.serializedObject.ApplyModifiedProperties();
} }
@ -502,87 +611,142 @@ namespace XNodeEditor {
//Cache data about the node for next frame //Cache data about the node for next frame
if (e.type == EventType.Repaint) { if (e.type == EventType.Repaint) {
Vector2 size = GUILayoutUtility.GetLastRect().size; var size = GUILayoutUtility.GetLastRect().size;
if (nodeSizes.ContainsKey(node)) nodeSizes[node] = size; if (nodeSizes.ContainsKey(node))
else nodeSizes.Add(node, size); {
nodeSizes[node] = size;
}
else
{
nodeSizes.Add(node, size);
}
foreach (var kvp in NodeEditor.portPositions) { foreach (var kvp in NodeEditor.portPositions) {
Vector2 portHandlePos = kvp.Value; var portHandlePos = kvp.Value;
portHandlePos += node.position; portHandlePos += node.position;
Rect rect = new Rect(portHandlePos.x - 8, portHandlePos.y - 8, 16, 16); var rect = new Rect(portHandlePos.x - 8, portHandlePos.y - 8, 16, 16);
portConnectionPoints[kvp.Key] = rect; portConnectionPoints[kvp.Key] = rect;
} }
} }
if (selected) GUILayout.EndVertical(); if (selected)
{
GUILayout.EndVertical();
}
if (e.type != EventType.Layout) { if (e.type != EventType.Layout) {
//Check if we are hovering this node //Check if we are hovering this node
Vector2 nodeSize = GUILayoutUtility.GetLastRect().size; var nodeSize = GUILayoutUtility.GetLastRect().size;
Rect windowRect = new Rect(nodePos, nodeSize); var windowRect = new Rect(nodePos, nodeSize);
if (windowRect.Contains(mousePos)) hoveredNode = node; if (windowRect.Contains(mousePos))
{
hoveredNode = node;
}
//If dragging a selection box, add nodes inside to selection //If dragging a selection box, add nodes inside to selection
if (currentActivity == NodeActivity.DragGrid) { if (currentActivity == NodeActivity.DragGrid) {
if (windowRect.Overlaps(selectionBox)) preSelection.Add(node); if (windowRect.Overlaps(selectionBox))
{
preSelection.Add(node);
}
} }
//Check if we are hovering any of this nodes ports //Check if we are hovering any of this nodes ports
//Check input ports //Check input ports
foreach (XNode.NodePort input in node.Inputs) { foreach (var input in node.Inputs) {
//Check if port rect is available //Check if port rect is available
if (!portConnectionPoints.ContainsKey(input)) continue; if (!portConnectionPoints.ContainsKey(input))
Rect r = GridToWindowRectNoClipped(portConnectionPoints[input]); {
if (r.Contains(mousePos)) hoveredPort = input; continue;
}
var r = GridToWindowRectNoClipped(portConnectionPoints[input]);
if (r.Contains(mousePos))
{
hoveredPort = input;
}
} }
//Check all output ports //Check all output ports
foreach (XNode.NodePort output in node.Outputs) { foreach (var output in node.Outputs) {
//Check if port rect is available //Check if port rect is available
if (!portConnectionPoints.ContainsKey(output)) continue; if (!portConnectionPoints.ContainsKey(output))
Rect r = GridToWindowRectNoClipped(portConnectionPoints[output]); {
if (r.Contains(mousePos)) hoveredPort = output; continue;
}
var r = GridToWindowRectNoClipped(portConnectionPoints[output]);
if (r.Contains(mousePos))
{
hoveredPort = output;
}
} }
} }
GUILayout.EndArea(); GUILayout.EndArea();
} }
if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid) Selection.objects = preSelection.ToArray(); if (e.type != EventType.Layout && currentActivity == NodeActivity.DragGrid)
{
Selection.objects = preSelection.ToArray();
}
EndZoomed(position, zoom, topPadding); EndZoomed(position, zoom, topPadding);
//If a change in is detected in the selected node, call OnValidate method. //If a change in is detected in the selected node, call OnValidate method.
//This is done through reflection because OnValidate is only relevant in editor, //This is done through reflection because OnValidate is only relevant in editor,
//and thus, the code should not be included in build. //and thus, the code should not be included in build.
if (onValidate != null && EditorGUI.EndChangeCheck()) onValidate.Invoke(Selection.activeObject, null); if (onValidate != null && EditorGUI.EndChangeCheck())
{
onValidate.Invoke(Selection.activeObject, null);
}
} }
private bool ShouldBeCulled(XNode.Node node) { private bool ShouldBeCulled(XNode.Node node) {
Vector2 nodePos = GridToWindowPositionNoClipped(node.position); var nodePos = GridToWindowPositionNoClipped(node.position);
if (nodePos.x / _zoom > position.width) return true; // Right if (nodePos.x / _zoom > position.width)
else if (nodePos.y / _zoom > position.height) return true; // Bottom {
return true; // Right
}
else if (nodePos.y / _zoom > position.height)
{
return true; // Bottom
}
else if (nodeSizes.ContainsKey(node)) { else if (nodeSizes.ContainsKey(node)) {
Vector2 size = nodeSizes[node]; var size = nodeSizes[node];
if (nodePos.x + size.x < 0) return true; // Left if (nodePos.x + size.x < 0)
else if (nodePos.y + size.y < 0) return true; // Top {
return true; // Left
}
else if (nodePos.y + size.y < 0)
{
return true; // Top
}
} }
return false; return false;
} }
private void DrawTooltip() { private void DrawTooltip() {
if (!NodeEditorPreferences.GetSettings().portTooltips || graphEditor == null) if (!NodeEditorPreferences.GetSettings().portTooltips || graphEditor == null)
{
return; return;
}
string tooltip = null; string tooltip = null;
if (hoveredPort != null) { if (hoveredPort != null) {
tooltip = graphEditor.GetPortTooltip(hoveredPort); tooltip = graphEditor.GetPortTooltip(hoveredPort);
} else if (hoveredNode != null && IsHoveringNode && IsHoveringTitle(hoveredNode)) { } else if (hoveredNode != null && IsHoveringNode && IsHoveringTitle(hoveredNode)) {
tooltip = NodeEditor.GetEditor(hoveredNode, this).GetHeaderTooltip(); tooltip = NodeEditor.GetEditor(hoveredNode, this).GetHeaderTooltip();
} }
if (string.IsNullOrEmpty(tooltip)) return; if (string.IsNullOrEmpty(tooltip))
GUIContent content = new GUIContent(tooltip); {
Vector2 size = NodeEditorResources.styles.tooltip.CalcSize(content); return;
}
var content = new GUIContent(tooltip);
var size = NodeEditorResources.styles.tooltip.CalcSize(content);
size.x += 8; size.x += 8;
Rect rect = new Rect(Event.current.mousePosition - (size), size); var rect = new Rect(Event.current.mousePosition - (size), size);
EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip); EditorGUI.LabelField(rect, content, NodeEditorResources.styles.tooltip);
Repaint(); Repaint();
} }

View File

@ -21,9 +21,13 @@ namespace XNodeEditor {
/// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary> /// <summary> Make a field for a serialized property. Automatically displays relevant node port. </summary>
public static void PropertyField(SerializedProperty property, GUIContent label, bool includeChildren = true, params GUILayoutOption[] options) { public static void PropertyField(SerializedProperty property, GUIContent label, bool includeChildren = true, params GUILayoutOption[] options) {
if (property == null) throw new NullReferenceException(); if (property == null)
XNode.Node node = property.serializedObject.targetObject as XNode.Node; {
XNode.NodePort port = node.GetPort(property.name); throw new NullReferenceException();
}
var node = property.serializedObject.targetObject as XNode.Node;
var port = node.GetPort(property.name);
PropertyField(property, label, port, includeChildren); PropertyField(property, label, port, includeChildren);
} }
@ -34,27 +38,33 @@ namespace XNodeEditor {
/// <summary> Make a field for a serialized property. Manual node port override. </summary> /// <summary> Make a field for a serialized property. Manual node port override. </summary>
public static void PropertyField(SerializedProperty property, GUIContent label, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) { public static void PropertyField(SerializedProperty property, GUIContent label, XNode.NodePort port, bool includeChildren = true, params GUILayoutOption[] options) {
if (property == null) throw new NullReferenceException(); if (property == null)
{
throw new NullReferenceException();
}
// If property is not a port, display a regular property field // If property is not a port, display a regular property field
if (port == null) EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); if (port == null)
{
EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
}
else { else {
Rect rect = new Rect(); var rect = new Rect();
List<PropertyAttribute> propertyAttributes = NodeEditorUtilities.GetCachedPropertyAttribs(port.node.GetType(), property.name); var propertyAttributes = NodeEditorUtilities.GetCachedPropertyAttribs(port.node.GetType(), property.name);
// If property is an input, display a regular property field and put a port handle on the left side // If property is an input, display a regular property field and put a port handle on the left side
if (port.direction == XNode.NodePort.IO.Input) { if (port.direction == XNode.NodePort.IO.Input) {
// Get data from [Input] attribute // Get data from [Input] attribute
XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected; var showBacking = XNode.Node.ShowBackingValue.Unconnected;
XNode.Node.InputAttribute inputAttribute; XNode.Node.InputAttribute inputAttribute;
bool dynamicPortList = false; var dynamicPortList = false;
if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out inputAttribute)) { if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out inputAttribute)) {
dynamicPortList = inputAttribute.dynamicPortList; dynamicPortList = inputAttribute.dynamicPortList;
showBacking = inputAttribute.backingValue; showBacking = inputAttribute.backingValue;
} }
bool usePropertyAttributes = dynamicPortList || var usePropertyAttributes = dynamicPortList ||
showBacking == XNode.Node.ShowBackingValue.Never || showBacking == XNode.Node.ShowBackingValue.Never ||
(showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected);
@ -62,33 +72,49 @@ namespace XNodeEditor {
string tooltip = null; string tooltip = null;
foreach (var attr in propertyAttributes) { foreach (var attr in propertyAttributes) {
if (attr is SpaceAttribute) { if (attr is SpaceAttribute) {
if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height); if (usePropertyAttributes)
else spacePadding += (attr as SpaceAttribute).height; {
GUILayout.Space((attr as SpaceAttribute).height);
}
else
{
spacePadding += (attr as SpaceAttribute).height;
}
} else if (attr is HeaderAttribute) { } else if (attr is HeaderAttribute) {
if (usePropertyAttributes) { if (usePropertyAttributes) {
//GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs //GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs
Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it. var position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it.
position.yMin += EditorGUIUtility.singleLineHeight * 0.5f; position.yMin += EditorGUIUtility.singleLineHeight * 0.5f;
position = EditorGUI.IndentedRect(position); position = EditorGUI.IndentedRect(position);
GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel); GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
} else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f; } else
{
spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
}
} else if (attr is TooltipAttribute) { } else if (attr is TooltipAttribute) {
tooltip = (attr as TooltipAttribute).tooltip; tooltip = (attr as TooltipAttribute).tooltip;
} }
} }
if (dynamicPortList) { if (dynamicPortList) {
Type type = GetType(property); var type = GetType(property);
XNode.Node.ConnectionType connectionType = inputAttribute != null ? inputAttribute.connectionType : XNode.Node.ConnectionType.Multiple; var connectionType = inputAttribute != null ? inputAttribute.connectionType : XNode.Node.ConnectionType.Multiple;
DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType); DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType);
return; return;
} }
switch (showBacking) { switch (showBacking) {
case XNode.Node.ShowBackingValue.Unconnected: case XNode.Node.ShowBackingValue.Unconnected:
// Display a label if port is connected // Display a label if port is connected
if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip)); if (port.IsConnected)
{
EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip));
}
// Display an editable property field if port is not connected // Display an editable property field if port is not connected
else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); else
{
EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
}
break; break;
case XNode.Node.ShowBackingValue.Never: case XNode.Node.ShowBackingValue.Never:
// Display a label // Display a label
@ -106,15 +132,15 @@ namespace XNodeEditor {
// If property is an output, display a text label and put a port handle on the right side // If property is an output, display a text label and put a port handle on the right side
} else if (port.direction == XNode.NodePort.IO.Output) { } else if (port.direction == XNode.NodePort.IO.Output) {
// Get data from [Output] attribute // Get data from [Output] attribute
XNode.Node.ShowBackingValue showBacking = XNode.Node.ShowBackingValue.Unconnected; var showBacking = XNode.Node.ShowBackingValue.Unconnected;
XNode.Node.OutputAttribute outputAttribute; XNode.Node.OutputAttribute outputAttribute;
bool dynamicPortList = false; var dynamicPortList = false;
if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out outputAttribute)) { if (NodeEditorUtilities.GetCachedAttrib(port.node.GetType(), property.name, out outputAttribute)) {
dynamicPortList = outputAttribute.dynamicPortList; dynamicPortList = outputAttribute.dynamicPortList;
showBacking = outputAttribute.backingValue; showBacking = outputAttribute.backingValue;
} }
bool usePropertyAttributes = dynamicPortList || var usePropertyAttributes = dynamicPortList ||
showBacking == XNode.Node.ShowBackingValue.Never || showBacking == XNode.Node.ShowBackingValue.Never ||
(showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected); (showBacking == XNode.Node.ShowBackingValue.Unconnected && port.IsConnected);
@ -122,33 +148,49 @@ namespace XNodeEditor {
string tooltip = null; string tooltip = null;
foreach (var attr in propertyAttributes) { foreach (var attr in propertyAttributes) {
if (attr is SpaceAttribute) { if (attr is SpaceAttribute) {
if (usePropertyAttributes) GUILayout.Space((attr as SpaceAttribute).height); if (usePropertyAttributes)
else spacePadding += (attr as SpaceAttribute).height; {
GUILayout.Space((attr as SpaceAttribute).height);
}
else
{
spacePadding += (attr as SpaceAttribute).height;
}
} else if (attr is HeaderAttribute) { } else if (attr is HeaderAttribute) {
if (usePropertyAttributes) { if (usePropertyAttributes) {
//GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs //GUI Values are from https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/ScriptAttributeGUI/Implementations/DecoratorDrawers.cs
Rect position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it. var position = GUILayoutUtility.GetRect(0, (EditorGUIUtility.singleLineHeight * 1.5f) - EditorGUIUtility.standardVerticalSpacing); //Layout adds standardVerticalSpacing after rect so we subtract it.
position.yMin += EditorGUIUtility.singleLineHeight * 0.5f; position.yMin += EditorGUIUtility.singleLineHeight * 0.5f;
position = EditorGUI.IndentedRect(position); position = EditorGUI.IndentedRect(position);
GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel); GUI.Label(position, (attr as HeaderAttribute).header, EditorStyles.boldLabel);
} else spacePadding += EditorGUIUtility.singleLineHeight * 1.5f; } else
{
spacePadding += EditorGUIUtility.singleLineHeight * 1.5f;
}
} else if (attr is TooltipAttribute) { } else if (attr is TooltipAttribute) {
tooltip = (attr as TooltipAttribute).tooltip; tooltip = (attr as TooltipAttribute).tooltip;
} }
} }
if (dynamicPortList) { if (dynamicPortList) {
Type type = GetType(property); var type = GetType(property);
XNode.Node.ConnectionType connectionType = outputAttribute != null ? outputAttribute.connectionType : XNode.Node.ConnectionType.Multiple; var connectionType = outputAttribute != null ? outputAttribute.connectionType : XNode.Node.ConnectionType.Multiple;
DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType); DynamicPortList(property.name, type, property.serializedObject, port.direction, connectionType);
return; return;
} }
switch (showBacking) { switch (showBacking) {
case XNode.Node.ShowBackingValue.Unconnected: case XNode.Node.ShowBackingValue.Unconnected:
// Display a label if port is connected // Display a label if port is connected
if (port.IsConnected) EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip), NodeEditorResources.OutputPort, GUILayout.MinWidth(30)); if (port.IsConnected)
{
EditorGUILayout.LabelField(label != null ? label : new GUIContent(property.displayName, tooltip), NodeEditorResources.OutputPort, GUILayout.MinWidth(30));
}
// Display an editable property field if port is not connected // Display an editable property field if port is not connected
else EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30)); else
{
EditorGUILayout.PropertyField(property, label, includeChildren, GUILayout.MinWidth(30));
}
break; break;
case XNode.Node.ShowBackingValue.Never: case XNode.Node.ShowBackingValue.Never:
// Display a label // Display a label
@ -167,20 +209,20 @@ namespace XNodeEditor {
rect.size = new Vector2(16, 16); rect.size = new Vector2(16, 16);
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port); var backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port);
Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); var col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port); var portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port);
DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background); DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background);
// Register the handle position // Register the handle position
Vector2 portPos = rect.center; var portPos = rect.center;
NodeEditor.portPositions[port] = portPos; NodeEditor.portPositions[port] = portPos;
} }
} }
private static System.Type GetType(SerializedProperty property) { private static System.Type GetType(SerializedProperty property) {
System.Type parentType = property.serializedObject.targetObject.GetType(); var parentType = property.serializedObject.targetObject.GetType();
System.Reflection.FieldInfo fi = parentType.GetFieldInfo(property.name); var fi = parentType.GetFieldInfo(property.name);
return fi.FieldType; return fi.FieldType;
} }
@ -191,17 +233,25 @@ namespace XNodeEditor {
/// <summary> Make a simple port field. </summary> /// <summary> Make a simple port field. </summary>
public static void PortField(GUIContent label, XNode.NodePort port, params GUILayoutOption[] options) { public static void PortField(GUIContent label, XNode.NodePort port, params GUILayoutOption[] options) {
if (port == null) return; if (port == null)
if (options == null) options = new GUILayoutOption[] { GUILayout.MinWidth(30) }; {
return;
}
if (options == null)
{
options = new GUILayoutOption[] { GUILayout.MinWidth(30) };
}
Vector2 position = Vector3.zero; Vector2 position = Vector3.zero;
GUIContent content = label != null ? label : new GUIContent(ObjectNames.NicifyVariableName(port.fieldName)); var content = label != null ? label : new GUIContent(ObjectNames.NicifyVariableName(port.fieldName));
// If property is an input, display a regular property field and put a port handle on the left side // If property is an input, display a regular property field and put a port handle on the left side
if (port.direction == XNode.NodePort.IO.Input) { if (port.direction == XNode.NodePort.IO.Input) {
// Display a label // Display a label
EditorGUILayout.LabelField(content, options); EditorGUILayout.LabelField(content, options);
Rect rect = GUILayoutUtility.GetLastRect(); var rect = GUILayoutUtility.GetLastRect();
float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left; float paddingLeft = NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.left;
position = rect.position - new Vector2(16 + paddingLeft, 0); position = rect.position - new Vector2(16 + paddingLeft, 0);
} }
@ -210,7 +260,7 @@ namespace XNodeEditor {
// Display a label // Display a label
EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options); EditorGUILayout.LabelField(content, NodeEditorResources.OutputPort, options);
Rect rect = GUILayoutUtility.GetLastRect(); var rect = GUILayoutUtility.GetLastRect();
rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right; rect.width += NodeEditorWindow.current.graphEditor.GetPortStyle(port).padding.right;
position = rect.position + new Vector2(rect.width, 0); position = rect.position + new Vector2(rect.width, 0);
} }
@ -219,25 +269,32 @@ namespace XNodeEditor {
/// <summary> Make a simple port field. </summary> /// <summary> Make a simple port field. </summary>
public static void PortField(Vector2 position, XNode.NodePort port) { public static void PortField(Vector2 position, XNode.NodePort port) {
if (port == null) return; if (port == null)
{
return;
}
Rect rect = new Rect(position, new Vector2(16, 16)); var rect = new Rect(position, new Vector2(16, 16));
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port); var backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port);
Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); var col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port); var portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port);
DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background); DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background);
// Register the handle position // Register the handle position
Vector2 portPos = rect.center; var portPos = rect.center;
NodeEditor.portPositions[port] = portPos; NodeEditor.portPositions[port] = portPos;
} }
/// <summary> Add a port field to previous layout element. </summary> /// <summary> Add a port field to previous layout element. </summary>
public static void AddPortField(XNode.NodePort port) { public static void AddPortField(XNode.NodePort port) {
if (port == null) return; if (port == null)
Rect rect = new Rect(); {
return;
}
var rect = new Rect();
// If property is an input, display a regular property field and put a port handle on the left side // If property is an input, display a regular property field and put a port handle on the left side
if (port.direction == XNode.NodePort.IO.Input) { if (port.direction == XNode.NodePort.IO.Input) {
@ -253,14 +310,14 @@ namespace XNodeEditor {
rect.size = new Vector2(16, 16); rect.size = new Vector2(16, 16);
Color backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port); var backgroundColor = NodeEditorWindow.current.graphEditor.GetPortBackgroundColor(port);
Color col = NodeEditorWindow.current.graphEditor.GetPortColor(port); var col = NodeEditorWindow.current.graphEditor.GetPortColor(port);
GUIStyle portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port); var portStyle = NodeEditorWindow.current.graphEditor.GetPortStyle(port);
DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background); DrawPortHandle(rect, backgroundColor, col, portStyle.normal.background, portStyle.active.background);
// Register the handle position // Register the handle position
Vector2 portPos = rect.center; var portPos = rect.center;
NodeEditor.portPositions[port] = portPos; NodeEditor.portPositions[port] = portPos;
} }
@ -281,7 +338,7 @@ namespace XNodeEditor {
/// <param name="border">texture for border of the dot port</param> /// <param name="border">texture for border of the dot port</param>
/// <param name="dot">texture for the dot port</param> /// <param name="dot">texture for the dot port</param>
public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor, Texture2D border, Texture2D dot) { public static void DrawPortHandle(Rect rect, Color backgroundColor, Color typeColor, Texture2D border, Texture2D dot) {
Color col = GUI.color; var col = GUI.color;
GUI.color = backgroundColor; GUI.color = backgroundColor;
GUI.DrawTexture(rect, border); GUI.DrawTexture(rect, border);
GUI.color = typeColor; GUI.color = typeColor;
@ -304,12 +361,19 @@ namespace XNodeEditor {
/// <summary> Is this port part of a DynamicPortList? </summary> /// <summary> Is this port part of a DynamicPortList? </summary>
public static bool IsDynamicPortListPort(XNode.NodePort port) { public static bool IsDynamicPortListPort(XNode.NodePort port) {
string[] parts = port.fieldName.Split(' '); var parts = port.fieldName.Split(' ');
if (parts.Length != 2) return false; if (parts.Length != 2)
{
return false;
}
Dictionary<string, ReorderableList> cache; Dictionary<string, ReorderableList> cache;
if (reorderableListCache.TryGetValue(port.node, out cache)) { if (reorderableListCache.TryGetValue(port.node, out cache)) {
ReorderableList list; ReorderableList list;
if (cache.TryGetValue(parts[0], out list)) return true; if (cache.TryGetValue(parts[0], out list))
{
return true;
}
} }
return false; return false;
} }
@ -321,33 +385,42 @@ namespace XNodeEditor {
/// <param name="connectionType">Connection type of added dynamic ports</param> /// <param name="connectionType">Connection type of added dynamic ports</param>
/// <param name="onCreation">Called on the list on creation. Use this if you want to customize the created ReorderableList</param> /// <param name="onCreation">Called on the list on creation. Use this if you want to customize the created ReorderableList</param>
public static void DynamicPortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action<ReorderableList> onCreation = null) { public static void DynamicPortList(string fieldName, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType = XNode.Node.ConnectionType.Multiple, XNode.Node.TypeConstraint typeConstraint = XNode.Node.TypeConstraint.None, Action<ReorderableList> onCreation = null) {
XNode.Node node = serializedObject.targetObject as XNode.Node; var node = serializedObject.targetObject as XNode.Node;
var indexedPorts = node.DynamicPorts.Select(x => { var indexedPorts = node.DynamicPorts.Select(x => {
string[] split = x.fieldName.Split(' '); var split = x.fieldName.Split(' ');
if (split != null && split.Length == 2 && split[0] == fieldName) { if (split != null && split.Length == 2 && split[0] == fieldName) {
int i = -1; var i = -1;
if (int.TryParse(split[1], out i)) { if (int.TryParse(split[1], out i)) {
return new { index = i, port = x }; return new { index = i, port = x };
} }
} }
return new { index = -1, port = (XNode.NodePort)null }; return new { index = -1, port = (XNode.NodePort)null };
}).Where(x => x.port != null); }).Where(x => x.port != null);
List<XNode.NodePort> dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); var dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList();
node.UpdatePorts(); node.UpdatePorts();
ReorderableList list = null; ReorderableList list = null;
Dictionary<string, ReorderableList> rlc; Dictionary<string, ReorderableList> rlc;
if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) { if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) {
if (!rlc.TryGetValue(fieldName, out list)) list = null; if (!rlc.TryGetValue(fieldName, out list))
{
list = null;
}
} }
// If a ReorderableList isn't cached for this array, do so. // If a ReorderableList isn't cached for this array, do so.
if (list == null) { if (list == null) {
SerializedProperty arrayData = serializedObject.FindProperty(fieldName); var arrayData = serializedObject.FindProperty(fieldName);
list = CreateReorderableList(fieldName, dynamicPorts, arrayData, type, serializedObject, io, connectionType, typeConstraint, onCreation); list = CreateReorderableList(fieldName, dynamicPorts, arrayData, type, serializedObject, io, connectionType, typeConstraint, onCreation);
if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc)) rlc.Add(fieldName, list); if (reorderableListCache.TryGetValue(serializedObject.targetObject, out rlc))
else reorderableListCache.Add(serializedObject.targetObject, new Dictionary<string, ReorderableList>() { { fieldName, list } }); {
rlc.Add(fieldName, list);
}
else
{
reorderableListCache.Add(serializedObject.targetObject, new Dictionary<string, ReorderableList>() { { fieldName, list } });
}
} }
list.list = dynamicPorts; list.list = dynamicPorts;
list.DoLayoutList(); list.DoLayoutList();
@ -355,34 +428,45 @@ namespace XNodeEditor {
} }
private static ReorderableList CreateReorderableList(string fieldName, List<XNode.NodePort> dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action<ReorderableList> onCreation) { private static ReorderableList CreateReorderableList(string fieldName, List<XNode.NodePort> dynamicPorts, SerializedProperty arrayData, Type type, SerializedObject serializedObject, XNode.NodePort.IO io, XNode.Node.ConnectionType connectionType, XNode.Node.TypeConstraint typeConstraint, Action<ReorderableList> onCreation) {
bool hasArrayData = arrayData != null && arrayData.isArray; var hasArrayData = arrayData != null && arrayData.isArray;
XNode.Node node = serializedObject.targetObject as XNode.Node; var node = serializedObject.targetObject as XNode.Node;
ReorderableList list = new ReorderableList(dynamicPorts, null, true, true, true, true); var list = new ReorderableList(dynamicPorts, null, true, true, true, true);
string label = arrayData != null ? arrayData.displayName : ObjectNames.NicifyVariableName(fieldName); var label = arrayData != null ? arrayData.displayName : ObjectNames.NicifyVariableName(fieldName);
list.drawElementCallback = list.drawElementCallback =
(Rect rect, int index, bool isActive, bool isFocused) => { (Rect rect, int index, bool isActive, bool isFocused) => {
XNode.NodePort port = node.GetPort(fieldName + " " + index); var port = node.GetPort(fieldName + " " + index);
if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String) { if (hasArrayData && arrayData.propertyType != SerializedPropertyType.String) {
if (arrayData.arraySize <= index) { if (arrayData.arraySize <= index) {
EditorGUI.LabelField(rect, "Array[" + index + "] data out of range"); EditorGUI.LabelField(rect, "Array[" + index + "] data out of range");
return; return;
} }
SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index); var itemData = arrayData.GetArrayElementAtIndex(index);
EditorGUI.PropertyField(rect, itemData, true); EditorGUI.PropertyField(rect, itemData, true);
} else EditorGUI.LabelField(rect, port != null ? port.fieldName : ""); } else
{
EditorGUI.LabelField(rect, port != null ? port.fieldName : "");
}
if (port != null) { if (port != null) {
Vector2 pos = rect.position + (port.IsOutput ? new Vector2(rect.width + 6, 0) : new Vector2(-36, 0)); var pos = rect.position + (port.IsOutput ? new Vector2(rect.width + 6, 0) : new Vector2(-36, 0));
NodeEditorGUILayout.PortField(pos, port); NodeEditorGUILayout.PortField(pos, port);
} }
}; };
list.elementHeightCallback = list.elementHeightCallback =
(int index) => { (int index) => {
if (hasArrayData) { if (hasArrayData) {
if (arrayData.arraySize <= index) return EditorGUIUtility.singleLineHeight; if (arrayData.arraySize <= index)
SerializedProperty itemData = arrayData.GetArrayElementAtIndex(index); {
return EditorGUIUtility.singleLineHeight;
}
var itemData = arrayData.GetArrayElementAtIndex(index);
return EditorGUI.GetPropertyHeight(itemData); return EditorGUI.GetPropertyHeight(itemData);
} else return EditorGUIUtility.singleLineHeight; } else
{
return EditorGUIUtility.singleLineHeight;
}
}; };
list.drawHeaderCallback = list.drawHeaderCallback =
(Rect rect) => { (Rect rect) => {
@ -395,15 +479,15 @@ namespace XNodeEditor {
list.onReorderCallback = list.onReorderCallback =
(ReorderableList rl) => { (ReorderableList rl) => {
serializedObject.Update(); serializedObject.Update();
bool hasRect = false; var hasRect = false;
bool hasNewRect = false; var hasNewRect = false;
Rect rect = Rect.zero; var rect = Rect.zero;
Rect newRect = Rect.zero; var newRect = Rect.zero;
// Move up // Move up
if (rl.index > reorderableListIndex) { if (rl.index > reorderableListIndex) {
for (int i = reorderableListIndex; i < rl.index; ++i) { for (var i = reorderableListIndex; i < rl.index; ++i) {
XNode.NodePort port = node.GetPort(fieldName + " " + i); var port = node.GetPort(fieldName + " " + i);
XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i + 1)); var nextPort = node.GetPort(fieldName + " " + (i + 1));
port.SwapConnections(nextPort); port.SwapConnections(nextPort);
// Swap cached positions to mitigate twitching // Swap cached positions to mitigate twitching
@ -415,9 +499,9 @@ namespace XNodeEditor {
} }
// Move down // Move down
else { else {
for (int i = reorderableListIndex; i > rl.index; --i) { for (var i = reorderableListIndex; i > rl.index; --i) {
XNode.NodePort port = node.GetPort(fieldName + " " + i); var port = node.GetPort(fieldName + " " + i);
XNode.NodePort nextPort = node.GetPort(fieldName + " " + (i - 1)); var nextPort = node.GetPort(fieldName + " " + (i - 1));
port.SwapConnections(nextPort); port.SwapConnections(nextPort);
// Swap cached positions to mitigate twitching // Swap cached positions to mitigate twitching
@ -445,12 +529,22 @@ namespace XNodeEditor {
list.onAddCallback = list.onAddCallback =
(ReorderableList rl) => { (ReorderableList rl) => {
// Add dynamic port postfixed with an index number // Add dynamic port postfixed with an index number
string newName = fieldName + " 0"; var newName = fieldName + " 0";
int i = 0; var i = 0;
while (node.HasPort(newName)) newName = fieldName + " " + (++i); while (node.HasPort(newName))
{
newName = fieldName + " " + (++i);
}
if (io == XNode.NodePort.IO.Output)
{
node.AddDynamicOutput(type, connectionType, XNode.Node.TypeConstraint.None, newName);
}
else
{
node.AddDynamicInput(type, connectionType, typeConstraint, newName);
}
if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, XNode.Node.TypeConstraint.None, newName);
else node.AddDynamicInput(type, connectionType, typeConstraint, newName);
serializedObject.Update(); serializedObject.Update();
EditorUtility.SetDirty(node); EditorUtility.SetDirty(node);
if (hasArrayData) { if (hasArrayData) {
@ -462,9 +556,9 @@ namespace XNodeEditor {
(ReorderableList rl) => { (ReorderableList rl) => {
var indexedPorts = node.DynamicPorts.Select(x => { var indexedPorts = node.DynamicPorts.Select(x => {
string[] split = x.fieldName.Split(' '); var split = x.fieldName.Split(' ');
if (split != null && split.Length == 2 && split[0] == fieldName) { if (split != null && split.Length == 2 && split[0] == fieldName) {
int i = -1; var i = -1;
if (int.TryParse(split[1], out i)) { if (int.TryParse(split[1], out i)) {
return new { index = i, port = x }; return new { index = i, port = x };
} }
@ -473,7 +567,7 @@ namespace XNodeEditor {
}).Where(x => x.port != null); }).Where(x => x.port != null);
dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList(); dynamicPorts = indexedPorts.OrderBy(x => x.index).Select(x => x.port).ToList();
int index = rl.index; var index = rl.index;
if (dynamicPorts[index] == null) { if (dynamicPorts[index] == null) {
Debug.LogWarning("No port found at index " + index + " - Skipped"); Debug.LogWarning("No port found at index " + index + " - Skipped");
@ -484,9 +578,9 @@ namespace XNodeEditor {
// Clear the removed ports connections // Clear the removed ports connections
dynamicPorts[index].ClearConnections(); dynamicPorts[index].ClearConnections();
// Move following connections one step up to replace the missing connection // Move following connections one step up to replace the missing connection
for (int k = index + 1; k < dynamicPorts.Count(); k++) { for (var k = index + 1; k < dynamicPorts.Count(); k++) {
for (int j = 0; j < dynamicPorts[k].ConnectionCount; j++) { for (var j = 0; j < dynamicPorts[k].ConnectionCount; j++) {
XNode.NodePort other = dynamicPorts[k].GetConnection(j); var other = dynamicPorts[k].GetConnection(j);
dynamicPorts[k].Disconnect(other); dynamicPorts[k].Disconnect(other);
dynamicPorts[k - 1].Connect(other); dynamicPorts[k - 1].Connect(other);
} }
@ -517,14 +611,25 @@ namespace XNodeEditor {
}; };
if (hasArrayData) { if (hasArrayData) {
int dynamicPortCount = dynamicPorts.Count; var dynamicPortCount = dynamicPorts.Count;
while (dynamicPortCount < arrayData.arraySize) { while (dynamicPortCount < arrayData.arraySize) {
// Add dynamic port postfixed with an index number // Add dynamic port postfixed with an index number
string newName = arrayData.name + " 0"; var newName = arrayData.name + " 0";
int i = 0; var i = 0;
while (node.HasPort(newName)) newName = arrayData.name + " " + (++i); while (node.HasPort(newName))
if (io == XNode.NodePort.IO.Output) node.AddDynamicOutput(type, connectionType, typeConstraint, newName); {
else node.AddDynamicInput(type, connectionType, typeConstraint, newName); newName = arrayData.name + " " + (++i);
}
if (io == XNode.NodePort.IO.Output)
{
node.AddDynamicOutput(type, connectionType, typeConstraint, newName);
}
else
{
node.AddDynamicInput(type, connectionType, typeConstraint, newName);
}
EditorUtility.SetDirty(node); EditorUtility.SetDirty(node);
dynamicPortCount++; dynamicPortCount++;
} }
@ -534,7 +639,11 @@ namespace XNodeEditor {
serializedObject.ApplyModifiedProperties(); serializedObject.ApplyModifiedProperties();
serializedObject.Update(); serializedObject.Update();
} }
if (onCreation != null) onCreation(list); if (onCreation != null)
{
onCreation(list);
}
return list; return list;
} }
} }

View File

@ -51,14 +51,22 @@ namespace XNodeEditor {
private Texture2D _gridTexture; private Texture2D _gridTexture;
public Texture2D gridTexture { public Texture2D gridTexture {
get { get {
if (_gridTexture == null) _gridTexture = NodeEditorResources.GenerateGridTexture(gridLineColor, gridBgColor); if (_gridTexture == null)
{
_gridTexture = NodeEditorResources.GenerateGridTexture(gridLineColor, gridBgColor);
}
return _gridTexture; return _gridTexture;
} }
} }
private Texture2D _crossTexture; private Texture2D _crossTexture;
public Texture2D crossTexture { public Texture2D crossTexture {
get { get {
if (_crossTexture == null) _crossTexture = NodeEditorResources.GenerateCrossTexture(gridLineColor); if (_crossTexture == null)
{
_crossTexture = NodeEditorResources.GenerateCrossTexture(gridLineColor);
}
return _crossTexture; return _crossTexture;
} }
} }
@ -66,8 +74,8 @@ namespace XNodeEditor {
public void OnAfterDeserialize() { public void OnAfterDeserialize() {
// Deserialize typeColorsData // Deserialize typeColorsData
typeColors = new Dictionary<string, Color>(); typeColors = new Dictionary<string, Color>();
string[] data = typeColorsData.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var data = typeColorsData.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < data.Length; i += 2) { for (var i = 0; i < data.Length; i += 2) {
Color col; Color col;
if (ColorUtility.TryParseHtmlString("#" + data[i + 1], out col)) { if (ColorUtility.TryParseHtmlString("#" + data[i + 1], out col)) {
typeColors.Add(data[i], col); typeColors.Add(data[i], col);
@ -86,24 +94,34 @@ namespace XNodeEditor {
/// <summary> Get settings of current active editor </summary> /// <summary> Get settings of current active editor </summary>
public static Settings GetSettings() { public static Settings GetSettings() {
if (XNodeEditor.NodeEditorWindow.current == null) return new Settings(); if (XNodeEditor.NodeEditorWindow.current == null)
{
return new Settings();
}
if (lastEditor != XNodeEditor.NodeEditorWindow.current.graphEditor) { if (lastEditor != XNodeEditor.NodeEditorWindow.current.graphEditor) {
object[] attribs = XNodeEditor.NodeEditorWindow.current.graphEditor.GetType().GetCustomAttributes(typeof(XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute), true); var attribs = XNodeEditor.NodeEditorWindow.current.graphEditor.GetType().GetCustomAttributes(typeof(XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute), true);
if (attribs.Length == 1) { if (attribs.Length == 1) {
XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute attrib = attribs[0] as XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute; var attrib = attribs[0] as XNodeEditor.NodeGraphEditor.CustomNodeGraphEditorAttribute;
lastEditor = XNodeEditor.NodeEditorWindow.current.graphEditor; lastEditor = XNodeEditor.NodeEditorWindow.current.graphEditor;
lastKey = attrib.editorPrefsKey; lastKey = attrib.editorPrefsKey;
} else return null; } else
{
return null;
} }
if (!settings.ContainsKey(lastKey)) VerifyLoaded(); }
if (!settings.ContainsKey(lastKey))
{
VerifyLoaded();
}
return settings[lastKey]; return settings[lastKey];
} }
#if UNITY_2019_1_OR_NEWER #if UNITY_2019_1_OR_NEWER
[SettingsProvider] [SettingsProvider]
public static SettingsProvider CreateXNodeSettingsProvider() { public static SettingsProvider CreateXNodeSettingsProvider() {
SettingsProvider provider = new SettingsProvider("Preferences/Node Editor", SettingsScope.User) { var provider = new SettingsProvider("Preferences/Node Editor", SettingsScope.User) {
guiHandler = (searchContext) => { XNodeEditor.NodeEditorPreferences.PreferencesGUI(); }, guiHandler = (searchContext) => { XNodeEditor.NodeEditorPreferences.PreferencesGUI(); },
keywords = new HashSet<string>(new [] { "xNode", "node", "editor", "graph", "connections", "noodles", "ports" }) keywords = new HashSet<string>(new [] { "xNode", "node", "editor", "graph", "connections", "noodles", "ports" })
}; };
@ -116,9 +134,13 @@ namespace XNodeEditor {
#endif #endif
private static void PreferencesGUI() { private static void PreferencesGUI() {
VerifyLoaded(); VerifyLoaded();
Settings settings = NodeEditorPreferences.settings[lastKey]; var settings = NodeEditorPreferences.settings[lastKey];
if (GUILayout.Button(new GUIContent("Documentation", "https://github.com/Siccity/xNode/wiki"), GUILayout.Width(100)))
{
Application.OpenURL("https://github.com/Siccity/xNode/wiki");
}
if (GUILayout.Button(new GUIContent("Documentation", "https://github.com/Siccity/xNode/wiki"), GUILayout.Width(100))) Application.OpenURL("https://github.com/Siccity/xNode/wiki");
EditorGUILayout.Space(); EditorGUILayout.Space();
NodeSettingsGUI(lastKey, settings); NodeSettingsGUI(lastKey, settings);
@ -155,7 +177,11 @@ namespace XNodeEditor {
EditorGUILayout.LabelField("System", EditorStyles.boldLabel); EditorGUILayout.LabelField("System", EditorStyles.boldLabel);
settings.autoSave = EditorGUILayout.Toggle(new GUIContent("Autosave", "Disable for better editor performance"), settings.autoSave); settings.autoSave = EditorGUILayout.Toggle(new GUIContent("Autosave", "Disable for better editor performance"), settings.autoSave);
settings.openOnCreate = EditorGUILayout.Toggle(new GUIContent("Open Editor on Create", "Disable to prevent openening the editor when creating a new graph"), settings.openOnCreate); settings.openOnCreate = EditorGUILayout.Toggle(new GUIContent("Open Editor on Create", "Disable to prevent openening the editor when creating a new graph"), settings.openOnCreate);
if (GUI.changed) SavePrefs(key, settings); if (GUI.changed)
{
SavePrefs(key, settings);
}
EditorGUILayout.Space(); EditorGUILayout.Space();
} }
@ -188,16 +214,23 @@ namespace XNodeEditor {
//Display type colors. Save them if they are edited by the user //Display type colors. Save them if they are edited by the user
foreach (var type in typeColorKeys) { foreach (var type in typeColorKeys) {
string typeColorKey = NodeEditorUtilities.PrettyName(type); var typeColorKey = NodeEditorUtilities.PrettyName(type);
Color col = typeColors[type]; var col = typeColors[type];
EditorGUI.BeginChangeCheck(); EditorGUI.BeginChangeCheck();
EditorGUILayout.BeginHorizontal(); EditorGUILayout.BeginHorizontal();
col = EditorGUILayout.ColorField(typeColorKey, col); col = EditorGUILayout.ColorField(typeColorKey, col);
EditorGUILayout.EndHorizontal(); EditorGUILayout.EndHorizontal();
if (EditorGUI.EndChangeCheck()) { if (EditorGUI.EndChangeCheck()) {
typeColors[type] = col; typeColors[type] = col;
if (settings.typeColors.ContainsKey(typeColorKey)) settings.typeColors[typeColorKey] = col; if (settings.typeColors.ContainsKey(typeColorKey))
else settings.typeColors.Add(typeColorKey, col); {
settings.typeColors[typeColorKey] = col;
}
else
{
settings.typeColors.Add(typeColorKey, col);
}
SavePrefs(key, settings); SavePrefs(key, settings);
NodeEditorWindow.RepaintAll(); NodeEditorWindow.RepaintAll();
} }
@ -208,16 +241,30 @@ namespace XNodeEditor {
private static Settings LoadPrefs() { private static Settings LoadPrefs() {
// Create settings if it doesn't exist // Create settings if it doesn't exist
if (!EditorPrefs.HasKey(lastKey)) { if (!EditorPrefs.HasKey(lastKey)) {
if (lastEditor != null) EditorPrefs.SetString(lastKey, JsonUtility.ToJson(lastEditor.GetDefaultPreferences())); if (lastEditor != null)
else EditorPrefs.SetString(lastKey, JsonUtility.ToJson(new Settings())); {
EditorPrefs.SetString(lastKey, JsonUtility.ToJson(lastEditor.GetDefaultPreferences()));
}
else
{
EditorPrefs.SetString(lastKey, JsonUtility.ToJson(new Settings()));
}
} }
return JsonUtility.FromJson<Settings>(EditorPrefs.GetString(lastKey)); return JsonUtility.FromJson<Settings>(EditorPrefs.GetString(lastKey));
} }
/// <summary> Delete all prefs </summary> /// <summary> Delete all prefs </summary>
public static void ResetPrefs() { public static void ResetPrefs() {
if (EditorPrefs.HasKey(lastKey)) EditorPrefs.DeleteKey(lastKey); if (EditorPrefs.HasKey(lastKey))
if (settings.ContainsKey(lastKey)) settings.Remove(lastKey); {
EditorPrefs.DeleteKey(lastKey);
}
if (settings.ContainsKey(lastKey))
{
settings.Remove(lastKey);
}
typeColors = new Dictionary<Type, Color>(); typeColors = new Dictionary<Type, Color>();
VerifyLoaded(); VerifyLoaded();
NodeEditorWindow.RepaintAll(); NodeEditorWindow.RepaintAll();
@ -230,20 +277,30 @@ namespace XNodeEditor {
/// <summary> Check if we have loaded settings for given key. If not, load them </summary> /// <summary> Check if we have loaded settings for given key. If not, load them </summary>
private static void VerifyLoaded() { private static void VerifyLoaded() {
if (!settings.ContainsKey(lastKey)) settings.Add(lastKey, LoadPrefs()); if (!settings.ContainsKey(lastKey))
{
settings.Add(lastKey, LoadPrefs());
}
} }
/// <summary> Return color based on type </summary> /// <summary> Return color based on type </summary>
public static Color GetTypeColor(System.Type type) { public static Color GetTypeColor(System.Type type) {
VerifyLoaded(); VerifyLoaded();
if (type == null) return Color.gray; if (type == null)
{
return Color.gray;
}
Color col; Color col;
if (!typeColors.TryGetValue(type, out col)) { if (!typeColors.TryGetValue(type, out col)) {
string typeName = type.PrettyName(); var typeName = type.PrettyName();
if (settings[lastKey].typeColors.ContainsKey(typeName)) typeColors.Add(type, settings[lastKey].typeColors[typeName]); if (settings[lastKey].typeColors.ContainsKey(typeName))
{
typeColors.Add(type, settings[lastKey].typeColors[typeName]);
}
else { else {
#if UNITY_5_4_OR_NEWER #if UNITY_5_4_OR_NEWER
UnityEngine.Random.State oldState = UnityEngine.Random.state; var oldState = UnityEngine.Random.state;
UnityEngine.Random.InitState(typeName.GetHashCode()); UnityEngine.Random.InitState(typeName.GetHashCode());
#else #else
int oldSeed = UnityEngine.Random.seed; int oldSeed = UnityEngine.Random.seed;

View File

@ -21,8 +21,8 @@ namespace XNodeEditor {
/// <summary> Return a delegate used to determine whether window is docked or not. It is faster to cache this delegate than run the reflection required each time. </summary> /// <summary> Return a delegate used to determine whether window is docked or not. It is faster to cache this delegate than run the reflection required each time. </summary>
public static Func<bool> GetIsDockedDelegate(this EditorWindow window) { public static Func<bool> GetIsDockedDelegate(this EditorWindow window) {
BindingFlags fullBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static; var fullBinding = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static;
MethodInfo isDockedMethod = typeof(EditorWindow).GetProperty("docked", fullBinding).GetGetMethod(true); var isDockedMethod = typeof(EditorWindow).GetProperty("docked", fullBinding).GetGetMethod(true);
return (Func<bool>) Delegate.CreateDelegate(typeof(Func<bool>), window, isDockedMethod); return (Func<bool>) Delegate.CreateDelegate(typeof(Func<bool>), window, isDockedMethod);
} }
@ -49,10 +49,14 @@ namespace XNodeEditor {
private static void CacheAttributes<V, A>(ref Dictionary<Type, V> dict, Func<A, V> getter) where A : Attribute { private static void CacheAttributes<V, A>(ref Dictionary<Type, V> dict, Func<A, V> getter) where A : Attribute {
dict = new Dictionary<Type, V>(); dict = new Dictionary<Type, V>();
for (int i = 0; i < nodeTypes.Length; i++) { for (var i = 0; i < nodeTypes.Length; i++) {
object[] attribs = nodeTypes[i].GetCustomAttributes(typeof(A), true); var attribs = nodeTypes[i].GetCustomAttributes(typeof(A), true);
if (attribs == null || attribs.Length == 0) continue; if (attribs == null || attribs.Length == 0)
A attrib = attribs[0] as A; {
continue;
}
var attrib = attribs[0] as A;
dict.Add(nodeTypes[i], getter(attrib)); dict.Add(nodeTypes[i], getter(attrib));
} }
} }
@ -60,17 +64,21 @@ namespace XNodeEditor {
/// <summary> Get FieldInfo of a field, including those that are private and/or inherited </summary> /// <summary> Get FieldInfo of a field, including those that are private and/or inherited </summary>
public static FieldInfo GetFieldInfo(this Type type, string fieldName) { public static FieldInfo GetFieldInfo(this Type type, string fieldName) {
// If we can't find field in the first run, it's probably a private field in a base class. // If we can't find field in the first run, it's probably a private field in a base class.
FieldInfo field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); var field = type.GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
// Search base classes for private fields only. Public fields are found above // Search base classes for private fields only. Public fields are found above
while (field == null && (type = type.BaseType) != typeof(XNode.Node)) field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); while (field == null && (type = type.BaseType) != typeof(XNode.Node))
{
field = type.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance);
}
return field; return field;
} }
/// <summary> Get all classes deriving from baseType via reflection </summary> /// <summary> Get all classes deriving from baseType via reflection </summary>
public static Type[] GetDerivedTypes(this Type baseType) { public static Type[] GetDerivedTypes(this Type baseType) {
List<System.Type> types = new List<System.Type>(); var types = new List<System.Type>();
System.Reflection.Assembly[] assemblies = System.AppDomain.CurrentDomain.GetAssemblies(); var assemblies = System.AppDomain.CurrentDomain.GetAssemblies();
foreach (Assembly assembly in assemblies) { foreach (var assembly in assemblies) {
try { try {
types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray()); types.AddRange(assembly.GetTypes().Where(t => !t.IsAbstract && baseType.IsAssignableFrom(t)).ToArray());
} catch (ReflectionTypeLoadException) { } } catch (ReflectionTypeLoadException) { }
@ -80,17 +88,17 @@ namespace XNodeEditor {
/// <summary> Find methods marked with the [ContextMenu] attribute and add them to the context menu </summary> /// <summary> Find methods marked with the [ContextMenu] attribute and add them to the context menu </summary>
public static void AddCustomContextMenuItems(this GenericMenu contextMenu, object obj) { public static void AddCustomContextMenuItems(this GenericMenu contextMenu, object obj) {
KeyValuePair<ContextMenu, MethodInfo>[] items = GetContextMenuMethods(obj); var items = GetContextMenuMethods(obj);
if (items.Length != 0) { if (items.Length != 0) {
contextMenu.AddSeparator(""); contextMenu.AddSeparator("");
List<string> invalidatedEntries = new List<string>(); var invalidatedEntries = new List<string>();
foreach (KeyValuePair<ContextMenu, MethodInfo> checkValidate in items) { foreach (var checkValidate in items) {
if (checkValidate.Key.validate && !(bool) checkValidate.Value.Invoke(obj, null)) { if (checkValidate.Key.validate && !(bool) checkValidate.Value.Invoke(obj, null)) {
invalidatedEntries.Add(checkValidate.Key.menuItem); invalidatedEntries.Add(checkValidate.Key.menuItem);
} }
} }
for (int i = 0; i < items.Length; i++) { for (var i = 0; i < items.Length; i++) {
KeyValuePair<ContextMenu, MethodInfo> kvp = items[i]; var kvp = items[i];
if (invalidatedEntries.Contains(kvp.Key.menuItem)) { if (invalidatedEntries.Contains(kvp.Key.menuItem)) {
contextMenu.AddDisabledItem(new GUIContent(kvp.Key.menuItem)); contextMenu.AddDisabledItem(new GUIContent(kvp.Key.menuItem));
} else { } else {
@ -105,17 +113,24 @@ namespace XNodeEditor {
System.Reflection.MethodInfo onValidate = null; System.Reflection.MethodInfo onValidate = null;
if (target != null) { if (target != null) {
onValidate = target.GetType().GetMethod("OnValidate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); onValidate = target.GetType().GetMethod("OnValidate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
if (onValidate != null) onValidate.Invoke(target, null); if (onValidate != null)
{
onValidate.Invoke(target, null);
}
} }
} }
public static KeyValuePair<ContextMenu, MethodInfo>[] GetContextMenuMethods(object obj) { public static KeyValuePair<ContextMenu, MethodInfo>[] GetContextMenuMethods(object obj) {
Type type = obj.GetType(); var type = obj.GetType();
MethodInfo[] methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); var methods = type.GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
List<KeyValuePair<ContextMenu, MethodInfo>> kvp = new List<KeyValuePair<ContextMenu, MethodInfo>>(); var kvp = new List<KeyValuePair<ContextMenu, MethodInfo>>();
for (int i = 0; i < methods.Length; i++) { for (var i = 0; i < methods.Length; i++) {
ContextMenu[] attribs = methods[i].GetCustomAttributes(typeof(ContextMenu), true).Select(x => x as ContextMenu).ToArray(); var attribs = methods[i].GetCustomAttributes(typeof(ContextMenu), true).Select(x => x as ContextMenu).ToArray();
if (attribs == null || attribs.Length == 0) continue; if (attribs == null || attribs.Length == 0)
{
continue;
}
if (methods[i].GetParameters().Length != 0) { if (methods[i].GetParameters().Length != 0) {
Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name + " has parameters and cannot be used for context menu commands."); Debug.LogWarning("Method " + methods[i].DeclaringType.Name + "." + methods[i].Name + " has parameters and cannot be used for context menu commands.");
continue; continue;
@ -125,7 +140,7 @@ namespace XNodeEditor {
continue; continue;
} }
for (int k = 0; k < attribs.Length; k++) { for (var k = 0; k < attribs.Length; k++) {
kvp.Add(new KeyValuePair<ContextMenu, MethodInfo>(attribs[k], methods[i])); kvp.Add(new KeyValuePair<ContextMenu, MethodInfo>(attribs[k], methods[i]));
} }
} }

View File

@ -21,7 +21,7 @@ namespace XNodeEditor {
public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip, nodeHighlight; public GUIStyle inputPort, outputPort, nodeHeader, nodeBody, tooltip, nodeHighlight;
public Styles() { public Styles() {
GUIStyle baseStyle = new GUIStyle("Label"); var baseStyle = new GUIStyle("Label");
baseStyle.fixedHeight = 18; baseStyle.fixedHeight = 18;
inputPort = new GUIStyle(baseStyle); inputPort = new GUIStyle(baseStyle);
@ -56,13 +56,21 @@ namespace XNodeEditor {
} }
public static Texture2D GenerateGridTexture(Color line, Color bg) { public static Texture2D GenerateGridTexture(Color line, Color bg) {
Texture2D tex = new Texture2D(64, 64); var tex = new Texture2D(64, 64);
Color[] cols = new Color[64 * 64]; var cols = new Color[64 * 64];
for (int y = 0; y < 64; y++) { for (var y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) { for (var x = 0; x < 64; x++) {
Color col = bg; var col = bg;
if (y % 16 == 0 || x % 16 == 0) col = Color.Lerp(line, bg, 0.65f); if (y % 16 == 0 || x % 16 == 0)
if (y == 63 || x == 63) col = Color.Lerp(line, bg, 0.35f); {
col = Color.Lerp(line, bg, 0.65f);
}
if (y == 63 || x == 63)
{
col = Color.Lerp(line, bg, 0.35f);
}
cols[(y * 64) + x] = col; cols[(y * 64) + x] = col;
} }
} }
@ -75,12 +83,16 @@ namespace XNodeEditor {
} }
public static Texture2D GenerateCrossTexture(Color line) { public static Texture2D GenerateCrossTexture(Color line) {
Texture2D tex = new Texture2D(64, 64); var tex = new Texture2D(64, 64);
Color[] cols = new Color[64 * 64]; var cols = new Color[64 * 64];
for (int y = 0; y < 64; y++) { for (var y = 0; y < 64; y++) {
for (int x = 0; x < 64; x++) { for (var x = 0; x < 64; x++) {
Color col = line; var col = line;
if (y != 31 && x != 31) col.a = 0; if (y != 31 && x != 31)
{
col.a = 0;
}
cols[(y * 64) + x] = col; cols[(y * 64) + x] = col;
} }
} }

View File

@ -22,12 +22,12 @@ namespace XNodeEditor {
private static Dictionary<Type, Dictionary<string, List<PropertyAttribute>>> typeOrderedPropertyAttributes = new Dictionary<Type, Dictionary<string, List<PropertyAttribute>>>(); private static Dictionary<Type, Dictionary<string, List<PropertyAttribute>>> typeOrderedPropertyAttributes = new Dictionary<Type, Dictionary<string, List<PropertyAttribute>>>();
public static bool GetAttrib<T>(Type classType, out T attribOut) where T : Attribute { public static bool GetAttrib<T>(Type classType, out T attribOut) where T : Attribute {
object[] attribs = classType.GetCustomAttributes(typeof(T), false); var attribs = classType.GetCustomAttributes(typeof(T), false);
return GetAttrib(attribs, out attribOut); return GetAttrib(attribs, out attribOut);
} }
public static bool GetAttrib<T>(object[] attribs, out T attribOut) where T : Attribute { public static bool GetAttrib<T>(object[] attribs, out T attribOut) where T : Attribute {
for (int i = 0; i < attribs.Length; i++) { for (var i = 0; i < attribs.Length; i++) {
if (attribs[i] is T) { if (attribs[i] is T) {
attribOut = attribs[i] as T; attribOut = attribs[i] as T;
return true; return true;
@ -39,19 +39,19 @@ namespace XNodeEditor {
public static bool GetAttrib<T>(Type classType, string fieldName, out T attribOut) where T : Attribute { public static bool GetAttrib<T>(Type classType, string fieldName, out T attribOut) where T : Attribute {
// If we can't find field in the first run, it's probably a private field in a base class. // If we can't find field in the first run, it's probably a private field in a base class.
FieldInfo field = classType.GetFieldInfo(fieldName); var field = classType.GetFieldInfo(fieldName);
// This shouldn't happen. Ever. // This shouldn't happen. Ever.
if (field == null) { if (field == null) {
Debug.LogWarning("Field " + fieldName + " couldnt be found"); Debug.LogWarning("Field " + fieldName + " couldnt be found");
attribOut = null; attribOut = null;
return false; return false;
} }
object[] attribs = field.GetCustomAttributes(typeof(T), true); var attribs = field.GetCustomAttributes(typeof(T), true);
return GetAttrib(attribs, out attribOut); return GetAttrib(attribs, out attribOut);
} }
public static bool HasAttrib<T>(object[] attribs) where T : Attribute { public static bool HasAttrib<T>(object[] attribs) where T : Attribute {
for (int i = 0; i < attribs.Length; i++) { for (var i = 0; i < attribs.Length; i++) {
if (attribs[i].GetType() == typeof(T)) { if (attribs[i].GetType() == typeof(T)) {
return true; return true;
} }
@ -77,7 +77,10 @@ namespace XNodeEditor {
if (GetAttrib<T>(classType, fieldName, out attribOut)) { if (GetAttrib<T>(classType, fieldName, out attribOut)) {
typeTypes.Add(typeof(T), attribOut); typeTypes.Add(typeof(T), attribOut);
return true; return true;
} else typeTypes.Add(typeof(T), null); } else
{
typeTypes.Add(typeof(T), null);
}
} }
if (attr == null) { if (attr == null) {
@ -98,8 +101,8 @@ namespace XNodeEditor {
List<PropertyAttribute> typeAttributes; List<PropertyAttribute> typeAttributes;
if (!typeFields.TryGetValue(fieldName, out typeAttributes)) { if (!typeFields.TryGetValue(fieldName, out typeAttributes)) {
FieldInfo field = classType.GetFieldInfo(fieldName); var field = classType.GetFieldInfo(fieldName);
object[] attribs = field.GetCustomAttributes(typeof(PropertyAttribute), true); var attribs = field.GetCustomAttributes(typeof(PropertyAttribute), true);
typeAttributes = attribs.Cast<PropertyAttribute>().Reverse().ToList(); //Unity draws them in reverse typeAttributes = attribs.Cast<PropertyAttribute>().Reverse().ToList(); //Unity draws them in reverse
typeFields.Add(fieldName, typeAttributes); typeFields.Add(fieldName, typeAttributes);
} }
@ -117,7 +120,11 @@ namespace XNodeEditor {
/// <summary> Returns true if this can be casted to <see cref="Type"/></summary> /// <summary> Returns true if this can be casted to <see cref="Type"/></summary>
public static bool IsCastableTo(this Type from, Type to) { public static bool IsCastableTo(this Type from, Type to) {
if (to.IsAssignableFrom(from)) return true; if (to.IsAssignableFrom(from))
{
return true;
}
var methods = from.GetMethods(BindingFlags.Public | BindingFlags.Static) var methods = from.GetMethods(BindingFlags.Public | BindingFlags.Static)
.Where( .Where(
m => m.ReturnType == to && m => m.ReturnType == to &&
@ -135,13 +142,15 @@ namespace XNodeEditor {
/// <param name="direction"></param> /// <param name="direction"></param>
/// <returns>True if NodeType has some port with value type compatible</returns> /// <returns>True if NodeType has some port with value type compatible</returns>
public static bool HasCompatiblePortType(Type nodeType, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) { public static bool HasCompatiblePortType(Type nodeType, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) {
Type findType = typeof(XNode.Node.InputAttribute); var findType = typeof(XNode.Node.InputAttribute);
if (direction == XNode.NodePort.IO.Output) if (direction == XNode.NodePort.IO.Output)
{
findType = typeof(XNode.Node.OutputAttribute); findType = typeof(XNode.Node.OutputAttribute);
}
//Get All fields from node type and we go filter only field with portAttribute. //Get All fields from node type and we go filter only field with portAttribute.
//This way is possible to know the values of the all ports and if have some with compatible value tue //This way is possible to know the values of the all ports and if have some with compatible value tue
foreach (FieldInfo f in XNode.NodeDataCache.GetNodeFields(nodeType)) { foreach (var f in XNode.NodeDataCache.GetNodeFields(nodeType)) {
var portAttribute = f.GetCustomAttributes(findType, false).FirstOrDefault(); var portAttribute = f.GetCustomAttributes(findType, false).FirstOrDefault();
if (portAttribute != null) { if (portAttribute != null) {
if (IsCastableTo(f.FieldType, compatibleType)) { if (IsCastableTo(f.FieldType, compatibleType)) {
@ -161,14 +170,14 @@ namespace XNodeEditor {
/// <returns>Return Only Node Types with ports compatible, or an empty list</returns> /// <returns>Return Only Node Types with ports compatible, or an empty list</returns>
public static List<Type> GetCompatibleNodesTypes(Type[] nodeTypes, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) { public static List<Type> GetCompatibleNodesTypes(Type[] nodeTypes, Type compatibleType, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) {
//Result List //Result List
List<Type> filteredTypes = new List<Type>(); var filteredTypes = new List<Type>();
//Return empty list //Return empty list
if (nodeTypes == null) { return filteredTypes; } if (nodeTypes == null) { return filteredTypes; }
if (compatibleType == null) { return filteredTypes; } if (compatibleType == null) { return filteredTypes; }
//Find compatiblity //Find compatiblity
foreach (Type findType in nodeTypes) { foreach (var findType in nodeTypes) {
if (HasCompatiblePortType(findType, compatibleType, direction)) { if (HasCompatiblePortType(findType, compatibleType, direction)) {
filteredTypes.Add(findType); filteredTypes.Add(findType);
} }
@ -180,55 +189,101 @@ namespace XNodeEditor {
/// <summary> Return a prettiefied type name. </summary> /// <summary> Return a prettiefied type name. </summary>
public static string PrettyName(this Type type) { public static string PrettyName(this Type type) {
if (type == null) return "null"; if (type == null)
if (type == typeof(System.Object)) return "object"; {
if (type == typeof(float)) return "float"; return "null";
else if (type == typeof(int)) return "int"; }
else if (type == typeof(long)) return "long";
else if (type == typeof(double)) return "double";
else if (type == typeof(string)) return "string";
else if (type == typeof(bool)) return "bool";
else if (type.IsGenericType) {
string s = "";
Type genericType = type.GetGenericTypeDefinition();
if (genericType == typeof(List<>)) s = "List";
else s = type.GetGenericTypeDefinition().ToString();
Type[] types = type.GetGenericArguments(); if (type == typeof(System.Object))
string[] stypes = new string[types.Length]; {
for (int i = 0; i < types.Length; i++) { return "object";
}
if (type == typeof(float))
{
return "float";
}
else if (type == typeof(int))
{
return "int";
}
else if (type == typeof(long))
{
return "long";
}
else if (type == typeof(double))
{
return "double";
}
else if (type == typeof(string))
{
return "string";
}
else if (type == typeof(bool))
{
return "bool";
}
else if (type.IsGenericType) {
var s = "";
var genericType = type.GetGenericTypeDefinition();
if (genericType == typeof(List<>))
{
s = "List";
}
else
{
s = type.GetGenericTypeDefinition().ToString();
}
var types = type.GetGenericArguments();
var stypes = new string[types.Length];
for (var i = 0; i < types.Length; i++) {
stypes[i] = types[i].PrettyName(); stypes[i] = types[i].PrettyName();
} }
return s + "<" + string.Join(", ", stypes) + ">"; return s + "<" + string.Join(", ", stypes) + ">";
} else if (type.IsArray) { } else if (type.IsArray) {
string rank = ""; var rank = "";
for (int i = 1; i < type.GetArrayRank(); i++) { for (var i = 1; i < type.GetArrayRank(); i++) {
rank += ","; rank += ",";
} }
Type elementType = type.GetElementType(); var elementType = type.GetElementType();
if (!elementType.IsArray) return elementType.PrettyName() + "[" + rank + "]"; if (!elementType.IsArray)
{
return elementType.PrettyName() + "[" + rank + "]";
}
else { else {
string s = elementType.PrettyName(); string s = elementType.PrettyName();
int i = s.IndexOf('['); var i = s.IndexOf('[');
return s.Substring(0, i) + "[" + rank + "]" + s.Substring(i); return s.Substring(0, i) + "[" + rank + "]" + s.Substring(i);
} }
} else return type.ToString(); } else
{
return type.ToString();
}
} }
/// <summary> Returns the default name for the node type. </summary> /// <summary> Returns the default name for the node type. </summary>
public static string NodeDefaultName(Type type) { public static string NodeDefaultName(Type type) {
string typeName = type.Name; var typeName = type.Name;
// Automatically remove redundant 'Node' postfix // Automatically remove redundant 'Node' postfix
if (typeName.EndsWith("Node")) typeName = typeName.Substring(0, typeName.LastIndexOf("Node")); if (typeName.EndsWith("Node"))
{
typeName = typeName.Substring(0, typeName.LastIndexOf("Node"));
}
typeName = UnityEditor.ObjectNames.NicifyVariableName(typeName); typeName = UnityEditor.ObjectNames.NicifyVariableName(typeName);
return typeName; return typeName;
} }
/// <summary> Returns the default creation path for the node type. </summary> /// <summary> Returns the default creation path for the node type. </summary>
public static string NodeDefaultPath(Type type) { public static string NodeDefaultPath(Type type) {
string typePath = type.ToString().Replace('.', '/'); var typePath = type.ToString().Replace('.', '/');
// Automatically remove redundant 'Node' postfix // Automatically remove redundant 'Node' postfix
if (typePath.EndsWith("Node")) typePath = typePath.Substring(0, typePath.LastIndexOf("Node")); if (typePath.EndsWith("Node"))
{
typePath = typePath.Substring(0, typePath.LastIndexOf("Node"));
}
typePath = UnityEditor.ObjectNames.NicifyVariableName(typePath); typePath = UnityEditor.ObjectNames.NicifyVariableName(typePath);
return typePath; return typePath;
} }
@ -236,12 +291,12 @@ namespace XNodeEditor {
/// <summary>Creates a new C# Class.</summary> /// <summary>Creates a new C# Class.</summary>
[MenuItem("Assets/Create/xNode/Node C# Script", false, 89)] [MenuItem("Assets/Create/xNode/Node C# Script", false, 89)]
private static void CreateNode() { private static void CreateNode() {
string[] guids = AssetDatabase.FindAssets("xNode_NodeTemplate.cs"); var guids = AssetDatabase.FindAssets("xNode_NodeTemplate.cs");
if (guids.Length == 0) { if (guids.Length == 0) {
Debug.LogWarning("xNode_NodeTemplate.cs.txt not found in asset database"); Debug.LogWarning("xNode_NodeTemplate.cs.txt not found in asset database");
return; return;
} }
string path = AssetDatabase.GUIDToAssetPath(guids[0]); var path = AssetDatabase.GUIDToAssetPath(guids[0]);
CreateFromTemplate( CreateFromTemplate(
"NewNode.cs", "NewNode.cs",
path path
@ -251,12 +306,12 @@ namespace XNodeEditor {
/// <summary>Creates a new C# Class.</summary> /// <summary>Creates a new C# Class.</summary>
[MenuItem("Assets/Create/xNode/NodeGraph C# Script", false, 89)] [MenuItem("Assets/Create/xNode/NodeGraph C# Script", false, 89)]
private static void CreateGraph() { private static void CreateGraph() {
string[] guids = AssetDatabase.FindAssets("xNode_NodeGraphTemplate.cs"); var guids = AssetDatabase.FindAssets("xNode_NodeGraphTemplate.cs");
if (guids.Length == 0) { if (guids.Length == 0) {
Debug.LogWarning("xNode_NodeGraphTemplate.cs.txt not found in asset database"); Debug.LogWarning("xNode_NodeGraphTemplate.cs.txt not found in asset database");
return; return;
} }
string path = AssetDatabase.GUIDToAssetPath(guids[0]); var path = AssetDatabase.GUIDToAssetPath(guids[0]);
CreateFromTemplate( CreateFromTemplate(
"NewNodeGraph.cs", "NewNodeGraph.cs",
path path
@ -276,21 +331,21 @@ namespace XNodeEditor {
/// Inherits from EndNameAction, must override EndNameAction.Action /// Inherits from EndNameAction, must override EndNameAction.Action
public class DoCreateCodeFile : UnityEditor.ProjectWindowCallback.EndNameEditAction { public class DoCreateCodeFile : UnityEditor.ProjectWindowCallback.EndNameEditAction {
public override void Action(int instanceId, string pathName, string resourceFile) { public override void Action(int instanceId, string pathName, string resourceFile) {
Object o = CreateScript(pathName, resourceFile); var o = CreateScript(pathName, resourceFile);
ProjectWindowUtil.ShowCreatedAsset(o); ProjectWindowUtil.ShowCreatedAsset(o);
} }
} }
/// <summary>Creates Script from Template's path.</summary> /// <summary>Creates Script from Template's path.</summary>
internal static UnityEngine.Object CreateScript(string pathName, string templatePath) { internal static UnityEngine.Object CreateScript(string pathName, string templatePath) {
string className = Path.GetFileNameWithoutExtension(pathName).Replace(" ", string.Empty); var className = Path.GetFileNameWithoutExtension(pathName).Replace(" ", string.Empty);
string templateText = string.Empty; var templateText = string.Empty;
UTF8Encoding encoding = new UTF8Encoding(true, false); var encoding = new UTF8Encoding(true, false);
if (File.Exists(templatePath)) { if (File.Exists(templatePath)) {
/// Read procedures. /// Read procedures.
StreamReader reader = new StreamReader(templatePath); var reader = new StreamReader(templatePath);
templateText = reader.ReadToEnd(); templateText = reader.ReadToEnd();
reader.Close(); reader.Close();
@ -302,7 +357,7 @@ namespace XNodeEditor {
/// Write procedures. /// Write procedures.
StreamWriter writer = new StreamWriter(Path.GetFullPath(pathName), false, encoding); var writer = new StreamWriter(Path.GetFullPath(pathName), false, encoding);
writer.Write(templateText); writer.Write(templateText);
writer.Close(); writer.Close();

View File

@ -18,7 +18,11 @@ namespace XNodeEditor {
private Func<bool> isDocked { private Func<bool> isDocked {
get { get {
if (_isDocked == null) _isDocked = this.GetIsDockedDelegate(); if (_isDocked == null)
{
_isDocked = this.GetIsDockedDelegate();
}
return _isDocked; return _isDocked;
} }
} }
@ -43,10 +47,10 @@ namespace XNodeEditor {
private void OnDisable() { private void OnDisable() {
// Cache portConnectionPoints before serialization starts // Cache portConnectionPoints before serialization starts
int count = portConnectionPoints.Count; var count = portConnectionPoints.Count;
_references = new NodePortReference[count]; _references = new NodePortReference[count];
_rects = new Rect[count]; _rects = new Rect[count];
int index = 0; var index = 0;
foreach (var portConnectionPoint in portConnectionPoints) { foreach (var portConnectionPoint in portConnectionPoints) {
_references[index] = new NodePortReference(portConnectionPoint.Key); _references[index] = new NodePortReference(portConnectionPoint.Key);
_rects[index] = portConnectionPoint.Value; _rects[index] = portConnectionPoint.Value;
@ -56,15 +60,17 @@ namespace XNodeEditor {
private void OnEnable() { private void OnEnable() {
// Reload portConnectionPoints if there are any // Reload portConnectionPoints if there are any
int length = _references.Length; var length = _references.Length;
if (length == _rects.Length) { if (length == _rects.Length) {
for (int i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
XNode.NodePort nodePort = _references[i].GetNodePort(); var nodePort = _references[i].GetNodePort();
if (nodePort != null) if (nodePort != null)
{
_portConnectionPoints.Add(nodePort, _rects[i]); _portConnectionPoints.Add(nodePort, _rects[i]);
} }
} }
} }
}
public Dictionary<XNode.Node, Vector2> nodeSizes { get { return _nodeSizes; } } public Dictionary<XNode.Node, Vector2> nodeSizes { get { return _nodeSizes; } }
private Dictionary<XNode.Node, Vector2> _nodeSizes = new Dictionary<XNode.Node, Vector2>(); private Dictionary<XNode.Node, Vector2> _nodeSizes = new Dictionary<XNode.Node, Vector2>();
@ -74,19 +80,25 @@ namespace XNodeEditor {
public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, NodeEditorPreferences.GetSettings().minZoom, NodeEditorPreferences.GetSettings().maxZoom); Repaint(); } } public float zoom { get { return _zoom; } set { _zoom = Mathf.Clamp(value, NodeEditorPreferences.GetSettings().minZoom, NodeEditorPreferences.GetSettings().maxZoom); Repaint(); } }
private float _zoom = 1; private float _zoom = 1;
void OnFocus() { private void OnFocus() {
current = this; current = this;
ValidateGraphEditor(); ValidateGraphEditor();
if (graphEditor != null) { if (graphEditor != null) {
graphEditor.OnWindowFocus(); graphEditor.OnWindowFocus();
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave)
{
AssetDatabase.SaveAssets();
}
} }
dragThreshold = Math.Max(1f, Screen.width / 1000f); dragThreshold = Math.Max(1f, Screen.width / 1000f);
} }
void OnLostFocus() { private void OnLostFocus() {
if (graphEditor != null) graphEditor.OnWindowFocusLost(); if (graphEditor != null)
{
graphEditor.OnWindowFocusLost();
}
} }
[InitializeOnLoadMethod] [InitializeOnLoadMethod]
@ -97,15 +109,18 @@ namespace XNodeEditor {
/// <summary> Handle Selection Change events</summary> /// <summary> Handle Selection Change events</summary>
private static void OnSelectionChanged() { private static void OnSelectionChanged() {
XNode.NodeGraph nodeGraph = Selection.activeObject as XNode.NodeGraph; var nodeGraph = Selection.activeObject as XNode.NodeGraph;
if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) { if (nodeGraph && !AssetDatabase.Contains(nodeGraph)) {
if (NodeEditorPreferences.GetSettings().openOnCreate) Open(nodeGraph); if (NodeEditorPreferences.GetSettings().openOnCreate)
{
Open(nodeGraph);
}
} }
} }
/// <summary> Make sure the graph editor is assigned and to the right object </summary> /// <summary> Make sure the graph editor is assigned and to the right object </summary>
private void ValidateGraphEditor() { private void ValidateGraphEditor() {
NodeGraphEditor graphEditor = NodeGraphEditor.GetEditor(graph, this); var graphEditor = NodeGraphEditor.GetEditor(graph, this);
if (this.graphEditor != graphEditor && graphEditor != null) { if (this.graphEditor != graphEditor && graphEditor != null) {
this.graphEditor = graphEditor; this.graphEditor = graphEditor;
graphEditor.OnOpen(); graphEditor.OnOpen();
@ -114,7 +129,7 @@ namespace XNodeEditor {
/// <summary> Create editor window </summary> /// <summary> Create editor window </summary>
public static NodeEditorWindow Init() { public static NodeEditorWindow Init() {
NodeEditorWindow w = CreateInstance<NodeEditorWindow>(); var w = CreateInstance<NodeEditorWindow>();
w.titleContent = new GUIContent("xNode"); w.titleContent = new GUIContent("xNode");
w.wantsMouseMove = true; w.wantsMouseMove = true;
w.Show(); w.Show();
@ -124,19 +139,35 @@ namespace XNodeEditor {
public void Save() { public void Save() {
if (AssetDatabase.Contains(graph)) { if (AssetDatabase.Contains(graph)) {
EditorUtility.SetDirty(graph); EditorUtility.SetDirty(graph);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave)
} else SaveAs(); {
AssetDatabase.SaveAssets();
}
} else
{
SaveAs();
}
} }
public void SaveAs() { public void SaveAs() {
string path = EditorUtility.SaveFilePanelInProject("Save NodeGraph", "NewNodeGraph", "asset", ""); var path = EditorUtility.SaveFilePanelInProject("Save NodeGraph", "NewNodeGraph", "asset", "");
if (string.IsNullOrEmpty(path)) return; if (string.IsNullOrEmpty(path))
{
return;
}
else { else {
XNode.NodeGraph existingGraph = AssetDatabase.LoadAssetAtPath<XNode.NodeGraph>(path); var existingGraph = AssetDatabase.LoadAssetAtPath<XNode.NodeGraph>(path);
if (existingGraph != null) AssetDatabase.DeleteAsset(path); if (existingGraph != null)
{
AssetDatabase.DeleteAsset(path);
}
AssetDatabase.CreateAsset(graph, path); AssetDatabase.CreateAsset(graph, path);
EditorUtility.SetDirty(graph); EditorUtility.SetDirty(graph);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave)
{
AssetDatabase.SaveAssets();
}
} }
} }
@ -164,30 +195,33 @@ namespace XNodeEditor {
} }
public Vector2 GridToWindowPositionNoClipped(Vector2 gridPosition) { public Vector2 GridToWindowPositionNoClipped(Vector2 gridPosition) {
Vector2 center = position.size * 0.5f; var center = position.size * 0.5f;
// UI Sharpness complete fix - Round final offset not panOffset // UI Sharpness complete fix - Round final offset not panOffset
float xOffset = Mathf.Round(center.x * zoom + (panOffset.x + gridPosition.x)); var xOffset = Mathf.Round(center.x * zoom + (panOffset.x + gridPosition.x));
float yOffset = Mathf.Round(center.y * zoom + (panOffset.y + gridPosition.y)); var yOffset = Mathf.Round(center.y * zoom + (panOffset.y + gridPosition.y));
return new Vector2(xOffset, yOffset); return new Vector2(xOffset, yOffset);
} }
public void SelectNode(XNode.Node node, bool add) { public void SelectNode(XNode.Node node, bool add) {
if (add) { if (add) {
List<Object> selection = new List<Object>(Selection.objects); var selection = new List<Object>(Selection.objects);
selection.Add(node); selection.Add(node);
Selection.objects = selection.ToArray(); Selection.objects = selection.ToArray();
} else Selection.objects = new Object[] { node }; } else
{
Selection.objects = new Object[] { node };
}
} }
public void DeselectNode(XNode.Node node) { public void DeselectNode(XNode.Node node) {
List<Object> selection = new List<Object>(Selection.objects); var selection = new List<Object>(Selection.objects);
selection.Remove(node); selection.Remove(node);
Selection.objects = selection.ToArray(); Selection.objects = selection.ToArray();
} }
[OnOpenAsset(0)] [OnOpenAsset(0)]
public static bool OnOpen(int instanceID, int line) { public static bool OnOpen(int instanceID, int line) {
XNode.NodeGraph nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as XNode.NodeGraph; var nodeGraph = EditorUtility.InstanceIDToObject(instanceID) as XNode.NodeGraph;
if (nodeGraph != null) { if (nodeGraph != null) {
Open(nodeGraph); Open(nodeGraph);
return true; return true;
@ -197,9 +231,12 @@ namespace XNodeEditor {
/// <summary>Open the provided graph in the NodeEditor</summary> /// <summary>Open the provided graph in the NodeEditor</summary>
public static NodeEditorWindow Open(XNode.NodeGraph graph) { public static NodeEditorWindow Open(XNode.NodeGraph graph) {
if (!graph) return null; if (!graph)
{
return null;
}
NodeEditorWindow w = GetWindow(typeof(NodeEditorWindow), false, "xNode", true) as NodeEditorWindow; var w = GetWindow(typeof(NodeEditorWindow), false, "xNode", true) as NodeEditorWindow;
w.wantsMouseMove = true; w.wantsMouseMove = true;
w.graph = graph; w.graph = graph;
return w; return w;
@ -207,8 +244,8 @@ namespace XNodeEditor {
/// <summary> Repaint all open NodeEditorWindows. </summary> /// <summary> Repaint all open NodeEditorWindows. </summary>
public static void RepaintAll() { public static void RepaintAll() {
NodeEditorWindow[] windows = Resources.FindObjectsOfTypeAll<NodeEditorWindow>(); var windows = Resources.FindObjectsOfTypeAll<NodeEditorWindow>();
for (int i = 0; i < windows.Length; i++) { for (var i = 0; i < windows.Length; i++) {
windows[i].Repaint(); windows[i].Repaint();
} }
} }

View File

@ -44,20 +44,28 @@ namespace XNodeEditor {
//Check if type has the CreateNodeMenuAttribute //Check if type has the CreateNodeMenuAttribute
XNode.Node.CreateNodeMenuAttribute attrib; XNode.Node.CreateNodeMenuAttribute attrib;
if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path
{
return attrib.menuName; return attrib.menuName;
}
else // Return generated path else // Return generated path
{
return NodeEditorUtilities.NodeDefaultPath(type); return NodeEditorUtilities.NodeDefaultPath(type);
} }
}
/// <summary> The order by which the menu items are displayed. </summary> /// <summary> The order by which the menu items are displayed. </summary>
public virtual int GetNodeMenuOrder(Type type) { public virtual int GetNodeMenuOrder(Type type) {
//Check if type has the CreateNodeMenuAttribute //Check if type has the CreateNodeMenuAttribute
XNode.Node.CreateNodeMenuAttribute attrib; XNode.Node.CreateNodeMenuAttribute attrib;
if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path if (NodeEditorUtilities.GetAttrib(type, out attrib)) // Return custom path
{
return attrib.order; return attrib.order;
}
else else
{
return 0; return 0;
} }
}
/// <summary> /// <summary>
/// Called before connecting two ports in the graph view to see if the output port is compatible with the input port /// Called before connecting two ports in the graph view to see if the output port is compatible with the input port
@ -74,7 +82,7 @@ namespace XNodeEditor {
/// <param name="compatibleType">Use it to filter only nodes with ports value type, compatible with this type</param> /// <param name="compatibleType">Use it to filter only nodes with ports value type, compatible with this type</param>
/// <param name="direction">Direction of the compatiblity</param> /// <param name="direction">Direction of the compatiblity</param>
public virtual void AddContextMenuItems(GenericMenu menu, Type compatibleType = null, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) { public virtual void AddContextMenuItems(GenericMenu menu, Type compatibleType = null, XNode.NodePort.IO direction = XNode.NodePort.IO.Input) {
Vector2 pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition); var pos = NodeEditorWindow.current.WindowToGridPosition(Event.current.mousePosition);
Type[] nodeTypes; Type[] nodeTypes;
@ -84,31 +92,53 @@ namespace XNodeEditor {
nodeTypes = NodeEditorReflection.nodeTypes.OrderBy(GetNodeMenuOrder).ToArray(); nodeTypes = NodeEditorReflection.nodeTypes.OrderBy(GetNodeMenuOrder).ToArray();
} }
for (int i = 0; i < nodeTypes.Length; i++) { for (var i = 0; i < nodeTypes.Length; i++) {
Type type = nodeTypes[i]; var type = nodeTypes[i];
//Get node context menu path //Get node context menu path
string path = GetNodeMenuName(type); var path = GetNodeMenuName(type);
if (string.IsNullOrEmpty(path)) continue; if (string.IsNullOrEmpty(path))
{
continue;
}
// Check if user is allowed to add more of given node type // Check if user is allowed to add more of given node type
XNode.Node.DisallowMultipleNodesAttribute disallowAttrib; XNode.Node.DisallowMultipleNodesAttribute disallowAttrib;
bool disallowed = false; var disallowed = false;
if (NodeEditorUtilities.GetAttrib(type, out disallowAttrib)) { if (NodeEditorUtilities.GetAttrib(type, out disallowAttrib)) {
int typeCount = target.nodes.Count(x => x.GetType() == type); var typeCount = target.nodes.Count(x => x.GetType() == type);
if (typeCount >= disallowAttrib.max) disallowed = true; if (typeCount >= disallowAttrib.max)
{
disallowed = true;
}
} }
// Add node entry to context menu // Add node entry to context menu
if (disallowed) menu.AddItem(new GUIContent(path), false, null); if (disallowed)
else menu.AddItem(new GUIContent(path), false, () => { {
XNode.Node node = CreateNode(type, pos); menu.AddItem(new GUIContent(path), false, null);
if (node != null) NodeEditorWindow.current.AutoConnect(node); // handle null nodes to avoid nullref exceptions }
else
{
menu.AddItem(new GUIContent(path), false, () => {
var node = CreateNode(type, pos);
if (node != null)
{
NodeEditorWindow.current.AutoConnect(node); // handle null nodes to avoid nullref exceptions
}
}); });
} }
}
menu.AddSeparator(""); menu.AddSeparator("");
if (NodeEditorWindow.copyBuffer != null && NodeEditorWindow.copyBuffer.Length > 0) menu.AddItem(new GUIContent("Paste"), false, () => NodeEditorWindow.current.PasteNodes(pos)); if (NodeEditorWindow.copyBuffer != null && NodeEditorWindow.copyBuffer.Length > 0)
else menu.AddDisabledItem(new GUIContent("Paste")); {
menu.AddItem(new GUIContent("Paste"), false, () => NodeEditorWindow.current.PasteNodes(pos));
}
else
{
menu.AddDisabledItem(new GUIContent("Paste"));
}
menu.AddItem(new GUIContent("Preferences"), false, () => NodeEditorReflection.OpenPreferences()); menu.AddItem(new GUIContent("Preferences"), false, () => NodeEditorReflection.OpenPreferences());
menu.AddCustomContextMenuItems(target); menu.AddCustomContextMenuItems(target);
} }
@ -117,11 +147,11 @@ namespace XNodeEditor {
/// <param name="output"> The output this noodle comes from. Never null. </param> /// <param name="output"> The output this noodle comes from. Never null. </param>
/// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param> /// <param name="input"> The output this noodle comes from. Can be null if we are dragging the noodle. </param>
public virtual Gradient GetNoodleGradient(XNode.NodePort output, XNode.NodePort input) { public virtual Gradient GetNoodleGradient(XNode.NodePort output, XNode.NodePort input) {
Gradient grad = new Gradient(); var grad = new Gradient();
// If dragging the noodle, draw solid, slightly transparent // If dragging the noodle, draw solid, slightly transparent
if (input == null) { if (input == null) {
Color a = GetTypeColor(output.ValueType); var a = GetTypeColor(output.ValueType);
grad.SetKeys( grad.SetKeys(
new GradientColorKey[] { new GradientColorKey(a, 0f) }, new GradientColorKey[] { new GradientColorKey(a, 0f) },
new GradientAlphaKey[] { new GradientAlphaKey(0.6f, 0f) } new GradientAlphaKey[] { new GradientAlphaKey(0.6f, 0f) }
@ -129,8 +159,8 @@ namespace XNodeEditor {
} }
// If normal, draw gradient fading from one input color to the other // If normal, draw gradient fading from one input color to the other
else { else {
Color a = GetTypeColor(output.ValueType); var a = GetTypeColor(output.ValueType);
Color b = GetTypeColor(input.ValueType); var b = GetTypeColor(input.ValueType);
// If any port is hovered, tint white // If any port is hovered, tint white
if (window.hoveredPort == output || window.hoveredPort == input) { if (window.hoveredPort == output || window.hoveredPort == input) {
a = Color.Lerp(a, Color.white, 0.8f); a = Color.Lerp(a, Color.white, 0.8f);
@ -176,7 +206,9 @@ namespace XNodeEditor {
/// <returns></returns> /// <returns></returns>
public virtual GUIStyle GetPortStyle(XNode.NodePort port) { public virtual GUIStyle GetPortStyle(XNode.NodePort port) {
if (port.direction == XNode.NodePort.IO.Input) if (port.direction == XNode.NodePort.IO.Input)
{
return NodeEditorResources.styles.inputPort; return NodeEditorResources.styles.inputPort;
}
return NodeEditorResources.styles.outputPort; return NodeEditorResources.styles.outputPort;
} }
@ -194,11 +226,11 @@ namespace XNodeEditor {
/// <summary> Override to display custom tooltips </summary> /// <summary> Override to display custom tooltips </summary>
public virtual string GetPortTooltip(XNode.NodePort port) { public virtual string GetPortTooltip(XNode.NodePort port) {
Type portType = port.ValueType; var portType = port.ValueType;
string tooltip = ""; var tooltip = "";
tooltip = portType.PrettyName(); tooltip = portType.PrettyName();
if (port.IsOutput) { if (port.IsOutput) {
object obj = port.node.GetValue(port); var obj = port.node.GetValue(port);
tooltip += " = " + (obj != null ? obj.ToString() : "null"); tooltip += " = " + (obj != null ? obj.ToString() : "null");
} }
return tooltip; return tooltip;
@ -206,19 +238,38 @@ namespace XNodeEditor {
/// <summary> Deal with objects dropped into the graph through DragAndDrop </summary> /// <summary> Deal with objects dropped into the graph through DragAndDrop </summary>
public virtual void OnDropObjects(UnityEngine.Object[] objects) { public virtual void OnDropObjects(UnityEngine.Object[] objects) {
if (GetType() != typeof(NodeGraphEditor)) Debug.Log("No OnDropObjects override defined for " + GetType()); if (GetType() != typeof(NodeGraphEditor))
{
Debug.Log("No OnDropObjects override defined for " + GetType());
}
} }
/// <summary> Create a node and save it in the graph asset </summary> /// <summary> Create a node and save it in the graph asset </summary>
public virtual XNode.Node CreateNode(Type type, Vector2 position) { public virtual XNode.Node CreateNode(Type type, Vector2 position) {
Undo.RecordObject(target, "Create Node"); Undo.RecordObject(target, "Create Node");
XNode.Node node = target.AddNode(type); var node = target.AddNode(type);
if (node == null) return null; // handle null nodes to avoid nullref exceptions if (node == null)
{
return null; // handle null nodes to avoid nullref exceptions
}
Undo.RegisterCreatedObjectUndo(node, "Create Node"); Undo.RegisterCreatedObjectUndo(node, "Create Node");
node.position = position; node.position = position;
if (node.name == null || node.name.Trim() == "") node.name = NodeEditorUtilities.NodeDefaultName(type); if (node.name == null || node.name.Trim() == "")
if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) AssetDatabase.AddObjectToAsset(node, target); {
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); node.name = NodeEditorUtilities.NodeDefaultName(type);
}
if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target)))
{
AssetDatabase.AddObjectToAsset(node, target);
}
if (NodeEditorPreferences.GetSettings().autoSave)
{
AssetDatabase.SaveAssets();
}
NodeEditorWindow.RepaintAll(); NodeEditorWindow.RepaintAll();
return node; return node;
} }
@ -226,19 +277,27 @@ namespace XNodeEditor {
/// <summary> Creates a copy of the original node in the graph </summary> /// <summary> Creates a copy of the original node in the graph </summary>
public virtual XNode.Node CopyNode(XNode.Node original) { public virtual XNode.Node CopyNode(XNode.Node original) {
Undo.RecordObject(target, "Duplicate Node"); Undo.RecordObject(target, "Duplicate Node");
XNode.Node node = target.CopyNode(original); var node = target.CopyNode(original);
Undo.RegisterCreatedObjectUndo(node, "Duplicate Node"); Undo.RegisterCreatedObjectUndo(node, "Duplicate Node");
node.name = original.name; node.name = original.name;
if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target))) AssetDatabase.AddObjectToAsset(node, target); if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(target)))
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); {
AssetDatabase.AddObjectToAsset(node, target);
}
if (NodeEditorPreferences.GetSettings().autoSave)
{
AssetDatabase.SaveAssets();
}
return node; return node;
} }
/// <summary> Return false for nodes that can't be removed </summary> /// <summary> Return false for nodes that can't be removed </summary>
public virtual bool CanRemove(XNode.Node node) { public virtual bool CanRemove(XNode.Node node) {
// Check graph attributes to see if this node is required // Check graph attributes to see if this node is required
Type graphType = target.GetType(); var graphType = target.GetType();
XNode.NodeGraph.RequireNodeAttribute[] attribs = Array.ConvertAll( var attribs = Array.ConvertAll(
graphType.GetCustomAttributes(typeof(XNode.NodeGraph.RequireNodeAttribute), true), x => x as XNode.NodeGraph.RequireNodeAttribute); graphType.GetCustomAttributes(typeof(XNode.NodeGraph.RequireNodeAttribute), true), x => x as XNode.NodeGraph.RequireNodeAttribute);
if (attribs.Any(x => x.Requires(node.GetType()))) { if (attribs.Any(x => x.Requires(node.GetType()))) {
if (target.nodes.Count(x => x.GetType() == node.GetType()) <= 1) { if (target.nodes.Count(x => x.GetType() == node.GetType()) <= 1) {
@ -250,17 +309,26 @@ namespace XNodeEditor {
/// <summary> Safely remove a node and all its connections. </summary> /// <summary> Safely remove a node and all its connections. </summary>
public virtual void RemoveNode(XNode.Node node) { public virtual void RemoveNode(XNode.Node node) {
if (!CanRemove(node)) return; if (!CanRemove(node))
{
return;
}
// Remove the node // Remove the node
Undo.RecordObject(node, "Delete Node"); Undo.RecordObject(node, "Delete Node");
Undo.RecordObject(target, "Delete Node"); Undo.RecordObject(target, "Delete Node");
foreach (var port in node.Ports) foreach (var port in node.Ports)
foreach (var conn in port.GetConnections()) foreach (var conn in port.GetConnections())
{
Undo.RecordObject(conn.node, "Delete Node"); Undo.RecordObject(conn.node, "Delete Node");
}
target.RemoveNode(node); target.RemoveNode(node);
Undo.DestroyObjectImmediate(node); Undo.DestroyObjectImmediate(node);
if (NodeEditorPreferences.GetSettings().autoSave) AssetDatabase.SaveAssets(); if (NodeEditorPreferences.GetSettings().autoSave)
{
AssetDatabase.SaveAssets();
}
} }
[AttributeUsage(AttributeTargets.Class)] [AttributeUsage(AttributeTargets.Class)]

View File

@ -0,0 +1,69 @@
using System;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEditor.Experimental.AssetImporters;
using UnityEngine;
using XNode;
namespace XNodeEditor {
/// <summary> Deals with modified assets </summary>
internal class NodeGraphImporter : AssetPostprocessor {
private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths) {
foreach (var path in importedAssets) {
// Skip processing anything without the .asset extension
if (Path.GetExtension(path) != ".asset")
{
continue;
}
// Get the object that is requested for deletion
var graph = AssetDatabase.LoadAssetAtPath<NodeGraph>(path);
if (graph == null)
{
continue;
}
// Get attributes
var graphType = graph.GetType();
var attribs = Array.ConvertAll(
graphType.GetCustomAttributes(typeof(NodeGraph.RequireNodeAttribute), true), x => x as NodeGraph.RequireNodeAttribute);
var position = Vector2.zero;
foreach (var attrib in attribs) {
if (attrib.type0 != null)
{
AddRequired(graph, attrib.type0, ref position);
}
if (attrib.type1 != null)
{
AddRequired(graph, attrib.type1, ref position);
}
if (attrib.type2 != null)
{
AddRequired(graph, attrib.type2, ref position);
}
}
}
}
private static void AddRequired(NodeGraph graph, Type type, ref Vector2 position) {
if (!graph.nodes.Any(x => x.GetType() == type)) {
var node = graph.AddNode(type);
node.position = position;
position.x += 200;
if (node.name == null || node.name.Trim() == "")
{
node.name = NodeEditorUtilities.NodeDefaultName(type);
}
if (!string.IsNullOrEmpty(AssetDatabase.GetAssetPath(graph)))
{
AssetDatabase.AddObjectToAsset(node, graph);
}
}
}
}
}

View File

@ -14,8 +14,12 @@ namespace XNodeEditor {
/// <summary> Show a rename popup for an asset at mouse position. Will trigger reimport of the asset on apply. /// <summary> Show a rename popup for an asset at mouse position. Will trigger reimport of the asset on apply.
public static RenamePopup Show(Object target, float width = 200) { public static RenamePopup Show(Object target, float width = 200) {
RenamePopup window = EditorWindow.GetWindow<RenamePopup>(true, "Rename " + target.name, true); var window = EditorWindow.GetWindow<RenamePopup>(true, "Rename " + target.name, true);
if (current != null) current.Close(); if (current != null)
{
current.Close();
}
current = window; current = window;
window.target = target; window.target = target;
window.input = target.name; window.input = target.name;
@ -26,9 +30,13 @@ namespace XNodeEditor {
} }
private void UpdatePositionToMouse() { private void UpdatePositionToMouse() {
if (Event.current == null) return; if (Event.current == null)
{
return;
}
Vector3 mousePoint = GUIUtility.GUIToScreenPoint(Event.current.mousePosition); Vector3 mousePoint = GUIUtility.GUIToScreenPoint(Event.current.mousePosition);
Rect pos = position; var pos = position;
pos.x = mousePoint.x - position.width * 0.5f; pos.x = mousePoint.x - position.width * 0.5f;
pos.y = mousePoint.y - 10; pos.y = mousePoint.y - 10;
position = pos; position = pos;
@ -47,7 +55,7 @@ namespace XNodeEditor {
GUI.SetNextControlName(inputControlName); GUI.SetNextControlName(inputControlName);
input = EditorGUILayout.TextField(input); input = EditorGUILayout.TextField(input);
EditorGUI.FocusTextInControl(inputControlName); EditorGUI.FocusTextInControl(inputControlName);
Event e = Event.current; var e = Event.current;
// If input is empty, revert name to default instead // If input is empty, revert name to default instead
if (input == null || input.Trim() == "") { if (input == null || input.Trim() == "") {
if (GUILayout.Button("Revert to default") || (e.isKey && e.keyCode == KeyCode.Return)) { if (GUILayout.Button("Revert to default") || (e.isKey && e.keyCode == KeyCode.Return)) {

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -16,10 +16,10 @@ namespace XNodeEditor {
if (sceneGraph.graph == null) { if (sceneGraph.graph == null) {
if (GUILayout.Button("New graph", GUILayout.Height(40))) { if (GUILayout.Button("New graph", GUILayout.Height(40))) {
if (graphType == null) { if (graphType == null) {
Type[] graphTypes = NodeEditorReflection.GetDerivedTypes(typeof(NodeGraph)); var graphTypes = NodeEditorReflection.GetDerivedTypes(typeof(NodeGraph));
GenericMenu menu = new GenericMenu(); var menu = new GenericMenu();
for (int i = 0; i < graphTypes.Length; i++) { for (var i = 0; i < graphTypes.Length; i++) {
Type graphType = graphTypes[i]; var graphType = graphTypes[i];
menu.AddItem(new GUIContent(graphType.Name), false, () => CreateGraph(graphType)); menu.AddItem(new GUIContent(graphType.Name), false, () => CreateGraph(graphType));
} }
menu.ShowAsContext(); menu.ShowAsContext();
@ -58,11 +58,11 @@ namespace XNodeEditor {
private void OnEnable() { private void OnEnable() {
sceneGraph = target as SceneGraph; sceneGraph = target as SceneGraph;
Type sceneGraphType = sceneGraph.GetType(); var sceneGraphType = sceneGraph.GetType();
if (sceneGraphType == typeof(SceneGraph)) { if (sceneGraphType == typeof(SceneGraph)) {
graphType = null; graphType = null;
} else { } else {
Type baseType = sceneGraphType.BaseType; var baseType = sceneGraphType.BaseType;
if (baseType.IsGenericType) { if (baseType.IsGenericType) {
graphType = sceneGraphType = baseType.GetGenericArguments() [0]; graphType = sceneGraphType = baseType.GetGenericArguments() [0];
} }

View File

@ -97,17 +97,41 @@ namespace XNode {
#endregion #endregion
/// <summary> Iterate over all ports on this node. </summary> /// <summary> Iterate over all ports on this node. </summary>
public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values) yield return port; } } public IEnumerable<NodePort> Ports { get { foreach (NodePort port in ports.Values)
{
yield return port;
}
} }
/// <summary> Iterate over all outputs on this node. </summary> /// <summary> Iterate over all outputs on this node. </summary>
public IEnumerable<NodePort> Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput) yield return port; } } } public IEnumerable<NodePort> Outputs { get { foreach (NodePort port in Ports) { if (port.IsOutput)
{
yield return port;
}
} } }
/// <summary> Iterate over all inputs on this node. </summary> /// <summary> Iterate over all inputs on this node. </summary>
public IEnumerable<NodePort> Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput) yield return port; } } } public IEnumerable<NodePort> Inputs { get { foreach (NodePort port in Ports) { if (port.IsInput)
{
yield return port;
}
} } }
/// <summary> Iterate over all dynamic ports on this node. </summary> /// <summary> Iterate over all dynamic ports on this node. </summary>
public IEnumerable<NodePort> DynamicPorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic) yield return port; } } } public IEnumerable<NodePort> DynamicPorts { get { foreach (NodePort port in Ports) { if (port.IsDynamic)
{
yield return port;
}
} } }
/// <summary> Iterate over all dynamic outputs on this node. </summary> /// <summary> Iterate over all dynamic outputs on this node. </summary>
public IEnumerable<NodePort> DynamicOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput) yield return port; } } } public IEnumerable<NodePort> DynamicOutputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsOutput)
{
yield return port;
}
} } }
/// <summary> Iterate over all dynamic inputs on this node. </summary> /// <summary> Iterate over all dynamic inputs on this node. </summary>
public IEnumerable<NodePort> DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput) yield return port; } } } public IEnumerable<NodePort> DynamicInputs { get { foreach (NodePort port in Ports) { if (port.IsDynamic && port.IsInput)
{
yield return port;
}
} } }
/// <summary> Parent <see cref="NodeGraph"/> </summary> /// <summary> Parent <see cref="NodeGraph"/> </summary>
[SerializeField] public NodeGraph graph; [SerializeField] public NodeGraph graph;
/// <summary> Position on the <see cref="NodeGraph"/> </summary> /// <summary> Position on the <see cref="NodeGraph"/> </summary>
@ -119,7 +143,11 @@ namespace XNode {
public static NodeGraph graphHotfix; public static NodeGraph graphHotfix;
protected void OnEnable() { protected void OnEnable() {
if (graphHotfix != null) graph = graphHotfix; if (graphHotfix != null)
{
graph = graphHotfix;
}
graphHotfix = null; graphHotfix = null;
UpdatePorts(); UpdatePorts();
Init(); Init();
@ -135,7 +163,10 @@ namespace XNode {
/// <summary> Checks all connections for invalid references, and removes them. </summary> /// <summary> Checks all connections for invalid references, and removes them. </summary>
public void VerifyConnections() { public void VerifyConnections() {
foreach (NodePort port in Ports) port.VerifyConnections(); foreach (NodePort port in Ports)
{
port.VerifyConnections();
}
} }
#region Dynamic Ports #region Dynamic Ports
@ -159,8 +190,11 @@ namespace XNode {
private NodePort AddDynamicPort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) { private NodePort AddDynamicPort(Type type, NodePort.IO direction, Node.ConnectionType connectionType = Node.ConnectionType.Multiple, Node.TypeConstraint typeConstraint = TypeConstraint.None, string fieldName = null) {
if (fieldName == null) { if (fieldName == null) {
fieldName = "dynamicInput_0"; fieldName = "dynamicInput_0";
int i = 0; var i = 0;
while (HasPort(fieldName)) fieldName = "dynamicInput_" + (++i); while (HasPort(fieldName))
{
fieldName = "dynamicInput_" + (++i);
}
} else if (HasPort(fieldName)) { } else if (HasPort(fieldName)) {
Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this); Debug.LogWarning("Port '" + fieldName + "' already exists in " + name, this);
return ports[fieldName]; return ports[fieldName];
@ -173,14 +207,25 @@ namespace XNode {
/// <summary> Remove an dynamic port from the node </summary> /// <summary> Remove an dynamic port from the node </summary>
public void RemoveDynamicPort(string fieldName) { public void RemoveDynamicPort(string fieldName) {
NodePort dynamicPort = GetPort(fieldName); NodePort dynamicPort = GetPort(fieldName);
if (dynamicPort == null) throw new ArgumentException("port " + fieldName + " doesn't exist"); if (dynamicPort == null)
{
throw new ArgumentException("port " + fieldName + " doesn't exist");
}
RemoveDynamicPort(GetPort(fieldName)); RemoveDynamicPort(GetPort(fieldName));
} }
/// <summary> Remove an dynamic port from the node </summary> /// <summary> Remove an dynamic port from the node </summary>
public void RemoveDynamicPort(NodePort port) { public void RemoveDynamicPort(NodePort port) {
if (port == null) throw new ArgumentNullException("port"); if (port == null)
else if (port.IsStatic) throw new ArgumentException("cannot remove static port"); {
throw new ArgumentNullException("port");
}
else if (port.IsStatic)
{
throw new ArgumentException("cannot remove static port");
}
port.ClearConnections(); port.ClearConnections();
ports.Remove(port.fieldName); ports.Remove(port.fieldName);
} }
@ -199,22 +244,40 @@ namespace XNode {
/// <summary> Returns output port which matches fieldName </summary> /// <summary> Returns output port which matches fieldName </summary>
public NodePort GetOutputPort(string fieldName) { public NodePort GetOutputPort(string fieldName) {
NodePort port = GetPort(fieldName); NodePort port = GetPort(fieldName);
if (port == null || port.direction != NodePort.IO.Output) return null; if (port == null || port.direction != NodePort.IO.Output)
else return port; {
return null;
}
else
{
return port;
}
} }
/// <summary> Returns input port which matches fieldName </summary> /// <summary> Returns input port which matches fieldName </summary>
public NodePort GetInputPort(string fieldName) { public NodePort GetInputPort(string fieldName) {
NodePort port = GetPort(fieldName); NodePort port = GetPort(fieldName);
if (port == null || port.direction != NodePort.IO.Input) return null; if (port == null || port.direction != NodePort.IO.Input)
else return port; {
return null;
}
else
{
return port;
}
} }
/// <summary> Returns port which matches fieldName </summary> /// <summary> Returns port which matches fieldName </summary>
public NodePort GetPort(string fieldName) { public NodePort GetPort(string fieldName) {
NodePort port; NodePort port;
if (ports.TryGetValue(fieldName, out port)) return port; if (ports.TryGetValue(fieldName, out port))
else return null; {
return port;
}
else
{
return null;
}
} }
public bool HasPort(string fieldName) { public bool HasPort(string fieldName) {
@ -228,8 +291,14 @@ namespace XNode {
/// <param name="fallback">If no ports are connected, this value will be returned</param> /// <param name="fallback">If no ports are connected, this value will be returned</param>
public T GetInputValue<T>(string fieldName, T fallback = default(T)) { public T GetInputValue<T>(string fieldName, T fallback = default(T)) {
NodePort port = GetPort(fieldName); NodePort port = GetPort(fieldName);
if (port != null && port.IsConnected) return port.GetInputValue<T>(); if (port != null && port.IsConnected)
else return fallback; {
return port.GetInputValue<T>();
}
else
{
return fallback;
}
} }
/// <summary> Return all input values for a specified port. Returns fallback value if no ports are connected </summary> /// <summary> Return all input values for a specified port. Returns fallback value if no ports are connected </summary>
@ -237,8 +306,14 @@ namespace XNode {
/// <param name="fallback">If no ports are connected, this value will be returned</param> /// <param name="fallback">If no ports are connected, this value will be returned</param>
public T[] GetInputValues<T>(string fieldName, params T[] fallback) { public T[] GetInputValues<T>(string fieldName, params T[] fallback) {
NodePort port = GetPort(fieldName); NodePort port = GetPort(fieldName);
if (port != null && port.IsConnected) return port.GetInputValues<T>(); if (port != null && port.IsConnected)
else return fallback; {
return port.GetInputValues<T>();
}
else
{
return fallback;
}
} }
/// <summary> Returns a value based on requested port output. Should be overridden in all derived nodes with outputs. </summary> /// <summary> Returns a value based on requested port output. Should be overridden in all derived nodes with outputs. </summary>
@ -259,7 +334,10 @@ namespace XNode {
/// <summary> Disconnect everything from this node </summary> /// <summary> Disconnect everything from this node </summary>
public void ClearConnections() { public void ClearConnections() {
foreach (NodePort port in Ports) port.ClearConnections(); foreach (NodePort port in Ports)
{
port.ClearConnections();
}
} }
#region Attributes #region Attributes
@ -411,11 +489,15 @@ namespace XNode {
#endif #endif
if (keys.Count != values.Count) if (keys.Count != values.Count)
{
throw new System.Exception("there are " + keys.Count + " keys and " + values.Count + " values after deserialization. Make sure that both key and value types are serializable."); throw new System.Exception("there are " + keys.Count + " keys and " + values.Count + " values after deserialization. Make sure that both key and value types are serializable.");
}
for (int i = 0; i < keys.Count; i++) for (var i = 0; i < keys.Count; i++)
{
this.Add(keys[i], values[i]); this.Add(keys[i], values[i]);
} }
} }
} }
} }
}

View File

@ -12,7 +12,10 @@ namespace XNode {
private static bool Initialized { get { return portDataCache != null; } } private static bool Initialized { get { return portDataCache != null; } }
public static string GetTypeQualifiedName(System.Type type) { public static string GetTypeQualifiedName(System.Type type) {
if(typeQualifiedNameCache == null) typeQualifiedNameCache = new Dictionary<System.Type, string>(); if(typeQualifiedNameCache == null)
{
typeQualifiedNameCache = new Dictionary<System.Type, string>();
}
string name; string name;
if (!typeQualifiedNameCache.TryGetValue(type, out name)) { if (!typeQualifiedNameCache.TryGetValue(type, out name)) {
@ -24,13 +27,19 @@ namespace XNode {
/// <summary> Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. </summary> /// <summary> Update static ports and dynamic ports managed by DynamicPortLists to reflect class fields. </summary>
public static void UpdatePorts(Node node, Dictionary<string, NodePort> ports) { public static void UpdatePorts(Node node, Dictionary<string, NodePort> ports) {
if (!Initialized) BuildCache(); if (!Initialized)
{
BuildCache();
}
Dictionary<string, List<NodePort>> removedPorts = new Dictionary<string, List<NodePort>>(); Dictionary<string, List<NodePort>> removedPorts = new Dictionary<string, List<NodePort>>();
System.Type nodeType = node.GetType(); System.Type nodeType = node.GetType();
Dictionary<string, string> formerlySerializedAs = null; Dictionary<string, string> formerlySerializedAs = null;
if (formerlySerializedAsCache != null) formerlySerializedAsCache.TryGetValue(nodeType, out formerlySerializedAs); if (formerlySerializedAsCache != null)
{
formerlySerializedAsCache.TryGetValue(nodeType, out formerlySerializedAs);
}
List<NodePort> dynamicListPorts = new List<NodePort>(); List<NodePort> dynamicListPorts = new List<NodePort>();
@ -49,17 +58,27 @@ namespace XNode {
// If port exists but with wrong settings, remove it. Re-add it later. // If port exists but with wrong settings, remove it. Re-add it later.
if (port.IsDynamic || port.direction != staticPort.direction || port.connectionType != staticPort.connectionType || port.typeConstraint != staticPort.typeConstraint) { if (port.IsDynamic || port.direction != staticPort.direction || port.connectionType != staticPort.connectionType || port.typeConstraint != staticPort.typeConstraint) {
// If port is not dynamic and direction hasn't changed, add it to the list so we can try reconnecting the ports connections. // If port is not dynamic and direction hasn't changed, add it to the list so we can try reconnecting the ports connections.
if (!port.IsDynamic && port.direction == staticPort.direction) removedPorts.Add(port.fieldName, port.GetConnections()); if (!port.IsDynamic && port.direction == staticPort.direction)
{
removedPorts.Add(port.fieldName, port.GetConnections());
}
port.ClearConnections(); port.ClearConnections();
ports.Remove(port.fieldName); ports.Remove(port.fieldName);
} else port.ValueType = staticPort.ValueType; } else
{
port.ValueType = staticPort.ValueType;
}
} }
// If port doesn't exist anymore, remove it // If port doesn't exist anymore, remove it
else if (port.IsStatic) { else if (port.IsStatic) {
//See if the field is tagged with FormerlySerializedAs, if so add the port with its new field name to removedPorts //See if the field is tagged with FormerlySerializedAs, if so add the port with its new field name to removedPorts
// so it can be reconnected in missing ports stage. // so it can be reconnected in missing ports stage.
string newName = null; string newName = null;
if (formerlySerializedAs != null && formerlySerializedAs.TryGetValue(port.fieldName, out newName)) removedPorts.Add(newName, port.GetConnections()); if (formerlySerializedAs != null && formerlySerializedAs.TryGetValue(port.fieldName, out newName))
{
removedPorts.Add(newName, port.GetConnections());
}
port.ClearConnections(); port.ClearConnections();
ports.Remove(port.fieldName); ports.Remove(port.fieldName);
@ -76,13 +95,20 @@ namespace XNode {
//If we just removed the port, try re-adding the connections //If we just removed the port, try re-adding the connections
List<NodePort> reconnectConnections; List<NodePort> reconnectConnections;
if (removedPorts.TryGetValue(staticPort.fieldName, out reconnectConnections)) { if (removedPorts.TryGetValue(staticPort.fieldName, out reconnectConnections)) {
for (int i = 0; i < reconnectConnections.Count; i++) { for (var i = 0; i < reconnectConnections.Count; i++) {
NodePort connection = reconnectConnections[i]; NodePort connection = reconnectConnections[i];
if (connection == null) continue; if (connection == null)
{
continue;
}
// CAVEAT: Ports connected under special conditions defined in graphEditor.CanConnect overrides will not auto-connect. // CAVEAT: Ports connected under special conditions defined in graphEditor.CanConnect overrides will not auto-connect.
// To fix this, this code would need to be moved to an editor script and call graphEditor.CanConnect instead of port.CanConnectTo. // To fix this, this code would need to be moved to an editor script and call graphEditor.CanConnect instead of port.CanConnectTo.
// This is only a problem in the rare edge case where user is using non-standard CanConnect overrides and changes port type of an already connected port // This is only a problem in the rare edge case where user is using non-standard CanConnect overrides and changes port type of an already connected port
if (port.CanConnectTo(connection)) port.Connect(connection); if (port.CanConnectTo(connection))
{
port.Connect(connection);
}
} }
} }
ports.Add(staticPort.fieldName, port); ports.Add(staticPort.fieldName, port);
@ -93,7 +119,7 @@ namespace XNode {
foreach (NodePort listPort in dynamicListPorts) { foreach (NodePort listPort in dynamicListPorts) {
// At this point we know that ports here are dynamic list ports // At this point we know that ports here are dynamic list ports
// which have passed name/"backing port" checks, ergo we can proceed more safely. // which have passed name/"backing port" checks, ergo we can proceed more safely.
string backingPortName = listPort.fieldName.Substring(0, listPort.fieldName.IndexOf(' ')); var backingPortName = listPort.fieldName.Substring(0, listPort.fieldName.IndexOf(' '));
NodePort backingPort = staticPorts[backingPortName]; NodePort backingPort = staticPorts[backingPortName];
// Update port constraints. Creating a new port instead will break the editor, mandating the need for setters. // Update port constraints. Creating a new port instead will break the editor, mandating the need for setters.
@ -124,13 +150,19 @@ namespace XNode {
// Ports flagged as "dynamicPortList = true" end up having a "backing port" and a name with an index, but we have // Ports flagged as "dynamicPortList = true" end up having a "backing port" and a name with an index, but we have
// no guarantee that a dynamic port called "output 0" is an element in a list backed by a static "output" port. // no guarantee that a dynamic port called "output 0" is an element in a list backed by a static "output" port.
// Thus, we need to check for attributes... (but at least we don't need to look at all fields this time) // Thus, we need to check for attributes... (but at least we don't need to look at all fields this time)
string[] fieldNameParts = port.fieldName.Split(' '); var fieldNameParts = port.fieldName.Split(' ');
if (fieldNameParts.Length != 2) return false; if (fieldNameParts.Length != 2)
{
return false;
}
FieldInfo backingPortInfo = port.node.GetType().GetField(fieldNameParts[0]); FieldInfo backingPortInfo = port.node.GetType().GetField(fieldNameParts[0]);
if (backingPortInfo == null) return false; if (backingPortInfo == null)
{
return false;
}
object[] attribs = backingPortInfo.GetCustomAttributes(true); var attribs = backingPortInfo.GetCustomAttributes(true);
return attribs.Any(x => { return attribs.Any(x => {
Node.InputAttribute inputAttribute = x as Node.InputAttribute; Node.InputAttribute inputAttribute = x as Node.InputAttribute;
Node.OutputAttribute outputAttribute = x as Node.OutputAttribute; Node.OutputAttribute outputAttribute = x as Node.OutputAttribute;
@ -149,9 +181,13 @@ namespace XNode {
// Loop through assemblies and add node types to list // Loop through assemblies and add node types to list
foreach (Assembly assembly in assemblies) { foreach (Assembly assembly in assemblies) {
// Skip certain dlls to improve performance // Skip certain dlls to improve performance
string assemblyName = assembly.GetName().Name; var assemblyName = assembly.GetName().Name;
int index = assemblyName.IndexOf('.'); var index = assemblyName.IndexOf('.');
if (index != -1) assemblyName = assemblyName.Substring(0, index); if (index != -1)
{
assemblyName = assemblyName.Substring(0, index);
}
switch (assemblyName) { switch (assemblyName) {
// The following assemblies, and sub-assemblies (eg. UnityEngine.UI) are skipped // The following assemblies, and sub-assemblies (eg. UnityEngine.UI) are skipped
case "UnityEditor": case "UnityEditor":
@ -167,7 +203,7 @@ namespace XNode {
} }
} }
for (int i = 0; i < nodeTypes.Count; i++) { for (var i = 0; i < nodeTypes.Count; i++) {
CachePorts(nodeTypes[i]); CachePorts(nodeTypes[i]);
} }
} }
@ -179,7 +215,7 @@ namespace XNode {
System.Type tempType = nodeType; System.Type tempType = nodeType;
while ((tempType = tempType.BaseType) != typeof(XNode.Node)) { while ((tempType = tempType.BaseType) != typeof(XNode.Node)) {
FieldInfo[] parentFields = tempType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance); FieldInfo[] parentFields = tempType.GetFields(BindingFlags.NonPublic | BindingFlags.Instance);
for (int i = 0; i < parentFields.Length; i++) { for (var i = 0; i < parentFields.Length; i++) {
// Ensure that we do not already have a member with this type and name // Ensure that we do not already have a member with this type and name
FieldInfo parentField = parentFields[i]; FieldInfo parentField = parentFields[i];
if (fieldInfo.TrueForAll(x => x.Name != parentField.Name)) { if (fieldInfo.TrueForAll(x => x.Name != parentField.Name)) {
@ -193,29 +229,52 @@ namespace XNode {
private static void CachePorts(System.Type nodeType) { private static void CachePorts(System.Type nodeType) {
List<System.Reflection.FieldInfo> fieldInfo = GetNodeFields(nodeType); List<System.Reflection.FieldInfo> fieldInfo = GetNodeFields(nodeType);
for (int i = 0; i < fieldInfo.Count; i++) { for (var i = 0; i < fieldInfo.Count; i++) {
//Get InputAttribute and OutputAttribute //Get InputAttribute and OutputAttribute
object[] attribs = fieldInfo[i].GetCustomAttributes(true); var attribs = fieldInfo[i].GetCustomAttributes(true);
Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute; Node.InputAttribute inputAttrib = attribs.FirstOrDefault(x => x is Node.InputAttribute) as Node.InputAttribute;
Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute; Node.OutputAttribute outputAttrib = attribs.FirstOrDefault(x => x is Node.OutputAttribute) as Node.OutputAttribute;
UnityEngine.Serialization.FormerlySerializedAsAttribute formerlySerializedAsAttribute = attribs.FirstOrDefault(x => x is UnityEngine.Serialization.FormerlySerializedAsAttribute) as UnityEngine.Serialization.FormerlySerializedAsAttribute; UnityEngine.Serialization.FormerlySerializedAsAttribute formerlySerializedAsAttribute = attribs.FirstOrDefault(x => x is UnityEngine.Serialization.FormerlySerializedAsAttribute) as UnityEngine.Serialization.FormerlySerializedAsAttribute;
if (inputAttrib == null && outputAttrib == null) continue; if (inputAttrib == null && outputAttrib == null)
{
continue;
}
if (inputAttrib != null && outputAttrib != null) Debug.LogError("Field " + fieldInfo[i].Name + " of type " + nodeType.FullName + " cannot be both input and output."); if (inputAttrib != null && outputAttrib != null)
{
Debug.LogError("Field " + fieldInfo[i].Name + " of type " + nodeType.FullName + " cannot be both input and output.");
}
else { else {
if (!portDataCache.ContainsKey(nodeType)) portDataCache.Add(nodeType, new Dictionary<string, NodePort>()); if (!portDataCache.ContainsKey(nodeType))
{
portDataCache.Add(nodeType, new Dictionary<string, NodePort>());
}
NodePort port = new NodePort(fieldInfo[i]); NodePort port = new NodePort(fieldInfo[i]);
portDataCache[nodeType].Add(port.fieldName, port); portDataCache[nodeType].Add(port.fieldName, port);
} }
if (formerlySerializedAsAttribute != null) { if (formerlySerializedAsAttribute != null) {
if (formerlySerializedAsCache == null) formerlySerializedAsCache = new Dictionary<System.Type, Dictionary<string, string>>(); if (formerlySerializedAsCache == null)
if (!formerlySerializedAsCache.ContainsKey(nodeType)) formerlySerializedAsCache.Add(nodeType, new Dictionary<string, string>()); {
formerlySerializedAsCache = new Dictionary<System.Type, Dictionary<string, string>>();
}
if (formerlySerializedAsCache[nodeType].ContainsKey(formerlySerializedAsAttribute.oldName)) Debug.LogError("Another FormerlySerializedAs with value '" + formerlySerializedAsAttribute.oldName + "' already exist on this node."); if (!formerlySerializedAsCache.ContainsKey(nodeType))
else formerlySerializedAsCache[nodeType].Add(formerlySerializedAsAttribute.oldName, fieldInfo[i].Name); {
formerlySerializedAsCache.Add(nodeType, new Dictionary<string, string>());
}
if (formerlySerializedAsCache[nodeType].ContainsKey(formerlySerializedAsAttribute.oldName))
{
Debug.LogError("Another FormerlySerializedAs with value '" + formerlySerializedAsAttribute.oldName + "' already exist on this node.");
}
else
{
formerlySerializedAsCache[nodeType].Add(formerlySerializedAsAttribute.oldName, fieldInfo[i].Name);
}
} }
} }
} }

View File

@ -40,14 +40,20 @@ namespace XNode {
public virtual void RemoveNode(Node node) { public virtual void RemoveNode(Node node) {
node.ClearConnections(); node.ClearConnections();
nodes.Remove(node); nodes.Remove(node);
if (Application.isPlaying) Destroy(node); if (Application.isPlaying)
{
Destroy(node);
}
} }
/// <summary> Remove all nodes and connections from the graph </summary> /// <summary> Remove all nodes and connections from the graph </summary>
public virtual void Clear() { public virtual void Clear() {
if (Application.isPlaying) { if (Application.isPlaying) {
for (int i = 0; i < nodes.Count; i++) { for (var i = 0; i < nodes.Count; i++) {
if (nodes[i] != null) Destroy(nodes[i]); if (nodes[i] != null)
{
Destroy(nodes[i]);
}
} }
} }
nodes.Clear(); nodes.Clear();
@ -58,8 +64,12 @@ namespace XNode {
// Instantiate a new nodegraph instance // Instantiate a new nodegraph instance
NodeGraph graph = Instantiate(this); NodeGraph graph = Instantiate(this);
// Instantiate all nodes inside the graph // Instantiate all nodes inside the graph
for (int i = 0; i < nodes.Count; i++) { for (var i = 0; i < nodes.Count; i++) {
if (nodes[i] == null) continue; if (nodes[i] == null)
{
continue;
}
Node.graphHotfix = graph; Node.graphHotfix = graph;
Node node = Instantiate(nodes[i]) as Node; Node node = Instantiate(nodes[i]) as Node;
node.graph = graph; node.graph = graph;
@ -67,8 +77,12 @@ namespace XNode {
} }
// Redirect all connections // Redirect all connections
for (int i = 0; i < graph.nodes.Count; i++) { for (var i = 0; i < graph.nodes.Count; i++) {
if (graph.nodes[i] == null) continue; if (graph.nodes[i] == null)
{
continue;
}
foreach (NodePort port in graph.nodes[i].Ports) { foreach (NodePort port in graph.nodes[i].Ports) {
port.Redirect(nodes, graph.nodes); port.Redirect(nodes, graph.nodes);
} }
@ -112,10 +126,24 @@ namespace XNode {
} }
public bool Requires(Type type) { public bool Requires(Type type) {
if (type == null) return false; if (type == null)
if (type == type0) return true; {
else if (type == type1) return true; return false;
else if (type == type2) return true; }
if (type == type0)
{
return true;
}
else if (type == type1)
{
return true;
}
else if (type == type2)
{
return true;
}
return false; return false;
} }
} }

View File

@ -12,8 +12,11 @@ namespace XNode {
/// <summary> Return the first non-null connection </summary> /// <summary> Return the first non-null connection </summary>
public NodePort Connection { public NodePort Connection {
get { get {
for (int i = 0; i < connections.Count; i++) { for (var i = 0; i < connections.Count; i++) {
if (connections[i] != null) return connections[i].Port; if (connections[i] != null)
{
return connections[i].Port;
}
} }
return null; return null;
} }
@ -43,13 +46,24 @@ namespace XNode {
public bool IsStatic { get { return !_dynamic; } } public bool IsStatic { get { return !_dynamic; } }
public Type ValueType { public Type ValueType {
get { get {
if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName)) valueType = Type.GetType(_typeQualifiedName, false); if (valueType == null && !string.IsNullOrEmpty(_typeQualifiedName))
{
valueType = Type.GetType(_typeQualifiedName, false);
}
return valueType; return valueType;
} }
set { set {
if (valueType == value) return; if (valueType == value)
{
return;
}
valueType = value; valueType = value;
if (value != null) _typeQualifiedName = NodeDataCache.GetTypeQualifiedName(value); if (value != null)
{
_typeQualifiedName = NodeDataCache.GetTypeQualifiedName(value);
}
} }
} }
private Type valueType; private Type valueType;
@ -69,7 +83,7 @@ namespace XNode {
ValueType = fieldInfo.FieldType; ValueType = fieldInfo.FieldType;
_dynamic = false; _dynamic = false;
var attribs = fieldInfo.GetCustomAttributes(false); var attribs = fieldInfo.GetCustomAttributes(false);
for (int i = 0; i < attribs.Length; i++) { for (var i = 0; i < attribs.Length; i++) {
if (attribs[i] is Node.InputAttribute) { if (attribs[i] is Node.InputAttribute) {
_direction = IO.Input; _direction = IO.Input;
_connectionType = (attribs[i] as Node.InputAttribute).connectionType; _connectionType = (attribs[i] as Node.InputAttribute).connectionType;
@ -110,11 +124,14 @@ namespace XNode {
/// <summary> Checks all connections for invalid references, and removes them. </summary> /// <summary> Checks all connections for invalid references, and removes them. </summary>
public void VerifyConnections() { public void VerifyConnections() {
for (int i = connections.Count - 1; i >= 0; i--) { for (var i = connections.Count - 1; i >= 0; i--) {
if (connections[i].node != null && if (connections[i].node != null &&
!string.IsNullOrEmpty(connections[i].fieldName) && !string.IsNullOrEmpty(connections[i].fieldName) &&
connections[i].node.GetPort(connections[i].fieldName) != null) connections[i].node.GetPort(connections[i].fieldName) != null)
{
continue; continue;
}
connections.RemoveAt(i); connections.RemoveAt(i);
} }
} }
@ -122,7 +139,11 @@ namespace XNode {
/// <summary> Return the output value of this node through its parent nodes GetValue override method. </summary> /// <summary> Return the output value of this node through its parent nodes GetValue override method. </summary>
/// <returns> <see cref="Node.GetValue(NodePort)"/> </returns> /// <returns> <see cref="Node.GetValue(NodePort)"/> </returns>
public object GetOutputValue() { public object GetOutputValue() {
if (direction == IO.Input) return null; if (direction == IO.Input)
{
return null;
}
return node.GetValue(this); return node.GetValue(this);
} }
@ -130,15 +151,19 @@ namespace XNode {
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public object GetInputValue() { public object GetInputValue() {
NodePort connectedPort = Connection; NodePort connectedPort = Connection;
if (connectedPort == null) return null; if (connectedPort == null)
{
return null;
}
return connectedPort.GetOutputValue(); return connectedPort.GetOutputValue();
} }
/// <summary> Return the output values of all connected ports. </summary> /// <summary> Return the output values of all connected ports. </summary>
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public object[] GetInputValues() { public object[] GetInputValues() {
object[] objs = new object[ConnectionCount]; var objs = new object[ConnectionCount];
for (int i = 0; i < ConnectionCount; i++) { for (var i = 0; i < ConnectionCount; i++) {
NodePort connectedPort = connections[i].Port; NodePort connectedPort = connections[i].Port;
if (connectedPort == null) { // if we happen to find a null port, remove it and look again if (connectedPort == null) { // if we happen to find a null port, remove it and look again
connections.RemoveAt(i); connections.RemoveAt(i);
@ -153,17 +178,20 @@ namespace XNode {
/// <summary> Return the output value of the first connected port. Returns null if none found or invalid. </summary> /// <summary> Return the output value of the first connected port. Returns null if none found or invalid. </summary>
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public T GetInputValue<T>() { public T GetInputValue<T>() {
object obj = GetInputValue(); var obj = GetInputValue();
return obj is T ? (T) obj : default(T); return obj is T ? (T) obj : default(T);
} }
/// <summary> Return the output values of all connected ports. </summary> /// <summary> Return the output values of all connected ports. </summary>
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public T[] GetInputValues<T>() { public T[] GetInputValues<T>() {
object[] objs = GetInputValues(); var objs = GetInputValues();
T[] ts = new T[objs.Length]; T[] ts = new T[objs.Length];
for (int i = 0; i < objs.Length; i++) { for (var i = 0; i < objs.Length; i++) {
if (objs[i] is T) ts[i] = (T) objs[i]; if (objs[i] is T)
{
ts[i] = (T) objs[i];
}
} }
return ts; return ts;
} }
@ -171,7 +199,7 @@ namespace XNode {
/// <summary> Return true if port is connected and has a valid input. </summary> /// <summary> Return true if port is connected and has a valid input. </summary>
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public bool TryGetInputValue<T>(out T value) { public bool TryGetInputValue<T>(out T value) {
object obj = GetInputValue(); var obj = GetInputValue();
if (obj is T) { if (obj is T) {
value = (T) obj; value = (T) obj;
return true; return true;
@ -184,11 +212,18 @@ namespace XNode {
/// <summary> Return the sum of all inputs. </summary> /// <summary> Return the sum of all inputs. </summary>
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public float GetInputSum(float fallback) { public float GetInputSum(float fallback) {
object[] objs = GetInputValues(); var objs = GetInputValues();
if (objs.Length == 0) return fallback; if (objs.Length == 0)
{
return fallback;
}
float result = 0; float result = 0;
for (int i = 0; i < objs.Length; i++) { for (var i = 0; i < objs.Length; i++) {
if (objs[i] is float) result += (float) objs[i]; if (objs[i] is float)
{
result += (float) objs[i];
}
} }
return result; return result;
} }
@ -196,11 +231,18 @@ namespace XNode {
/// <summary> Return the sum of all inputs. </summary> /// <summary> Return the sum of all inputs. </summary>
/// <returns> <see cref="NodePort.GetOutputValue"/> </returns> /// <returns> <see cref="NodePort.GetOutputValue"/> </returns>
public int GetInputSum(int fallback) { public int GetInputSum(int fallback) {
object[] objs = GetInputValues(); var objs = GetInputValues();
if (objs.Length == 0) return fallback; if (objs.Length == 0)
int result = 0; {
for (int i = 0; i < objs.Length; i++) { return fallback;
if (objs[i] is int) result += (int) objs[i]; }
var result = 0;
for (var i = 0; i < objs.Length; i++) {
if (objs[i] is int)
{
result += (int) objs[i];
}
} }
return result; return result;
} }
@ -208,7 +250,11 @@ namespace XNode {
/// <summary> Connect this <see cref="NodePort"/> to another </summary> /// <summary> Connect this <see cref="NodePort"/> to another </summary>
/// <param name="port">The <see cref="NodePort"/> to connect to</param> /// <param name="port">The <see cref="NodePort"/> to connect to</param>
public void Connect(NodePort port) { public void Connect(NodePort port) {
if (connections == null) connections = new List<PortConnection>(); if (connections == null)
{
connections = new List<PortConnection>();
}
if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; } if (port == null) { Debug.LogWarning("Cannot connect to null port"); return; }
if (port == this) { Debug.LogWarning("Cannot connect port to self."); return; } if (port == this) { Debug.LogWarning("Cannot connect port to self."); return; }
if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; } if (IsConnectedTo(port)) { Debug.LogWarning("Port already connected. "); return; }
@ -220,17 +266,28 @@ namespace XNode {
if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); } if (port.connectionType == Node.ConnectionType.Override && port.ConnectionCount != 0) { port.ClearConnections(); }
if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); } if (connectionType == Node.ConnectionType.Override && ConnectionCount != 0) { ClearConnections(); }
connections.Add(new PortConnection(port)); connections.Add(new PortConnection(port));
if (port.connections == null) port.connections = new List<PortConnection>(); if (port.connections == null)
if (!port.IsConnectedTo(this)) port.connections.Add(new PortConnection(this)); {
port.connections = new List<PortConnection>();
}
if (!port.IsConnectedTo(this))
{
port.connections.Add(new PortConnection(this));
}
node.OnCreateConnection(this, port); node.OnCreateConnection(this, port);
port.node.OnCreateConnection(this, port); port.node.OnCreateConnection(this, port);
} }
public List<NodePort> GetConnections() { public List<NodePort> GetConnections() {
List<NodePort> result = new List<NodePort>(); List<NodePort> result = new List<NodePort>();
for (int i = 0; i < connections.Count; i++) { for (var i = 0; i < connections.Count; i++) {
NodePort port = GetConnection(i); NodePort port = GetConnection(i);
if (port != null) result.Add(port); if (port != null)
{
result.Add(port);
}
} }
return result; return result;
} }
@ -251,15 +308,21 @@ namespace XNode {
/// <summary> Get index of the connection connecting this and specified ports </summary> /// <summary> Get index of the connection connecting this and specified ports </summary>
public int GetConnectionIndex(NodePort port) { public int GetConnectionIndex(NodePort port) {
for (int i = 0; i < ConnectionCount; i++) { for (var i = 0; i < ConnectionCount; i++) {
if (connections[i].Port == port) return i; if (connections[i].Port == port)
{
return i;
}
} }
return -1; return -1;
} }
public bool IsConnectedTo(NodePort port) { public bool IsConnectedTo(NodePort port) {
for (int i = 0; i < connections.Count; i++) { for (var i = 0; i < connections.Count; i++) {
if (connections[i].Port == port) return true; if (connections[i].Port == port)
{
return true;
}
} }
return false; return false;
} }
@ -268,22 +331,72 @@ namespace XNode {
public bool CanConnectTo(NodePort port) { public bool CanConnectTo(NodePort port) {
// Figure out which is input and which is output // Figure out which is input and which is output
NodePort input = null, output = null; NodePort input = null, output = null;
if (IsInput) input = this; if (IsInput)
else output = this; {
if (port.IsInput) input = port; input = this;
else output = port; }
else
{
output = this;
}
if (port.IsInput)
{
input = port;
}
else
{
output = port;
}
// If there isn't one of each, they can't connect // If there isn't one of each, they can't connect
if (input == null || output == null) return false; if (input == null || output == null)
{
return false;
}
// Check input type constraints // Check input type constraints
if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; if (input.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType))
if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; {
if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; return false;
if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; }
if (input.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType)
{
return false;
}
if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType))
{
return false;
}
if (input.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType))
{
return false;
}
// Check output type constraints // Check output type constraints
if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType)) return false; if (output.typeConstraint == XNode.Node.TypeConstraint.Inherited && !input.ValueType.IsAssignableFrom(output.ValueType))
if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType) return false; {
if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; return false;
if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType)) return false; }
if (output.typeConstraint == XNode.Node.TypeConstraint.Strict && input.ValueType != output.ValueType)
{
return false;
}
if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedInverse && !output.ValueType.IsAssignableFrom(input.ValueType))
{
return false;
}
if (output.typeConstraint == XNode.Node.TypeConstraint.InheritedAny && !input.ValueType.IsAssignableFrom(output.ValueType) && !output.ValueType.IsAssignableFrom(input.ValueType))
{
return false;
}
// Success // Success
return true; return true;
} }
@ -291,14 +404,14 @@ namespace XNode {
/// <summary> Disconnect this port from another port </summary> /// <summary> Disconnect this port from another port </summary>
public void Disconnect(NodePort port) { public void Disconnect(NodePort port) {
// Remove this ports connection to the other // Remove this ports connection to the other
for (int i = connections.Count - 1; i >= 0; i--) { for (var i = connections.Count - 1; i >= 0; i--) {
if (connections[i].Port == port) { if (connections[i].Port == port) {
connections.RemoveAt(i); connections.RemoveAt(i);
} }
} }
if (port != null) { if (port != null) {
// Remove the other ports connection to this port // Remove the other ports connection to this port
for (int i = 0; i < port.connections.Count; i++) { for (var i = 0; i < port.connections.Count; i++) {
if (port.connections[i].Port == this) { if (port.connections[i].Port == this) {
port.connections.RemoveAt(i); port.connections.RemoveAt(i);
// Trigger OnRemoveConnection from this side port // Trigger OnRemoveConnection from this side port
@ -322,7 +435,10 @@ namespace XNode {
// Trigger OnRemoveConnection // Trigger OnRemoveConnection
node.OnRemoveConnection(this); node.OnRemoveConnection(this);
if (otherPort != null) otherPort.node.OnRemoveConnection(otherPort); if (otherPort != null)
{
otherPort.node.OnRemoveConnection(otherPort);
}
} }
public void ClearConnections() { public void ClearConnections() {
@ -338,37 +454,44 @@ namespace XNode {
/// <summary> Swap connections with another node </summary> /// <summary> Swap connections with another node </summary>
public void SwapConnections(NodePort targetPort) { public void SwapConnections(NodePort targetPort) {
int aConnectionCount = connections.Count; var aConnectionCount = connections.Count;
int bConnectionCount = targetPort.connections.Count; var bConnectionCount = targetPort.connections.Count;
List<NodePort> portConnections = new List<NodePort>(); List<NodePort> portConnections = new List<NodePort>();
List<NodePort> targetPortConnections = new List<NodePort>(); List<NodePort> targetPortConnections = new List<NodePort>();
// Cache port connections // Cache port connections
for (int i = 0; i < aConnectionCount; i++) for (var i = 0; i < aConnectionCount; i++)
{
portConnections.Add(connections[i].Port); portConnections.Add(connections[i].Port);
}
// Cache target port connections // Cache target port connections
for (int i = 0; i < bConnectionCount; i++) for (var i = 0; i < bConnectionCount; i++)
{
targetPortConnections.Add(targetPort.connections[i].Port); targetPortConnections.Add(targetPort.connections[i].Port);
}
ClearConnections(); ClearConnections();
targetPort.ClearConnections(); targetPort.ClearConnections();
// Add port connections to targetPort // Add port connections to targetPort
for (int i = 0; i < portConnections.Count; i++) for (var i = 0; i < portConnections.Count; i++)
{
targetPort.Connect(portConnections[i]); targetPort.Connect(portConnections[i]);
}
// Add target port connections to this one // Add target port connections to this one
for (int i = 0; i < targetPortConnections.Count; i++) for (var i = 0; i < targetPortConnections.Count; i++)
{
Connect(targetPortConnections[i]); Connect(targetPortConnections[i]);
}
} }
/// <summary> Copy all connections pointing to a node and add them to this one </summary> /// <summary> Copy all connections pointing to a node and add them to this one </summary>
public void AddConnections(NodePort targetPort) { public void AddConnections(NodePort targetPort) {
int connectionCount = targetPort.ConnectionCount; var connectionCount = targetPort.ConnectionCount;
for (int i = 0; i < connectionCount; i++) { for (var i = 0; i < connectionCount; i++) {
PortConnection connection = targetPort.connections[i]; PortConnection connection = targetPort.connections[i];
NodePort otherPort = connection.Port; NodePort otherPort = connection.Port;
Connect(otherPort); Connect(otherPort);
@ -377,10 +500,10 @@ namespace XNode {
/// <summary> Move all connections pointing to this node, to another node </summary> /// <summary> Move all connections pointing to this node, to another node </summary>
public void MoveConnections(NodePort targetPort) { public void MoveConnections(NodePort targetPort) {
int connectionCount = connections.Count; var connectionCount = connections.Count;
// Add connections to target port // Add connections to target port
for (int i = 0; i < connectionCount; i++) { for (var i = 0; i < connectionCount; i++) {
PortConnection connection = targetPort.connections[i]; PortConnection connection = targetPort.connections[i];
NodePort otherPort = connection.Port; NodePort otherPort = connection.Port;
Connect(otherPort); Connect(otherPort);
@ -391,8 +514,11 @@ namespace XNode {
/// <summary> Swap connected nodes from the old list with nodes from the new list </summary> /// <summary> Swap connected nodes from the old list with nodes from the new list </summary>
public void Redirect(List<Node> oldNodes, List<Node> newNodes) { public void Redirect(List<Node> oldNodes, List<Node> newNodes) {
foreach (PortConnection connection in connections) { foreach (PortConnection connection in connections) {
int index = oldNodes.IndexOf(connection.node); var index = oldNodes.IndexOf(connection.node);
if (index >= 0) connection.node = newNodes[index]; if (index >= 0)
{
connection.node = newNodes[index];
}
} }
} }
@ -414,7 +540,11 @@ namespace XNode {
/// <summary> Returns the port that this <see cref="PortConnection"/> points to </summary> /// <summary> Returns the port that this <see cref="PortConnection"/> points to </summary>
private NodePort GetPort() { private NodePort GetPort() {
if (node == null || string.IsNullOrEmpty(fieldName)) return null; if (node == null || string.IsNullOrEmpty(fieldName))
{
return null;
}
return node.GetPort(fieldName); return node.GetPort(fieldName);
} }
} }

Some files were not shown because too many files have changed in this diff Show More