Add VRChat and GitHub packages to Unity project

This commit is contained in:
gunnbr committed 2021-01-18 09:14:18 -08:00
1 parent 0334a52b21
commit 85538aaccc
3297 files changed
+847877

No files matched your search

@@ -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);
}
}
}
}
}
@@ -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];
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 602ffcf431b3e4f41a18bd868751439a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 42d9183d7c7ce67448d1e010456e36f9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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);
}
}
}
@@ -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);
}
}
}
@@ -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:
@@ -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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 469db50616185d04e8a46dcd75db12d2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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";
}
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7f257a6eeae213a4db991d486cace003
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f4f0ade55ae13b6468a765826f1f2540
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 7e5916b8dd19e4445a9156a457b82ee4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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);
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ba3ecc4c46929404d8c2ec920743b823
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1b8045222a10ce04b815642b9cd5ca17
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b006d67642298f04e895b6709ef12429
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dcd657bc1dcf357448d27bcfa8c5dc36
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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();
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 8f83d1d3578dd28498c71a980bca86dd
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c5cfbbfcd7bb5ad4487f1f9388a3a168
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -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:
@@ -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
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6581176c97993bb40976acff208bd0b1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: b721120e6c1d320448a55fe87a7de824
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e94c084f399869b42a21244fd07778c4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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();
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6a6c453fae11b5349a33399e258d1578
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e5a10bb1987c27944bd08a88119b2844
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d825ed3ba6aa7f14294e73efefc217d0
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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
}
}
@@ -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;
}
}
}
@@ -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:
@@ -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,
};
}
@@ -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);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 54dd824c6c614b94183d92710efe4f5f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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); }
}
}
}
@@ -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;
}
}
}
@@ -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();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5dcd92112af21784ba5bf6383abab768
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 70616b8b964e3664780fc03f65f27f4f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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;
}
}
}
}
@@ -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();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e5786fc577943ae45953c6f54c97116b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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; }
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: aabdd863f82551d40bd3a1b0835d2fc3
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: