Closed OndrejPetrzilka closed 5 years ago
Great! Both approach #4 and #5 crossed my mind but didn't get concrete. I did explore the UIElements way before, but lost my way (code was highly undergoing change too). So anyway, this is awesome and props to you for finding a way. It's pretty clean, like really clean compared to the current solution and #4 ;). Only potential downside that comes up now is that it's likely to break because of UIElements undergoing changes, but that's ok.
I could "properly" implement it tomorrow morning? Or you create a PR.
Do you have some idea how layout serialization works? It looks like magic to me... visualTree.Add
seems like cleaner solution, however it might be problematic if layout serializes UIElements somehow.
No I haven't looked into it. But serialisation is also problematic with the current solution, that's why I added this code that prevents it: https://github.com/marijnz/unity-toolbar-extender/blob/master/Assets/ToolbarExtender/Scripts/Editor/ExtendedToolbarWindow.cs#L134. That's also an option with UIElements
Okay, I'm afraid it might get broken when visualTree
is modified.
I've tested modification when toolbar object is cached in static field. Since it's ScriptableObject
, it's possible to detect when it's destroyed by null check. This makes EditorApplication.update
very fast in most cases (single null check). Everything else still works (resizing, layout changes).
I've also removed [InitializeOnLoad]
since it's not necessary, when callback is added to OnToolbarGUI
, hook gets installed (through static constructor).
I'll make PR which will add this class soon. Together with one more helper class which helps with positioning around existing toolbar controls.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEditor;
using System.Reflection;
using UnityEngine.Experimental.UIElements;
public static class ToolbarCallback
{
static Type m_toolbarType = typeof(Editor).Assembly.GetType("UnityEditor.Toolbar");
static Type m_guiViewType = typeof(Editor).Assembly.GetType("UnityEditor.GUIView");
static PropertyInfo m_viewVisualTree = m_guiViewType.GetProperty("visualTree", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
static FieldInfo m_imguiContainerOnGui = typeof(IMGUIContainer).GetField("m_OnGUIHandler", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
static ScriptableObject m_currentToolbar;
/// <summary>
/// Callback for toolbar OnGUI method.
/// </summary>
public static Action OnToolbarGUI;
static ToolbarCallback()
{
EditorApplication.update -= OnUpdate;
EditorApplication.update += OnUpdate;
}
static void OnUpdate()
{
// Relying on the fact that toolbar is ScriptableObject and gets deleted when layout changes
if (m_currentToolbar == null)
{
// Find toolbar
m_currentToolbar = (ScriptableObject)Resources.FindObjectsOfTypeAll(m_toolbarType).At(0);
if (m_currentToolbar != null)
{
// Get it's visual tree
var visualTree = (VisualElement)m_viewVisualTree.GetValue(m_currentToolbar);
// Get first child which 'happens' to be toolbar IMGUIContainer
var container = (IMGUIContainer)visualTree[0];
// (Re)attach handler
var handler = (Action)m_imguiContainerOnGui.GetValue(container);
handler -= OnGUI;
handler += OnGUI;
m_imguiContainerOnGui.SetValue(container, handler);
}
}
}
static void OnGUI()
{
OnToolbarGUI?.Invoke();
}
}
By taking advantage of UIElements, we can attach drawing handler right AFTER the toolbar.
This works well when resizing window and changing layouts. Tested on Unity 2018.2.10f1. Implementation could be probably improved:
IMGUIContainer
try adding a new one (visualTree.Add
)