Add VRChat and GitHub packages to Unity project
This commit is contained in:
1 parent
0334a52b21
commit
85538aaccc
3297 files changed
+847877
No files matched your search
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9f7ba6c00dc6a7146aa8c8fe4f152775
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 713065eccc01a204abcfa8107034ee5b
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,102 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b6535096cfa29340897276abbdd015f
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
'': Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Android: 1
|
||||
Exclude Editor: 0
|
||||
Exclude Linux: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude LinuxUniversal: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
- first:
|
||||
Android: Android
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: ARMv7
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
DefaultValueInitialized: true
|
||||
OS: AnyOS
|
||||
- first:
|
||||
Facebook: Win
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Facebook: Win64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Linux
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86
|
||||
- first:
|
||||
Standalone: Linux64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: x86_64
|
||||
- first:
|
||||
Standalone: LinuxUniversal
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: None
|
||||
- first:
|
||||
Standalone: OSXUniversal
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Win
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Standalone: Win64
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,46 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 585dd63e377866248b16bdba915820ed
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
'': Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Android: 1
|
||||
Exclude Editor: 0
|
||||
Exclude Linux: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude LinuxUniversal: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,46 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b335798a4f28bec40ba9b3d4a15acee7
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
'': Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Android: 1
|
||||
Exclude Editor: 0
|
||||
Exclude Linux: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude LinuxUniversal: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,46 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21dcba1a47cc8c84381629950b692129
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
'': Any
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
Exclude Android: 1
|
||||
Exclude Editor: 0
|
||||
Exclude Linux: 1
|
||||
Exclude Linux64: 1
|
||||
Exclude LinuxUniversal: 1
|
||||
Exclude OSXUniversal: 1
|
||||
Exclude Win: 1
|
||||
Exclude Win64: 1
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 161140ecae894b84ba7bdd6e44ff4371
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 19cff77330d183441a69ff6c69e07629
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
iconMap: {}
|
||||
executionOrder: {}
|
||||
defineConstraints: []
|
||||
isPreloaded: 0
|
||||
isOverridable: 0
|
||||
isExplicitlyReferenced: 0
|
||||
validateReferences: 1
|
||||
platformData:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Windows Store Apps: WindowsStoreApps
|
||||
second:
|
||||
enabled: 0
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9163c50fd2ce4dc4db94582224a3b122
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using VRC.Udon.Graph.Interfaces;
|
||||
using VRC.Udon.Graph.Attributes;
|
||||
using VRC.Udon.Graph.NodeRegistries;
|
||||
|
||||
[assembly: UdonGraphNodeRegistry(typeof(VRCInstantiateNodeRegistry), "VRCInstantiateNodeRegistry")]
|
||||
namespace VRC.Udon.Graph.NodeRegistries
|
||||
{
|
||||
public class VRCInstantiateNodeRegistry : BaseNodeRegistry
|
||||
{
|
||||
protected override Dictionary<string, INodeRegistry> NextRegistries => _nextRegistries;
|
||||
private static readonly Dictionary<string, INodeRegistry> _nextRegistries = new Dictionary<string, INodeRegistry>();
|
||||
|
||||
protected override Dictionary<string, UdonNodeDefinition> NodeDefinitions => _nodeDefinitions;
|
||||
|
||||
private static readonly Dictionary<string, UdonNodeDefinition> _nodeDefinitions = new Dictionary<string, UdonNodeDefinition>
|
||||
{
|
||||
{
|
||||
"VRCInstantiate.__Instantiate__UnityEngineGameObject__UnityEngineGameObject",
|
||||
new UdonNodeDefinition(
|
||||
"VRChat Instantiate",
|
||||
"VRCInstantiate.__Instantiate__UnityEngineGameObject__UnityEngineGameObject",
|
||||
typeof(UnityEngine.Object),
|
||||
new []
|
||||
{
|
||||
new UdonNodeParameter
|
||||
{
|
||||
name = "original",
|
||||
type = typeof(UnityEngine.GameObject),
|
||||
parameterType = UdonNodeParameter.ParameterType.IN
|
||||
},
|
||||
new UdonNodeParameter
|
||||
{
|
||||
name = "clone",
|
||||
type = typeof(UnityEngine.GameObject),
|
||||
parameterType = UdonNodeParameter.ParameterType.OUT
|
||||
}
|
||||
},
|
||||
new string[] { },
|
||||
new string[] { },
|
||||
new object[] { },
|
||||
true
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cac80b40f57c41d4b941dc5059271583
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 56226d3778b7b2848bdceafeedfa7e0e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 516ce23587a76704f8ae9b50f3e59ac5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.Attributes
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
|
||||
public class UdonProgramSourceNewMenuAttribute : Attribute
|
||||
{
|
||||
public Type Type { get; }
|
||||
public string DisplayName { get; }
|
||||
|
||||
public UdonProgramSourceNewMenuAttribute(Type type, string displayName)
|
||||
{
|
||||
Type = type;
|
||||
DisplayName = displayName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e5ced9511d591140b191bbd9e948e61
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using UnityEditor;
|
||||
using VRC.Udon.Common.Interfaces;
|
||||
using VRC.Udon.ProgramSources;
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources
|
||||
{
|
||||
[CustomEditor(typeof(SerializedUdonProgramAsset))]
|
||||
public class SerializedUdonProgramAssetEditor : UnityEditor.Editor
|
||||
{
|
||||
private SerializedProperty _serializedProgramBytesStringSerializedProperty;
|
||||
private SerializedProperty _serializationDataFormatSerializedProperty;
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
_serializedProgramBytesStringSerializedProperty = serializedObject.FindProperty("serializedProgramBytesString");
|
||||
_serializationDataFormatSerializedProperty = serializedObject.FindProperty("serializationDataFormat");
|
||||
}
|
||||
|
||||
public override void OnInspectorGUI()
|
||||
{
|
||||
DrawSerializationDebug();
|
||||
}
|
||||
|
||||
[Conditional("UDON_DEBUG")]
|
||||
private void DrawSerializationDebug()
|
||||
{
|
||||
EditorGUILayout.LabelField($"DataFormat: {(DataFormat)_serializationDataFormatSerializedProperty.enumValueIndex}");
|
||||
|
||||
if(string.IsNullOrEmpty(_serializedProgramBytesStringSerializedProperty.stringValue))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(_serializationDataFormatSerializedProperty.enumValueIndex == (int)DataFormat.JSON)
|
||||
{
|
||||
using(new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUILayout.TextArea(System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(_serializedProgramBytesStringSerializedProperty.stringValue)));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
using(new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
SerializedUdonProgramAsset serializedUdonProgramAsset = (SerializedUdonProgramAsset)target;
|
||||
IUdonProgram udonProgram = serializedUdonProgramAsset.RetrieveProgram();
|
||||
byte[] serializedBytes = SerializationUtility.SerializeValue(udonProgram, DataFormat.JSON, out List<UnityEngine.Object> _);
|
||||
EditorGUILayout.TextArea(System.Text.Encoding.UTF8.GetString(serializedBytes));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e1b5b45f24b268b42826fc5c5497dc15
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bdc4fbcfbda0aa74ba1360d53fde5928
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,96 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VRC.Udon.Editor;
|
||||
using VRC.Udon.Editor.ProgramSources;
|
||||
using VRC.Udon.Editor.ProgramSources.Attributes;
|
||||
|
||||
[assembly: UdonProgramSourceNewMenu(typeof(UdonAssemblyProgramAsset), "Udon Assembly Program Asset")]
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources
|
||||
{
|
||||
[CreateAssetMenu(menuName = "VRChat/Udon/Udon Assembly Program Asset", fileName = "New Udon Assembly Program Asset")]
|
||||
public class UdonAssemblyProgramAsset : UdonProgramAsset
|
||||
{
|
||||
[SerializeField]
|
||||
protected string udonAssembly = "";
|
||||
|
||||
[SerializeField]
|
||||
protected string assemblyError = null;
|
||||
|
||||
public delegate void AssembleDelegate(bool success, string assembly);
|
||||
public event AssembleDelegate OnAssemble;
|
||||
|
||||
protected override void DrawProgramSourceGUI(UdonBehaviour udonBehaviour, ref bool dirty)
|
||||
{
|
||||
DrawAssemblyTextArea(!Application.isPlaying, ref dirty);
|
||||
DrawAssemblyErrorTextArea();
|
||||
|
||||
base.DrawProgramSourceGUI(udonBehaviour, ref dirty);
|
||||
}
|
||||
|
||||
protected override void RefreshProgramImpl()
|
||||
{
|
||||
AssembleProgram();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
protected virtual void DrawAssemblyTextArea(bool allowEditing, ref bool dirty)
|
||||
{
|
||||
EditorGUILayout.LabelField("Assembly Code", EditorStyles.boldLabel);
|
||||
if(GUILayout.Button("Copy Assembly To Clipboard"))
|
||||
{
|
||||
EditorGUIUtility.systemCopyBuffer = udonAssembly;
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
using(new EditorGUI.DisabledScope(!allowEditing))
|
||||
{
|
||||
string newAssembly = EditorGUILayout.TextArea(udonAssembly);
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
dirty = true;
|
||||
Undo.RecordObject(this, "Edit Assembly Program Code");
|
||||
udonAssembly = newAssembly;
|
||||
UdonEditorManager.Instance.QueueAndRefreshProgram(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
protected void DrawAssemblyErrorTextArea()
|
||||
{
|
||||
if(string.IsNullOrEmpty(assemblyError))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUILayout.LabelField("Assembly Error", EditorStyles.boldLabel);
|
||||
using(new EditorGUI.DisabledScope(true))
|
||||
{
|
||||
EditorGUILayout.TextArea(assemblyError);
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
protected void AssembleProgram()
|
||||
{
|
||||
try
|
||||
{
|
||||
program = UdonEditorManager.Instance.Assemble(udonAssembly);
|
||||
assemblyError = null;
|
||||
OnAssemble?.Invoke(true, udonAssembly);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
program = null;
|
||||
assemblyError = e.Message;
|
||||
Debug.LogException(e);
|
||||
OnAssemble?.Invoke(false, assemblyError);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 22203902d63dec94194fefc3e155c43b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources
|
||||
{
|
||||
[CustomEditor(typeof(UdonAssemblyProgramAsset))]
|
||||
public class UdonAssemblyProgramAssetEditor : UdonProgramAssetEditor
|
||||
{
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3df823f3ab561fc43bcb81286e14b91d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using System.IO;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Experimental.AssetImporters;
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources
|
||||
{
|
||||
[ScriptedImporter(1, "uasm")]
|
||||
[UsedImplicitly]
|
||||
public class UdonAssemblyProgramAssetImporter : ScriptedImporter
|
||||
{
|
||||
public override void OnImportAsset(AssetImportContext ctx)
|
||||
{
|
||||
UdonAssemblyProgramAsset udonAssemblyProgramAsset = ScriptableObject.CreateInstance<UdonAssemblyProgramAsset>();
|
||||
SerializedObject serializedUdonAssemblyProgramAsset = new SerializedObject(udonAssemblyProgramAsset);
|
||||
SerializedProperty udonAssemblyProperty = serializedUdonAssemblyProgramAsset.FindProperty("udonAssembly");
|
||||
udonAssemblyProperty.stringValue = File.ReadAllText(ctx.assetPath);
|
||||
serializedUdonAssemblyProgramAsset.ApplyModifiedProperties();
|
||||
|
||||
udonAssemblyProgramAsset.RefreshProgram();
|
||||
|
||||
ctx.AddObjectToAsset("Imported Udon Assembly Program", udonAssemblyProgramAsset);
|
||||
ctx.SetMainObject(udonAssemblyProgramAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 3c0638314c289c24193b47d1c53c9fca
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4c6dab170f134274597a46948afbeca8
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c71d92e0517c54f4497d8723964647b1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5d4407e68fb16a84b98b1731bc369172
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f6fb8c8470693f4bbd2502cd1b997f0
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI
|
||||
{
|
||||
public class ByteField : TextInputFieldBase<byte>
|
||||
{
|
||||
public ByteField()
|
||||
: base(3, char.MinValue)
|
||||
{
|
||||
this.AddToClassList("UdonValueField");
|
||||
this.isDelayed = true;
|
||||
}
|
||||
|
||||
public override byte value
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.value;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.value = value;
|
||||
this.text = value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ExecuteDefaultAction(EventBase evt)
|
||||
{
|
||||
base.ExecuteDefaultAction(evt);
|
||||
|
||||
if (this.text.Length > 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
||||
var byteValue = Convert.ToByte(this.text);
|
||||
this.value = byteValue;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError(e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c66f97d5a5a38cb478fd4df11ece7be7
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI
|
||||
{
|
||||
public class CharField : TextInputFieldBase<char>
|
||||
{
|
||||
public CharField()
|
||||
: base(1, char.MinValue)
|
||||
{
|
||||
this.AddToClassList("UdonValueField");
|
||||
}
|
||||
|
||||
public override char value
|
||||
{
|
||||
get
|
||||
{
|
||||
return base.value;
|
||||
}
|
||||
set
|
||||
{
|
||||
base.value = value;
|
||||
this.text = value.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ExecuteDefaultAction(EventBase evt)
|
||||
{
|
||||
base.ExecuteDefaultAction(evt);
|
||||
|
||||
if (this.text.Length > 0)
|
||||
{
|
||||
this.value = this.text.ToCharArray()[0];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 602ffcf431b3e4f41a18bd868751439a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.Experimental.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using UIElements = UnityEditor.Experimental.UIElements;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI
|
||||
{
|
||||
public class LayerMaskField : BaseField<LayerMask>
|
||||
{
|
||||
public LayerMaskField()
|
||||
{
|
||||
// Set up styling
|
||||
AddToClassList("UdonValueField");
|
||||
|
||||
// Create LayerMask Editor and listen for changes
|
||||
UIElements.LayerMaskField field = new UIElements.LayerMaskField();
|
||||
field.OnValueChanged(e =>
|
||||
{
|
||||
this.value = e.newValue;
|
||||
});
|
||||
|
||||
Add(field);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 42d9183d7c7ce67448d1e010456e36f9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.Experimental.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI
|
||||
{
|
||||
public class QuaternionField : BaseField<Quaternion>
|
||||
{
|
||||
public QuaternionField()
|
||||
{
|
||||
// Set up styling
|
||||
AddToClassList("UdonValueField");
|
||||
|
||||
// Create Vector4 Editor and listen for changes
|
||||
Vector4Field field = new Vector4Field();
|
||||
field.OnValueChanged(
|
||||
e =>
|
||||
value = new Quaternion(e.newValue.x, e.newValue.y, e.newValue.z, e.newValue.w)
|
||||
);
|
||||
Add(field);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c96407d2b7698c4c8a0476efa2c765d
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,35 @@
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using VRC.SDKBase;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI
|
||||
{
|
||||
public class VRCUrlField : TextInputFieldBase<VRCUrl>
|
||||
{
|
||||
public VRCUrlField()
|
||||
: base(-1, char.MinValue)
|
||||
{
|
||||
AddToClassList("UdonValueField");
|
||||
RegisterCallback<BlurEvent>(OnBlur);
|
||||
}
|
||||
|
||||
private void OnBlur(BlurEvent evt)
|
||||
{
|
||||
base.value = new VRCUrl(text);
|
||||
}
|
||||
|
||||
public override VRCUrl value
|
||||
{
|
||||
get => base.value;
|
||||
set
|
||||
{
|
||||
base.value = value;
|
||||
text = value?.Get() ?? string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
~VRCUrlField()
|
||||
{
|
||||
UnregisterCallback<BlurEvent>(OnBlur);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eeb751ae1c234a04291c5039626f3470
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85d42af61040c754c808c387b736a441
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView;
|
||||
|
||||
namespace UnityEditor.Experimental.UIElements.GraphView
|
||||
{
|
||||
public static class GraphElementExtension
|
||||
{
|
||||
public static void Reserialize(this GraphElement element)
|
||||
{
|
||||
MarkDirty();
|
||||
|
||||
var evt = new Event()
|
||||
{
|
||||
type = EventType.ExecuteCommand,
|
||||
commandName = UdonGraphCommands.Reserialize,
|
||||
};
|
||||
using (var e = ExecuteCommandEvent.GetPooled(evt))
|
||||
{
|
||||
element.SendEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Reload(this GraphElement element)
|
||||
{
|
||||
var evt = new Event()
|
||||
{
|
||||
type = EventType.ExecuteCommand,
|
||||
commandName = UdonGraphCommands.Reload
|
||||
};
|
||||
using (var e = ExecuteCommandEvent.GetPooled(evt))
|
||||
{
|
||||
element.SendEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveNewData(this GraphElement element)
|
||||
{
|
||||
var evt = new Event()
|
||||
{
|
||||
type = EventType.ExecuteCommand,
|
||||
commandName = UdonGraphCommands.SaveNewData
|
||||
};
|
||||
using (var e = ExecuteCommandEvent.GetPooled(evt))
|
||||
{
|
||||
element.SendEvent(e);
|
||||
}
|
||||
|
||||
MarkDirty();
|
||||
}
|
||||
|
||||
public static void MarkDirty()
|
||||
{
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
EditorSceneManager.MarkSceneDirty(EditorSceneManager.GetActiveScene());
|
||||
}
|
||||
}
|
||||
|
||||
public static Vector2 GetSnappedPosition(Vector2 position)
|
||||
{
|
||||
// don't snap at 0 size
|
||||
var snap = Settings.GridSnapSize;
|
||||
if (snap == 0) return position;
|
||||
|
||||
position.x = (float)System.Math.Round(position.x / snap) * snap;
|
||||
position.y = (float)System.Math.Round(position.y / snap) * snap;
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
public static Rect GetSnappedRect(Rect rect)
|
||||
{
|
||||
rect.position = GetSnappedPosition(rect.position);
|
||||
return rect;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 469db50616185d04e8a46dcd75db12d2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using System;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonArrayEditor : VisualElement
|
||||
{
|
||||
private IArrayProvider _inspector;
|
||||
private Button _editArrayButton;
|
||||
private System.Action<object> _setValueCallback;
|
||||
private Type _type;
|
||||
private object _value;
|
||||
private bool _inspectorOpen = false;
|
||||
|
||||
public UdonArrayEditor(Type t, System.Action<object> valueChangedAction, object value)
|
||||
{
|
||||
_setValueCallback = valueChangedAction;
|
||||
_value = value;
|
||||
_type = t.GetElementType();
|
||||
|
||||
_editArrayButton = new Button(EditArray)
|
||||
{
|
||||
text = "Edit",
|
||||
name = "array-editor",
|
||||
};
|
||||
|
||||
Add(_editArrayButton);
|
||||
}
|
||||
|
||||
private void EditArray()
|
||||
{
|
||||
if (_inspector == null)
|
||||
{
|
||||
// Create it new
|
||||
Type typedArrayInspector = (typeof(UdonArrayInspector<>)).MakeGenericType(_type);
|
||||
_inspector = (Activator.CreateInstance(typedArrayInspector, null, _value) as IArrayProvider);
|
||||
|
||||
Add(_inspector as VisualElement);
|
||||
_inspectorOpen = true;
|
||||
_editArrayButton.text = "Save";
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Update Values when 'Save' is clicked
|
||||
if(_inspectorOpen)
|
||||
{
|
||||
// Update Values
|
||||
var values = _inspector.GetValues();
|
||||
_setValueCallback(values);
|
||||
|
||||
// Remove Inspector
|
||||
_inspector.RemoveFromHierarchy();
|
||||
|
||||
// Update Button Text
|
||||
_editArrayButton.text = "Edit";
|
||||
_inspectorOpen = false;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Inspector exists, it's just removed
|
||||
_inspectorOpen = true;
|
||||
Add(_inspector as VisualElement);
|
||||
_editArrayButton.text = "Save";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7f257a6eeae213a4db991d486cace003
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using UnityEditor.Experimental.UIElements;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonArrayInspector<T> : VisualElement, IArrayProvider
|
||||
{
|
||||
private ScrollView _scroller;
|
||||
private VisualContainer _container;
|
||||
private List<VisualElement> _fields = new List<VisualElement>();
|
||||
private IntegerField _sizeField;
|
||||
private System.Action<object> _setValueCallback;
|
||||
|
||||
public UdonArrayInspector(System.Action<object> valueChangedAction, object value)
|
||||
{
|
||||
_setValueCallback = valueChangedAction;
|
||||
|
||||
AddToClassList("input-inspector");
|
||||
var resizeContainer = new VisualElement()
|
||||
{
|
||||
name = "resize-container",
|
||||
};
|
||||
resizeContainer.Add(new Label("size"));
|
||||
|
||||
_sizeField = new IntegerField();
|
||||
_sizeField.isDelayed = true;
|
||||
_sizeField.OnValueChanged((evt) =>
|
||||
{
|
||||
ResizeTo(evt.newValue);
|
||||
});
|
||||
resizeContainer.Add(_sizeField);
|
||||
Add(resizeContainer);
|
||||
|
||||
_scroller = new ScrollView()
|
||||
{
|
||||
name = "array-scroll",
|
||||
};
|
||||
Add(_scroller);
|
||||
|
||||
_container = new VisualContainer();
|
||||
_scroller.SetContents(_container);
|
||||
|
||||
if (value == null)
|
||||
{
|
||||
// Can't show if array is empty
|
||||
_sizeField.value = 0;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
IEnumerable<T> values = (value as IEnumerable<T>);
|
||||
if (values == null)
|
||||
{
|
||||
Debug.LogError($"Couldn't convert {value} to {typeof(T).ToString()} Array");
|
||||
return;
|
||||
}
|
||||
|
||||
// Populate fields and their values from passed-in array
|
||||
_fields = new List<VisualElement>();
|
||||
foreach (var item in values)
|
||||
{
|
||||
var field = GetValueField(item);
|
||||
_fields.Add(field);
|
||||
|
||||
_container.Add(field);
|
||||
}
|
||||
|
||||
_sizeField.value = values.Count();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void ResizeTo(int newValue)
|
||||
{
|
||||
_sizeField.value = newValue;
|
||||
|
||||
// Create from scratch if currentFields are null
|
||||
if(_fields == null)
|
||||
{
|
||||
Debug.Log($"Creating from Scratch");
|
||||
_fields = new List<VisualElement>();
|
||||
for (int i = 0; i < newValue; i++)
|
||||
{
|
||||
var field = GetValueField(null);
|
||||
_fields.Add(field);
|
||||
_container.Add(field as VisualElement);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Shrink list if new value is less than old one
|
||||
if(newValue < _fields.Count)
|
||||
{
|
||||
for (int i = _fields.Count - 1; i >= newValue; i--)
|
||||
{
|
||||
(_fields[i] as VisualElement).RemoveFromHierarchy();
|
||||
_fields.RemoveAt(i);
|
||||
}
|
||||
MarkDirtyRepaint();
|
||||
return;
|
||||
}
|
||||
|
||||
// Expand list if new value is more than old one.
|
||||
if(newValue > _fields.Count)
|
||||
{
|
||||
int numberToAdd = newValue - _fields.Count;
|
||||
for (int i = 0; i < numberToAdd; i++)
|
||||
{
|
||||
var field = GetValueField(null);
|
||||
if (field == null)
|
||||
{
|
||||
Debug.LogWarning($"Sorry, can't edit object of type {typeof(T).ToString()} yet.");
|
||||
return;
|
||||
}
|
||||
_fields.Add(field);
|
||||
|
||||
_container.Add(field as VisualElement);
|
||||
}
|
||||
MarkDirtyRepaint();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private VisualElement GetValueField(object value)
|
||||
{
|
||||
return UdonFieldFactory.CreateField(typeof(T), value, _setValueCallback);
|
||||
}
|
||||
|
||||
public object GetValues()
|
||||
{
|
||||
var result = new List<T>();
|
||||
for (int i = 0; i < _fields.Count; i++)
|
||||
{
|
||||
var f = (_fields[i] as INotifyValueChanged<T>);
|
||||
result.Add(f.value);
|
||||
}
|
||||
return result.ToArray();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface IArrayProvider
|
||||
{
|
||||
object GetValues();
|
||||
void RemoveFromHierarchy(); // in VisualElement
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f4f0ade55ae13b6468a765826f1f2540
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+192
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using UnityEngine.Experimental.UIElements.StyleSheets;
|
||||
using UnityEditor;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
|
||||
public class UdonComment : UdonGraphElement, IUdonGraphElementDataProvider
|
||||
{
|
||||
private VisualElement _mainContainer;
|
||||
private Label _label;
|
||||
private TextField _textField;
|
||||
private CustomData _customData = new CustomData();
|
||||
private UdonGraph _graph;
|
||||
public UdonGroup group;
|
||||
|
||||
// Called from Context menu
|
||||
public static UdonComment Create(string value, Rect position, UdonGraph graph)
|
||||
{
|
||||
var comment = new UdonComment("", graph);
|
||||
|
||||
// make sure rect size is not 0
|
||||
position.width = position.width > 0 ? position.width : 128;
|
||||
position.height = position.height > 0 ? position.height : 40;
|
||||
|
||||
comment._customData.layout = position;
|
||||
comment._customData.title = value;
|
||||
|
||||
comment.UpdateFromData();
|
||||
graph.MarkSceneDirty();
|
||||
|
||||
return comment;
|
||||
}
|
||||
|
||||
public static UdonComment Create(UdonGraphElementData elementData, UdonGraph graph)
|
||||
{
|
||||
var comment = new UdonComment(elementData.jsonData, graph);
|
||||
|
||||
comment.UpdateFromData();
|
||||
graph.MarkSceneDirty();
|
||||
|
||||
return comment;
|
||||
}
|
||||
|
||||
private UdonComment(string jsonData, UdonGraph graph)
|
||||
{
|
||||
title = "Comment";
|
||||
name = "comment";
|
||||
_graph = graph;
|
||||
|
||||
capabilities |= Capabilities.Selectable | Capabilities.Movable | Capabilities.Deletable | Capabilities.Resizable;
|
||||
pickingMode = PickingMode.Ignore;
|
||||
|
||||
type = UdonGraphElementType.UdonComment;
|
||||
|
||||
if(!string.IsNullOrEmpty(jsonData))
|
||||
{
|
||||
EditorJsonUtility.FromJsonOverwrite(jsonData, _customData);
|
||||
}
|
||||
|
||||
_mainContainer = new VisualElement();
|
||||
_mainContainer.StretchToParentSize();
|
||||
_mainContainer.AddToClassList("mainContainer");
|
||||
Add(_mainContainer);
|
||||
|
||||
_label = new Label();
|
||||
_label.RegisterCallback<MouseDownEvent>(OnLabelClick);
|
||||
_mainContainer.Add(_label);
|
||||
|
||||
_textField = new TextField(1000, true, false, '*');
|
||||
_textField.isDelayed = true;
|
||||
// Support IME
|
||||
_textField.RegisterCallback<FocusInEvent>(evt =>{ Input.imeCompositionMode = IMECompositionMode.On;});
|
||||
_textField.RegisterCallback<FocusOutEvent>(evt => {
|
||||
SetText(_textField.text);
|
||||
Input.imeCompositionMode = IMECompositionMode.Auto;
|
||||
SwitchToEditMode(false);
|
||||
});
|
||||
|
||||
_textField.OnValueChanged((evt) =>
|
||||
{
|
||||
SetText(evt.newValue);
|
||||
SwitchToEditMode(false);
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateFromData()
|
||||
{
|
||||
if(_customData != null)
|
||||
{
|
||||
layer = _customData.layer;
|
||||
if(string.IsNullOrEmpty(_customData.uid))
|
||||
{
|
||||
_customData.uid = Guid.NewGuid().ToString();
|
||||
}
|
||||
uid = _customData.uid;
|
||||
|
||||
SetPosition(_customData.layout);
|
||||
SetText(_customData.title);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnStyleResolved(ICustomStyle style)
|
||||
{
|
||||
base.OnStyleResolved(style);
|
||||
// Something is forcing style! Resetting a few things here, grrr.
|
||||
|
||||
this.style.borderBottomWidth = 1;
|
||||
|
||||
var resizer = this.Q(null, "resizer");
|
||||
if(resizer != null)
|
||||
{
|
||||
resizer.style.paddingTop = 0;
|
||||
resizer.style.paddingLeft = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetPosition(Rect newPos)
|
||||
{
|
||||
newPos = GraphElementExtension.GetSnappedRect(newPos);
|
||||
base.SetPosition(newPos);
|
||||
}
|
||||
|
||||
public override void UpdatePresenterPosition()
|
||||
{
|
||||
base.UpdatePresenterPosition();
|
||||
_customData.layout = GraphElementExtension.GetSnappedRect(GetPosition());
|
||||
this.SaveNewData();
|
||||
}
|
||||
|
||||
private double lastClickTime;
|
||||
private const double doubleClickSpeed = 0.5;
|
||||
|
||||
private void OnLabelClick(MouseDownEvent evt)
|
||||
{
|
||||
var newTime = UnityEditor.EditorApplication.timeSinceStartup;
|
||||
if(newTime - lastClickTime < doubleClickSpeed)
|
||||
{
|
||||
SwitchToEditMode(true);
|
||||
}
|
||||
lastClickTime = newTime;
|
||||
}
|
||||
|
||||
private void SwitchToEditMode(bool switchingToEdit)
|
||||
{
|
||||
if (switchingToEdit)
|
||||
{
|
||||
_mainContainer.Remove(_label);
|
||||
|
||||
_textField.value = _label.text;
|
||||
_mainContainer.Add(_textField);
|
||||
_textField.Focus();
|
||||
_textField.SelectAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
_mainContainer.Remove(_textField);
|
||||
_mainContainer.Add(_label);
|
||||
}
|
||||
MarkDirtyRepaint();
|
||||
}
|
||||
|
||||
public void SetText(string value)
|
||||
{
|
||||
Undo.RecordObject(_graph.graphProgramAsset, "Rename Comment");
|
||||
value = value.TrimEnd();
|
||||
_customData.title = value;
|
||||
_label.text = value;
|
||||
_graph.SaveNewData();
|
||||
MarkDirtyRepaint();
|
||||
}
|
||||
|
||||
public UdonGraphElementData GetData()
|
||||
{
|
||||
return new UdonGraphElementData(UdonGraphElementType.UdonComment, uid, EditorJsonUtility.ToJson(_customData));
|
||||
}
|
||||
|
||||
public class CustomData
|
||||
{
|
||||
public string uid;
|
||||
public Rect layout;
|
||||
public string title = "Comment";
|
||||
public int layer;
|
||||
public Color elementTypeColor;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7e5916b8dd19e4445a9156a457b82ee4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonGraphElement : GraphElement
|
||||
{
|
||||
// Upgrade note - persistenceKey turns into viewDataKey in Unity 2019, this getter will make that transition easier
|
||||
public string uid { get => persistenceKey; set => persistenceKey = value; }
|
||||
internal UdonGraphElementType type = UdonGraphElementType.GraphElement;
|
||||
|
||||
internal UdonGraphElement()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// save this update to the asset
|
||||
public override void UpdatePresenterPosition()
|
||||
{
|
||||
base.UpdatePresenterPosition();
|
||||
this.Reserialize();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public interface IUdonGraphElementDataProvider
|
||||
{
|
||||
UdonGraphElementData GetData();
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class GraphRect
|
||||
{
|
||||
public float x;
|
||||
public float y;
|
||||
public float width;
|
||||
public float height;
|
||||
|
||||
public GraphRect(Rect input)
|
||||
{
|
||||
this.x = Mathf.Round(input.x);
|
||||
this.y = Mathf.Round(input.y);
|
||||
this.width = Mathf.Round(input.width);
|
||||
this.height = Mathf.Round(input.height);
|
||||
}
|
||||
|
||||
public Rect rect => new Rect(this.x, this.y, this.width, this.height);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ba3ecc4c46929404d8c2ec920743b823
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
|
||||
public class UdonGroup : Group, IUdonGraphElementDataProvider
|
||||
{
|
||||
|
||||
public string uid;
|
||||
private CustomData _customData = new CustomData();
|
||||
private UdonGraph _graph;
|
||||
|
||||
public static UdonGroup Create(string value, Rect position, UdonGraph graph)
|
||||
{
|
||||
var group = new UdonGroup("", graph);
|
||||
|
||||
group.uid = Guid.NewGuid().ToString();
|
||||
|
||||
// make sure rect size is not 0
|
||||
position.width = position.width > 0 ? position.width : 128;
|
||||
position.height = position.height > 0 ? position.height : 128;
|
||||
|
||||
group._customData.uid = group.uid;
|
||||
group._customData.layout = position;
|
||||
group._customData.title = value;
|
||||
|
||||
return group;
|
||||
}
|
||||
|
||||
public static UdonGroup Create(UdonGraphElementData elementData, UdonGraph graph)
|
||||
{
|
||||
return new UdonGroup(elementData.jsonData, graph);
|
||||
}
|
||||
|
||||
// current order of operations issue when creating a group from the context menu means this isn't set until first save. This allows us to force it.
|
||||
public void UpdateDataId()
|
||||
{
|
||||
_customData.uid = uid;
|
||||
}
|
||||
|
||||
// Build a Group from jsonData, save to userData
|
||||
private UdonGroup(string jsonData, UdonGraph graph)
|
||||
{
|
||||
title = "Group";
|
||||
_graph = graph;
|
||||
|
||||
if (!string.IsNullOrEmpty(jsonData))
|
||||
{
|
||||
EditorJsonUtility.FromJsonOverwrite(jsonData, _customData);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnPersistentDataReady()
|
||||
{
|
||||
base.OnPersistentDataReady();
|
||||
if(_customData != null)
|
||||
{
|
||||
// Propagate data to useful places
|
||||
title = _customData.title;
|
||||
layer = _customData.layer;
|
||||
if (string.IsNullOrEmpty(_customData.uid))
|
||||
{
|
||||
_customData.uid = Guid.NewGuid().ToString();
|
||||
}
|
||||
uid = _customData.uid;
|
||||
|
||||
SetPosition(_customData.layout);
|
||||
|
||||
// Add all elements from graph to self
|
||||
var childUIDs = _customData.containedElements;
|
||||
if (childUIDs != null)
|
||||
{
|
||||
foreach (var item in childUIDs)
|
||||
{
|
||||
var childNode = _graph.GetElementByGuid(item);
|
||||
if (childNode != null)
|
||||
{
|
||||
if (!ContainsElement(childNode))
|
||||
{
|
||||
AddElement(childNode);
|
||||
// nodes can wind up behind the group since they're added first
|
||||
childNode.BringToFront();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool containsElement(string uid)
|
||||
{
|
||||
return _customData.containedElements.Contains(uid);
|
||||
}
|
||||
|
||||
public override void SetPosition(Rect newPos)
|
||||
{
|
||||
newPos = GraphElementExtension.GetSnappedRect(newPos);
|
||||
base.SetPosition(newPos);
|
||||
}
|
||||
|
||||
// Save data to asset after new position set
|
||||
public override void UpdatePresenterPosition()
|
||||
{
|
||||
base.UpdatePresenterPosition();
|
||||
_customData.layout = GraphElementExtension.GetSnappedRect(GetPosition());
|
||||
this.SaveNewData();
|
||||
}
|
||||
|
||||
// Save data to asset after rename
|
||||
protected override void OnGroupRenamed(string oldName, string newName)
|
||||
{
|
||||
// limit name to 100 characters
|
||||
title = newName.Substring(0, Mathf.Min(newName.Length, 100));
|
||||
_customData.title = title;
|
||||
this.SaveNewData();
|
||||
}
|
||||
|
||||
protected override void OnElementsAdded(IEnumerable<GraphElement> elements)
|
||||
{
|
||||
base.OnElementsAdded(elements);
|
||||
foreach (var element in elements)
|
||||
{
|
||||
if (!_customData.containedElements.Contains(element.persistenceKey))
|
||||
{
|
||||
// don't add StackNode children directly to group, messes up reload
|
||||
if(!(element.parent is UdonStackNode))
|
||||
{
|
||||
_customData.containedElements.Add(element.persistenceKey);
|
||||
}
|
||||
}
|
||||
|
||||
// Set group variable on UdonNodes
|
||||
if (element is UdonNode)
|
||||
{
|
||||
(element as UdonNode).group = this;
|
||||
}
|
||||
if (element is UdonComment)
|
||||
{
|
||||
(element as UdonComment).group = this;
|
||||
}
|
||||
}
|
||||
this.SaveNewData();
|
||||
}
|
||||
|
||||
protected override void OnElementsRemoved(IEnumerable<GraphElement> elements)
|
||||
{
|
||||
base.OnElementsRemoved(elements);
|
||||
foreach (var element in elements)
|
||||
{
|
||||
if (_customData.containedElements.Contains(element.persistenceKey))
|
||||
{
|
||||
_customData.containedElements.Remove(element.persistenceKey);
|
||||
if(element is UdonNode)
|
||||
{
|
||||
(element as UdonNode).group = null;
|
||||
}
|
||||
else if (element is UdonComment)
|
||||
{
|
||||
(element as UdonComment).group = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.SaveNewData();
|
||||
}
|
||||
|
||||
public override bool AcceptsElement(GraphElement element, ref string reasonWhyNotAccepted)
|
||||
{
|
||||
return base.AcceptsElement(element, ref reasonWhyNotAccepted);
|
||||
}
|
||||
|
||||
public UdonGraphElementData GetData()
|
||||
{
|
||||
return new UdonGraphElementData(UdonGraphElementType.UdonGroup, uid, EditorJsonUtility.ToJson(_customData));
|
||||
}
|
||||
|
||||
public class CustomData
|
||||
{
|
||||
public string uid;
|
||||
public Rect layout;
|
||||
public List<string> containedElements = new List<string>();
|
||||
public string title;
|
||||
public int layer;
|
||||
public Color elementTypeColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1b8045222a10ce04b815642b9cd5ca17
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonMinimap : MiniMap
|
||||
{
|
||||
private CustomData _customData = new CustomData();
|
||||
private UdonGraph _graph;
|
||||
|
||||
public UdonMinimap(UdonGraph graph)
|
||||
{
|
||||
_graph = graph;
|
||||
|
||||
name = "UdonMap";
|
||||
maxWidth = 200;
|
||||
maxHeight = 100;
|
||||
anchored = false;
|
||||
SetPosition(_customData.layout);
|
||||
}
|
||||
|
||||
private void SaveNewData()
|
||||
{
|
||||
if (!_graph.IsReloading)
|
||||
{
|
||||
_graph.SaveNewData();
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVisible(bool value)
|
||||
{
|
||||
visible = value;
|
||||
_customData.visible = value;
|
||||
SaveNewData();
|
||||
}
|
||||
|
||||
public override void UpdatePresenterPosition()
|
||||
{
|
||||
_customData.layout = GetPosition();
|
||||
SaveNewData();
|
||||
}
|
||||
|
||||
public UdonGraphElementData GetData()
|
||||
{
|
||||
return new UdonGraphElementData(UdonGraphElementType.Minimap, persistenceKey, JsonUtility.ToJson(_customData));
|
||||
}
|
||||
|
||||
internal void LoadData(UdonGraphElementData data)
|
||||
{
|
||||
JsonUtility.FromJsonOverwrite(data.jsonData, _customData);
|
||||
SetPosition(_customData.layout);
|
||||
visible = _customData.visible;
|
||||
}
|
||||
|
||||
public class CustomData
|
||||
{
|
||||
public bool visible = true;
|
||||
public Rect layout = new Rect(new Vector2(10, 20), Vector2.zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b006d67642298f04e895b6709ef12429
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+877
@@ -0,0 +1,877 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using EditorUI = UnityEditor.Experimental.UIElements;
|
||||
using EngineUI = UnityEngine.Experimental.UIElements;
|
||||
|
||||
using VRC.Udon.Graph.Interfaces;
|
||||
using VRC.Udon.Graph;
|
||||
using VRC.Udon.Serialization;
|
||||
using UnityEditor;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonNode : Node, IEdgeConnectorListener
|
||||
{
|
||||
// name is inherited from parent VisualElement class
|
||||
public Type type;
|
||||
public GameObject gameObject;
|
||||
private UdonGraph _graphView;
|
||||
private EditorUI.PopupField<string> _popup;
|
||||
public UdonNodeDefinition definition;
|
||||
public UdonNodeData data;
|
||||
public Dictionary<int, UdonPort> portsIn;
|
||||
public Dictionary<int, UdonPort> portsOut;
|
||||
public List<UdonPort> portsFlowIn;
|
||||
public List<UdonPort> portsFlowOut;
|
||||
private INodeRegistry _registry;
|
||||
public UdonGroup group;
|
||||
|
||||
// Overload handling
|
||||
private IList<UdonNodeDefinition> overloadDefinitions;
|
||||
private readonly Dictionary<UdonNodeDefinition, string> _optionNameCache = new Dictionary<UdonNodeDefinition, string>();
|
||||
private readonly Dictionary<UdonNodeDefinition, string> _cleanerOptionNameCache = new Dictionary<UdonNodeDefinition, string>();
|
||||
|
||||
public UdonGraph Graph { get => _graphView; private set { } }
|
||||
public INodeRegistry Registry { get => _registry; private set { } }
|
||||
|
||||
private readonly string[] _specialFlows =
|
||||
{
|
||||
"Block",
|
||||
"Branch",
|
||||
"For",
|
||||
"Foreach",
|
||||
"While",
|
||||
};
|
||||
|
||||
// Upgrade note - persistenceKey turns into viewDataKey in Unity 2019, this getter will make that transition easier
|
||||
public string uid { get => persistenceKey; set => persistenceKey = value; }
|
||||
|
||||
// Called when creating from Asset
|
||||
public static UdonNode CreateNode(UdonNodeData nodeData, UdonGraph view)
|
||||
{
|
||||
UdonNodeDefinition definition = UdonEditorManager.Instance.GetNodeDefinition(nodeData.fullName);
|
||||
if (definition == null)
|
||||
{
|
||||
Debug.LogError($"Cannot create node {nodeData.fullName} because there is no matching Node Definition");
|
||||
return null;
|
||||
}
|
||||
return CreateNode(definition, view, nodeData);
|
||||
}
|
||||
|
||||
// Called when creating from scratch
|
||||
public static UdonNode CreateNode(UdonNodeDefinition definition, UdonGraph view, UdonNodeData nodeData = null)
|
||||
{
|
||||
return new UdonNode(definition, view, nodeData);
|
||||
}
|
||||
|
||||
// Constructor is private to force all paths through Static factory method
|
||||
private UdonNode(UdonNodeDefinition nodeDefinition, UdonGraph view, UdonNodeData nodeData = null)
|
||||
{
|
||||
_graphView = view;
|
||||
definition = nodeDefinition;
|
||||
|
||||
var registry = UdonGraphExtensions.GetRegistryForDefinition(nodeDefinition);
|
||||
if(registry != null)
|
||||
{
|
||||
this._registry = registry;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"Couldn't find registry for {nodeDefinition.fullName}");
|
||||
}
|
||||
|
||||
VisualContainer titleContainer = new VisualContainer()
|
||||
{
|
||||
name = "title-container",
|
||||
};
|
||||
this.Q("title").Insert(0, titleContainer);
|
||||
|
||||
titleContainer.Add(this.Q("title-label"));
|
||||
|
||||
var subtitle = new EngineUI.Label("")
|
||||
{
|
||||
name = "subtitle",
|
||||
};
|
||||
bool skipSubtitle = (
|
||||
_specialFlows.Contains(nodeDefinition.fullName)
|
||||
|| nodeDefinition.fullName.EndsWith("et_Variable")
|
||||
|| nodeDefinition.fullName.StartsWithCached("Const_")
|
||||
);
|
||||
|
||||
if (!skipSubtitle)
|
||||
{
|
||||
titleContainer.Insert(0, subtitle);
|
||||
}
|
||||
|
||||
// Set Title
|
||||
var displayTitle = UdonGraphExtensions.PrettyString(nodeDefinition.name).FriendlyNameify();
|
||||
if (displayTitle == "Const_VRCUdonCommonInterfacesIUdonEventReceiver")
|
||||
{
|
||||
displayTitle = "UdonBehaviour";
|
||||
}
|
||||
else if(displayTitle == "==" || displayTitle == "!=" || displayTitle == "+")
|
||||
{
|
||||
displayTitle = $"{nodeDefinition.type.Name} {displayTitle}";
|
||||
}
|
||||
|
||||
if (displayTitle.StartsWith("Op "))
|
||||
displayTitle = displayTitle.Replace("Op ", "");
|
||||
|
||||
title = displayTitle;
|
||||
|
||||
name = nodeDefinition.fullName;
|
||||
elementTypeColor = UnityEngine.Random.ColorHSV(0.5f, 0.6f, 0.1f, 0.2f, 0.8f, 0.9f);
|
||||
|
||||
string className = nodeDefinition.name.Split(' ').FirstOrDefault().Split('_').FirstOrDefault();
|
||||
AddToClassList(className);
|
||||
// Null is a type here, so handle it special
|
||||
if(nodeDefinition.type == null)
|
||||
{
|
||||
AddToClassList("null");
|
||||
}
|
||||
else
|
||||
{
|
||||
AddToClassList(nodeDefinition.type.Namespace);
|
||||
AddToClassList(nodeDefinition.type.Name);
|
||||
}
|
||||
AddToClassList(displayTitle.Replace(" ", "").ToLowerFirstChar());
|
||||
if (nodeDefinition.fullName.Contains('_'))
|
||||
{
|
||||
AddToClassList(nodeDefinition.fullName.Substring(0, nodeDefinition.fullName.IndexOf('_')));
|
||||
}
|
||||
|
||||
if (!skipSubtitle)
|
||||
{
|
||||
if (nodeDefinition.fullName.StartsWith("Event_"))
|
||||
{
|
||||
subtitle.text = "Event";
|
||||
}
|
||||
else
|
||||
{
|
||||
subtitle.text = className;
|
||||
// temp title shenanigans
|
||||
int firstSplit = nodeDefinition.fullName.IndexOf("__")+2;
|
||||
if (firstSplit > 1)
|
||||
{
|
||||
int lastSplit = nodeDefinition.fullName.IndexOf("__", firstSplit);
|
||||
int stringLength = (lastSplit > -1) ? lastSplit - firstSplit : nodeDefinition.fullName.Length - firstSplit;
|
||||
string line2 = nodeDefinition.fullName.Substring(firstSplit, stringLength).Replace("_", " ").UppercaseFirst();
|
||||
if (line2.StartsWith("Op "))
|
||||
{
|
||||
line2 = line2.Replace("Op ", "");
|
||||
subtitle.text = nodeDefinition.type.Name;
|
||||
}
|
||||
title = line2;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log($"Couldn't find classname for {nodeDefinition.fullName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create or validate nodeData
|
||||
if (nodeData == null)
|
||||
{
|
||||
data = _graphView.graphData.AddNode(nodeDefinition.fullName);
|
||||
PopulateDefaultValues();
|
||||
ValidateNodeData();
|
||||
}
|
||||
else
|
||||
{
|
||||
data = nodeData;
|
||||
ValidateNodeData();
|
||||
SetPosition(new Rect(data.position.x, data.position.y, 0, 0));
|
||||
}
|
||||
|
||||
uid = data.uid;
|
||||
|
||||
// Fill in all fields, etc and add to the graph view
|
||||
if (UdonGraphExtensions.ShouldShowDocumentationLink(definition))
|
||||
{
|
||||
DrawHelpButton();
|
||||
}
|
||||
|
||||
// Show overloads for nodes EXCEPT type, those have too many entries and break Unity UI
|
||||
if (!nodeDefinition.fullName.StartsWith("Type_"))
|
||||
{
|
||||
RefreshPopup();
|
||||
}
|
||||
LayoutPorts(nodeDefinition);
|
||||
|
||||
view.MarkSceneDirty();
|
||||
}
|
||||
|
||||
private void DrawHelpButton()
|
||||
{
|
||||
EngineUI.Button helpButton = new EngineUI.Button(ShowNodeDocs)
|
||||
{
|
||||
name = "help-button",
|
||||
};
|
||||
helpButton.Add(new EngineUI.TextElement()
|
||||
{
|
||||
name = "icon",
|
||||
text = "?"
|
||||
});
|
||||
titleButtonContainer.Add(helpButton);
|
||||
}
|
||||
|
||||
private void ShowNodeDocs()
|
||||
{
|
||||
string url = UdonGraphExtensions.GetDocumentationLink(definition);
|
||||
if (!string.IsNullOrEmpty(url))
|
||||
{
|
||||
Help.BrowseURL(url);
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetPosition(Rect newPos)
|
||||
{
|
||||
newPos.position = GraphElementExtension.GetSnappedPosition(newPos.position);
|
||||
base.SetPosition(newPos);
|
||||
data.position = newPos.position;
|
||||
}
|
||||
|
||||
public override void UpdatePresenterPosition()
|
||||
{
|
||||
base.UpdatePresenterPosition();
|
||||
this.Reserialize();
|
||||
}
|
||||
|
||||
private void RefreshPopup()
|
||||
{
|
||||
// Get overloads, draw them if we have more than one signature for this method
|
||||
overloadDefinitions = CacheOverloads();
|
||||
if (overloadDefinitions != null && overloadDefinitions.Count > 1)
|
||||
{
|
||||
// Get index of currently selected (could cache this on node instead)
|
||||
// TODO: switch to just reading this from Popup, which probably stores it
|
||||
int currentIndex = 0;
|
||||
for (int i = 0; i < overloadDefinitions.Count; i++)
|
||||
{
|
||||
if (overloadDefinitions.ElementAt(i).fullName != name)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
currentIndex = i;
|
||||
break;
|
||||
}
|
||||
|
||||
// Build dropdown list
|
||||
List<string> options = new List<string>();
|
||||
for (int i = 0; i < overloadDefinitions.Count; i++)
|
||||
{
|
||||
UdonNodeDefinition nodeDefinition = overloadDefinitions.ElementAt(i);
|
||||
if (!_optionNameCache.TryGetValue(nodeDefinition, out string optionName))
|
||||
{
|
||||
optionName = nodeDefinition.fullName;
|
||||
// don't add overload types that take pointers, not supported
|
||||
string[] splitOptionName = optionName.Split(new[] { "__" }, StringSplitOptions.None);
|
||||
if (splitOptionName.Length >= 3)
|
||||
{
|
||||
optionName = $"({splitOptionName[2].Replace("_", ", ")})";
|
||||
}
|
||||
optionName = optionName.FriendlyNameify();
|
||||
_optionNameCache.Add(nodeDefinition, optionName);
|
||||
}
|
||||
|
||||
if (!_cleanerOptionNameCache.TryGetValue(nodeDefinition, out string cleanerOptionName))
|
||||
{
|
||||
cleanerOptionName =
|
||||
optionName.Replace("UnityEngine", "").Replace("System", "").Replace("Variable_", "");
|
||||
_cleanerOptionNameCache.Add(nodeDefinition, cleanerOptionName);
|
||||
}
|
||||
|
||||
options.Add(cleanerOptionName);
|
||||
// optionName is what was used as the tooltip. Do we need the tooltip?
|
||||
}
|
||||
|
||||
// Clear out old one
|
||||
if (inputContainer.Contains(_popup))
|
||||
{
|
||||
inputContainer.Remove(_popup);
|
||||
}
|
||||
|
||||
_popup = new EditorUI.PopupField<string>(options, currentIndex);
|
||||
_popup.OnValueChanged((e) =>
|
||||
{
|
||||
// TODO - store data in the dropdown and use formatListItemCallback?
|
||||
data.fullName = overloadDefinitions.ElementAt(_popup.index).fullName;
|
||||
_graphView.Reload();
|
||||
});
|
||||
inputContainer.Add(_popup);
|
||||
}
|
||||
}
|
||||
|
||||
private List<UdonNodeDefinition> CacheOverloads()
|
||||
{
|
||||
string baseIdentifier = name;
|
||||
string[] splitBaseIdentifier = baseIdentifier.Split(new[] { "__" }, StringSplitOptions.None);
|
||||
if (splitBaseIdentifier.Length >= 2)
|
||||
{
|
||||
baseIdentifier = $"{splitBaseIdentifier[0]}__{splitBaseIdentifier[1]}__";
|
||||
}
|
||||
|
||||
if (baseIdentifier.StartsWithCached("Const_"))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (baseIdentifier.StartsWithCached("Type_"))
|
||||
{
|
||||
baseIdentifier = "Type_";
|
||||
}
|
||||
|
||||
if (baseIdentifier.StartsWithCached("Variable_"))
|
||||
{
|
||||
baseIdentifier = "Variable_";
|
||||
}
|
||||
|
||||
// This used to be cached on graph instead of calculated per-node
|
||||
// TODO: cache this somewhere, maybe UdonEditorManager? Is that worth it for performance?
|
||||
IEnumerable<UdonNodeDefinition> matchingNodeDefinitions =
|
||||
UdonEditorManager.Instance.GetNodeDefinitions(baseIdentifier);
|
||||
|
||||
var result = new List<UdonNodeDefinition>();
|
||||
foreach (var definition in matchingNodeDefinitions)
|
||||
{
|
||||
// don't add definitions with pointer parameters, not supported in Udon
|
||||
if (!definition.fullName.Contains('*'))
|
||||
{
|
||||
result.Add(definition);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
internal void RestoreConnections()
|
||||
{
|
||||
RestoreInputs();
|
||||
RestoreFlows();
|
||||
}
|
||||
|
||||
private void RestoreFlows()
|
||||
{
|
||||
for (int i = 0; i < data.flowUIDs.Length; i++)
|
||||
{
|
||||
// skip if flow uid is empty
|
||||
string nodeUID = data.flowUIDs[i];
|
||||
if (string.IsNullOrEmpty(nodeUID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find connected node via Graph
|
||||
UdonNode connectedNode = _graphView.GetNodeByGuid(nodeUID) as UdonNode;
|
||||
if (connectedNode == null)
|
||||
{
|
||||
Debug.Log($"Couldn't find node with GUID {nodeUID}");
|
||||
}
|
||||
|
||||
// Trying to move a Block's flow that was left at the end to the beginning
|
||||
if (portsFlowOut != null && i >= portsFlowOut.Count)
|
||||
{
|
||||
Debug.LogWarning($"Trying to restore flow to {connectedNode.name} from a non-existent port, skipping");
|
||||
|
||||
for (int j = 0; j < data.flowUIDs.Length; j++)
|
||||
{
|
||||
bool didRestoreFlow = false;
|
||||
if (string.IsNullOrEmpty(data.flowUIDs[j]))
|
||||
{
|
||||
data.flowUIDs[j] = data.flowUIDs[i];
|
||||
data.flowUIDs[i] = "";
|
||||
didRestoreFlow = true;
|
||||
}
|
||||
if (didRestoreFlow)
|
||||
{
|
||||
RestoreFlows();
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
UdonPort sourcePort = null;
|
||||
// Edge case, but its possible that this is null in broken graphs
|
||||
// Skip if we can't find the source port
|
||||
if (portsFlowOut != null)
|
||||
{
|
||||
sourcePort = portsFlowOut.Count > 1 ? portsFlowOut[i] : portsFlowOut.FirstOrDefault();
|
||||
if (sourcePort == null)
|
||||
{
|
||||
Debug.LogError($"Failed to find output flow port for node {uid}");
|
||||
// clear the flow uid, user will have to reconnect by hand
|
||||
data.flowUIDs[i] = "";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Failed to find output flow port for node {uid}");
|
||||
// clear the flow uid, user will have to reconnect by hand
|
||||
data.flowUIDs[i] = "";
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
UdonPort destPort = null;
|
||||
// Edge case, but its possible that this is null in broken graphs
|
||||
if(connectedNode.portsFlowIn != null)
|
||||
{
|
||||
destPort = connectedNode.portsFlowIn.FirstOrDefault();
|
||||
if (destPort == null)
|
||||
{
|
||||
Debug.LogError($"Failed to find input flow port node node {nodeUID}");
|
||||
// clear the flow uid, user will have to reconnect by hand
|
||||
data.flowUIDs[i] = "";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogError($"Failed to find input flow port node node {nodeUID}");
|
||||
// clear the flow uid, user will have to reconnect by hand
|
||||
data.flowUIDs[i] = "";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Passed the tests! ready to connect
|
||||
var edge = sourcePort.ConnectTo(destPort);
|
||||
edge.AddToClassList("flow");
|
||||
_graphView.AddElement(edge);
|
||||
}
|
||||
}
|
||||
|
||||
private void RestoreInputs()
|
||||
{
|
||||
for (int i = 0; i < definition.Inputs.Count; i++)
|
||||
{
|
||||
// Skip to next input if we don't have a node to check at this index
|
||||
if (data.nodeUIDs.Length <= i)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip to next input if we have a bad node reference
|
||||
if (string.IsNullOrEmpty(data.nodeUIDs[i]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// get otherIndex. not 100% sure what this refers to yet, maybe a port index?
|
||||
string[] splitUID = data.nodeUIDs[i].Split('|');
|
||||
string nodeUID = splitUID[0];
|
||||
int otherIndex = 0;
|
||||
if (splitUID.Length > 1)
|
||||
{
|
||||
otherIndex = int.Parse(splitUID[1]);
|
||||
}
|
||||
// Skip if we don't have a good uid for the other node
|
||||
if (string.IsNullOrEmpty(nodeUID))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find connected node via Graph
|
||||
UdonNode connectedNode = _graphView.GetNodeByGuid(nodeUID) as UdonNode;
|
||||
if (connectedNode == null)
|
||||
{
|
||||
Debug.Log($"Couldn't find node with GUID {nodeUID}");
|
||||
}
|
||||
|
||||
// No matching port for this data, skip
|
||||
if (!portsIn.TryGetValue(i, out UdonPort destPort))
|
||||
{
|
||||
Debug.LogError($"Failed to find input data slot (index {i}) for node {uid} {data.fullName}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Copied from Legacy, not sure what conditions would cause this
|
||||
if (otherIndex < 0 || connectedNode.portsOut.Keys.Count <= otherIndex)
|
||||
{
|
||||
otherIndex = 0;
|
||||
}
|
||||
|
||||
// skip if we can't find the sourcePort - comment better once you understand what this is exactly
|
||||
if (!connectedNode.portsOut.TryGetValue(otherIndex, out UdonPort sourcePort))
|
||||
{
|
||||
Debug.LogError($"Failed to find output data slot for node {nodeUID}");
|
||||
continue;
|
||||
}
|
||||
|
||||
// Passed the tests! ready to connect
|
||||
var edge = sourcePort.ConnectTo(destPort);
|
||||
_graphView.AddElement(edge);
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy, haven't gone through yet
|
||||
void ValidateNodeData()
|
||||
{
|
||||
bool modifiedData = false;
|
||||
for (int i = 0; i < data.nodeValues.Length; i++)
|
||||
{
|
||||
if (definition.Inputs.Count <= i)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Type expectedType = definition.Inputs[i].type;
|
||||
|
||||
if (data.nodeValues[i] == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
object value = data.nodeValues[i].Deserialize();
|
||||
if (value == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!expectedType.IsInstanceOfType(value))
|
||||
{
|
||||
data.nodeValues[i] = SerializableObjectContainer.Serialize(null, expectedType);
|
||||
modifiedData = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (modifiedData)
|
||||
{
|
||||
_graphView.ReSerializeData();
|
||||
}
|
||||
}
|
||||
|
||||
void PopulateDefaultValues()
|
||||
{
|
||||
// No default values so I'm just...making them?
|
||||
int count = definition.Inputs.Count;
|
||||
|
||||
data.nodeValues = new SerializableObjectContainer[count];
|
||||
data.nodeUIDs = new string[count];
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
object value = definition.defaultValues.Count > i ? definition.defaultValues[i] : default;
|
||||
data.nodeValues[i] = SerializableObjectContainer.Serialize(value, definition.Inputs[i].type);
|
||||
}
|
||||
}
|
||||
|
||||
private enum VariableNodeType { Get, Set};
|
||||
|
||||
// renamed from MakePorts to match Legacy implementation for now
|
||||
private void LayoutPorts(UdonNodeDefinition udonNodeDefinition)
|
||||
{
|
||||
SetupFlowPorts(udonNodeDefinition);
|
||||
|
||||
// Don't setup in ports for Get_Variable node types, instead add variable popup
|
||||
if (name.CompareTo("Get_Variable") == 0)
|
||||
{
|
||||
AddVariablePopup(VariableNodeType.Get);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Add Variable popup and in-ports for Set_Variable
|
||||
if (name.CompareTo("Set_Variable") == 0)
|
||||
{
|
||||
AddVariablePopup(VariableNodeType.Set);
|
||||
}
|
||||
|
||||
SetupInPorts(udonNodeDefinition);
|
||||
}
|
||||
|
||||
SetupOutPorts(udonNodeDefinition);
|
||||
|
||||
RefreshExpandedState();
|
||||
RefreshPorts();
|
||||
}
|
||||
|
||||
// TODO: Test this again after we have the new graph serializing the addition of nodes
|
||||
private void AddVariablePopup(VariableNodeType varType)
|
||||
{
|
||||
// Legacy method of determining currently selected index
|
||||
// TODO: upgrade this logic path from the legacy method of determining Variable indices
|
||||
// Get Variable nodes only have one value, get it and deserialize
|
||||
var value = data.nodeValues[0].Deserialize();
|
||||
|
||||
// Make copy of options so we can add the Create New Variable option
|
||||
List<string> options = new List<string>(_graphView.GetVariableNames)
|
||||
{
|
||||
"Create New Variable"
|
||||
};
|
||||
|
||||
// Get value of selected node in rather roundabout way
|
||||
int currentIndex = _graphView.GetVariableNodes
|
||||
.IndexOf(_graphView.GetVariableNodes.FirstOrDefault(v => v.uid == (string)value));
|
||||
|
||||
if (currentIndex < 0)
|
||||
{
|
||||
Debug.LogWarning($"Node {name} didn't have a variable assigned, removing");
|
||||
_graphView.graphData.RemoveNode(data);
|
||||
_graphView.RemoveElement(this);
|
||||
_graphView.Reload();
|
||||
return;
|
||||
}
|
||||
|
||||
// Create popup, set current value and set function to update data when it's changed.
|
||||
var popup = new EditorUI.PopupField<string>(options, currentIndex);
|
||||
popup.OnValueChanged((e) => {
|
||||
// Test whether we've selected an existing variable or the 'Create New Variable' option
|
||||
if(popup.index < _graphView.GetVariableNames.Count)
|
||||
{
|
||||
// not currently using event value, which is variable name. Instead using legacy method of comparing index to graph variable nodes array index
|
||||
string newUid = _graphView.GetVariableNodes[popup.index].uid;
|
||||
// Get Variable nodes only have one entry, so index is 0 below
|
||||
SetNewValue(newUid, 0);
|
||||
// include 'Set' in title for Set Variable, just name for Get Variable
|
||||
title = (varType == VariableNodeType.Set ? "Set " : "") + e.newValue;
|
||||
_graphView.Reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
// User selected 'Create New Variable'
|
||||
string newUid = _graphView.AddNewVariable();
|
||||
SetNewValue(newUid, 0, typeof(string));
|
||||
// TODO: see if we can remove the need for a second reload here
|
||||
_graphView.Reload();
|
||||
}
|
||||
});
|
||||
|
||||
string startingUid = _graphView.GetVariableNodes[currentIndex].uid;
|
||||
SetNewValue(startingUid, 0);
|
||||
|
||||
// include 'Set' in title for Set Variable, just name for Get Variable
|
||||
title = (varType == VariableNodeType.Set ? "Set " : "") + _graphView.GetVariableNames[currentIndex];
|
||||
|
||||
// Add newly created popup to node
|
||||
inputContainer.Add(popup);
|
||||
}
|
||||
|
||||
public void SetNewValue(object newValue, int index, Type inType = null)
|
||||
{
|
||||
data.nodeValues[index] = SerializableObjectContainer.Serialize(newValue, inType);
|
||||
this.Reserialize();
|
||||
}
|
||||
|
||||
private void SetupOutPorts(UdonNodeDefinition udonNodeDefinition)
|
||||
{
|
||||
portsOut = new Dictionary<int, UdonPort>();
|
||||
for (int i = 0; i < udonNodeDefinition.Outputs.Count; i++)
|
||||
{
|
||||
var item = udonNodeDefinition.Outputs[i];
|
||||
|
||||
// Convert object type to variable type for Get_Variable nodes, or run them through the SlotTypeConverter for all other nodes
|
||||
Type type = (udonNodeDefinition.fullName.Contains("Get_Variable")) ?
|
||||
GetTypeForDefinition(udonNodeDefinition) :
|
||||
UdonGraphExtensions.SlotTypeConverter(item.type, udonNodeDefinition.fullName);
|
||||
|
||||
string label = UdonGraphExtensions.FriendlyTypeName(type).FriendlyNameify();
|
||||
if (label == "IUdonEventReceiver")
|
||||
{
|
||||
label = "UdonBehaviour";
|
||||
}
|
||||
|
||||
if (item.name != null) label = $"{label} {item.name}";
|
||||
UdonPort port = (UdonPort) UdonPort.Create(label, Direction.Output, this, type, data, i);
|
||||
outputContainer.Add(port);
|
||||
portsOut[i] = port;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupInPorts(UdonNodeDefinition udonNodeDefinition)
|
||||
{
|
||||
portsIn = new Dictionary<int, UdonPort>();
|
||||
|
||||
// Expand node data to hold values for all inputs
|
||||
data.Resize(udonNodeDefinition.Inputs.Count);
|
||||
|
||||
int startIndex = 0;
|
||||
// Skip first input for Set_Variable since that's the eventName which is set via dropdown
|
||||
if (name.CompareTo("Set_Variable") == 0)
|
||||
{
|
||||
startIndex = 1;
|
||||
}
|
||||
|
||||
// Skip inputs for Null and This nodes
|
||||
if (name.Contains("Const_Null") || name.Contains("Const_This"))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int index = startIndex; index < udonNodeDefinition.Inputs.Count; index++)
|
||||
{
|
||||
UdonNodeParameter input = udonNodeDefinition.Inputs[index];
|
||||
string label = "";
|
||||
// TODO: Ask Cubed what this does? Or figure it out.
|
||||
if (udonNodeDefinition.Inputs.Count > index && index >= 0)
|
||||
{
|
||||
label = udonNodeDefinition.Inputs[index].name;
|
||||
}
|
||||
|
||||
if (label == "IUdonEventReceiver")
|
||||
{
|
||||
label = "UdonBehaviour";
|
||||
}
|
||||
|
||||
|
||||
label = label.FriendlyNameify();
|
||||
string typeName = UdonGraphExtensions.FriendlyTypeName(input.type);
|
||||
// skip over types with pointers. Should remove these from included overloads in the first place!
|
||||
if (typeName.Contains('*'))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Convert object type to variable type for Set_Variable nodes, or run them through the SlotTypeConverter for all other nodes
|
||||
Type type = (udonNodeDefinition.fullName.Contains("Set_Variable")) ?
|
||||
type = GetTypeForDefinition(udonNodeDefinition) :
|
||||
UdonGraphExtensions.SlotTypeConverter(input.type, udonNodeDefinition.fullName);
|
||||
|
||||
// not 100% sure if I should use label or typeName here
|
||||
UdonPort p = UdonPort.Create(label, Direction.Input, this, type, data, index) as UdonPort;
|
||||
inputContainer.Add(p);
|
||||
portsIn.Add(index, p);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Type GetTypeForDefinition(UdonNodeDefinition udonNodeDefinition)
|
||||
{
|
||||
string targetUid = data.nodeValues[0].Deserialize().ToString();
|
||||
UdonNodeData varData = _graphView.GetVariableNodes.Where(n => n.uid == targetUid).FirstOrDefault();
|
||||
if (varData != null)
|
||||
{
|
||||
var targetDefinition = UdonEditorManager.Instance.GetNodeDefinition(varData.fullName);
|
||||
if (targetDefinition != null)
|
||||
{
|
||||
return UdonGraphExtensions.SlotTypeConverter(targetDefinition.type, udonNodeDefinition.fullName);
|
||||
}
|
||||
}
|
||||
// if we fail, return generic object type
|
||||
return typeof(object);
|
||||
}
|
||||
|
||||
private void SetupFlowPorts(UdonNodeDefinition udonNodeDefinition)
|
||||
{
|
||||
if (udonNodeDefinition.flow)
|
||||
{
|
||||
portsFlowIn = new List<UdonPort>();
|
||||
portsFlowOut = new List<UdonPort>();
|
||||
|
||||
string label = "";
|
||||
|
||||
int inFlowIndex = -1;
|
||||
int outFlowIndex = -1;
|
||||
// don't add input flow for events, they're called from above
|
||||
if (!udonNodeDefinition.fullName.StartsWith("Event_"))
|
||||
{
|
||||
label = udonNodeDefinition.inputFlowNames.Count > 0 ? udonNodeDefinition.inputFlowNames[0] : "";
|
||||
AddFlowPort(Direction.Input, label, ++inFlowIndex);
|
||||
}
|
||||
|
||||
// add output flow
|
||||
label = udonNodeDefinition.outputFlowNames.Count > 0 ? udonNodeDefinition.outputFlowNames[0] : "";
|
||||
AddFlowPort(Direction.Output, label, ++outFlowIndex);
|
||||
if (_specialFlows.Contains(udonNodeDefinition.fullName))
|
||||
{
|
||||
label = udonNodeDefinition.outputFlowNames.Count > 1 ? udonNodeDefinition.outputFlowNames[1] : "";
|
||||
AddFlowPort(Direction.Output, label, ++outFlowIndex);
|
||||
}
|
||||
|
||||
// Add the number of output flows we need for a Block
|
||||
if (udonNodeDefinition.fullName == "Block")
|
||||
{
|
||||
data.flowUIDs = data.flowUIDs.Where(f => !string.IsNullOrEmpty(f)).ToArray();
|
||||
int connectedFlows = data.flowUIDs.Length;
|
||||
if (connectedFlows > 1)
|
||||
{
|
||||
for (int i = 0; i < connectedFlows - 1; i++)
|
||||
{
|
||||
AddFlowPort(Direction.Output, "", ++outFlowIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void AddFlowPort(Direction d, string label, int index)
|
||||
{
|
||||
UdonPort p = (UdonPort) UdonPort.Create(label, d, this, null, data, index);
|
||||
p.AddToClassList("flow");
|
||||
if(d == Direction.Input)
|
||||
{
|
||||
inputContainer.Add(p);
|
||||
portsFlowIn.Add(p);
|
||||
}
|
||||
else
|
||||
{
|
||||
outputContainer.Add(p);
|
||||
portsFlowOut.Add(p);
|
||||
}
|
||||
}
|
||||
|
||||
private bool HasRecursiveFlow(Port fromSlot, Port toSlot)
|
||||
{
|
||||
// No need to check connections to value slots
|
||||
if (toSlot.portType != null) return false;
|
||||
|
||||
// Check out ports of node being connected TO
|
||||
foreach (var port in (toSlot.node as UdonNode).portsFlowOut)
|
||||
{
|
||||
// if any of its ports connect to fromSlot, it's recursive. using foreach for convenience, should be just one edge
|
||||
foreach (var edge in port.connections)
|
||||
{
|
||||
// if this connection goes to the node that started this all, then it's recursion
|
||||
if(edge.input.node == fromSlot.node)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// Need to run this recursively to check all ports
|
||||
if(HasRecursiveFlow(fromSlot, edge.input))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#region IEdgeConnectorListener
|
||||
public void OnDrop(UnityEditor.Experimental.UIElements.GraphView.GraphView graphView, Edge edge)
|
||||
{
|
||||
if (edge.output != null && edge.input != null && !HasRecursiveFlow(edge.output, edge.input))
|
||||
{
|
||||
edge.output.Connect(edge);
|
||||
edge.input.Connect(edge);
|
||||
graphView.AddElement(edge);
|
||||
|
||||
// Reload block nodes after new connections
|
||||
if(definition.fullName == "Block")
|
||||
{
|
||||
this.Reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void OnDropOutsidePort(Edge edge, Vector2 position)
|
||||
{
|
||||
if (!Settings.SearchOnNoodleDrop) return;
|
||||
|
||||
if (edge.output != null && edge.output.portType != null)
|
||||
{
|
||||
_graphView.OpenPortSearch(edge.output.portType, position, edge.output, Direction.Input);
|
||||
}
|
||||
else if (edge.input != null && edge.input.portType != null)
|
||||
{
|
||||
_graphView.OpenPortSearch(edge.input.portType, position, edge.input, Direction.Output);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcd657bc1dcf357448d27bcfa8c5dc36
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+431
@@ -0,0 +1,431 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using UnityEngine.UI;
|
||||
using VRC.Udon.Graph;
|
||||
using VRC.Udon.Serialization;
|
||||
using EditorUI = UnityEditor.Experimental.UIElements;
|
||||
using EngineUI = UnityEngine.Experimental.UIElements;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
[Serializable]
|
||||
public class UdonPort : Port
|
||||
{
|
||||
public string FullName;
|
||||
private UdonNodeData _udonNodeData;
|
||||
private int _nodeValueIndex;
|
||||
|
||||
private VisualElement _inputField;
|
||||
private VisualElement _inputFieldTypeLabel;
|
||||
|
||||
private IArrayProvider _inspector;
|
||||
|
||||
private bool _waitToReserialize = false;
|
||||
|
||||
protected UdonPort(Orientation portOrientation, Direction portDirection, Capacity portCapacity, Type type) : base(portOrientation, portDirection, portCapacity, type)
|
||||
{
|
||||
}
|
||||
|
||||
public static Port Create(string portName, Direction portDirection, IEdgeConnectorListener connectorListener, Type type, UdonNodeData data, int index, Orientation orientation = Orientation.Horizontal)
|
||||
{
|
||||
Capacity capacity = Capacity.Single;
|
||||
if(portDirection == Direction.Input && type == null || portDirection == Direction.Output && type != null)
|
||||
{
|
||||
capacity = Capacity.Multi;
|
||||
}
|
||||
var port = new UdonPort(orientation, portDirection, capacity, type)
|
||||
{
|
||||
m_EdgeConnector = new EdgeConnector<Edge>(connectorListener),
|
||||
};
|
||||
|
||||
port.portName = portName;
|
||||
port._udonNodeData = data;
|
||||
port._nodeValueIndex = index;
|
||||
|
||||
port.SetupPort();
|
||||
return port;
|
||||
}
|
||||
|
||||
public int GetIndex()
|
||||
{
|
||||
return _nodeValueIndex;
|
||||
}
|
||||
|
||||
private void SetupPort()
|
||||
{
|
||||
this.AddManipulator(m_EdgeConnector);
|
||||
|
||||
tooltip = UdonGraphExtensions.FriendlyTypeName(portType);
|
||||
|
||||
if (portType == null || direction == Direction.Output)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var value = TryGetValueObject(out object result);
|
||||
var field = UdonFieldFactory.CreateField(
|
||||
portType,
|
||||
result,
|
||||
newValue => SetNewValue(newValue)
|
||||
);
|
||||
|
||||
if(field != null)
|
||||
{
|
||||
SetupField(field);
|
||||
}
|
||||
|
||||
if (_udonNodeData.fullName.StartsWith("Const"))
|
||||
{
|
||||
RemoveConnector();
|
||||
}
|
||||
|
||||
UpdateLabel(connected);
|
||||
}
|
||||
|
||||
// Made its own method for now as we have issues auto-converting between string and char in a TextField
|
||||
// TODO: refactor SetupField so we can do just the field.value part separately to combine with this
|
||||
private VisualElement SetupCharField()
|
||||
{
|
||||
TextField field = new TextField();
|
||||
field.AddToClassList("portField");
|
||||
if (TryGetValueObject(out object result))
|
||||
{
|
||||
field.value = UdonGraphExtensions.UnescapeLikeALiteral((char)result);
|
||||
}
|
||||
|
||||
field.isDelayed = true;
|
||||
|
||||
// Special handling for escaping char value
|
||||
field.OnValueChanged((e) =>
|
||||
{
|
||||
if(e.newValue[0] == '\\' && e.newValue.Length > 1)
|
||||
{
|
||||
SetNewValue(UdonGraphExtensions.EscapeLikeALiteral(e.newValue.Substring(0, 2)));
|
||||
}
|
||||
else
|
||||
{
|
||||
SetNewValue(e.newValue[0]);
|
||||
}
|
||||
});
|
||||
_inputField = field;
|
||||
|
||||
// Add label, shown when input is connected. Not shown by default
|
||||
var friendlyName = UdonGraphExtensions.FriendlyTypeName(typeof(char)).FriendlyNameify();
|
||||
var label = new EngineUI.Label(friendlyName);
|
||||
_inputFieldTypeLabel = label;
|
||||
|
||||
return _inputField;
|
||||
}
|
||||
|
||||
private void SetupField(VisualElement field)
|
||||
{
|
||||
// Delay color fields so they don't break UI
|
||||
if(portType.IsAssignableFrom(typeof(EditorUI.ColorField)))
|
||||
{
|
||||
_waitToReserialize = true;
|
||||
}
|
||||
|
||||
// Custom Event fields need their event names sanitized after input and their connectors removed
|
||||
if (_udonNodeData.fullName.CompareTo("Event_Custom") == 0)
|
||||
{
|
||||
var tfield = (TextField)field;
|
||||
tfield.OnValueChanged((e) =>
|
||||
{
|
||||
string newValue = e.newValue.SanitizeVariableName();
|
||||
tfield.value = newValue;
|
||||
SetNewValue(newValue);
|
||||
});
|
||||
RemoveConnector();
|
||||
}
|
||||
|
||||
// Add label, shown when input is connected. Not shown by default
|
||||
var friendlyName = UdonGraphExtensions.FriendlyTypeName(portType).FriendlyNameify();
|
||||
var label = new EngineUI.Label(friendlyName);
|
||||
_inputFieldTypeLabel = label;
|
||||
field.AddToClassList("portField");
|
||||
|
||||
_inputField = field;
|
||||
Add(_inputField);
|
||||
}
|
||||
|
||||
private void RemoveConnector()
|
||||
{
|
||||
this.Q("connector")?.RemoveFromHierarchy();
|
||||
this.Q(null, "connectorText")?.RemoveFromHierarchy();
|
||||
}
|
||||
|
||||
#pragma warning disable 0649 // variable never assigned
|
||||
private EngineUI.Button _editArrayButton;
|
||||
private void EditArray(Type elementType)
|
||||
{
|
||||
// Update Values when 'Save' is clicked
|
||||
if(_inspector != null)
|
||||
{
|
||||
// Update Values
|
||||
SetNewValue(_inspector.GetValues());
|
||||
|
||||
// Remove Inspector
|
||||
_inspector.RemoveFromHierarchy();
|
||||
_inspector = null;
|
||||
|
||||
// Update Button Text
|
||||
_editArrayButton.text = "Edit";
|
||||
return;
|
||||
}
|
||||
|
||||
// Otherwise set up the inspector
|
||||
_editArrayButton.text = "Save";
|
||||
|
||||
// Get value object, null is ok
|
||||
TryGetValueObject(out object value);
|
||||
|
||||
// Create it new
|
||||
Type typedArrayInspector = (typeof(UdonArrayInspector<>)).MakeGenericType(elementType);
|
||||
_inspector = (Activator.CreateInstance(typedArrayInspector, value) as IArrayProvider);
|
||||
|
||||
parent.Add(_inspector as VisualElement);
|
||||
}
|
||||
|
||||
// Update elements on connect
|
||||
public override void Connect(Edge edge)
|
||||
{
|
||||
base.Connect(edge);
|
||||
|
||||
// The below logic is just for Output ports
|
||||
if (edge.input.Equals(this)) return;
|
||||
|
||||
// hide field, show label
|
||||
var input = ((UdonPort)edge.input);
|
||||
input.UpdateLabel(true);
|
||||
|
||||
if (IsReloading())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// update data
|
||||
if (portType == null)
|
||||
{
|
||||
// We are a flow port
|
||||
SetFlowUID(((UdonNode)input.node).uid);
|
||||
}
|
||||
else
|
||||
{
|
||||
// We are a value port, we need to send our info over to the OTHER node
|
||||
string myNodeUid = ((UdonNode)node).uid;
|
||||
input.SetDataFromNewConnection($"{myNodeUid}|{_nodeValueIndex}", input.GetIndex());
|
||||
}
|
||||
|
||||
// in this case, we catch the method on the left node, with input as the right node
|
||||
SendReserializeEvent();
|
||||
}
|
||||
|
||||
public override void OnStopEdgeDragging()
|
||||
{
|
||||
base.OnStopEdgeDragging();
|
||||
|
||||
if (edgeConnector.edgeDragHelper.draggedPort == this)
|
||||
{
|
||||
if (capacity == Capacity.Single && connections.Count() > 0)
|
||||
{
|
||||
// This port could only have one connection. Fixed in Reserialize, need to reload to show the change
|
||||
this.Reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetFlowUID(string newValue)
|
||||
{
|
||||
if (_udonNodeData.flowUIDs.Length <= _nodeValueIndex)
|
||||
{
|
||||
// If we don't have space for this flow value, create a new array
|
||||
// TODO: handle this elsewhere?
|
||||
var newFlowArray = new string[_nodeValueIndex + 1];
|
||||
for (int i = 0; i < _udonNodeData.flowUIDs.Length; i++)
|
||||
{
|
||||
newFlowArray[i] = _udonNodeData.flowUIDs[i];
|
||||
}
|
||||
_udonNodeData.flowUIDs = newFlowArray;
|
||||
|
||||
_udonNodeData.flowUIDs.SetValue(newValue, _nodeValueIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
_udonNodeData.flowUIDs.SetValue(newValue, _nodeValueIndex);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsReloading()
|
||||
{
|
||||
if(node is UdonNode)
|
||||
{
|
||||
return ((UdonNode)node).Graph.IsReloading;
|
||||
}
|
||||
else if(node is UdonStackNode)
|
||||
{
|
||||
return ((UdonStackNode)node).Graph.IsReloading;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetDataFromNewConnection(string uidAndPort, int index)
|
||||
{
|
||||
// can't do this for Reg stack nodes yet so skipping for demo
|
||||
if (_udonNodeData == null) return;
|
||||
|
||||
if (_udonNodeData.nodeUIDs.Length <= _nodeValueIndex)
|
||||
{
|
||||
Debug.Log("Couldn't set it");
|
||||
}
|
||||
else
|
||||
{
|
||||
_udonNodeData.nodeUIDs.SetValue(uidAndPort, index);
|
||||
}
|
||||
}
|
||||
|
||||
// Update elements on disconnect
|
||||
public override void Disconnect(Edge edge)
|
||||
{
|
||||
base.Disconnect(edge);
|
||||
|
||||
// hide label, show field
|
||||
if(direction == Direction.Input)
|
||||
{
|
||||
UpdateLabel(false);
|
||||
}
|
||||
|
||||
if (IsReloading())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// update data
|
||||
if (direction == Direction.Output && portType == null)
|
||||
{
|
||||
// We are a flow port
|
||||
SetFlowUID("");
|
||||
}
|
||||
else if (direction == Direction.Input && portType != null)
|
||||
{
|
||||
// Direction is input
|
||||
// We are a value port
|
||||
SetDataFromNewConnection("", GetIndex());
|
||||
}
|
||||
|
||||
// in this case, we catch the method on the left node, with input as the right node
|
||||
SendReserializeEvent();
|
||||
}
|
||||
|
||||
public void UpdateLabel(bool isConnected)
|
||||
{
|
||||
// Port has a 'connected' bool but it doesn't seem to update, so passing 'isConnected' for now
|
||||
|
||||
if (isConnected)
|
||||
{
|
||||
if (_inputField != null && Contains(_inputField))
|
||||
{
|
||||
_inputField.RemoveFromHierarchy();
|
||||
}
|
||||
if (_inputFieldTypeLabel != null && !Contains(_inputFieldTypeLabel))
|
||||
{
|
||||
Add(_inputFieldTypeLabel);
|
||||
}
|
||||
if(_editArrayButton != null && Contains(_editArrayButton))
|
||||
{
|
||||
_editArrayButton.RemoveFromHierarchy();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_inputField != null && !Contains(_inputField))
|
||||
{
|
||||
Add(_inputField);
|
||||
}
|
||||
if (_inputFieldTypeLabel != null && Contains(_inputFieldTypeLabel))
|
||||
{
|
||||
_inputFieldTypeLabel.RemoveFromHierarchy();
|
||||
}
|
||||
if(_editArrayButton != null && !Contains(_editArrayButton))
|
||||
{
|
||||
Add(_editArrayButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetValueObject(out object result)
|
||||
{
|
||||
result = null;
|
||||
SerializableObjectContainer container = null;
|
||||
// For Get and Set variable nodes, reach into the actual value of the linked variable
|
||||
if (_udonNodeData.fullName.Contains("et_Variable"))
|
||||
{
|
||||
string targetVariableUid = (string)_udonNodeData.nodeValues[0].Deserialize();
|
||||
if (targetVariableUid == null) return false;
|
||||
|
||||
var data = _udonNodeData.GetGraph().FindNode(targetVariableUid);
|
||||
if (data == null) return false;
|
||||
|
||||
container = data.nodeValues[0];
|
||||
}
|
||||
else
|
||||
{
|
||||
container = _udonNodeData.nodeValues[_nodeValueIndex];
|
||||
}
|
||||
|
||||
if (container == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
result = container.Deserialize();
|
||||
if(result == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetNewValue(object newValue)
|
||||
{
|
||||
var container = SerializableObjectContainer.Serialize(newValue, portType);
|
||||
|
||||
if (_udonNodeData.fullName.Contains("Set_Variable"))
|
||||
{
|
||||
// Variables need to be traced back to their actual data points
|
||||
string targetVariableUid = (string)_udonNodeData.nodeValues[0].Deserialize();
|
||||
if (targetVariableUid == null) return;
|
||||
|
||||
var data = _udonNodeData.GetGraph().FindNode(targetVariableUid);
|
||||
if (data == null) return;
|
||||
|
||||
data.nodeValues[0] = container;
|
||||
}
|
||||
else
|
||||
{
|
||||
// regular value setting
|
||||
_udonNodeData.nodeValues[_nodeValueIndex] = container;
|
||||
}
|
||||
|
||||
if (!_waitToReserialize)
|
||||
{
|
||||
SendReserializeEvent();
|
||||
}
|
||||
}
|
||||
|
||||
private void SendReserializeEvent()
|
||||
{
|
||||
if (!IsReloading())
|
||||
{
|
||||
this.Reserialize();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8f83d1d3578dd28498c71a980bca86dd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using EditorUI = UnityEditor.Experimental.UIElements;
|
||||
|
||||
using VRC.Udon.Graph.Interfaces;
|
||||
using VRC.Udon.Graph;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using UnityEditor;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
|
||||
public class UdonStackNode : StackNode, IEdgeConnectorListener, IUdonGraphElementDataProvider
|
||||
{
|
||||
private UdonGraph _graph;
|
||||
private INodeRegistry _registry;
|
||||
public UdonPort refPort;
|
||||
private CustomData _customData = new CustomData();
|
||||
|
||||
public UdonGraph Graph { get => _graph; private set { } }
|
||||
|
||||
// Upgrade note - persistenceKey turns into viewDataKey in Unity 2019, this getter will make that transition easier
|
||||
public string uid { get => persistenceKey; set => persistenceKey = value; }
|
||||
|
||||
// Called when restoring from asset
|
||||
public static UdonStackNode Create(UdonGraphElementData elementData, UdonGraph graph)
|
||||
{
|
||||
return new UdonStackNode(elementData.jsonData, graph);
|
||||
}
|
||||
|
||||
// Called when creating from Search
|
||||
public static UdonStackNode Create(INodeRegistry registry, Vector2 position, UdonGraph graph)
|
||||
{
|
||||
var stackNode = new UdonStackNode("", graph);
|
||||
string registryName = registry.ToString();
|
||||
registryName = registryName.Substring(registryName.LastIndexOf('.') + 1);
|
||||
stackNode._customData.registryName = registryName;
|
||||
stackNode._customData.layout = new Rect(position, Vector2.zero);
|
||||
return stackNode;
|
||||
}
|
||||
|
||||
private string GetSimpleNameForRegistry(INodeRegistry registry)
|
||||
{
|
||||
string registryName = registry.ToString().Replace("NodeRegistry", "").FriendlyNameify();
|
||||
registryName = registryName.Substring(registryName.LastIndexOf(".") + 1);
|
||||
registryName = registryName.Replace("UnityEngine", "");
|
||||
return registryName;
|
||||
}
|
||||
|
||||
private UdonStackNode(string jsonData, UdonGraph graph)
|
||||
{
|
||||
_graph = graph;
|
||||
|
||||
if (!string.IsNullOrEmpty(jsonData))
|
||||
{
|
||||
EditorJsonUtility.FromJsonOverwrite(jsonData, _customData);
|
||||
}
|
||||
}
|
||||
|
||||
private bool _initialized = false;
|
||||
public override void OnPersistentDataReady()
|
||||
{
|
||||
if (_initialized) return;
|
||||
|
||||
if(_customData != null)
|
||||
{
|
||||
// Exit early if we can't find the registry
|
||||
if (!UdonEditorManager.Instance.TryGetRegistry(_customData.registryName, out _registry))
|
||||
{
|
||||
Debug.LogError($"Couldn't make Stack Node from Registry {_customData.registryName}");
|
||||
return;
|
||||
}
|
||||
|
||||
_customData.title = GetSimpleNameForRegistry(_registry);
|
||||
layer = _customData.layer;
|
||||
if (string.IsNullOrEmpty(_customData.uid))
|
||||
{
|
||||
_customData.uid = Guid.NewGuid().ToString();
|
||||
}
|
||||
uid = _customData.uid;
|
||||
name = _customData.title;
|
||||
|
||||
headerContainer.Add(new Label() { text = _customData.title });
|
||||
|
||||
MakePorts(_registry);
|
||||
SetPosition(_customData.layout);
|
||||
UseDefaultStyling();
|
||||
|
||||
// add all contained elements from graph to self
|
||||
var childUIDs = _customData.containedElements;
|
||||
if (childUIDs != null)
|
||||
{
|
||||
foreach (var item in childUIDs)
|
||||
{
|
||||
var childNode = _graph.GetElementByGuid(item);
|
||||
if (childNode != null)
|
||||
{
|
||||
AddElement(childNode);
|
||||
// nodes can wind up behind the stacknode since they're added first
|
||||
childNode.BringToFront();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
this.SaveNewData();
|
||||
}
|
||||
|
||||
private void MakePorts(INodeRegistry registry)
|
||||
{
|
||||
Dictionary<string, UdonNodeDefinition> objectMethods = new Dictionary<string, UdonNodeDefinition>();
|
||||
|
||||
RefreshExpandedState();
|
||||
RefreshPorts();
|
||||
}
|
||||
|
||||
public INodeRegistry GetRegistry()
|
||||
{
|
||||
return _registry;
|
||||
}
|
||||
|
||||
public void OnElementsAdded(IEnumerable<GraphElement> elements)
|
||||
{
|
||||
foreach (var element in elements)
|
||||
{
|
||||
if (!_customData.containedElements.Contains(element.persistenceKey))
|
||||
{
|
||||
_customData.containedElements.Add(element.persistenceKey);
|
||||
Debug.Log($"StackNode {name} adding {element.name}");
|
||||
}
|
||||
}
|
||||
this.SaveNewData();
|
||||
}
|
||||
|
||||
public void OnElementsRemoved(IEnumerable<GraphElement> elements)
|
||||
{
|
||||
foreach (var element in elements)
|
||||
{
|
||||
if (_customData.containedElements.Contains(element.persistenceKey))
|
||||
{
|
||||
Debug.Log($"StackNode {name} removing {element.name}");
|
||||
_customData.containedElements.Remove(element.persistenceKey);
|
||||
}
|
||||
}
|
||||
this.SaveNewData();
|
||||
}
|
||||
|
||||
protected override bool AcceptsElement(GraphElement element, ref int proposedIndex, int maxIndex)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public override void UpdatePresenterPosition()
|
||||
{
|
||||
base.UpdatePresenterPosition();
|
||||
_customData.layout = GetPosition();
|
||||
this.SaveNewData();
|
||||
}
|
||||
|
||||
#region IEdgeConnectorListener
|
||||
|
||||
public void OnDropOutsidePort(Edge edge, Vector2 position)
|
||||
{
|
||||
Debug.Log($"OnDropOutside from {name}");
|
||||
}
|
||||
|
||||
public void OnDrop(EditorUI.GraphView.GraphView graphView, Edge edge)
|
||||
{
|
||||
Debug.Log($"OnDrop from {name}");
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public UdonGraphElementData GetData()
|
||||
{
|
||||
return new UdonGraphElementData(UdonGraphElementType.UdonStackNode, uid, EditorJsonUtility.ToJson(_customData));
|
||||
}
|
||||
|
||||
public class CustomData
|
||||
{
|
||||
public string uid;
|
||||
public Rect layout;
|
||||
public List<string> containedElements = new List<string>();
|
||||
public string registryName;
|
||||
public string title;
|
||||
public int layer;
|
||||
public Color elementTypeColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5cfbbfcd7bb5ad4487f1f9388a3a168
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonVariablesBlackboard : Blackboard, IUdonGraphElementDataProvider
|
||||
{
|
||||
private CustomData _customData = new CustomData();
|
||||
private UdonGraph _graph;
|
||||
|
||||
public UdonVariablesBlackboard(UdonGraph graph)
|
||||
{
|
||||
_graph = graph;
|
||||
title = "Variables";
|
||||
name = "Parameters";
|
||||
scrollable = true;
|
||||
|
||||
// Remove subtitle
|
||||
var subtitle = this.Query<Label>("subTitleLabel").AtIndex(0);
|
||||
if (subtitle != null)
|
||||
{
|
||||
subtitle.RemoveFromHierarchy();
|
||||
}
|
||||
|
||||
// Improve resizer UI
|
||||
style.borderBottomWidth = 1;
|
||||
|
||||
var resizer = this.Q(null, "resizer");
|
||||
if (resizer != null)
|
||||
{
|
||||
resizer.style.paddingTop = 0;
|
||||
resizer.style.paddingLeft = 0;
|
||||
}
|
||||
|
||||
SetPosition(_customData.layout);
|
||||
}
|
||||
|
||||
public void SetVisible(bool value)
|
||||
{
|
||||
visible = value;
|
||||
_customData.visible = value;
|
||||
SaveNewData();
|
||||
}
|
||||
|
||||
public override void UpdatePresenterPosition()
|
||||
{
|
||||
_customData.layout = GetPosition();
|
||||
SaveNewData();
|
||||
}
|
||||
|
||||
private void SaveNewData()
|
||||
{
|
||||
if (!_graph.IsReloading)
|
||||
{
|
||||
_graph.SaveNewData();
|
||||
}
|
||||
}
|
||||
|
||||
public UdonGraphElementData GetData()
|
||||
{
|
||||
return new UdonGraphElementData(UdonGraphElementType.VariablesWindow, persistenceKey, JsonUtility.ToJson(_customData));
|
||||
}
|
||||
|
||||
public class CustomData {
|
||||
public bool visible = true;
|
||||
public Rect layout = new Rect(10, 130, 200, 150);
|
||||
}
|
||||
|
||||
internal void LoadData(UdonGraphElementData data)
|
||||
{
|
||||
JsonUtility.FromJsonOverwrite(data.jsonData, _customData);
|
||||
SetPosition(_customData.layout);
|
||||
this.visible = _customData.visible;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2d0a4730c5f61b247b27b54f280300b5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 69e9f65a249840b4f90c418888aade45
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using System.Collections.Generic;
|
||||
using VRC.Udon.Graph.Interfaces;
|
||||
using VRC.Udon.Graph;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
|
||||
public class UdonFocusedSearchWindow : UdonSearchWindowBase
|
||||
{
|
||||
|
||||
public INodeRegistry targetRegistry;
|
||||
internal List<SearchTreeEntry> _fullRegistry;
|
||||
|
||||
#region ISearchWindowProvider
|
||||
|
||||
override public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context)
|
||||
{
|
||||
|
||||
_fullRegistry = new List<SearchTreeEntry>();
|
||||
|
||||
Texture2D icon = EditorGUIUtility.FindTexture("cs Script Icon");
|
||||
|
||||
var registryName = GetSimpleNameForRegistry(targetRegistry);
|
||||
_fullRegistry.Add(new SearchTreeGroupEntry(new GUIContent($"{registryName} Search"), 0));
|
||||
|
||||
// add Registry Level
|
||||
AddEntriesForRegistry(_fullRegistry, targetRegistry, 1, true);
|
||||
|
||||
return _fullRegistry;
|
||||
}
|
||||
|
||||
override public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context)
|
||||
{
|
||||
// should check for adding duplicate event nodes here, look at Legacy.UdonGraph.TryCreateNodeInstance
|
||||
// Assuming that if we've made it to this point we're allowed to add the node
|
||||
|
||||
UdonNode node;
|
||||
// checking type so we can support selecting registries as well
|
||||
if (entry.userData is UdonNodeDefinition && !_graphView.IsDuplicateEventNode((entry.userData as UdonNodeDefinition).fullName))
|
||||
{
|
||||
node = UdonNode.CreateNode(entry.userData as UdonNodeDefinition, _graphView);
|
||||
_graphView.AddElement(node);
|
||||
node.SetPosition(new Rect(GetGraphPositionFromContext(context), Vector2.zero));
|
||||
node.Select(_graphView, false);
|
||||
|
||||
// Do we need to do this to reserialize, etc?
|
||||
_graphView.ReSerializeData();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move this to Extension
|
||||
private string GetSimpleNameForRegistry(INodeRegistry registry)
|
||||
{
|
||||
string registryName = registry.ToString().Replace("NodeRegistry", "").FriendlyNameify();
|
||||
registryName = registryName.Substring(registryName.LastIndexOf(".") + 1);
|
||||
registryName = registryName.Replace("UnityEngine", "");
|
||||
return registryName;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6581176c97993bb40976acff208bd0b1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using System.Collections.Generic;
|
||||
using VRC.Udon.Graph.Interfaces;
|
||||
using System.Linq;
|
||||
using VRC.Udon.Graph;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
|
||||
public class UdonFullSearchWindow : UdonSearchWindowBase
|
||||
{
|
||||
|
||||
static private List<SearchTreeEntry> _slowRegistryCache;
|
||||
#region ISearchWindowProvider
|
||||
|
||||
override public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context)
|
||||
{
|
||||
if (_slowRegistryCache != null && _slowRegistryCache.Count > 0) return _slowRegistryCache;
|
||||
|
||||
_slowRegistryCache = new List<SearchTreeEntry>
|
||||
{
|
||||
new SearchTreeGroupEntry(new GUIContent("Full Search"), 0)
|
||||
};
|
||||
|
||||
var topRegistries = UdonEditorManager.Instance.GetTopRegistries();
|
||||
|
||||
Texture2D icon = null;
|
||||
var groupEntries = new Dictionary<string, SearchTreeGroupEntry>();
|
||||
foreach (var topRegistry in topRegistries)
|
||||
{
|
||||
string topName = topRegistry.Key.Replace("NodeRegistry", "");
|
||||
|
||||
if (topName != "Udon")
|
||||
{
|
||||
_slowRegistryCache.Add(new SearchTreeGroupEntry(new GUIContent(topName), 1));
|
||||
}
|
||||
|
||||
// get all registries, save into registryName > INodeRegistry Lookup
|
||||
var subRegistries = new Dictionary<string, INodeRegistry>();
|
||||
foreach (KeyValuePair<string, INodeRegistry> registry in topRegistry.Value.OrderBy(s => s.Key))
|
||||
{
|
||||
string baseRegistryName = registry.Key.Replace("NodeRegistry", "").FriendlyNameify().ReplaceFirst(topName, "");
|
||||
string registryName = baseRegistryName.UppercaseFirst();
|
||||
subRegistries.Add(registryName, registry.Value);
|
||||
}
|
||||
|
||||
// Go through each registry entry and add the top-level registry and associated array registry
|
||||
foreach (KeyValuePair<string, INodeRegistry> regEntry in subRegistries)
|
||||
{
|
||||
INodeRegistry registry = regEntry.Value;
|
||||
string registryName = regEntry.Key;
|
||||
|
||||
int level = 2;
|
||||
// Special cases for Udon sub-levels, added at top
|
||||
if (topName == "Udon")
|
||||
{
|
||||
level = 1;
|
||||
if (registryName == "Event" || registryName == "Type")
|
||||
{
|
||||
registryName = $"{registryName}s";
|
||||
}
|
||||
}
|
||||
|
||||
if (!registryName.EndsWith("[]"))
|
||||
{
|
||||
// add Registry Level
|
||||
var groupEntry = new SearchTreeGroupEntry(new GUIContent(registryName, icon), level) { userData = registry };
|
||||
_slowRegistryCache.Add(groupEntry);
|
||||
}
|
||||
|
||||
// Check for Array Type first
|
||||
string regArrayType = $"{registryName}[]";
|
||||
if (subRegistries.TryGetValue(regArrayType, out INodeRegistry arrayRegistry))
|
||||
{
|
||||
// we have a matching subRegistry, add that next
|
||||
var arrayLevel = level + 1;
|
||||
var arrayGroupEntry = new SearchTreeGroupEntry(new GUIContent(regArrayType, icon), arrayLevel) { userData = registry };
|
||||
_slowRegistryCache.Add(arrayGroupEntry);
|
||||
|
||||
// Add all array entries
|
||||
AddEntriesForRegistry(_slowRegistryCache, arrayRegistry, arrayLevel + 1);
|
||||
}
|
||||
|
||||
AddEntriesForRegistry(_slowRegistryCache, registry, level + 1, true);
|
||||
|
||||
}
|
||||
}
|
||||
return _slowRegistryCache;
|
||||
}
|
||||
|
||||
override public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context)
|
||||
{
|
||||
// should check for adding duplicate event nodes here, look at Legacy.UdonGraph.TryCreateNodeInstance
|
||||
// Assuming that if we've made it to this point we're allowed to add the node
|
||||
|
||||
UdonNode node;
|
||||
// checking type so we can support selecting registries as well
|
||||
if (entry.userData is UdonNodeDefinition && !_graphView.IsDuplicateEventNode((entry.userData as UdonNodeDefinition).fullName))
|
||||
{
|
||||
node = UdonNode.CreateNode(entry.userData as UdonNodeDefinition, _graphView);
|
||||
_graphView.AddElement(node);
|
||||
|
||||
node.SetPosition(new Rect(GetGraphPositionFromContext(context), Vector2.zero));
|
||||
node.Select(_graphView, false);
|
||||
|
||||
// Do we need to do this to reserialize, etc?
|
||||
_graphView.ReSerializeData();
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b721120e6c1d320448a55fe87a7de824
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+144
@@ -0,0 +1,144 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using VRC.Udon.Graph;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
|
||||
public class UdonPortSearchWindow : UdonSearchWindowBase
|
||||
{
|
||||
|
||||
internal List<SearchTreeEntry> _fullRegistry;
|
||||
|
||||
#region ISearchWindowProvider
|
||||
|
||||
public Type typeToSearch;
|
||||
public Port startingPort;
|
||||
public Direction direction;
|
||||
|
||||
public class VariableInfo
|
||||
{
|
||||
public string uid;
|
||||
public bool isGetter;
|
||||
|
||||
public VariableInfo(string uid, bool isGetter)
|
||||
{
|
||||
this.uid = uid;
|
||||
this.isGetter = isGetter;
|
||||
}
|
||||
}
|
||||
|
||||
override public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context)
|
||||
{
|
||||
_fullRegistry = new List<SearchTreeEntry>
|
||||
{
|
||||
new SearchTreeGroupEntry(new GUIContent($"{direction.ToString()} Search"), 0)
|
||||
};
|
||||
|
||||
var defsToAdd = new Dictionary<string, List<UdonNodeDefinition>>();
|
||||
var registries = UdonEditorManager.Instance.GetNodeRegistries();
|
||||
foreach (var item in registries)
|
||||
{
|
||||
var definitions = item.Value.GetNodeDefinitions().ToList();
|
||||
|
||||
var registryName = item.Key.FriendlyNameify().Replace("NodeRegistry", "");
|
||||
defsToAdd.Add(registryName, new List<UdonNodeDefinition>());
|
||||
|
||||
foreach (var def in definitions)
|
||||
{
|
||||
var collection = direction == Direction.Input ? def.Inputs : def.Outputs;
|
||||
if(collection.Any(p=>p.type == typeToSearch))
|
||||
{
|
||||
defsToAdd[registryName].Add(def);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var variables = _graphView.GetVariableNodes;
|
||||
|
||||
// Add Getters and Setters for matched variable types
|
||||
Texture2D icon = EditorGUIUtility.FindTexture("GameManager Icon");
|
||||
string typeToSearchSimple = typeToSearch.ToString().Replace(".", "");
|
||||
foreach (var item in variables)
|
||||
{
|
||||
string variableSimpleName = item.fullName.Replace("Variable_", "");
|
||||
string getOrSet = direction == Direction.Output ? "Get" : "Set";
|
||||
if(variableSimpleName == typeToSearchSimple)
|
||||
{
|
||||
string customVariableName = item.nodeValues[1].Deserialize().ToString();
|
||||
_fullRegistry.Add(new SearchTreeEntry(new GUIContent($"{getOrSet} {customVariableName}", icon))
|
||||
{
|
||||
level = 1,
|
||||
userData = new VariableInfo(item.uid, direction == Direction.Output),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var item in defsToAdd)
|
||||
{
|
||||
// Skip empty lists
|
||||
if (item.Value.Count == 0) continue;
|
||||
|
||||
_fullRegistry.Add(new SearchTreeGroupEntry(new GUIContent(item.Key), 1));
|
||||
AddEntries(_fullRegistry, item.Value, 2);
|
||||
}
|
||||
|
||||
return _fullRegistry;
|
||||
}
|
||||
|
||||
override public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context)
|
||||
{
|
||||
UdonNode node;
|
||||
// checking type so we can support selecting registries as well
|
||||
if (entry.userData is UdonNodeDefinition && !_graphView.IsDuplicateEventNode((entry.userData as UdonNodeDefinition).fullName))
|
||||
{
|
||||
node = UdonNode.CreateNode(entry.userData as UdonNodeDefinition, _graphView);
|
||||
_graphView.AddElement(node);
|
||||
var position = GetGraphPositionFromContext(context);
|
||||
position.x -= 140; // this offset is added for the search window, remove it for the node
|
||||
node.SetPosition(new Rect(position, Vector2.zero));
|
||||
node.Select(_graphView, false);
|
||||
var collection = direction == Direction.Input ? node.portsIn : node.portsOut;
|
||||
var port = collection.FirstOrDefault(p => p.Value.portType == typeToSearch).Value;
|
||||
if(port != null)
|
||||
{
|
||||
var e = startingPort.ConnectTo(port);
|
||||
_graphView.AddElement(e);
|
||||
}
|
||||
|
||||
// Do we need to do this to reserialize, etc?
|
||||
_graphView.ReSerializeData();
|
||||
return true;
|
||||
}
|
||||
else if(entry.userData is VariableInfo)
|
||||
{
|
||||
var data = entry.userData as VariableInfo;
|
||||
var position = GetGraphPositionFromContext(context);
|
||||
position.x -= 140; // this offset is added for the search window, remove it for the node
|
||||
|
||||
UdonNode udonNode = _graphView.MakeVariableNode(data.uid, position, data.isGetter);
|
||||
_graphView.AddElement(udonNode);
|
||||
var collection = direction == Direction.Input ? udonNode.portsIn : udonNode.portsOut;
|
||||
var port = collection.First().Value;
|
||||
if (port != null)
|
||||
{
|
||||
var e = startingPort.ConnectTo(port);
|
||||
_graphView.AddElement(e);
|
||||
}
|
||||
_graphView.ReSerializeData();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e94c084f399869b42a21244fd07778c4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using System.Collections.Generic;
|
||||
using VRC.Udon.Graph.Interfaces;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using VRC.Udon.Graph;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonRegistrySearchWindow : UdonSearchWindowBase
|
||||
{
|
||||
private UdonSearchManager _searchManager;
|
||||
private static List<SearchTreeEntry> _registryCache;
|
||||
private List<(string, string)> _shortcutRegistries = new List<(string, string)>()
|
||||
{
|
||||
("UnityEngine","Debug"),
|
||||
("Udon","Special"),
|
||||
("Udon","Type"),
|
||||
("VRC", "UdonCommonInterfacesIUdonEventReceiver")
|
||||
};
|
||||
|
||||
public void Initialize(UdonGraphWindow editorWindow, UdonGraph graphView, UdonSearchManager manager)
|
||||
{
|
||||
base.Initialize(editorWindow, graphView);
|
||||
_searchManager = manager;
|
||||
}
|
||||
|
||||
#region ISearchWindowProvider
|
||||
|
||||
override public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context)
|
||||
{
|
||||
if (_registryCache != null && _registryCache.Count > 0) return _registryCache;
|
||||
|
||||
_registryCache = new List<SearchTreeEntry>();
|
||||
|
||||
Texture2D icon = EditorGUIUtility.FindTexture("cs Script Icon");
|
||||
_registryCache.Add(new SearchTreeGroupEntry(new GUIContent("Quick Search"), 0));
|
||||
|
||||
var topRegistriesLookup = new Dictionary<string, List<KeyValuePair<string, INodeRegistry>>>();
|
||||
foreach (var entry in UdonEditorManager.Instance.GetTopRegistries())
|
||||
{
|
||||
topRegistriesLookup.Add(entry.Key, new List<KeyValuePair<string, INodeRegistry>>(entry.Value));
|
||||
}
|
||||
|
||||
// Add shortcut registries
|
||||
foreach (var item in _shortcutRegistries)
|
||||
{
|
||||
if (topRegistriesLookup.TryGetValue(item.Item1, out var searchRegistry))
|
||||
{
|
||||
string subRegistryName = $"{item.Item1}{item.Item2}NodeRegistry";
|
||||
var subRegistry = searchRegistry.FindAll(r => r.Key == subRegistryName);
|
||||
if (subRegistry.Count == 1)
|
||||
{
|
||||
topRegistriesLookup.Add(item.Item2, subRegistry);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning($"Could not find sub-registry {subRegistryName}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Combine Events into special top-level element
|
||||
var vrcEvents = topRegistriesLookup["VRC"].Find(r=>r.Key == "VRCEventNodeRegistry").Value.GetNodeDefinitions();
|
||||
var udonEvents = topRegistriesLookup["Udon"].Find(r => r.Key == "UdonEventNodeRegistry").Value.GetNodeDefinitions();
|
||||
var allEvents = vrcEvents.Concat(udonEvents);
|
||||
var eventRegistry = new EventRegistry();
|
||||
foreach (var item in allEvents)
|
||||
{
|
||||
eventRegistry.definitions.Add(item);
|
||||
}
|
||||
topRegistriesLookup.Add("Events", new List<KeyValuePair<string, INodeRegistry>>() { new KeyValuePair<string, INodeRegistry>("Events", eventRegistry) });
|
||||
|
||||
// Build lookup from newly organized list
|
||||
var topRegistries = topRegistriesLookup.OrderBy(s => s.Key);
|
||||
foreach (var topRegistry in topRegistries)
|
||||
{
|
||||
string topName = topRegistry.Key.Replace("NodeRegistry", "");
|
||||
|
||||
// Handle Shortcut registries with only 1 top-level registry listed
|
||||
if (topRegistry.Value.Count == 1)
|
||||
{
|
||||
string registryName = UdonGraphExtensions.FriendlyNameify(topName);
|
||||
_registryCache.Add(new SearchTreeGroupEntry(new GUIContent(registryName)) { level = 1, userData = topRegistry.Value });
|
||||
AddEntriesForRegistry(_registryCache, topRegistry.Value.First().Value, 2);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip empty 'Udon' top level
|
||||
if (topName != "Udon")
|
||||
{
|
||||
_registryCache.Add(new SearchTreeGroupEntry(new GUIContent(topName), 1));
|
||||
}
|
||||
|
||||
foreach (KeyValuePair<string, INodeRegistry> registry in topRegistry.Value.OrderBy(s => s.Key))
|
||||
{
|
||||
string baseRegistryName = registry.Key.Replace("NodeRegistry", "").FriendlyNameify().ReplaceFirst(topName, "");
|
||||
string registryName = baseRegistryName.UppercaseFirst();
|
||||
|
||||
// Plural-ize Event->Events and Type->Types
|
||||
if (topName == "Udon" && (registryName == "Event" || registryName == "Type"))
|
||||
{
|
||||
registryName = $"{registryName}s";
|
||||
}
|
||||
else
|
||||
{
|
||||
// add Registry Level
|
||||
if (registryName.StartsWithCached("Object") || registryName.StartsWithCached("Type"))
|
||||
{
|
||||
registryName = $"{topName}.{registryName}";
|
||||
}
|
||||
_registryCache.Add(new SearchTreeEntry(new GUIContent(registryName, icon, $"{topName}.{registryName}")) { level = 2, userData = registry.Value });
|
||||
}
|
||||
}
|
||||
}
|
||||
return _registryCache;
|
||||
}
|
||||
|
||||
override public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context)
|
||||
{
|
||||
// should check for adding duplicate event nodes here, look at Legacy.UdonGraph.TryCreateNodeInstance
|
||||
// Assuming that if we've made it to this point we're allowed to add the node
|
||||
|
||||
// checking type so we can support selecting registries as well
|
||||
if (entry.userData is INodeRegistry)
|
||||
{
|
||||
_searchManager.QueueOpenFocusedSearch(entry.userData as INodeRegistry, context.screenMousePosition);
|
||||
return true;
|
||||
}
|
||||
else if (entry.userData is UdonNodeDefinition && !_graphView.IsDuplicateEventNode((entry.userData as UdonNodeDefinition).fullName))
|
||||
{
|
||||
UdonNode node = UdonNode.CreateNode(entry.userData as UdonNodeDefinition, _graphView);
|
||||
|
||||
_graphView.AddElement(node);
|
||||
|
||||
node.SetPosition(new Rect(GetGraphPositionFromContext(context), Vector2.zero));
|
||||
node.Select(_graphView, false);
|
||||
|
||||
// Do we need to do this to reserialize, etc?
|
||||
_graphView.ReSerializeData();
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public class EventRegistry : INodeRegistry
|
||||
{
|
||||
public List<UdonNodeDefinition> definitions = new List<UdonNodeDefinition>();
|
||||
|
||||
public UdonNodeDefinition GetNodeDefinition(string identifier)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IEnumerable<UdonNodeDefinition> GetNodeDefinitions()
|
||||
{
|
||||
return definitions;
|
||||
}
|
||||
|
||||
public IEnumerable<UdonNodeDefinition> GetNodeDefinitions(string baseIdentifier)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public Dictionary<string, INodeRegistry> GetNodeRegistries()
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 6a6c453fae11b5349a33399e258d1578
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+114
@@ -0,0 +1,114 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using UnityEngine;
|
||||
using VRC.Udon.Graph.Interfaces;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonSearchManager
|
||||
{
|
||||
private UdonGraph _view;
|
||||
private UdonGraphWindow _window;
|
||||
|
||||
// Search Windows
|
||||
private UdonFocusedSearchWindow _focusedSearchWindow;
|
||||
private UdonRegistrySearchWindow _registrySearchWindow;
|
||||
private UdonFullSearchWindow _fullSearchWindow;
|
||||
public UdonVariableTypeWindow _variableSearchWindow;
|
||||
public UdonPortSearchWindow _portSearchWindow;
|
||||
|
||||
public UdonSearchManager(UdonGraph view, UdonGraphWindow window)
|
||||
{
|
||||
_view = view;
|
||||
_window = window;
|
||||
|
||||
SetupSearchTypes();
|
||||
|
||||
view.nodeCreationRequest += OnRequestNodeCreation;
|
||||
}
|
||||
|
||||
private void SetupSearchTypes()
|
||||
{
|
||||
if (_registrySearchWindow == null)
|
||||
_registrySearchWindow = ScriptableObject.CreateInstance<UdonRegistrySearchWindow>();
|
||||
_registrySearchWindow.Initialize(_window, _view, this);
|
||||
|
||||
if (_fullSearchWindow == null)
|
||||
_fullSearchWindow = ScriptableObject.CreateInstance<UdonFullSearchWindow>();
|
||||
_fullSearchWindow.Initialize(_window, _view);
|
||||
|
||||
if (_focusedSearchWindow == null)
|
||||
_focusedSearchWindow = ScriptableObject.CreateInstance<UdonFocusedSearchWindow>();
|
||||
_focusedSearchWindow.Initialize(_window, _view);
|
||||
|
||||
if (_variableSearchWindow == null)
|
||||
_variableSearchWindow = ScriptableObject.CreateInstance<UdonVariableTypeWindow>();
|
||||
_variableSearchWindow.Initialize(_window, _view);
|
||||
|
||||
if (_portSearchWindow == null)
|
||||
_portSearchWindow = ScriptableObject.CreateInstance<UdonPortSearchWindow>();
|
||||
_portSearchWindow.Initialize(_window, _view);
|
||||
}
|
||||
|
||||
private void OnRequestNodeCreation(NodeCreationContext context)
|
||||
{
|
||||
// started on empty space
|
||||
if (context.target == null)
|
||||
{
|
||||
// If we have a node selected (but not set as context.target because that's a container for new nodes to go into), search within that node's registry
|
||||
if (Settings.SearchOnSelectedNodeRegistry && _view.selection.Count > 0 && _view.selection.First() is UdonNode)
|
||||
{
|
||||
_focusedSearchWindow.targetRegistry = (_view.selection.First() as UdonNode).Registry;
|
||||
SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), _focusedSearchWindow);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Create Search Window that only searches Top-Level Registries
|
||||
SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), _registrySearchWindow);
|
||||
}
|
||||
}
|
||||
else if (context.target is UdonGraph)
|
||||
{
|
||||
// Slightly hacky method to figure out that we want a full-search window
|
||||
SearchWindow.Open(new SearchWindowContext(context.screenMousePosition), _fullSearchWindow);
|
||||
}
|
||||
}
|
||||
|
||||
public void OpenVariableSearch(Vector2 screenMousePosition)
|
||||
{
|
||||
// offset search window to appear next to mouse
|
||||
screenMousePosition.x += 140;
|
||||
screenMousePosition.y += 0;
|
||||
SearchWindow.Open(new SearchWindowContext(screenMousePosition), _variableSearchWindow);
|
||||
}
|
||||
|
||||
public void OpenPortSearch(Type type, Vector2 screenMousePosition, Port port, Direction direction)
|
||||
{
|
||||
// offset search window to appear next to mouse
|
||||
screenMousePosition.x += 140;
|
||||
screenMousePosition.y += 0;
|
||||
_portSearchWindow.typeToSearch = type;
|
||||
_portSearchWindow.startingPort = port;
|
||||
_portSearchWindow.direction = direction;
|
||||
SearchWindow.Open(new SearchWindowContext(screenMousePosition, 400, 360), _portSearchWindow);
|
||||
}
|
||||
|
||||
private Vector2 _searchWindowPosition;
|
||||
public void QueueOpenFocusedSearch(INodeRegistry registry, Vector2 position)
|
||||
{
|
||||
_searchWindowPosition = position;
|
||||
_focusedSearchWindow.targetRegistry = registry;
|
||||
EditorApplication.update += TryOpenFocusedSearch;
|
||||
}
|
||||
|
||||
private void TryOpenFocusedSearch()
|
||||
{
|
||||
if (SearchWindow.Open(new SearchWindowContext(_searchWindowPosition), _focusedSearchWindow))
|
||||
{
|
||||
EditorApplication.update -= TryOpenFocusedSearch;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5a10bb1987c27944bd08a88119b2844
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using UnityEditor.Experimental.UIElements;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using System.Collections.Generic;
|
||||
using VRC.Udon.Graph.Interfaces;
|
||||
using System.Linq;
|
||||
using System;
|
||||
using VRC.Udon.Graph;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
|
||||
public class UdonSearchWindowBase : ScriptableObject, ISearchWindowProvider
|
||||
{
|
||||
// Reference to actual Graph View
|
||||
internal UdonGraph _graphView;
|
||||
private List<SearchTreeEntry> _exampleLookup;
|
||||
internal UdonGraphWindow _editorWindow;
|
||||
|
||||
public virtual void Initialize(UdonGraphWindow editorWindow, UdonGraph graphView)
|
||||
{
|
||||
_editorWindow = editorWindow;
|
||||
_graphView = graphView;
|
||||
}
|
||||
|
||||
#region ISearchWindowProvider
|
||||
|
||||
public virtual List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context)
|
||||
{
|
||||
if (_exampleLookup != null && _exampleLookup.Count > 0) return _exampleLookup;
|
||||
|
||||
_exampleLookup = new List<SearchTreeEntry>();
|
||||
|
||||
Texture2D icon = EditorGUIUtility.FindTexture("cs Script Icon");
|
||||
_exampleLookup.Add(new SearchTreeGroupEntry(new GUIContent("Create Node"), 0));
|
||||
|
||||
return _exampleLookup;
|
||||
}
|
||||
|
||||
public virtual bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal Vector2 GetGraphPositionFromContext(SearchWindowContext context)
|
||||
{
|
||||
var windowRoot = _editorWindow.GetRootVisualContainer();
|
||||
var windowMousePosition = windowRoot.ChangeCoordinatesTo(windowRoot.parent, context.screenMousePosition - _editorWindow.position.position);
|
||||
var graphMousePosition = _graphView.contentViewContainer.WorldToLocal(windowMousePosition);
|
||||
return graphMousePosition;
|
||||
}
|
||||
|
||||
internal void AddEntries(List<SearchTreeEntry> cache, IEnumerable<UdonNodeDefinition> definitions, int level, bool stripToLastDot = false)
|
||||
{
|
||||
Texture2D icon = EditorGUIUtility.FindTexture("cs Script Icon");
|
||||
Dictionary<string, UdonNodeDefinition> baseNodeDefinition = new Dictionary<string, UdonNodeDefinition>();
|
||||
|
||||
foreach (UdonNodeDefinition nodeDefinition in definitions.OrderBy(s => UdonGraphExtensions.PrettyFullName(s)))
|
||||
{
|
||||
string baseIdentifier = nodeDefinition.fullName;
|
||||
string[] splitBaseIdentifier = baseIdentifier.Split(new[] { "__" }, StringSplitOptions.None);
|
||||
if (splitBaseIdentifier.Length >= 2)
|
||||
{
|
||||
baseIdentifier = $"{splitBaseIdentifier[0]}__{splitBaseIdentifier[1]}";
|
||||
}
|
||||
if (baseNodeDefinition.ContainsKey(baseIdentifier))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
baseNodeDefinition.Add(baseIdentifier, nodeDefinition);
|
||||
}
|
||||
|
||||
// add all subTypes
|
||||
foreach (KeyValuePair<string, UdonNodeDefinition> nodeDefinitionsEntry in baseNodeDefinition)
|
||||
{
|
||||
string nodeName = UdonGraphExtensions.PrettyBaseName(nodeDefinitionsEntry.Key);
|
||||
nodeName = nodeName.UppercaseFirst();
|
||||
nodeName = nodeName.Replace("_", " ");
|
||||
if (stripToLastDot)
|
||||
{
|
||||
int lastDotIndex = nodeName.LastIndexOf('.');
|
||||
nodeName = nodeName.Substring(lastDotIndex + 1);
|
||||
}
|
||||
|
||||
// don't add Variable or Comment nodes
|
||||
if (nodeName.StartsWithCached("Variable") || nodeName.StartsWithCached("Get Var") || nodeName.StartsWithCached("Set Var") || nodeName.StartsWithCached("Comment"))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (nodeName.StartsWithCached("Object"))
|
||||
{
|
||||
nodeName = $"{nodeDefinitionsEntry.Value.type.Namespace}.{nodeName}";
|
||||
}
|
||||
cache.Add(new SearchTreeEntry(new GUIContent(nodeName, icon)) { level = level, userData = nodeDefinitionsEntry.Value });
|
||||
}
|
||||
}
|
||||
|
||||
// adds all entries so we can use this for regular and array registries
|
||||
internal void AddEntriesForRegistry(List<SearchTreeEntry> cache, INodeRegistry registry, int level, bool stripToLastDot = false)
|
||||
{
|
||||
AddEntries(cache, registry.GetNodeDefinitions(), level, stripToLastDot);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d825ed3ba6aa7f14294e73efefc217d0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
using UnityEngine;
|
||||
using UnityEditor.Experimental.UIElements.GraphView;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonVariableTypeWindow : UdonSearchWindowBase
|
||||
{
|
||||
internal List<SearchTreeEntry> _fullRegistry;
|
||||
|
||||
#region ISearchWindowProvider
|
||||
|
||||
override public List<SearchTreeEntry> CreateSearchTree(SearchWindowContext context)
|
||||
{
|
||||
if (_fullRegistry != null) return _fullRegistry;
|
||||
|
||||
_fullRegistry = new List<SearchTreeEntry>();
|
||||
|
||||
_fullRegistry.Add(new SearchTreeGroupEntry(new GUIContent("Variable Type Search"), 0));
|
||||
|
||||
var definitions = UdonEditorManager.Instance.GetNodeDefinitions("Variable_").ToList().OrderBy(n=>n.name);
|
||||
foreach (var definition in definitions)
|
||||
{
|
||||
_fullRegistry.Add(new SearchTreeEntry(new GUIContent(UdonGraphExtensions.FriendlyTypeName(definition.type).FriendlyNameify()))
|
||||
{
|
||||
level = 1,
|
||||
userData = definition.fullName,
|
||||
});
|
||||
}
|
||||
|
||||
return _fullRegistry;
|
||||
}
|
||||
|
||||
override public bool OnSelectEntry(SearchTreeEntry entry, SearchWindowContext context)
|
||||
{
|
||||
if(_graphView == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
_graphView.AddNewVariable((string)entry.userData);
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16fc7a7a059deeb458fdcdf719b467a4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Linq.Expressions;
|
||||
using UnityEngine;
|
||||
using VRC.Udon.Common.Interfaces;
|
||||
|
||||
// nicked from GraphProcessor project
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram
|
||||
{
|
||||
public static class TypeExtension
|
||||
{
|
||||
public static bool IsReallyAssignableFrom(this Type type, Type otherType)
|
||||
{
|
||||
if (type == null && otherType != null) return false;
|
||||
if (otherType == null && type != null) return false;
|
||||
|
||||
if (type == otherType)
|
||||
return true;
|
||||
if (type.IsAssignableFrom(otherType))
|
||||
return true;
|
||||
if (otherType.IsAssignableFrom(type))
|
||||
return true;
|
||||
if (type == typeof(IUdonEventReceiver) && otherType == typeof(Component))
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
var v = Expression.Variable(otherType);
|
||||
var expr = Expression.Convert(v, type);
|
||||
return expr.Method != null && expr.Method.Name != "op_Implicit";
|
||||
}
|
||||
catch (InvalidOperationException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e2f2300f99ce0ea4a8d9a20b464384df
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,158 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Experimental.UIElements;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Globalization;
|
||||
using System.Reflection.Emit;
|
||||
using VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI
|
||||
{
|
||||
public static class UdonFieldFactory
|
||||
{
|
||||
static readonly Dictionary< Type, Type > fieldDrawers = new Dictionary< Type, Type >();
|
||||
|
||||
static readonly MethodInfo createFieldMethod = typeof(UdonFieldFactory).GetMethod("CreateFieldSpecific", BindingFlags.Static | BindingFlags.Public);
|
||||
|
||||
static UdonFieldFactory()
|
||||
{
|
||||
AddDrawer(typeof(bool), typeof(Toggle));
|
||||
AddDrawer(typeof(int), typeof(IntegerField));
|
||||
AddDrawer(typeof(long), typeof(LongField));
|
||||
AddDrawer(typeof(float), typeof(FloatField));
|
||||
AddDrawer(typeof(double), typeof(DoubleField));
|
||||
AddDrawer(typeof(string), typeof(TextField));
|
||||
AddDrawer(typeof(Bounds), typeof(BoundsField));
|
||||
AddDrawer(typeof(Color), typeof(ColorField));
|
||||
AddDrawer(typeof(Vector2), typeof(Vector2Field));
|
||||
AddDrawer(typeof(Vector2Int), typeof(Vector2IntField));
|
||||
AddDrawer(typeof(Vector3), typeof(Vector3Field));
|
||||
AddDrawer(typeof(Vector3Int), typeof(Vector3IntField));
|
||||
AddDrawer(typeof(Vector4), typeof(Vector4Field));
|
||||
AddDrawer(typeof(AnimationCurve), typeof(CurveField));
|
||||
AddDrawer(typeof(Enum), typeof(EnumField));
|
||||
AddDrawer(typeof(Gradient), typeof(GradientField));
|
||||
AddDrawer(typeof(UnityEngine.Object), typeof(ObjectField));
|
||||
AddDrawer(typeof(Rect), typeof(RectField));
|
||||
AddDrawer(typeof(RectInt), typeof(RectIntField));
|
||||
AddDrawer(typeof(char), typeof(CharField));
|
||||
AddDrawer(typeof(byte), typeof(ByteField));
|
||||
AddDrawer(typeof(Quaternion), typeof(QuaternionField));
|
||||
AddDrawer(typeof(LayerMask), typeof(LayerMaskField));
|
||||
AddDrawer(typeof(SDKBase.VRCUrl), typeof(VRCUrlField));
|
||||
}
|
||||
|
||||
static void AddDrawer(Type fieldType, Type drawerType)
|
||||
{
|
||||
var iNotifyType = typeof(INotifyValueChanged<>).MakeGenericType(fieldType);
|
||||
|
||||
if (!iNotifyType.IsAssignableFrom(drawerType))
|
||||
{
|
||||
Debug.LogWarning("The custom field drawer " + drawerType + " does not implements INotifyValueChanged< " + fieldType + " >");
|
||||
return ;
|
||||
}
|
||||
|
||||
fieldDrawers[fieldType] = drawerType;
|
||||
}
|
||||
|
||||
public static INotifyValueChanged< T > CreateField< T >(T value)
|
||||
{
|
||||
return CreateField(value != null ? value.GetType() : typeof(T)) as INotifyValueChanged< T >;
|
||||
}
|
||||
|
||||
public static VisualElement CreateField(Type t)
|
||||
{
|
||||
Type drawerType;
|
||||
|
||||
fieldDrawers.TryGetValue(t, out drawerType);
|
||||
|
||||
if (drawerType == null)
|
||||
drawerType = fieldDrawers.FirstOrDefault(kp => kp.Key.IsReallyAssignableFrom(t)).Value;
|
||||
|
||||
if (drawerType == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
object field;
|
||||
|
||||
if (drawerType == typeof(EnumField))
|
||||
{
|
||||
field = new EnumField(Activator.CreateInstance(t) as Enum);
|
||||
}
|
||||
else
|
||||
{
|
||||
try {
|
||||
field = Activator.CreateInstance(drawerType,
|
||||
BindingFlags.CreateInstance |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.NonPublic |
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.OptionalParamBinding, null,
|
||||
new object[]{Type.Missing }, CultureInfo.CurrentCulture);
|
||||
} catch {
|
||||
field = Activator.CreateInstance(drawerType,
|
||||
BindingFlags.CreateInstance |
|
||||
BindingFlags.Public |
|
||||
BindingFlags.NonPublic |
|
||||
BindingFlags.Instance |
|
||||
BindingFlags.OptionalParamBinding, null,
|
||||
new object[]{}, CultureInfo.CurrentCulture);
|
||||
}
|
||||
}
|
||||
|
||||
// For mutiline
|
||||
if (field is TextField textField)
|
||||
textField.multiline = true;
|
||||
|
||||
return field as VisualElement;
|
||||
}
|
||||
|
||||
public static INotifyValueChanged< T > CreateFieldSpecific< T >(T value, Action< object > onValueChanged)
|
||||
{
|
||||
var fieldDrawer = CreateField< T >(value);
|
||||
|
||||
if (fieldDrawer == null)
|
||||
return null;
|
||||
|
||||
fieldDrawer.value = value;
|
||||
if (onValueChanged != null)
|
||||
{
|
||||
fieldDrawer.OnValueChanged((e) => onValueChanged(e.newValue));
|
||||
}
|
||||
return fieldDrawer as INotifyValueChanged< T >;
|
||||
}
|
||||
|
||||
public static VisualElement CreateField(Type fieldType, object value, Action< object > onValueChanged)
|
||||
{
|
||||
if (typeof(Enum).IsAssignableFrom(fieldType))
|
||||
fieldType = typeof(Enum);
|
||||
|
||||
if (fieldType.IsArray)
|
||||
{
|
||||
return new UdonArrayEditor(fieldType, onValueChanged, value);
|
||||
}
|
||||
|
||||
VisualElement field = null;
|
||||
|
||||
try
|
||||
{
|
||||
var createFieldSpecificMethod = createFieldMethod.MakeGenericMethod(fieldType);
|
||||
field = createFieldSpecificMethod.Invoke(null, new object[]{value, onValueChanged}) as VisualElement;
|
||||
|
||||
// delay textFields
|
||||
if (field is TextField) ((TextField) field).isDelayed = true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError(e);
|
||||
}
|
||||
|
||||
return field;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dfca99fb2e099d84da6c504cd521aa70
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9214873dab0ea8a4b91861cd5a04dae3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram
|
||||
{
|
||||
[Serializable]
|
||||
public class UdonGraphElementData
|
||||
{
|
||||
public UdonGraphElementType type;
|
||||
public string uid;
|
||||
public string jsonData;
|
||||
|
||||
public UdonGraphElementData(UdonGraphElementType type, string uid, string jsonData)
|
||||
{
|
||||
this.type = type;
|
||||
this.jsonData = jsonData;
|
||||
this.uid = uid;
|
||||
}
|
||||
}
|
||||
|
||||
public enum UdonGraphElementType
|
||||
{
|
||||
GraphElement,
|
||||
UdonStackNode,
|
||||
UdonGroup,
|
||||
UdonComment,
|
||||
Minimap,
|
||||
VariablesWindow,
|
||||
};
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f166d8f1c152ef34899019ab9a4fd0f2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,135 @@
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using UnityEditor.Experimental.UIElements;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonGraphStatus : ToolbarToggle
|
||||
{
|
||||
private VisualElement _detailsContainer;
|
||||
private UdonProgramSourceView _programSourceView;
|
||||
private UdonGraphProgramAsset _graphAsset;
|
||||
private TextElement _label;
|
||||
|
||||
public UdonGraphStatus(VisualElement detailsContainer)
|
||||
{
|
||||
_detailsContainer = detailsContainer;
|
||||
|
||||
_label = new TextElement() { text = "-", name = "Content" };
|
||||
_label.StretchToParentSize();
|
||||
Add(_label);
|
||||
OnValueChanged(ShowGraphAssetDetails);
|
||||
|
||||
_programSourceView = new UdonProgramSourceView();
|
||||
}
|
||||
|
||||
private void ShowGraphAssetDetails(ChangeEvent<bool> changeEvent)
|
||||
{
|
||||
// Just remove it
|
||||
if (!changeEvent.newValue)
|
||||
{
|
||||
RemoveIfContaining(_programSourceView);
|
||||
return;
|
||||
}
|
||||
|
||||
// else we need to make a new one and show it
|
||||
if (_graphAsset == null)
|
||||
{
|
||||
Debug.LogError("Can't show Asset Details for a null asset.");
|
||||
return;
|
||||
}
|
||||
|
||||
RemoveIfContaining(_programSourceView);
|
||||
_detailsContainer.Add(_programSourceView);
|
||||
_programSourceView.BringToFront();
|
||||
}
|
||||
|
||||
private void RemoveIfContaining(VisualElement element)
|
||||
{
|
||||
if (_detailsContainer.Contains(element))
|
||||
{
|
||||
_detailsContainer.Remove(element);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnAssemble(bool success, string assembly)
|
||||
{
|
||||
|
||||
if (!enabledInHierarchy || _label == null) return;
|
||||
|
||||
string newText;
|
||||
Color flashColor;
|
||||
Color targetColor;
|
||||
|
||||
// change visuals based on success
|
||||
if(success)
|
||||
{
|
||||
newText = "OK";
|
||||
flashColor = new Color(0, 1, 0);
|
||||
targetColor = new Color(0.1f, 0.25f, 0.1f, alpha);
|
||||
}
|
||||
else
|
||||
{
|
||||
newText = "!";
|
||||
flashColor = new Color(1, 0, 0);
|
||||
targetColor = new Color(0.25f, 0.1f, 0.1f, alpha);
|
||||
}
|
||||
|
||||
// Update visuals
|
||||
_label.text = newText;
|
||||
_label.style.backgroundColor = flashColor;
|
||||
LerpColor(targetColor);
|
||||
_programSourceView.SetText(assembly);
|
||||
}
|
||||
|
||||
private Color targetColor;
|
||||
private Color startColor;
|
||||
private float lerpAmount = 0f;
|
||||
private float alpha = 0.75f;
|
||||
|
||||
private void LerpColor(Color newColor)
|
||||
{
|
||||
startColor = _label.style.backgroundColor.value;
|
||||
targetColor = newColor;
|
||||
lerpAmount = 0f;
|
||||
EditorApplication.update += UpdateColor;
|
||||
}
|
||||
|
||||
private void UpdateColor()
|
||||
{
|
||||
lerpAmount += 0.01f;
|
||||
|
||||
if (lerpAmount >= 1)
|
||||
{
|
||||
EditorApplication.update -= UpdateColor;
|
||||
}
|
||||
|
||||
if(_label != null)
|
||||
{
|
||||
_label.style.backgroundColor = Color.Lerp(startColor, targetColor, lerpAmount);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Blur()
|
||||
{
|
||||
EditorApplication.update -= UpdateColor;
|
||||
}
|
||||
|
||||
~UdonGraphStatus()
|
||||
{
|
||||
EditorApplication.update -= UpdateColor;
|
||||
if(_graphAsset != null)
|
||||
{
|
||||
_graphAsset.OnAssemble -= OnAssemble;
|
||||
}
|
||||
}
|
||||
|
||||
internal void LoadAsset(UdonGraphProgramAsset asset)
|
||||
{
|
||||
_graphAsset = asset;
|
||||
_graphAsset.OnAssemble += OnAssemble;
|
||||
_programSourceView.LoadAsset(asset);
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 54dd824c6c614b94183d92710efe4f5f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public static class Settings
|
||||
{
|
||||
private static string UseNeonStyleString = "UdonGraphViewSettings.UseNeonStyle";
|
||||
private static string LastGraphGuidString = "UdonGraphViewSettings.LastGraphGuid";
|
||||
private static string LastUdonBehaviourPathString = "UdonGraphViewSettings.LastUdonBehaviourPath";
|
||||
private static string LastUdonBehaviourScenePathString = "UdonGraphViewSettings.LastUdonBehaviourScenePath";
|
||||
private static string SearchOnSelectedNodeRegistryString = "UdonGraphViewSettings.SearchOnSelectedNodeRegistry";
|
||||
private static string GridSnapSizeString = "UdonGraphViewSettings.GridSnapSize";
|
||||
private static string SearchOnNoodleDropString = "UdonGraphViewSettings.SearchOnNoodleDrop";
|
||||
|
||||
public static bool UseNeonStyle
|
||||
{
|
||||
get { return PlayerPrefs.GetInt(UseNeonStyleString, 0) == 1; }
|
||||
set { PlayerPrefs.SetInt(UseNeonStyleString, value ? 1 : 0); }
|
||||
}
|
||||
|
||||
public static string LastGraphGuid
|
||||
{
|
||||
get { return PlayerPrefs.GetString(LastGraphGuidString, ""); }
|
||||
set { PlayerPrefs.SetString(LastGraphGuidString, value); }
|
||||
}
|
||||
|
||||
public static string LastUdonBehaviourPath
|
||||
{
|
||||
get { return PlayerPrefs.GetString(LastUdonBehaviourPathString, ""); }
|
||||
set { PlayerPrefs.SetString(LastUdonBehaviourPathString, value); }
|
||||
}
|
||||
|
||||
public static string LastUdonBehaviourScenePath
|
||||
{
|
||||
get { return PlayerPrefs.GetString(LastUdonBehaviourScenePathString, ""); }
|
||||
set { PlayerPrefs.SetString(LastUdonBehaviourScenePathString, value); }
|
||||
}
|
||||
|
||||
public static bool SearchOnSelectedNodeRegistry
|
||||
{
|
||||
get { return PlayerPrefs.GetInt(SearchOnSelectedNodeRegistryString, 1) == 1; }
|
||||
set { PlayerPrefs.SetInt(SearchOnSelectedNodeRegistryString, value ? 1 : 0); }
|
||||
}
|
||||
|
||||
public static int GridSnapSize
|
||||
{
|
||||
get { return PlayerPrefs.GetInt(GridSnapSizeString, 0); }
|
||||
set { PlayerPrefs.SetInt(GridSnapSizeString, value); }
|
||||
}
|
||||
|
||||
public static bool SearchOnNoodleDrop
|
||||
{
|
||||
get { return PlayerPrefs.GetInt(SearchOnNoodleDropString, 0) == 1; }
|
||||
set { PlayerPrefs.SetInt(SearchOnNoodleDropString, value ? 1 : 0); }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 87e2044d3bcb715499ac68cc7380a9ed
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,260 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.Experimental.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using UnityEngine.Experimental.UIElements.StyleEnums;
|
||||
using System.Linq;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonGraphWindow : EditorWindow
|
||||
{
|
||||
|
||||
private VisualElement _rootView;
|
||||
// Reference to actual Graph View
|
||||
[SerializeField]private UdonGraph _graphView;
|
||||
|
||||
private UdonGraphProgramAsset _graphAsset;
|
||||
private UdonWelcomeView _welcomeView;
|
||||
private VisualElement _curtain;
|
||||
|
||||
// Toolbar and Buttons
|
||||
private Toolbar _toolbar;
|
||||
private Label _graphAssetName;
|
||||
private ToolbarMenu _toolbarOptions;
|
||||
private UdonGraphStatus _graphStatus;
|
||||
private ToolbarButton _graphReload;
|
||||
private ToolbarButton _graphCompile;
|
||||
|
||||
[MenuItem("VRChat SDK/Udon Graph")]
|
||||
private static void ShowWindow()
|
||||
{
|
||||
// Get or focus the window
|
||||
var window = GetWindow<UdonGraphWindow>("Udon Graph", true, typeof(SceneView));
|
||||
window.titleContent = new GUIContent("Udon Graph");
|
||||
}
|
||||
|
||||
private void LogPlayModeState(PlayModeStateChange state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case PlayModeStateChange.EnteredEditMode:
|
||||
if (_rootView.Contains(_curtain))
|
||||
{
|
||||
_curtain.RemoveFromHierarchy();
|
||||
}
|
||||
break;
|
||||
case PlayModeStateChange.ExitingEditMode:
|
||||
break;
|
||||
case PlayModeStateChange.EnteredPlayMode:
|
||||
_rootView.Add(_curtain);
|
||||
break;
|
||||
case PlayModeStateChange.ExitingPlayMode:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
EditorApplication.playModeStateChanged += LogPlayModeState;
|
||||
|
||||
InitializeRootView();
|
||||
|
||||
_curtain = new VisualElement()
|
||||
{
|
||||
name = "curtain",
|
||||
};
|
||||
_curtain.Add(new Label("Graph Locked in Play Mode"));
|
||||
|
||||
_welcomeView = new UdonWelcomeView();
|
||||
_welcomeView.StretchToParentSize();
|
||||
_rootView.Add(_welcomeView);
|
||||
|
||||
SetupToolbar();
|
||||
|
||||
Undo.undoRedoPerformed -= OnUndoRedo; //Remove old handler if present to prevent duplicates, doesn't cause errors if not present
|
||||
Undo.undoRedoPerformed += OnUndoRedo;
|
||||
|
||||
if(_graphAsset != null)
|
||||
{
|
||||
UdonBehaviour udonBehaviour = null;
|
||||
string gPath = Settings.LastUdonBehaviourPath;
|
||||
string sPath = Settings.LastUdonBehaviourScenePath;
|
||||
if (!string.IsNullOrEmpty(gPath) && !string.IsNullOrEmpty(sPath))
|
||||
{
|
||||
var targetScene = UnityEditor.SceneManagement.EditorSceneManager.GetSceneByPath(sPath);
|
||||
if (targetScene != null && targetScene.isLoaded && targetScene.IsValid())
|
||||
{
|
||||
var targetObject = UnityEngine.GameObject.Find(gPath);
|
||||
if (targetObject != null)
|
||||
{
|
||||
udonBehaviour = targetObject.GetComponent<UdonBehaviour>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InitializeGraph(_graphAsset, udonBehaviour);
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeRootView()
|
||||
{
|
||||
_rootView = this.GetRootVisualContainer();
|
||||
_rootView.AddStyleSheetPath("UdonGraphStyle");
|
||||
}
|
||||
|
||||
public void InitializeGraph(UdonGraphProgramAsset graph, UdonBehaviour udonBehaviour = null)
|
||||
{
|
||||
this._graphAsset = graph;
|
||||
|
||||
InitializeWindow();
|
||||
|
||||
_graphView = _rootView.Children().FirstOrDefault(e => e is UdonGraph) as UdonGraph;
|
||||
if (_graphView == null)
|
||||
{
|
||||
Debug.LogError("GraphView has not been added to the BaseGraph root view!");
|
||||
return;
|
||||
}
|
||||
|
||||
_graphView.Initialize(graph, udonBehaviour);
|
||||
|
||||
_graphStatus.LoadAsset(graph);
|
||||
// Store GUID for this asset to settings for easy reload later
|
||||
if (AssetDatabase.TryGetGUIDAndLocalFileIdentifier(graph, out string guid, out long localId)){
|
||||
Settings.LastGraphGuid = guid;
|
||||
}
|
||||
if (udonBehaviour != null)
|
||||
{
|
||||
Settings.LastUdonBehaviourPath = udonBehaviour.transform.GetHierarchyPath();
|
||||
Settings.LastUdonBehaviourScenePath = udonBehaviour.gameObject.scene.path;
|
||||
}
|
||||
_graphAssetName.text = graph.name;
|
||||
ShowGraphTools(true);
|
||||
|
||||
}
|
||||
|
||||
private void InitializeWindow()
|
||||
{
|
||||
if (_graphView == null)
|
||||
{
|
||||
_graphView = new UdonGraph(this);
|
||||
// we could add the toolbar in here
|
||||
}
|
||||
RemoveIfContaining(_welcomeView);
|
||||
_rootView.Insert(0, _graphView);
|
||||
}
|
||||
|
||||
private void ReloadWelcome()
|
||||
{
|
||||
RemoveIfContaining(_welcomeView);
|
||||
_rootView.Add(_welcomeView);
|
||||
ShowGraphTools(false);
|
||||
}
|
||||
|
||||
// TODO: maybe move this to GraphView since it's so connected?
|
||||
private void SetupToolbar()
|
||||
{
|
||||
_toolbar = new Toolbar()
|
||||
{
|
||||
name = "UdonToolbar",
|
||||
};
|
||||
_rootView.Add(_toolbar);
|
||||
|
||||
_toolbar.Add(new ToolbarButton(() =>
|
||||
{
|
||||
ReloadWelcome();
|
||||
})
|
||||
{ text = "Welcome" });
|
||||
|
||||
_graphAssetName = new Label()
|
||||
{
|
||||
name = "assetName",
|
||||
};
|
||||
_toolbar.Add(_graphAssetName);
|
||||
|
||||
_toolbar.Add(new ToolbarFlexSpacer());
|
||||
|
||||
_toolbarOptions = new ToolbarMenu
|
||||
{
|
||||
text = "Settings"
|
||||
};
|
||||
// Show Variables Window
|
||||
_toolbarOptions.menu.AppendAction("Show Variables", (m) => {
|
||||
_graphView.ToggleShowVariables(!_graphView.GetBlackboardVisible());
|
||||
}, (s)=> { return BoolToStatus(_graphView.GetBlackboardVisible());});
|
||||
// Show Minimap
|
||||
_toolbarOptions.menu.AppendAction("Show MiniMap", (m) => {
|
||||
_graphView.ToggleShowMiniMap(!_graphView.GetMinimapVisible());
|
||||
}, (s) => { return BoolToStatus(_graphView.GetMinimapVisible()); });
|
||||
_toolbarOptions.menu.AppendSeparator();
|
||||
// Search On Noodle Drop
|
||||
_toolbarOptions.menu.AppendAction("Search on Noodle Drop", (m) => {
|
||||
Settings.SearchOnNoodleDrop = !Settings.SearchOnNoodleDrop;
|
||||
}, (s) => { return BoolToStatus(Settings.SearchOnNoodleDrop); });
|
||||
// Search On Selected Node
|
||||
_toolbarOptions.menu.AppendAction("Search on Selected Node", (m) => {
|
||||
Settings.SearchOnSelectedNodeRegistry = !Settings.SearchOnSelectedNodeRegistry;
|
||||
}, (s) => { return BoolToStatus(Settings.SearchOnSelectedNodeRegistry); });
|
||||
_toolbar.Add(_toolbarOptions);
|
||||
|
||||
_graphCompile = new ToolbarButton(() =>
|
||||
{
|
||||
|
||||
if (_graphAsset != null && _graphAsset is AbstractUdonProgramSource udonProgramSource)
|
||||
{
|
||||
UdonEditorManager.Instance.QueueAndRefreshProgram(udonProgramSource);
|
||||
}
|
||||
|
||||
})
|
||||
{ text = "Compile" };
|
||||
_toolbar.Add(_graphCompile);
|
||||
|
||||
_graphReload = new ToolbarButton(() =>
|
||||
{
|
||||
_graphView.Reload();
|
||||
})
|
||||
{ text = "Reload" };
|
||||
_toolbar.Add(_graphReload);
|
||||
|
||||
_graphStatus = new UdonGraphStatus(_rootView);
|
||||
_toolbar.Add(_graphStatus);
|
||||
|
||||
ShowGraphTools(false);
|
||||
}
|
||||
|
||||
private DropdownMenu.MenuAction.StatusFlags BoolToStatus(bool value)
|
||||
{
|
||||
return value ? DropdownMenu.MenuAction.StatusFlags.Checked : DropdownMenu.MenuAction.StatusFlags.Normal;
|
||||
}
|
||||
|
||||
public void ShowGraphTools(bool value)
|
||||
{
|
||||
_graphAssetName.style.visibility = value ? Visibility.Visible : Visibility.Hidden;
|
||||
_toolbarOptions.style.visibility = value ? Visibility.Visible : Visibility.Hidden;
|
||||
_graphCompile.style.visibility = value ? Visibility.Visible : Visibility.Hidden;
|
||||
_graphReload.style.visibility = value ? Visibility.Visible : Visibility.Hidden;
|
||||
_graphStatus.style.visibility = value ? Visibility.Visible : Visibility.Hidden;
|
||||
}
|
||||
|
||||
private void RemoveIfContaining(VisualElement element)
|
||||
{
|
||||
if (_rootView.Contains(element))
|
||||
{
|
||||
_rootView.Remove(element);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnUndoRedo()
|
||||
{
|
||||
Repaint();
|
||||
}
|
||||
|
||||
public Graph.UdonGraphData GetGraphDataFromAsset(UdonGraphProgramAsset asset)
|
||||
{
|
||||
InitializeGraph(asset);
|
||||
return _graphView.graphData;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c6f017dc2674fec4da54a57b2655a948
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,57 @@
|
||||
using EditorUI = UnityEditor.Experimental.UIElements;
|
||||
|
||||
using VRC.Udon.Graph;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using VRC.Udon.Serialization;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonParameterField : EditorUI.GraphView.BlackboardField
|
||||
{
|
||||
private UdonGraph udonGraph;
|
||||
private UdonNodeData nodeData;
|
||||
public UdonNodeData Data => nodeData;
|
||||
|
||||
public UdonParameterField(UdonGraph udonGraph, UdonNodeData nodeData)
|
||||
{
|
||||
this.udonGraph = udonGraph;
|
||||
this.nodeData = nodeData;
|
||||
|
||||
// Get Definition or exit early
|
||||
UdonNodeDefinition definition = UdonEditorManager.Instance.GetNodeDefinition(nodeData.fullName);
|
||||
if (definition == null)
|
||||
{
|
||||
UnityEngine.Debug.LogWarning($"Couldn't create Parameter Field for {nodeData.fullName}");
|
||||
return;
|
||||
}
|
||||
|
||||
this.text = (string)nodeData.nodeValues[(int)UdonParameterProperty.ValueIndices.name].Deserialize();
|
||||
this.typeText = UdonGraphExtensions.PrettyString(definition.name).FriendlyNameify();
|
||||
|
||||
this.AddManipulator(new ContextualMenuManipulator(BuildContextualMenu));
|
||||
|
||||
this.Q("icon").AddToClassList("parameter-" + definition.type);
|
||||
this.Q("icon").visible = true;
|
||||
|
||||
var textField = (TextField)this.Q("textField");
|
||||
textField.isDelayed = true;
|
||||
textField.OnValueChanged((e) => {
|
||||
UnityEditor.Undo.RecordObject(udonGraph.graphProgramAsset, "Rename Variable");
|
||||
// Sanitize value for variable name
|
||||
string newVariableName = e.newValue.SanitizeVariableName();
|
||||
text = newVariableName;
|
||||
nodeData.nodeValues[(int)UdonParameterProperty.ValueIndices.name] = SerializableObjectContainer.Serialize(newVariableName);
|
||||
});
|
||||
}
|
||||
|
||||
void BuildContextualMenu(ContextualMenuPopulateEvent evt)
|
||||
{
|
||||
evt.menu.AppendAction("Rename", (a) => OpenTextEditor(), DropdownMenu.MenuAction.AlwaysEnabled);
|
||||
evt.menu.AppendAction("Delete", (a) => udonGraph.RemoveNodeData(nodeData), DropdownMenu.MenuAction.AlwaysEnabled);
|
||||
|
||||
evt.StopPropagation();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5dcd92112af21784ba5bf6383abab768
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using VRC.Udon.Graph;
|
||||
using VRC.Udon.Serialization;
|
||||
using EditorUI = UnityEditor.Experimental.UIElements;
|
||||
using EngineUI = UnityEngine.Experimental.UIElements;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonParameterProperty : EngineUI.VisualElement
|
||||
{
|
||||
protected UdonGraph graph;
|
||||
protected UdonNodeData nodeData;
|
||||
protected UdonNodeDefinition definition;
|
||||
|
||||
//public ExposedParameter parameter { get; private set; }
|
||||
|
||||
public EngineUI.Toggle isPublic { get; private set; }
|
||||
public EngineUI.Toggle isSynced { get; private set; }
|
||||
public EngineUI.VisualElement defaultValueContainer { get; private set; }
|
||||
public EditorUI.PopupField<string> syncField { get; private set; }
|
||||
private VisualElement _inputField;
|
||||
|
||||
public enum ValueIndices
|
||||
{
|
||||
value = 0,
|
||||
name = 1,
|
||||
isPublic = 2,
|
||||
isSynced = 3,
|
||||
syncType = 4
|
||||
}
|
||||
|
||||
private static SerializableObjectContainer[] GetDefaultNodeValues()
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
SerializableObjectContainer.Serialize("", typeof(string)),
|
||||
SerializableObjectContainer.Serialize("newVariableName", typeof(string)),
|
||||
SerializableObjectContainer.Serialize(false, typeof(bool)),
|
||||
SerializableObjectContainer.Serialize(false, typeof(bool)),
|
||||
SerializableObjectContainer.Serialize("none", typeof(string))
|
||||
};
|
||||
}
|
||||
|
||||
// 0 = Value, 1 = name, 2 = public, 3 = synced, 4 = syncType
|
||||
public UdonParameterProperty(UdonGraph graphView, UdonNodeDefinition definition, UdonNodeData nodeData)
|
||||
{
|
||||
this.graph = graphView;
|
||||
this.definition = definition;
|
||||
this.nodeData = nodeData;
|
||||
|
||||
// Make sure the incoming nodeData has the right number of nodeValues (super old graphs didn't have sync info)
|
||||
if (this.nodeData.nodeValues.Length != 5)
|
||||
{
|
||||
this.nodeData.nodeValues = GetDefaultNodeValues();
|
||||
for (int i = 0; i < nodeData.nodeValues.Length; i++)
|
||||
{
|
||||
this.nodeData.nodeValues[i] = nodeData.nodeValues[i];
|
||||
}
|
||||
}
|
||||
|
||||
// Public Toggle
|
||||
isPublic = new EngineUI.Toggle
|
||||
{
|
||||
text = "public",
|
||||
value = (bool)GetValue(ValueIndices.isPublic)
|
||||
};
|
||||
isPublic.OnValueChanged(e =>
|
||||
{
|
||||
SetNewValue(e.newValue, ValueIndices.isPublic);
|
||||
});
|
||||
Add(isPublic);
|
||||
|
||||
// Is Synced Field
|
||||
isSynced = new EngineUI.Toggle
|
||||
{
|
||||
text = "synced",
|
||||
value = (bool)GetValue(ValueIndices.isSynced),
|
||||
};
|
||||
|
||||
isSynced.OnValueChanged(e =>
|
||||
{
|
||||
SetNewValue(e.newValue, ValueIndices.isSynced);
|
||||
syncField.visible = e.newValue;
|
||||
});
|
||||
Add(isSynced);
|
||||
|
||||
// Sync Field, add to isSynced
|
||||
List<string> choices = new List<string>()
|
||||
{
|
||||
"none", "linear", "smooth"
|
||||
};
|
||||
syncField = new EditorUI.PopupField<string>(choices, 0)
|
||||
{
|
||||
visible = isSynced.value
|
||||
};
|
||||
syncField.OnValueChanged(e =>
|
||||
{
|
||||
SetNewValue(e.newValue, ValueIndices.syncType);
|
||||
});
|
||||
isSynced.Add(syncField);
|
||||
|
||||
// Container to show/edit Default Value
|
||||
var friendlyName = UdonGraphExtensions.FriendlyTypeName(definition.type).FriendlyNameify();
|
||||
defaultValueContainer = new VisualElement
|
||||
{
|
||||
new Label("default value") { name = "default-value-label" }
|
||||
};
|
||||
|
||||
// Generate Default Value Field
|
||||
var value = TryGetValueObject(out object result);
|
||||
_inputField = UdonFieldFactory.CreateField(
|
||||
definition.type,
|
||||
result,
|
||||
newValue => SetNewValue(newValue, ValueIndices.value)
|
||||
);
|
||||
if (_inputField != null)
|
||||
{
|
||||
defaultValueContainer.Add(_inputField);
|
||||
Add(defaultValueContainer);
|
||||
}
|
||||
}
|
||||
|
||||
private object GetValue(ValueIndices index)
|
||||
{
|
||||
if((int)index >= nodeData.nodeValues.Length)
|
||||
{
|
||||
Debug.LogWarning($"Can't get {index} from {definition.name} variable");
|
||||
return null;
|
||||
}
|
||||
return nodeData.nodeValues[(int)index].Deserialize();
|
||||
}
|
||||
|
||||
private bool TryGetValueObject(out object result)
|
||||
{
|
||||
result = null;
|
||||
|
||||
var container = nodeData.nodeValues[0];
|
||||
if (container == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
result = container.Deserialize();
|
||||
if (result == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void SetNewValue(object newValue, ValueIndices index)
|
||||
{
|
||||
nodeData.nodeValues[(int)index] = SerializableObjectContainer.Serialize(newValue);
|
||||
graph.ReSerializeData();
|
||||
graph.SaveGraphToDisk();
|
||||
}
|
||||
|
||||
// Convenience wrapper for field types that don't need special initialization
|
||||
private VisualElement SetupField<TField, TType>() where TField : VisualElement, INotifyValueChanged<TType>, new()
|
||||
{
|
||||
var field = new TField();
|
||||
return SetupField<TField, TType>(field);
|
||||
}
|
||||
|
||||
// Works for any TextValueField types, needs to know fieldType and object type
|
||||
private VisualElement SetupField<TField, TType>(TField field) where TField : VisualElement, INotifyValueChanged<TType>
|
||||
{
|
||||
field.AddToClassList("portField");
|
||||
if (TryGetValueObject(out object result))
|
||||
{
|
||||
field.value = (TType)result;
|
||||
}
|
||||
field.OnValueChanged((e) => SetNewValue(e.newValue, ValueIndices.value));
|
||||
_inputField = field;
|
||||
return _inputField;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 70616b8b964e3664780fc03f65f27f4f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loaded 100 of 3297 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user