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: 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:
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class UdonProgramSourceView : VisualElement
|
||||
{
|
||||
private UdonProgramAsset _asset;
|
||||
|
||||
private VisualContainer _assemblyContainer;
|
||||
private ScrollView _scrollView;
|
||||
private Label _assemblyHeader;
|
||||
private TextElement _assemblyField;
|
||||
|
||||
public UdonProgramSourceView ()
|
||||
{
|
||||
// Create and add container and children to display latest Assembly
|
||||
_assemblyContainer = new VisualContainer() { name = "Container", };
|
||||
_assemblyHeader = new Label("Udon Assembly")
|
||||
{
|
||||
name = "Header",
|
||||
};
|
||||
|
||||
_scrollView = new ScrollView();
|
||||
|
||||
_assemblyField = new TextElement()
|
||||
{
|
||||
name = "AssemblyField",
|
||||
};
|
||||
|
||||
_assemblyContainer.Add(_assemblyHeader);
|
||||
_assemblyContainer.Add(_scrollView);
|
||||
_assemblyContainer.Add(_assemblyField);
|
||||
_scrollView.SetContents(_assemblyField);
|
||||
|
||||
Add(_assemblyContainer);
|
||||
}
|
||||
|
||||
public void LoadAsset(UdonGraphProgramAsset asset)
|
||||
{
|
||||
_asset = asset;
|
||||
}
|
||||
|
||||
public void SetText(string newValue)
|
||||
{
|
||||
_assemblyField.text = newValue;
|
||||
}
|
||||
|
||||
public void Unload()
|
||||
{
|
||||
if(_asset != null)
|
||||
{
|
||||
_asset = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fddc146e8502d7b49a294b6264d66dfd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,185 @@
|
||||
using UnityEditor;
|
||||
using UnityEditor.Experimental.UIElements;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
|
||||
public class UdonWelcomeView : VisualElement
|
||||
{
|
||||
private Button _openLastGraphButton;
|
||||
|
||||
public UdonWelcomeView()
|
||||
{
|
||||
name = "udon-welcome";
|
||||
this.RegisterCallback<AttachToPanelEvent>(Initialize);
|
||||
}
|
||||
|
||||
|
||||
private void Initialize(AttachToPanelEvent evt)
|
||||
{
|
||||
// switch event to do some UI updates instead of initialization from here on out
|
||||
UnregisterCallback<AttachToPanelEvent>(Initialize);
|
||||
// this.RegisterCallback<AttachToPanelEvent>(OnAttach);
|
||||
|
||||
// Add Header
|
||||
Add(new TextElement()
|
||||
{
|
||||
name = "intro",
|
||||
text = "Udon Graph",
|
||||
});
|
||||
|
||||
Add(new TextElement()
|
||||
{
|
||||
name = "header-message",
|
||||
text = "The Udon Graph is your gateway to creating amazing things in VRChat.\nCheck out the Readme and UdonExampleScene in the VRChat Examples folder to get started."
|
||||
});
|
||||
|
||||
var mainContainer = new VisualElement()
|
||||
{
|
||||
name = "main",
|
||||
};
|
||||
|
||||
Add(mainContainer);
|
||||
|
||||
var template = EditorGUIUtility.Load("Assets/Udon/Editor/Resources/UdonChangelog.uxml") as VisualTreeAsset;
|
||||
var changelog = template.CloneTree(null);
|
||||
changelog.name = "changelog";
|
||||
mainContainer.Add(changelog);
|
||||
|
||||
var column2 = new VisualContainer() { name = "column-2" };
|
||||
mainContainer.Add(column2);
|
||||
|
||||
// Add Button for Last Graph
|
||||
if (!string.IsNullOrEmpty(Settings.LastGraphGuid))
|
||||
{
|
||||
_openLastGraphButton = new Button(() =>
|
||||
{
|
||||
var assetPath = AssetDatabase.GUIDToAssetPath(Settings.LastGraphGuid);
|
||||
var graphName = assetPath.Substring(assetPath.LastIndexOf("/") + 1).Replace(".asset", "");
|
||||
|
||||
// Find actual asset from guid
|
||||
var asset = AssetDatabase.LoadAssetAtPath<UdonGraphProgramAsset>(assetPath);
|
||||
if (asset != null)
|
||||
{
|
||||
var w = EditorWindow.GetWindow<UdonGraphWindow>("Udon Graph", true, typeof(SceneView));
|
||||
// get reference to saved UdonBehaviour if possible
|
||||
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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize graph with restored udonBehaviour or null if not found / not saved
|
||||
w.InitializeGraph(asset, udonBehaviour);
|
||||
}
|
||||
});
|
||||
|
||||
UpdateLastGraphButtonLabel();
|
||||
column2.Add(_openLastGraphButton);
|
||||
}
|
||||
|
||||
var settingsTemplate = EditorGUIUtility.Load("Assets/Udon/Editor/Resources/UdonSettings.uxml") as VisualTreeAsset;
|
||||
var settings = settingsTemplate.CloneTree(null);
|
||||
settings.name = "settings";
|
||||
column2.Add(settings);
|
||||
|
||||
// get reference to first settings section
|
||||
var section = settings.Q("section");
|
||||
|
||||
// Add Grid Snap setting
|
||||
var gridSnapContainer = new VisualElement();
|
||||
gridSnapContainer.AddToClassList("settings-item-container");
|
||||
var gridSnapField = new IntegerField(3)
|
||||
{
|
||||
value = Settings.GridSnapSize
|
||||
};
|
||||
gridSnapField.OnValueChanged(e =>
|
||||
{
|
||||
Settings.GridSnapSize = e.newValue;
|
||||
});
|
||||
gridSnapContainer.Add(new Label("Grid Snap Size"));
|
||||
gridSnapContainer.Add(gridSnapField);
|
||||
section.Add(gridSnapContainer);
|
||||
var gridSnapLabel = new Label("Snap elements to a grid as you move them. 0 for No Snapping.");
|
||||
gridSnapLabel.AddToClassList("settings-label");
|
||||
section.Add(gridSnapLabel);
|
||||
|
||||
// Add Search On Selected Node settings
|
||||
var searchOnSelectedNode = (new Toggle()
|
||||
{
|
||||
text = "Focus Search On Selected Node",
|
||||
value = Settings.SearchOnSelectedNodeRegistry,
|
||||
});
|
||||
searchOnSelectedNode.OnValueChanged((toggleEvent) =>
|
||||
{
|
||||
Settings.SearchOnSelectedNodeRegistry = toggleEvent.newValue;
|
||||
});
|
||||
section.Add(searchOnSelectedNode);
|
||||
var searchOnLabel = new Label("Highlight a node and press Spacebar to open a Search Window focused on nodes for that type. ");
|
||||
searchOnLabel.AddToClassList("settings-label");
|
||||
section.Add(searchOnLabel);
|
||||
|
||||
// Add Search On Noodle Drop settings
|
||||
var searchOnNoodleDrop = (new Toggle()
|
||||
{
|
||||
text = "Search On Noodle Drop",
|
||||
value = Settings.SearchOnNoodleDrop,
|
||||
});
|
||||
searchOnNoodleDrop.OnValueChanged((toggleEvent) =>
|
||||
{
|
||||
Settings.SearchOnNoodleDrop = toggleEvent.newValue;
|
||||
});
|
||||
section.Add(searchOnNoodleDrop);
|
||||
var searchOnDropLabel = new Label("Drop a noodle into empty space to search for anything that can be connected.");
|
||||
searchOnDropLabel.AddToClassList("settings-label");
|
||||
section.Add(searchOnDropLabel);
|
||||
|
||||
// Add UseNeonStyle setting
|
||||
var useNeonStyle = (new Toggle()
|
||||
{
|
||||
text = "Use Neon Style",
|
||||
value = Settings.UseNeonStyle,
|
||||
});
|
||||
useNeonStyle.OnValueChanged((toggleEvent) =>
|
||||
{
|
||||
Settings.UseNeonStyle = toggleEvent.newValue;
|
||||
});
|
||||
section.Add(useNeonStyle);
|
||||
var useNeonStyleLabel = new Label("Try out an experimental Neon Style. We will support User Styles in an upcoming version.");
|
||||
useNeonStyleLabel.AddToClassList("settings-label");
|
||||
section.Add(useNeonStyleLabel);
|
||||
|
||||
}
|
||||
|
||||
private void UpdateLastGraphButtonLabel()
|
||||
{
|
||||
if (_openLastGraphButton == null) return;
|
||||
|
||||
string currentButtonAssetGuid = (string)_openLastGraphButton.userData;
|
||||
if (string.Compare(currentButtonAssetGuid, Settings.LastGraphGuid) != 0)
|
||||
{
|
||||
var assetPath = AssetDatabase.GUIDToAssetPath(Settings.LastGraphGuid);
|
||||
var graphName = assetPath.Substring(assetPath.LastIndexOf("/") + 1).Replace(".asset", "");
|
||||
|
||||
_openLastGraphButton.userData = Settings.LastGraphGuid;
|
||||
_openLastGraphButton.text = $"Open {graphName}";
|
||||
}
|
||||
}
|
||||
|
||||
private void OnAttach(AttachToPanelEvent evt)
|
||||
{
|
||||
UpdateLastGraphButtonLabel();
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5786fc577943ae45953c6f54c97116b
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+189
@@ -0,0 +1,189 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine.Experimental.UIElements;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Experimental.UIElements;
|
||||
using UnityEngine.Video;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine.SceneManagement;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI.GraphView
|
||||
{
|
||||
public class VideoPlayerElement : VisualContainer
|
||||
{
|
||||
|
||||
private VideoPlayer _player;
|
||||
private Scene _tempScene;
|
||||
private Toolbar _toolbar;
|
||||
private TextElement _header;
|
||||
private Image _videoProxy;
|
||||
|
||||
public VideoPlayerElement()
|
||||
{
|
||||
// Constructing Items
|
||||
_header = new TextElement() {
|
||||
text = "Using Focused Search",
|
||||
name = "header",
|
||||
};
|
||||
|
||||
_videoProxy = new Image()
|
||||
{
|
||||
name = "video-proxy",
|
||||
image = Resources.Load<Texture2D>("videoStill"),
|
||||
scaleMode = ScaleMode.ScaleToFit,
|
||||
};
|
||||
|
||||
_toolbar = new Toolbar
|
||||
{
|
||||
// Add buttons by hand in order to set callbacks
|
||||
new ToolbarButton(Play) { text = "Play", name = "button-play" },
|
||||
|
||||
new ToolbarButton(Pause) { text = "Pause", name = "button-pause" }
|
||||
};
|
||||
|
||||
// Adding Items
|
||||
Add(_header);
|
||||
Add(_videoProxy);
|
||||
Add(_toolbar);
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
ShowFrame();
|
||||
RegisterCallback<MouseDownEvent>(OnMouseDown);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
UnregisterCallback<MouseDownEvent>(OnMouseDown);
|
||||
if (_tempScene != null && _tempScene.IsValid())
|
||||
{
|
||||
EditorSceneManager.UnloadSceneAsync(_tempScene);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnMouseDown(MouseDownEvent evt)
|
||||
{
|
||||
PlayPauseToggle();
|
||||
}
|
||||
|
||||
public void PlayPauseToggle()
|
||||
{
|
||||
if (GetCurrentPlayer().isPlaying)
|
||||
{
|
||||
Pause();
|
||||
}
|
||||
else if (GetCurrentPlayer().isPaused)
|
||||
{
|
||||
Play();
|
||||
}
|
||||
}
|
||||
|
||||
public void Play()
|
||||
{
|
||||
GetCurrentPlayer().Play();
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
GetCurrentPlayer().Pause();
|
||||
}
|
||||
|
||||
public void LoadVideo(string url)
|
||||
{
|
||||
var player = GetCurrentPlayer();
|
||||
player.url = url;
|
||||
player.sendFrameReadyEvents = true;
|
||||
player.frameReady += OnFrameReady;
|
||||
player.isLooping = true;
|
||||
ShowFrame();
|
||||
}
|
||||
|
||||
public void ShowFrame()
|
||||
{
|
||||
var player = GetCurrentPlayer();
|
||||
if(player == null || string.IsNullOrEmpty(player.url))
|
||||
{
|
||||
return;
|
||||
}
|
||||
player.frameReady += PauseOnNextFrame;
|
||||
player.Play();
|
||||
}
|
||||
|
||||
public void UnloadVideo()
|
||||
{
|
||||
var player = GetCurrentPlayer();
|
||||
player.url = null;
|
||||
player.sendFrameReadyEvents = false;
|
||||
player.frameReady -= OnFrameReady;
|
||||
}
|
||||
|
||||
private void OnFrameReady(VideoPlayer source, long frameIdx)
|
||||
{
|
||||
_videoProxy.image = source.texture;
|
||||
MarkDirtyRepaint();
|
||||
}
|
||||
|
||||
private void PauseOnNextFrame(VideoPlayer source, long frameIdx)
|
||||
{
|
||||
var player = GetCurrentPlayer();
|
||||
player.frameReady -= PauseOnNextFrame;
|
||||
player.Pause();
|
||||
}
|
||||
|
||||
private Scene GetTempScene()
|
||||
{
|
||||
if (_tempScene == null)
|
||||
{
|
||||
_tempScene = EditorSceneManager.NewPreviewScene();
|
||||
var root = new GameObject("VideoPlayer");
|
||||
EditorSceneManager.MoveGameObjectToScene(root, _tempScene);
|
||||
}
|
||||
return _tempScene;
|
||||
}
|
||||
|
||||
private VideoPlayer GetCurrentPlayer()
|
||||
{
|
||||
// Try to get player from scene if it's not cached
|
||||
if (_player == null)
|
||||
{
|
||||
_player = GetTempScene().GetRootGameObjects()[0].GetComponent<VideoPlayer>();
|
||||
|
||||
// Make new player if it doesn't exist in the scene
|
||||
if (_player == null)
|
||||
{
|
||||
_player = GetTempScene().GetRootGameObjects()[0].AddComponent<VideoPlayer>();
|
||||
_player.renderMode = VideoRenderMode.APIOnly;
|
||||
_player.source = VideoSource.Url;
|
||||
_player.audioOutputMode = VideoAudioOutputMode.None;
|
||||
_player.playOnAwake = false;
|
||||
_player.Prepare();
|
||||
}
|
||||
}
|
||||
return _player;
|
||||
}
|
||||
|
||||
|
||||
public new class UxmlFactory : UxmlFactory<VideoPlayerElement, UxmlTraits> { }
|
||||
|
||||
public new class UxmlTraits : UnityEngine.Experimental.UIElements.UxmlTraits
|
||||
{
|
||||
UxmlStringAttributeDescription m_Url = new UxmlStringAttributeDescription { name = "url-attr", defaultValue = "" };
|
||||
|
||||
public override IEnumerable<UxmlChildElementDescription> uxmlChildElementsDescription { get; }
|
||||
|
||||
public override void Init(VisualElement ve, IUxmlAttributes bag, CreationContext cc)
|
||||
{
|
||||
base.Init(ve, bag, cc);
|
||||
|
||||
var ate = ve as VideoPlayerElement;
|
||||
|
||||
ate.Clear();
|
||||
|
||||
ate.urlAttr = m_Url.GetValueFromBag(bag, cc);
|
||||
}
|
||||
}
|
||||
|
||||
public string urlAttr { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aabdd863f82551d40bd3a1b0835d2fc3
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,618 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using Microsoft.CSharp;
|
||||
using System.CodeDom;
|
||||
using UnityEngine;
|
||||
using System.Security.Cryptography;
|
||||
using System.IO;
|
||||
using System.IO.Compression;
|
||||
using VRC.Udon.Graph;
|
||||
using VRC.Udon.Graph.Interfaces;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram.UI
|
||||
{
|
||||
public static class UdonGraphExtensions
|
||||
{
|
||||
private static readonly Dictionary<string, string> FriendlyNameCache;
|
||||
|
||||
static UdonGraphExtensions()
|
||||
{
|
||||
FriendlyNameCache = new Dictionary<string, string>();
|
||||
StartsWithCache = new Dictionary<(string s, string prefix), bool>();
|
||||
}
|
||||
|
||||
#region Serialization Utilities
|
||||
private const byte ZIP_VERSION = 0;
|
||||
public static string ZipString(string str)
|
||||
{
|
||||
using (MemoryStream output = new MemoryStream())
|
||||
{
|
||||
using (DeflateStream gzip =
|
||||
new DeflateStream(output, System.IO.Compression.CompressionLevel.Optimal)) //, CompressionMode.Compress
|
||||
{
|
||||
using (StreamWriter writer =
|
||||
new StreamWriter(gzip, System.Text.Encoding.UTF8))
|
||||
{
|
||||
writer.Write(str);
|
||||
}
|
||||
}
|
||||
List<byte> outputList = output.ToArray().ToList();
|
||||
outputList.Insert(0, ZIP_VERSION); //Version Number
|
||||
return Convert.ToBase64String(outputList.ToArray());
|
||||
}
|
||||
}
|
||||
|
||||
public static string UnZipString(string input)
|
||||
{
|
||||
List<byte> inputList = new List<byte>(Convert.FromBase64String(input));
|
||||
if (inputList[0] != ZIP_VERSION) //Version Number
|
||||
{
|
||||
return "";
|
||||
}
|
||||
inputList.RemoveAt(0);
|
||||
using (MemoryStream inputStream = new MemoryStream(inputList.ToArray()))
|
||||
{
|
||||
using (DeflateStream gzip =
|
||||
new DeflateStream(inputStream, CompressionMode.Decompress))
|
||||
{
|
||||
using (StreamReader reader =
|
||||
new StreamReader(gzip, System.Text.Encoding.UTF8))
|
||||
{
|
||||
return reader.ReadToEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Color Utilities
|
||||
|
||||
public static class NodeColors
|
||||
{
|
||||
public static readonly Color Base = new Color(77f / 255f, 157f / 255f, 1);
|
||||
public static readonly Color Const = new Color(0.4f, 0.14f, 1f);
|
||||
public static readonly Color Variable = new Color(1f, 0.86f, 0.3f);
|
||||
public static readonly Color Function = new Color(1f, 0.42f, 0.14f);
|
||||
public static readonly Color Event = new Color(0.53f, 1f, 0.3f);
|
||||
public static readonly Color Return = new Color(0.89f, 0.2f, 0.2f);
|
||||
}
|
||||
|
||||
private static readonly MD5 _md5Hasher = MD5.Create();
|
||||
private static readonly Dictionary<Type, Color> _typeColors = new Dictionary<Type, Color>();
|
||||
|
||||
public static Color MapTypeToColor(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return Color.white;
|
||||
}
|
||||
|
||||
if (type.IsPrimitive)
|
||||
{
|
||||
return new Color(0.12f, 0.53f, 0.9f);
|
||||
}
|
||||
|
||||
if (typeof(UnityEngine.Object).IsAssignableFrom(type))
|
||||
{
|
||||
return new Color(0.9f, 0.23f, 0.39f);
|
||||
}
|
||||
|
||||
if (type.IsValueType)
|
||||
{
|
||||
return NodeColors.Variable;
|
||||
}
|
||||
|
||||
if (_typeColors.ContainsKey(type))
|
||||
{
|
||||
return _typeColors[type];
|
||||
}
|
||||
|
||||
byte[] hashed = _md5Hasher.ComputeHash(type.ToString() == "T"
|
||||
? System.Text.Encoding.UTF8.GetBytes("T")
|
||||
: System.Text.Encoding.UTF8.GetBytes(type.Name));
|
||||
int iValue = BitConverter.ToInt32(hashed, 0);
|
||||
|
||||
//TODO: Make this provide more varied colors
|
||||
Color color = Color.HSVToRGB((iValue & 0xff) / 255f, .69f, 1f);
|
||||
|
||||
|
||||
_typeColors.Add(type, color);
|
||||
return color;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Documentation Utilities
|
||||
|
||||
public static bool ShouldShowDocumentationLink(UdonNodeDefinition definition)
|
||||
{
|
||||
List<string> specialNames = new List<string>
|
||||
{
|
||||
"Block",
|
||||
"Branch",
|
||||
"For",
|
||||
"While",
|
||||
"Foreach",
|
||||
"Get_Variable",
|
||||
"Set_Variable",
|
||||
"Event_Custom",
|
||||
"Event_OnDataStorageAdded",
|
||||
"Event_OnDataStorageChanged",
|
||||
"Event_OnDataStorageRemoved",
|
||||
"Event_OnDrop",
|
||||
"Event_Interact",
|
||||
"Event_OnNetworkReady",
|
||||
"Event_OnOwnershipTransferred",
|
||||
"Event_OnPickup",
|
||||
"Event_OnPickupUseDown",
|
||||
"Event_OnPickupUseUp",
|
||||
"Event_OnPlayerJoined",
|
||||
"Event_OnPlayerLeft",
|
||||
"Event_OnSpawn",
|
||||
"Event_OnStationEntered",
|
||||
"Event_OnStationExited",
|
||||
"Event_OnVideoEnd",
|
||||
"Event_OnVideoPause",
|
||||
"Event_OnVideoPlay",
|
||||
"Event_OnVideoStart",
|
||||
"VRCUdonCommonInterfacesIUdonEventReceiver.__SendCustomEvent__SystemString__SystemVoid",
|
||||
"VRCUdonCommonInterfacesIUdonEventReceiver.__SetHeapVariable__SystemString_SystemObject__SystemVoid",
|
||||
"VRCUdonCommonInterfacesIUdonEventReceiver.__GetHeapVariable__SystemString__SystemObject",
|
||||
"Const_VRCUdonCommonInterfacesIUdonEventReceiver",
|
||||
};
|
||||
|
||||
// Don't show for any of these
|
||||
return !(definition.type == null ||
|
||||
definition.type.Namespace == null ||
|
||||
specialNames.Contains(definition.fullName) ||
|
||||
(!definition.type.Namespace.Contains("UnityEngine") &&
|
||||
!definition.type.Namespace.Contains("System")));
|
||||
}
|
||||
|
||||
public static string GetDocumentationLink(UdonNodeDefinition definition)
|
||||
{
|
||||
if (definition.fullName.StartsWithCached("Event_"))
|
||||
{
|
||||
string url = "https://docs.unity3d.com/2018.4/Documentation/ScriptReference/MonoBehaviour.";
|
||||
url += definition.name;
|
||||
url += ".html";
|
||||
return url;
|
||||
}
|
||||
|
||||
if (definition.fullName.Contains("Array.__ctor"))
|
||||
{
|
||||
//I couldn't find the array constructor documentation
|
||||
return "https://docs.microsoft.com/en-us/dotnet/api/system.array?view=netframework-4.8";
|
||||
}
|
||||
|
||||
if (definition.fullName.Contains("Array.__Get"))
|
||||
{
|
||||
return "https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#indexer-operator-";
|
||||
}
|
||||
|
||||
if (definition.fullName.Contains(".__Equals__SystemObject"))
|
||||
{
|
||||
return "https://docs.microsoft.com/en-us/dotnet/api/system.object.equals?view=netframework-4.8";
|
||||
}
|
||||
|
||||
if (definition.name.Contains("[]"))
|
||||
{
|
||||
string url = "https://docs.microsoft.com/en-us/dotnet/api/system.array.";
|
||||
url += definition.name.Split(' ')[1];
|
||||
url += "?view=netframework-4.8";
|
||||
return url;
|
||||
}
|
||||
|
||||
if (definition.type.Namespace.Contains("UnityEngine"))
|
||||
{
|
||||
string url = "https://docs.unity3d.com/2018.4/Documentation/ScriptReference/";
|
||||
if (definition.type.Namespace != "UnityEngine")
|
||||
{
|
||||
url += definition.type.Namespace.Replace("UnityEngine.", "");
|
||||
url += ".";
|
||||
}
|
||||
url += definition.type.Name;
|
||||
|
||||
if (definition.fullName.Contains("__get_") || definition.fullName.Contains("__set_"))
|
||||
{
|
||||
if (definition.fullName.Contains("__get_"))
|
||||
{
|
||||
url += "-" + definition.name.Split(new[] { "get_" }, StringSplitOptions.None)[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
url += "-" + definition.name.Split(new[] { "set_" }, StringSplitOptions.None)[1];
|
||||
}
|
||||
|
||||
url += ".html";
|
||||
return url;
|
||||
}
|
||||
|
||||
if (definition.fullName.Contains("Const_") || definition.fullName.Contains("Type_") || definition.fullName.Contains("Variable_"))
|
||||
{
|
||||
url += ".html";
|
||||
return url;
|
||||
}
|
||||
|
||||
{
|
||||
// Methods
|
||||
url += "." + definition.name.Split(' ')[1];
|
||||
url += ".html";
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
if (definition.type.Namespace.Contains("System"))
|
||||
{
|
||||
string url = "https://docs.microsoft.com/en-us/dotnet/api/system.";
|
||||
url += definition.type.Name;
|
||||
if (definition.fullName.Contains("__get_") || definition.fullName.Contains("__set_"))
|
||||
{
|
||||
url += "." + definition.name.Split(' ')[1].Replace("get_", "").Replace("set_", "");
|
||||
url += "?view=netframework-4.8";
|
||||
return url;
|
||||
}
|
||||
|
||||
if (definition.name == "ctor")
|
||||
{
|
||||
url += ".-ctor";
|
||||
url += "?view=netframework-4.8#System_";
|
||||
url += definition.type.Name + "__ctor_";
|
||||
foreach (var pType in definition.Inputs)
|
||||
{
|
||||
url += "System_" + pType.type.Name.Replace('[', '_').Replace(']', '_') + "_";
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
if (definition.fullName.Contains("Const_") || definition.fullName.Contains("Type_"))
|
||||
{
|
||||
url += "?view=netframework-4.8";
|
||||
return url;
|
||||
}
|
||||
|
||||
{
|
||||
// Methods
|
||||
// not entirely sure what case this catches, but we were always doing the split before, and it was breaking if we didn't have . in the name.
|
||||
if (definition.name.Contains('.'))
|
||||
{
|
||||
url += "." + definition.name.Split(' ')[1];
|
||||
}
|
||||
url += "?view=netframework-4.8";
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static string FriendlyNameify(this string typeString)
|
||||
{
|
||||
if (typeString == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (FriendlyNameCache.ContainsKey(typeString))
|
||||
{
|
||||
return FriendlyNameCache[typeString];
|
||||
}
|
||||
string originalString = typeString;
|
||||
typeString = typeString.Replace("Single", "float");
|
||||
typeString = typeString.Replace("Int32", "int");
|
||||
typeString = typeString.Replace("String", "string");
|
||||
typeString = typeString.Replace("VRCUdonCommonInterfacesIUdonEventReceiver", "UdonBehaviour");
|
||||
typeString = typeString.Replace("UdonCommonInterfacesIUdonEventReceiver", "UdonBehaviour");
|
||||
typeString = typeString.Replace("IUdonEventReceiver", "UdonBehaviour");
|
||||
typeString = typeString.Replace("Const_VRCUdonCommonInterfacesIUdonEventReceiver", "UdonBehaviour");
|
||||
typeString = typeString.Replace("Array", "[]");
|
||||
typeString = typeString.Replace("SDK3VideoComponentsBaseBase", "");
|
||||
typeString = typeString.Replace("SDKBase", "");
|
||||
typeString = typeString.Replace("SDK3Components", "");
|
||||
typeString = typeString.Replace("VRCVRC", "VRC");
|
||||
typeString = typeString.Replace("TMPro", "");
|
||||
// ReSharper disable once StringLiteralTypo
|
||||
if (typeString.Replace("ector", "").Contains("ctor")) //Handle "Vector/vector"
|
||||
{
|
||||
typeString = typeString.ReplaceLast("ctor", "constructor");
|
||||
}
|
||||
|
||||
if (typeString == "IUdonEventReceiver")
|
||||
{
|
||||
typeString = "UdonBehaviour";
|
||||
}
|
||||
FriendlyNameCache.Add(originalString, typeString);
|
||||
return typeString;
|
||||
}
|
||||
|
||||
private static readonly Dictionary<(string s, string prefix), bool> StartsWithCache;
|
||||
public static bool StartsWithCached(this string s, string prefix)
|
||||
{
|
||||
if (StartsWithCache.ContainsKey((s, prefix)))
|
||||
{
|
||||
return StartsWithCache[(s, prefix)];
|
||||
}
|
||||
bool doesStartWith = s.StartsWith(prefix);
|
||||
StartsWithCache.Add((s, prefix), doesStartWith);
|
||||
return doesStartWith;
|
||||
}
|
||||
|
||||
public static string UppercaseFirst(this string s)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
char[] a = s.ToCharArray();
|
||||
a[0] = char.ToUpper(a[0]);
|
||||
return new string(a);
|
||||
}
|
||||
|
||||
public static string ReplaceFirst(this string text, string search, string replace)
|
||||
{
|
||||
int pos = text.IndexOf(search, StringComparison.Ordinal);
|
||||
if (pos < 0)
|
||||
{
|
||||
return text;
|
||||
}
|
||||
return text.Substring(0, pos) + replace + text.Substring(pos + search.Length);
|
||||
}
|
||||
|
||||
public static Type SlotTypeConverter(Type type, string fullName)
|
||||
{
|
||||
if (type == null)
|
||||
{
|
||||
return typeof(object);
|
||||
}
|
||||
|
||||
if (fullName.Contains("IUdonEventReceiver") && type == typeof(UnityEngine.Object))
|
||||
{
|
||||
return typeof(UdonBehaviour);
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
public static string FriendlyTypeName(Type t)
|
||||
{
|
||||
if (t == null)
|
||||
{
|
||||
return "Flow";
|
||||
}
|
||||
|
||||
if (!t.IsPrimitive)
|
||||
{
|
||||
if (t == typeof(UnityEngine.Object))
|
||||
{
|
||||
return "Unity Object";
|
||||
}
|
||||
return t.Name;
|
||||
}
|
||||
using (CSharpCodeProvider provider = new CSharpCodeProvider())
|
||||
{
|
||||
CodeTypeReference typeRef = new CodeTypeReference(t);
|
||||
return provider.GetTypeOutput(typeRef);
|
||||
}
|
||||
}
|
||||
|
||||
public static string ReplaceLast(this string source, string find, string replace)
|
||||
{
|
||||
int place = source.LastIndexOf(find, StringComparison.Ordinal);
|
||||
|
||||
if (place == -1)
|
||||
return source;
|
||||
|
||||
string result = source.Remove(place, find.Length).Insert(place, replace);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string PrettyBaseName(string baseIdentifier)
|
||||
{
|
||||
string result = baseIdentifier.Replace("UnityEngine", "").Replace("System", "");
|
||||
string[] resultSplit = result.Split(new[] { "__" }, StringSplitOptions.None);
|
||||
if (resultSplit.Length >= 2)
|
||||
{
|
||||
result = $"{resultSplit[0]}{resultSplit[1]}";
|
||||
}
|
||||
result = result.FriendlyNameify();
|
||||
result = result.Replace("op_", "");
|
||||
result = result.Replace("_", " ");
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string PrettyString(string s)
|
||||
{
|
||||
switch (s)
|
||||
{
|
||||
case "op_Equality":
|
||||
s = "==";
|
||||
break;
|
||||
|
||||
case "op_Inequality":
|
||||
s = "!=";
|
||||
break;
|
||||
|
||||
case "op_Addition":
|
||||
s = "+";
|
||||
break;
|
||||
case "VRCUdonCommonInterfacesIUdonEventReceiver":
|
||||
s = "UdonBehaviour";
|
||||
break;
|
||||
// ReSharper disable once RedundantEmptySwitchSection
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
public static string ParseByCase(string strInput)
|
||||
{
|
||||
string strOutput = "";
|
||||
int intCurrentCharPos = 0;
|
||||
int intLastCharPos = strInput.Length - 1;
|
||||
for (intCurrentCharPos = 0; intCurrentCharPos <= intLastCharPos; intCurrentCharPos++)
|
||||
{
|
||||
char chrCurrentInputChar = strInput[intCurrentCharPos];
|
||||
char chrPreviousInputChar = chrCurrentInputChar;
|
||||
if (intCurrentCharPos > 0)
|
||||
{
|
||||
chrPreviousInputChar = strInput[intCurrentCharPos - 1];
|
||||
}
|
||||
|
||||
if (char.IsUpper(chrCurrentInputChar) && char.IsLower(chrPreviousInputChar))
|
||||
{
|
||||
strOutput += " ";
|
||||
}
|
||||
|
||||
strOutput += chrCurrentInputChar;
|
||||
}
|
||||
|
||||
return strOutput;
|
||||
}
|
||||
|
||||
public static string PrettyFullName(Graph.UdonNodeDefinition nodeDefinition, bool keepLong = false)
|
||||
{
|
||||
string fullName = nodeDefinition.fullName;
|
||||
string result;
|
||||
if (keepLong)
|
||||
{
|
||||
result = fullName.Replace("UnityEngine", "UnityEngine.").Replace("System", "System.");
|
||||
}
|
||||
else
|
||||
{
|
||||
result = fullName.Replace("UnityEngine", "").Replace("System", "");
|
||||
}
|
||||
|
||||
string[] resultSplit = result.Split(new[] { "__" }, StringSplitOptions.None);
|
||||
if (resultSplit.Length >= 3)
|
||||
{
|
||||
string outName = "";
|
||||
if (nodeDefinition.type != typeof(void))
|
||||
{
|
||||
if (nodeDefinition.Outputs.Count > 0)
|
||||
{
|
||||
outName = string.Join(", ", nodeDefinition.Outputs.Select(o => o.name));
|
||||
}
|
||||
}
|
||||
|
||||
result = nodeDefinition.Inputs.Count > 0
|
||||
? $"{resultSplit[0]}{resultSplit[1]}({string.Join(", ", nodeDefinition.Inputs.Select(s => s.name))}{outName})"
|
||||
: $"{resultSplit[0]}{resultSplit[1]}({resultSplit[2].Replace("_", ", ")}{outName})";
|
||||
}
|
||||
else if (resultSplit.Length >= 2)
|
||||
{
|
||||
result = $"{resultSplit[0]}{resultSplit[1]}()";
|
||||
}
|
||||
|
||||
if (!keepLong)
|
||||
{
|
||||
result = result.FriendlyNameify();
|
||||
result = result.Replace("op_", "");
|
||||
result = result.Replace("_", " ");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public static string GetSimpleNameForRegistry(Graph.Interfaces.INodeRegistry registry)
|
||||
{
|
||||
string registryName = registry.ToString().Replace("NodeRegistry", "").FriendlyNameify();
|
||||
registryName = registryName.Substring(registryName.LastIndexOf(".") + 1);
|
||||
registryName = registryName.Replace("UnityEngine", "");
|
||||
return registryName;
|
||||
}
|
||||
|
||||
private static Dictionary<UdonNodeDefinition, INodeRegistry> _definitionToRegistryLookup;
|
||||
public static INodeRegistry GetRegistryForDefinition(UdonNodeDefinition definition)
|
||||
{
|
||||
// Create lookup if needed
|
||||
if(_definitionToRegistryLookup == null)
|
||||
{
|
||||
_definitionToRegistryLookup = new Dictionary<UdonNodeDefinition, INodeRegistry>();
|
||||
|
||||
foreach (var registry in UdonEditorManager.Instance.GetNodeRegistries())
|
||||
{
|
||||
foreach (var nodeDefinition in registry.Value.GetNodeDefinitions())
|
||||
{
|
||||
_definitionToRegistryLookup.Add(nodeDefinition, registry.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return found Registry or Null
|
||||
if (_definitionToRegistryLookup.ContainsKey(definition))
|
||||
{
|
||||
return _definitionToRegistryLookup[definition];
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static string ToLowerFirstChar(this string input)
|
||||
{
|
||||
string newString = input;
|
||||
if (!String.IsNullOrEmpty(newString) && Char.IsUpper(newString[0]))
|
||||
newString = Char.ToLower(newString[0]) + newString.Substring(1);
|
||||
return newString;
|
||||
}
|
||||
|
||||
public static string SanitizeVariableName(this string result)
|
||||
{
|
||||
result = result.Replace(" ", "");
|
||||
if (char.IsNumber(result[0]))
|
||||
{
|
||||
result = $"A{result}";
|
||||
}
|
||||
Regex rgx = new Regex("[^a-zA-Z0-9 _]");
|
||||
result = rgx.Replace(result, "");
|
||||
return result;
|
||||
}
|
||||
|
||||
public static char EscapeLikeALiteral(string src)
|
||||
{
|
||||
switch (src)
|
||||
{
|
||||
//case "\\'": return '\'';
|
||||
//case "\\"": return '\"';
|
||||
case "\\0": return '\0';
|
||||
case "\\a": return '\a';
|
||||
case "\\b": return '\b';
|
||||
case "\\f": return '\f';
|
||||
case "\\n": return '\n';
|
||||
case "\\r": return '\r';
|
||||
case "\\t": return '\t';
|
||||
case "\\v": return '\v';
|
||||
case "\\": return '\\';
|
||||
default:
|
||||
throw new InvalidOperationException($"src was {src}");
|
||||
}
|
||||
}
|
||||
|
||||
public static string UnescapeLikeALiteral(char src)
|
||||
{
|
||||
switch (src)
|
||||
{
|
||||
//case "\\'": return '\'';
|
||||
//case "\\"": return '\"';
|
||||
case '\0': return "\\0";
|
||||
case '\a': return "\\a";
|
||||
case '\b': return "\\b";
|
||||
case '\f': return "\\f";
|
||||
case '\n': return "\\n";
|
||||
case '\r': return "\\r";
|
||||
case '\t': return "\\t";
|
||||
case '\v': return "\\v";
|
||||
//case '\\': return "\\";
|
||||
default:
|
||||
return src.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 57422d3fdb0cc124189c68f87b7157cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,207 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
using VRC.Udon.Common.Interfaces;
|
||||
using VRC.Udon.Editor.ProgramSources.UdonGraphProgram;
|
||||
using VRC.Udon.Editor.ProgramSources.Attributes;
|
||||
using VRC.Udon.Graph;
|
||||
using VRC.Udon.Graph.Interfaces;
|
||||
using VRC.Udon.Serialization.OdinSerializer;
|
||||
|
||||
[assembly: UdonProgramSourceNewMenu(typeof(UdonGraphProgramAsset), "Udon Graph Program Asset")]
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram
|
||||
{
|
||||
[CreateAssetMenu(menuName = "VRChat/Udon/Udon Graph Program Asset", fileName = "New Udon Graph Program Asset")]
|
||||
public class UdonGraphProgramAsset : UdonAssemblyProgramAsset, IUdonGraphDataProvider
|
||||
{
|
||||
[SerializeField]
|
||||
public UdonGraphData graphData = new UdonGraphData();
|
||||
|
||||
[SerializeField]
|
||||
public UdonGraphElementData[] graphElementData;
|
||||
|
||||
[SerializeField]
|
||||
public UI.GraphView.UdonGraph.ViewTransformData viewTransform = new UI.GraphView.UdonGraph.ViewTransformData();
|
||||
|
||||
[SerializeField]
|
||||
public string version = "1.0.0";
|
||||
|
||||
[SerializeField]
|
||||
private bool showAssembly = false;
|
||||
|
||||
[NonSerialized, OdinSerialize]
|
||||
private Dictionary<string, (object value, Type type)> heapDefaultValues = new Dictionary<string, (object value, Type type)>();
|
||||
|
||||
protected override void DrawProgramSourceGUI(UdonBehaviour udonBehaviour, ref bool dirty)
|
||||
{
|
||||
if (GUILayout.Button("Open Udon Graph", "LargeButton"))
|
||||
{
|
||||
var w = EditorWindow.GetWindow<UI.GraphView.UdonGraphWindow>("Udon Graph", true, typeof(SceneView));
|
||||
w.InitializeGraph(this, udonBehaviour);
|
||||
}
|
||||
|
||||
DrawPublicVariables(udonBehaviour, ref dirty);
|
||||
DrawAssemblyErrorTextArea();
|
||||
DrawAssemblyTextArea(false, ref dirty);
|
||||
}
|
||||
|
||||
protected override void RefreshProgramImpl()
|
||||
{
|
||||
if(graphData == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CompileGraph();
|
||||
base.RefreshProgramImpl();
|
||||
ApplyDefaultValuesToHeap();
|
||||
}
|
||||
|
||||
protected override void DrawAssemblyTextArea(bool allowEditing, ref bool dirty)
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
bool newShowAssembly = EditorGUILayout.Foldout(showAssembly, "Compiled Graph Assembly");
|
||||
if(EditorGUI.EndChangeCheck())
|
||||
{
|
||||
Undo.RecordObject(this, "Toggle Assembly Foldout");
|
||||
showAssembly = newShowAssembly;
|
||||
}
|
||||
|
||||
if(!showAssembly)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
EditorGUI.indentLevel++;
|
||||
base.DrawAssemblyTextArea(allowEditing, ref dirty);
|
||||
EditorGUI.indentLevel--;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
protected void CompileGraph()
|
||||
{
|
||||
udonAssembly = UdonEditorManager.Instance.CompileGraph(graphData, null, out Dictionary<string, (string uid, string fullName, int index)> _, out heapDefaultValues);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
protected void ApplyDefaultValuesToHeap()
|
||||
{
|
||||
IUdonSymbolTable symbolTable = program?.SymbolTable;
|
||||
IUdonHeap heap = program?.Heap;
|
||||
if(symbolTable == null || heap == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach(KeyValuePair<string, (object value, Type type)> defaultValue in heapDefaultValues)
|
||||
{
|
||||
if(!symbolTable.HasAddressForSymbol(defaultValue.Key))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uint symbolAddress = symbolTable.GetAddressFromSymbol(defaultValue.Key);
|
||||
(object value, Type declaredType) = defaultValue.Value;
|
||||
if(typeof(UnityEngine.Object).IsAssignableFrom(declaredType))
|
||||
{
|
||||
if(value != null && !declaredType.IsInstanceOfType(value))
|
||||
{
|
||||
heap.SetHeapVariable(symbolAddress, null, declaredType);
|
||||
continue;
|
||||
}
|
||||
|
||||
if((UnityEngine.Object)value == null)
|
||||
{
|
||||
heap.SetHeapVariable(symbolAddress, null, declaredType);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(value != null)
|
||||
{
|
||||
if(!declaredType.IsInstanceOfType(value))
|
||||
{
|
||||
value = declaredType.IsValueType ? Activator.CreateInstance(declaredType) : null;
|
||||
}
|
||||
}
|
||||
|
||||
if(declaredType == null)
|
||||
{
|
||||
declaredType = typeof(object);
|
||||
}
|
||||
heap.SetHeapVariable(symbolAddress, value, declaredType);
|
||||
}
|
||||
}
|
||||
|
||||
protected override object GetPublicVariableDefaultValue(string symbol, Type type)
|
||||
{
|
||||
IUdonSymbolTable symbolTable = program?.SymbolTable;
|
||||
IUdonHeap heap = program?.Heap;
|
||||
if(symbolTable == null || heap == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if(!heapDefaultValues.ContainsKey(symbol))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
(object value, Type declaredType) = heapDefaultValues[symbol];
|
||||
if(!typeof(UnityEngine.Object).IsAssignableFrom(declaredType))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return (UnityEngine.Object)value == null ? null : value;
|
||||
}
|
||||
|
||||
protected override object DrawPublicVariableField(string symbol, object variableValue, Type variableType, ref bool dirty,
|
||||
bool enabled)
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal();
|
||||
variableValue = base.DrawPublicVariableField(symbol, variableValue, variableType, ref dirty, enabled);
|
||||
object defaultValue = null;
|
||||
if(heapDefaultValues.ContainsKey(symbol))
|
||||
{
|
||||
defaultValue = heapDefaultValues[symbol].value;
|
||||
}
|
||||
|
||||
if(variableValue == null || !variableValue.Equals(defaultValue))
|
||||
{
|
||||
if(defaultValue != null)
|
||||
{
|
||||
if(!dirty && GUILayout.Button("Reset to Default Value"))
|
||||
{
|
||||
variableValue = defaultValue;
|
||||
dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
|
||||
return variableValue;
|
||||
}
|
||||
|
||||
#region Serialization Methods
|
||||
|
||||
protected override void OnAfterDeserialize()
|
||||
{
|
||||
foreach(UdonNodeData node in graphData.nodes)
|
||||
{
|
||||
node.SetGraph(graphData);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public UdonGraphData GetGraphData()
|
||||
{
|
||||
return graphData;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f11136daadff0b44ac2278a314682ab
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,9 @@
|
||||
using UnityEditor;
|
||||
|
||||
namespace VRC.Udon.Editor.ProgramSources.UdonGraphProgram
|
||||
{
|
||||
[CustomEditor(typeof(UdonGraphProgramAsset))]
|
||||
public class UdonGraphProgramAssetEditor : UdonAssemblyProgramAssetEditor
|
||||
{
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 31d6811854f59254aa1a263a8d566eb2
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in new issue
Block a user