Skip to content

Commit

Permalink
feat: ui toolkit network behaviour editor for unity 2022.2 and newer (#…
Browse files Browse the repository at this point in the history
…1121)

Squashed commit of the following:

commit 53f5515
Author: Hertzole <hampusringstrom@gmail.com>
Date:   Wed Dec 28 15:38:55 2022 +0100

    moved UI toolkit code to own files

commit 9b2e013
Author: Hertzole <hampusringstrom@gmail.com>
Date:   Sat Dec 24 02:32:26 2022 +0100

    add foldout event ui toolkit editor

commit 765f919
Author: Hertzole <hampusringstrom@gmail.com>
Date:   Sat Dec 24 02:21:11 2022 +0100

    add readonly decortator ui toolkit editor

commit 94e2c53
Author: Hertzole <hampusringstrom@gmail.com>
Date:   Sat Dec 24 02:15:56 2022 +0100

    add syncvar ui toolkit editor

commit a514494
Author: Hertzole <hampusringstrom@gmail.com>
Date:   Sat Dec 24 02:06:32 2022 +0100

    made imgui editor match ui toolkit editor

    You can now click the label to expand sync lists and it has a new empty state, along with an outlined box.

commit ec345d8
Author: Hertzole <hampusringstrom@gmail.com>
Date:   Sat Dec 24 01:59:52 2022 +0100

    add ui toolkit inspector
  • Loading branch information
Hertzole authored and James-Frowen committed Jan 6, 2023
1 parent 8fb7264 commit f626c77
Show file tree
Hide file tree
Showing 12 changed files with 324 additions and 19 deletions.
2 changes: 1 addition & 1 deletion Assets/Mirage/Editor/FoldoutEventDrawer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
namespace Mirage
{
[CustomPropertyDrawer(typeof(FoldoutEventAttribute))]
public class FoldoutEventDrawer : PropertyDrawer
public partial class FoldoutEventDrawer : PropertyDrawer
{
private UnityEventDrawer _unityEventDrawer;

Expand Down
22 changes: 22 additions & 0 deletions Assets/Mirage/Editor/FoldoutEventDrawerUIToolkit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#if UNITY_2022_2_OR_NEWER
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;

namespace Mirage
{
public partial class FoldoutEventDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var foldout = new Foldout { text = property.displayName };

foldout.Add(UnityEventDrawer.CreatePropertyGUI(property));

foldout.BindProperty(property);

return foldout;
}
}
}
#endif
3 changes: 3 additions & 0 deletions Assets/Mirage/Editor/FoldoutEventDrawerUIToolkit.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 38 additions & 16 deletions Assets/Mirage/Editor/NetworkBehaviourInspector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Mirage
#if !EXCLUDE_NETWORK_BEHAVIOUR_INSPECTOR
[CustomEditor(typeof(NetworkBehaviour), true)]
[CanEditMultipleObjects]
public class NetworkBehaviourInspector : Editor
public partial class NetworkBehaviourInspector : Editor
{
private static readonly ILogger logger = LogFactory.GetLogger(typeof(NetworkBehaviourInspector));
private NetworkBehaviourInspectorDrawer _drawer;
Expand Down Expand Up @@ -40,7 +40,7 @@ public override void OnInspectorGUI()
/// <summary>
/// split from Editor class so that people can use this with custom inspectoer
/// </summary>
public class NetworkBehaviourInspectorDrawer
public partial class NetworkBehaviourInspectorDrawer
{
/// <summary>
/// List of all visible syncVars in target class
Expand Down Expand Up @@ -141,7 +141,7 @@ public void DrawDefaultSyncSettings()
/// Draws sync lists in inspector
/// <para>because synclists are not serailzied by unity they will not show up in the default inspector, so we need a custom draw to draw them</para>
/// </summary>
public class SyncListDrawer
public partial class SyncListDrawer
{
private readonly Object _targetObject;
private readonly List<SyncListField> _syncListFields;
Expand Down Expand Up @@ -174,25 +174,31 @@ public void Draw()

private void DrawSyncList(SyncListField syncListField)
{
syncListField.visible = EditorGUILayout.Foldout(syncListField.visible, syncListField.label);
syncListField.visible = EditorGUILayout.Foldout(syncListField.visible, syncListField.label, true);
if (syncListField.visible)
{
using (new EditorGUI.IndentLevelScope())
EditorGUILayout.BeginVertical("OL box");
int count = 0;
var fieldValue = syncListField.field.GetValue(_targetObject);
if (fieldValue is IEnumerable synclist)
{
var fieldValue = syncListField.field.GetValue(_targetObject);
if (fieldValue is IEnumerable synclist)
var index = 0;
foreach (var item in synclist)
{
var index = 0;
foreach (var item in synclist)
{
var itemValue = item != null ? item.ToString() : "NULL";
var itemLabel = "Element " + index;
EditorGUILayout.LabelField(itemLabel, itemValue);

index++;
}
var itemValue = item != null ? item.ToString() : "NULL";
var itemLabel = "Element " + index;
EditorGUILayout.LabelField(itemLabel, itemValue);

index++;
count++;
}
}

if (count == 0)
{
EditorGUILayout.LabelField("List is empty");
}
EditorGUILayout.EndVertical();
}
}

Expand All @@ -201,12 +207,28 @@ private class SyncListField
public bool visible;
public readonly FieldInfo field;
public readonly string label;
public readonly List<object> items;

public SyncListField(FieldInfo field)
{
this.field = field;
visible = false;
label = field.Name + " [" + field.FieldType.Name + "]";
items = new List<object>();
}

public void UpdateItems(object target)
{
var fieldValue = field.GetValue(target);
if (fieldValue is IEnumerable syncList)
{
items.Clear();

foreach (var item in syncList)
{
items.Add(item);
}
}
}
}
}
Expand Down
181 changes: 181 additions & 0 deletions Assets/Mirage/Editor/NetworkBehaviourInspectorUIToolkit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
#if UNITY_2022_2_OR_NEWER // Unity uses UI toolkit by default for inspectors in 2022.2 and up.
using UnityEditor.UIElements;
using UnityEngine.UIElements;
using System.Globalization;
using UnityEngine;

namespace Mirage
{
#if !EXCLUDE_NETWORK_BEHAVIOUR_INSPECTOR
public partial class NetworkBehaviourInspector
{
public override VisualElement CreateInspectorGUI()
{
var root = new VisualElement();

// Create the default inspector.
var iterator = serializedObject.GetIterator();
for (bool enterChildren = true; iterator.NextVisible(enterChildren); enterChildren = false)
{
var field = new PropertyField(iterator);
field.Bind(serializedObject);

// Disable the script field.
if (iterator.propertyPath == "m_Script")
{
field.SetEnabled(false);
}

root.Add(field);
}

// Create the sync lists editor.
var syncLists = _drawer.CreateDefaultSyncLists();
if (syncLists != null)
{
root.Add(syncLists);
}

// Crete the sync settings editor.
var syncSettings = _drawer.CreateDefaultSyncSettings();
if (syncSettings != null)
{
root.Add(syncSettings);
}

return root;
}
}
#endif // !EXCLUDE_NETWORK_BEHAVIOUR_INSPECTOR

public partial class NetworkBehaviourInspectorDrawer
{
public VisualElement CreateDefaultSyncLists()
{
return _syncListDrawer?.Create();
}

public VisualElement CreateDefaultSyncSettings()
{
if (!_syncsAnything)
{
return null;
}

var root = new VisualElement();

root.Add(CreateHeader("Sync Settings"));

var syncMode = new PropertyField(_serializedObject.FindProperty("syncMode"));
syncMode.Bind(_serializedObject);
root.Add(syncMode);

var syncInterval = new PropertyField(_serializedObject.FindProperty("syncInterval"));
syncInterval.Bind(_serializedObject);
root.Add(syncInterval);

return root;
}

public static VisualElement CreateHeader(string text)
{
var root = new VisualElement();
root.AddToClassList("unity-decorator-drawers-container");

var label = new Label(text);
label.AddToClassList("unity-header-drawer__label");

root.Add(label);

return root;
}
}

public partial class SyncListDrawer
{
public VisualElement Create()
{
if (_syncListFields.Count == 0) { return null; }

var root = new VisualElement();

root.Add(NetworkBehaviourInspectorDrawer.CreateHeader("Sync Lists"));

foreach (var syncListField in _syncListFields)
{
root.Add(CreateSyncList(syncListField));
}

return root;
}

private VisualElement CreateSyncList(SyncListField syncListField)
{
syncListField.UpdateItems(_targetObject);

var list = new ListView(syncListField.items)
{
showBorder = true,
showFoldoutHeader = true,
headerTitle = syncListField.label,
showAddRemoveFooter = false,
showBoundCollectionSize = false,
showAlternatingRowBackgrounds = AlternatingRowBackground.All,
makeItem = MakeSyncListItem,
bindItem = (element, i) => BindSyncListItem(element, i, syncListField),
viewDataKey = "mirage-sync-list-" + syncListField.field.Name // Used for the expanded state
};

// Because we can't listen for any lists updates, just refresh the list completely every 1 second.
list.schedule.Execute(() =>
{
// No need to update the game is not playing.
if (Application.isPlaying)
{
syncListField.UpdateItems(_targetObject);
list.Rebuild();
}
}).Every(1000);

return list;
}

private static VisualElement MakeSyncListItem()
{
var root = new VisualElement();
root.AddToClassList("unity-base-field");
root.AddToClassList("unity-base-field__aligned");
root.AddToClassList("unity-base-field__inspector-field");

var elementLabel = new Label
{
name = "element-label"
};

elementLabel.AddToClassList("unity-base-field__label");
elementLabel.AddToClassList("unity-property-field__label");

var elementValue = new Label("Test")
{
name = "element-value"
};

root.Add(elementLabel);
root.Add(elementValue);

return root;
}

private static void BindSyncListItem(VisualElement element, int index, SyncListField field)
{
var elementLabel = element.Q<Label>("element-label");
var elementValue = element.Q<Label>("element-value");

elementLabel.text = $"Element {index.ToString(CultureInfo.InvariantCulture)}";

elementValue.text = field.items[index] != null ? field.items[index].ToString() : "NULL";
}
}
}
#endif // UNITY_2022_2_OR_NEWER

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Assets/Mirage/Editor/ReadOnlyDecoratorDrawer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Mirage
{
[CustomPropertyDrawer(typeof(ReadOnlyInspectorAttribute))]
public class ReadOnlyDecoratorDrawer : PropertyDrawer
public partial class ReadOnlyDecoratorDrawer : PropertyDrawer
{
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
Expand Down
18 changes: 18 additions & 0 deletions Assets/Mirage/Editor/ReadOnlyDecoratorDrawerUIToolkit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#if UNITY_2022_2_OR_NEWER
using UnityEditor;
using UnityEditor.UIElements;
using UnityEngine.UIElements;

namespace Mirage
{
public partial class ReadOnlyDecoratorDrawer
{
public override VisualElement CreatePropertyGUI(SerializedProperty property)
{
var field = new PropertyField(property);
field.SetEnabled(false);
return field;
}
}
}
#endif // UNITY_2022_2_OR_NEWER
3 changes: 3 additions & 0 deletions Assets/Mirage/Editor/ReadOnlyDecoratorDrawerUIToolkit.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Assets/Mirage/Editor/SyncVarAttributeDrawer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Mirage
{
[CustomPropertyDrawer(typeof(SyncVarAttribute))]
public class SyncVarAttributeDrawer : PropertyDrawer
public partial class SyncVarAttributeDrawer : PropertyDrawer
{
private static readonly GUIContent syncVarIndicatorContent = new GUIContent("SyncVar", "This variable has been marked with the [SyncVar] attribute.");

Expand Down

0 comments on commit f626c77

Please sign in to comment.