Skip to content

Commit

Permalink
Editable Input Fields (#85)
Browse files Browse the repository at this point in the history
* Editable Input Fields

Added shader graph kinda serializefield support. Added an example node.

* renamed few things and fixed a value not reseted but

Co-authored-by: Antoine Lelièvre <antoinel@unity3d.com>
  • Loading branch information
alelievr committed Sep 7, 2020
2 parents 7fa3258 + d37b666 commit e04eb80
Show file tree
Hide file tree
Showing 4 changed files with 237 additions and 25 deletions.
131 changes: 123 additions & 8 deletions Editor/Resources/GraphProcessorStyles/BaseNodeView.uss
Expand Up @@ -16,7 +16,7 @@

#settings-button > #icon {
-unity-background-scale-mode: scale-to-fit;
background-image : resource("Icons/SettingsIcons");
background-image: resource("Icons/SettingsIcons");
flex-grow: 1;
}

Expand All @@ -29,13 +29,11 @@
#contents #top .true #connector, #contents #top .true #connector #cap,
#contents #top .false #connector, #contents #top .false #connector #cap,
#contents #top .loopBody #connector, #contents #top .loopBody #connector #cap,
#contents #top .loopCompleted #connector, #contents #top .loopCompleted #connector #cap,
{
#contents #top .loopCompleted #connector, #contents #top .loopCompleted #connector #cap {
border-radius: 0px;
}

.Highlight
{
.Highlight {
background-color: rgba(0, 63, 63, 0.8);
}

Expand Down Expand Up @@ -93,9 +91,126 @@ ParameterNodeView #controls EnumField > VisualElement > VisualElement {
margin-right: -2px;
}

#RightTitleContainer
{
#RightTitleContainer {
justify-content: flex-end;
flex-grow: 1;
flex-direction: row;
}
}

#input-container {
position: absolute;
right: 100%;
top: 45px;
align-items: flex-end;
--layer: -50;
}

#input-container > .port-input-element > IntegerField,
#input-container > .port-input-element > FloatField {
min-width: 30px;
max-width: 100px;
}

#input-container > .port-input-element > ColorField,
#input-container > .port-input-element > ObjectField,
#input-container > .port-input-element > CurveField {
margin-top: 0;
margin-bottom: 0;
}

#input-container > .port-input-element > TextField {
min-width: 50px;
max-width: 150px;
}

#input-container > .port-input-element > CurveField {
width: 100px;
}

#input-container > .port-input-element > Vector4Field Label,
#input-container > .port-input-element > Vector3Field Label,
#input-container > .port-input-element > Vector2Field Label {
font-size: 8px;
min-width: 8px;
flex-basis: 8px;
padding-top: 2px;
margin-right: 1px;
}

#input-container > .port-input-element > Vector4Field FloatInput,
#input-container > .port-input-element > Vector3Field FloatInput,
#input-container > .port-input-element > Vector2Field FloatInput {
min-width: 28px;
}

#input-container > .port-input-element > Vector2Field .unity-composite-field__field-spacer {
flex-grow: 0.01;
}

#input-container ObjectFieldSelector {
width: 17px;
height: 15px;
border-top-right-radius: 2px;
border-bottom-right-radius: 2px;
}

#input-container ObjectFieldDisplay > Label {
margin-top: 2px;
}

#input-container ObjectFieldDisplay > Image {
margin: 0;
}

#input-container > .port-input-element > ColorField {
width: 60px;
}

#input-container > .port-input-element > * {
margin-left: 2px;
}

#input-container > .port-input-element * {
font-size: 8px;
}

#input-container > .port-input-element {
max-width: 180px;
background-color: rgba(72, 72, 72, 0.6);
margin-top: 3px;
margin-bottom: 2px;
padding-top: 1px;
padding-bottom: 1px;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
height: 19px;
overflow: hidden;
padding-right: 1px;
}

#input-container > .port-input-element.empty {
width: 0;
}

.collapsed #input-container {
visibility: hidden;
}

#input-container {
opacity: 0.6;
}

.node:checked > #input-container {
opacity: 1;
}

#input-container > .port-input-element IntegerInput,
#input-container > .port-input-element FloatInput,
#input-container > .port-input-element TextInput {
border-top-left-radius: 1px;
border-bottom-left-radius: 1px;
border-top-right-radius: 1px;
border-bottom-right-radius: 1px;
padding-bottom: 1px;
height: 15px;
}
122 changes: 107 additions & 15 deletions Editor/Views/BaseNodeView.cs
Expand Up @@ -28,6 +28,7 @@ public class BaseNodeView : NodeView
public VisualElement controlsContainer;
protected VisualElement debugContainer;
protected VisualElement rightTitleContainer;
private VisualElement inputContainerElement;

VisualElement settings;
NodeSettingsView settingsContainer;
Expand Down Expand Up @@ -154,6 +155,8 @@ void InitializeView()
initializing = true;

SetPosition(nodeTarget.position);

AddInputContainer();
}

void InitializeSettings()
Expand Down Expand Up @@ -537,32 +540,58 @@ void ComputeOrderUpdatedCallback()
Dictionary<string, VisualElement> hideElementIfConnected = new Dictionary<string, VisualElement>();
Dictionary<FieldInfo, List<VisualElement>> fieldControlsMap = new Dictionary<FieldInfo, List<VisualElement>>();

protected void AddInputContainer()
{
inputContainerElement = new VisualElement {name = "input-container"};
mainContainer.parent.Add(inputContainerElement);
inputContainerElement.SendToBack();
inputContainerElement.pickingMode = PickingMode.Ignore;
}

protected virtual void DrawDefaultInspector(bool fromInspector = false)
{
var fields = nodeTarget.GetType().GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly);

foreach (var field in fields)
{
//skip if the field is not serializable
if (!field.IsPublic && field.GetCustomAttribute(typeof(SerializeField)) == null)
continue ;
if(!field.IsPublic && field.GetCustomAttribute(typeof(SerializeField)) == null)
{
AddEmptyField(field);
continue;
}


//skip if the field is an input/output and not marked as SerializedField
bool hasInputAttribute = field.GetCustomAttribute(typeof(InputAttribute)) != null;
bool hasInputAttribute = field.GetCustomAttribute(typeof(InputAttribute)) != null;
bool hasInputOrOutputAttribute = hasInputAttribute || field.GetCustomAttribute(typeof(OutputAttribute)) != null;
if (field.GetCustomAttribute(typeof(SerializeField)) == null && hasInputOrOutputAttribute)
continue ;
bool showAsDrawer = !fromInspector && field.GetCustomAttribute(typeof(ShowAsDrawer)) != null;
if (field.GetCustomAttribute(typeof(SerializeField)) == null && hasInputOrOutputAttribute && !showAsDrawer)
{
AddEmptyField(field);
continue;
}

//skip if marked with NonSerialized or HideInInspector
if (field.GetCustomAttribute(typeof(System.NonSerializedAttribute)) != null || field.GetCustomAttribute(typeof(HideInInspector)) != null)
continue ;
//skip if marked with NonSerialized or HideInInspector
if (field.GetCustomAttribute(typeof(System.NonSerializedAttribute)) != null || field.GetCustomAttribute(typeof(HideInInspector)) != null)
{
AddEmptyField(field);
continue;
}

// Hide the field if we want to display in in the inspector
var showInInspector = field.GetCustomAttribute<ShowInInspector>();
if (showInInspector != null && !showInInspector.showInNode && !fromInspector)
{
AddEmptyField(field);
continue;
}

var elem = AddControlField(field, ObjectNames.NicifyVariableName(field.Name));
var showInputDrawer = field.GetCustomAttribute(typeof(InputAttribute)) != null && field.GetCustomAttribute(typeof(SerializeField)) != null;
showInputDrawer |= field.GetCustomAttribute(typeof(InputAttribute)) != null && field.GetCustomAttribute(typeof(ShowAsDrawer)) != null;
showInputDrawer &= !fromInspector; // We can't show a drawer in the inspector

var elem = AddControlField(field, ObjectNames.NicifyVariableName(field.Name), showInputDrawer);
if (hasInputAttribute)
{
hideElementIfConnected[field.Name] = elem;
Expand All @@ -575,6 +604,16 @@ protected virtual void DrawDefaultInspector(bool fromInspector = false)
}
}

private void AddEmptyField(FieldInfo field)
{
if(field.GetCustomAttribute(typeof(InputAttribute)) == null) return;

var box = new VisualElement {name = field.Name};
box.AddToClassList("port-input-element");
box.AddToClassList("empty");
inputContainerElement.Add(box);
}

void UpdateFieldVisibility(string fieldName, object newValue)
{
if (visibleConditions.TryGetValue(fieldName, out var list))
Expand Down Expand Up @@ -609,10 +648,33 @@ void UpdateOtherFieldValue(FieldInfo info, object newValue)
genericUpdate.Invoke(this, new object[]{info, newValue});
}

protected VisualElement AddControlField(string fieldName, string label = null, Action valueChangedCallback = null)
=> AddControlField(nodeTarget.GetType().GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), label, valueChangedCallback);
object GetInputFieldValueSpecific<T>(FieldInfo field)
{
if (fieldControlsMap.TryGetValue(field, out var list))
{
foreach (var inputField in list)
{
if (inputField is INotifyValueChanged<T> notify)
return notify.value;
}
}
return null;
}

static MethodInfo specificGetValue = typeof(BaseNodeView).GetMethod(nameof(GetInputFieldValueSpecific), BindingFlags.NonPublic | BindingFlags.Instance);
object GetInputFieldValue(FieldInfo info)
{
// Warning: Keep in sync with FieldFactory CreateField
var fieldType = info.FieldType.IsSubclassOf(typeof(UnityEngine.Object)) ? typeof(UnityEngine.Object) : info.FieldType;
var genericUpdate = specificGetValue.MakeGenericMethod(fieldType);

protected VisualElement AddControlField(FieldInfo field, string label = null, Action valueChangedCallback = null)
return genericUpdate.Invoke(this, new object[]{info});
}

protected VisualElement AddControlField(string fieldName, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null)
=> AddControlField(nodeTarget.GetType().GetField(fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance), label, showInputDrawer, valueChangedCallback);

protected VisualElement AddControlField(FieldInfo field, string label = null, bool showInputDrawer = false, Action valueChangedCallback = null)
{
if (field == null)
return null;
Expand All @@ -626,14 +688,26 @@ protected VisualElement AddControlField(FieldInfo field, string label = null, Ac
// When you have the node inspector, it's possible to have multiple input fields pointing to the same
// property. We need to update those manually otherwise they still have the old value in the inspector.
UpdateOtherFieldValue(field, newValue);
}, label);
}, showInputDrawer ? "" : label);

if (!fieldControlsMap.TryGetValue(field, out var inputFieldList))
inputFieldList = fieldControlsMap[field] = new List<VisualElement>();
inputFieldList.Add(element);

if (element != null)
controlsContainer.Add(element);
if(element != null)
{
if (showInputDrawer)
{
var box = new VisualElement {name = field.Name};
box.AddToClassList("port-input-element");
box.Add(element);
inputContainerElement.Add(box);
}
else
{
controlsContainer.Add(element);
}
}

var visibleCondition = field.GetCustomAttribute(typeof(VisibleIf)) as VisibleIf;
if (visibleCondition != null)
Expand Down Expand Up @@ -663,6 +737,9 @@ void UpdateFieldValues()

internal void OnPortConnected(PortView port)
{
if(port.direction == Direction.Input && inputContainerElement?.Q(port.fieldName) != null)
inputContainerElement.Q(port.fieldName).AddToClassList("empty");

if (hideElementIfConnected.TryGetValue(port.fieldName, out var elem))
elem.style.display = DisplayStyle.None;

Expand All @@ -671,6 +748,21 @@ internal void OnPortConnected(PortView port)

internal void OnPortDisconnected(PortView port)
{
if (port.direction == Direction.Input && inputContainerElement?.Q(port.fieldName) != null)
{
inputContainerElement.Q(port.fieldName).RemoveFromClassList("empty");

if (nodeTarget.nodeFields.TryGetValue(port.fieldName, out var fieldInfo))
{
var valueBeforeConnection = GetInputFieldValue(fieldInfo.info);

if (valueBeforeConnection != null)
{
fieldInfo.info.SetValue(nodeTarget, valueBeforeConnection);
}
}
}

if (hideElementIfConnected.TryGetValue(port.fieldName, out var elem))
elem.style.display = DisplayStyle.Flex;

Expand Down
4 changes: 2 additions & 2 deletions Runtime/Elements/BaseNode.cs
Expand Up @@ -103,15 +103,15 @@ public abstract class BaseNode
public virtual bool needsInspector => _needsInspector;

[NonSerialized]
Dictionary< string, NodeFieldInformation > nodeFields = new Dictionary< string, NodeFieldInformation >();
internal Dictionary< string, NodeFieldInformation > nodeFields = new Dictionary< string, NodeFieldInformation >();

[NonSerialized]
List< string > messages = new List< string >();

[NonSerialized]
protected BaseGraph graph;

class NodeFieldInformation
internal class NodeFieldInformation
{
public string name;
public string fieldName;
Expand Down
5 changes: 5 additions & 0 deletions Runtime/Graph/Attributes.cs
Expand Up @@ -196,4 +196,9 @@ public ShowInInspector(bool showInNode = false)
this.showInNode = showInNode;
}
}

[AttributeUsage(AttributeTargets.Field, AllowMultiple = false)]
public class ShowAsDrawer : Attribute
{
}
}

0 comments on commit e04eb80

Please sign in to comment.