How to access ToolbarOverlay elements?

Hi

Inside an instance of ToolbarOverlay, how to access the instances of its toolbarElements? (not the toolbarElementIds). The only way I found is to access the visual element in CreatePanelContent and query it using the Q method, but this has two issues:

  • The rootVisualElement is not null inside CreatePanelContent, but becomes null when I access it later. (before any call to OnWillBeDestroyed). Any element queried inside CreatePanelContent becomes null too.

  • It seems too complicated.

Thanks

[Overlay(
    typeof(EditorWindow),
    "MyToolbarOverlay",
    true
)]
public class MyToolbarOverlay : ToolbarOverlay
{
    private VisualElement rootVisualElement;
    private MyToolbarOverlay() : base(
        nameof(MyToolbarToggle)
    )
    {

    }

    public override VisualElement CreatePanelContent()
    {
        rootVisualElement = base.CreatePanelContent();
        return rootVisualElement;
    }   
}

[EditorToolbarElement(
    nameof(MyToolbarToggle),
    typeof(SceneView)
)]
public class MyToolbarToggle : ToolbarToggle
{
    public MyToolbarToggle() =>
        text = "MyToolbarToggle";
}

If you need access to the toolbar elements in the toolbar overlay, you can create the visual elements yourself. Toolbar creation via IDs is meant as a convenience for re-using toolbar elements, and if you don’t need that aspect it’s often simpler to build the toolbar manually.

using UnityEditor;
using UnityEditor.Overlays;
using UnityEditor.Toolbars;
using UnityEngine;
using UnityEngine.UIElements;

[Overlay(typeof(SceneView), "Basic Toolbar", defaultDisplay = true)]
class BasicToolbar : Overlay, ICreateHorizontalToolbar, ICreateVerticalToolbar
{
    public override VisualElement CreatePanelContent() => new Label("Contents in Panel Layout");

    public OverlayToolbar CreateHorizontalToolbarContent()
    {
            var toolbar = new OverlayToolbar();
        toolbar.Add(new EditorToolbarButton("Contents in Horizontal Layout", () => Debug.Log("Hello!")));
        return toolbar;
    }

    public OverlayToolbar CreateVerticalToolbarContent()
    {
            var toolbar = new OverlayToolbar();
        toolbar.Add(new EditorToolbarButton(EditorGUIUtility.IconContent("MoveTool").image as Texture2D, () => Debug.Log("Hello!")));
            return toolbar;
    }
}
1 Like

Thanks for the answer. Will try this later. Will update here if needed.

Thanks for the sample. I tried this and it basically works.
Several questions:

For public override VisualElement CreatePanelContent() you created a Label. How to show the controls instead like it is automatically done in ToolbarOverlay?

When is ICreateVerticalToolbar.CreateVerticalToolbarContent() called and do we need it?

Ok, managed what I wanted to achieve.
It was a horrible experience and took me almost 2 days. Here is what I learned. Maybe it helps someone.

Goal was to get access to the controls to change state, like changing icon on button when something happens (elsewhere).
Wasn’t able to get that going by creating controls by myself. So back to the highest level class ToolbarOverlay.
This happens:
Anytime user changes the Toolbar- Layout, all controls are created new. Old controls are still around and don’t get deleted, so if you lets say request a OnScene() callback in control’s Constructor for three buttons, you get:

  • 3 callbacks
  • user changes layout
  • now you get 6 callbacks
  • user changes layout
  • now you get 9 callbacks
  • …

Also registering controls in a list somewhere doesn’t make sense, because there is only one active control of every type.
In the end I registered every control in a dictionary (in their Constructor) so newly created controls overwrite their reference in the dictionary. I ended up with:

  public class UsefulButton : EditorToolbarButton // back to C++ stone age
    {
        // holds cur UsefulButton as these are newly created as user changes Toolbar
        public static Dictionary<string, UsefulButton> activeUsefulButtons = new Dictionary<string, UsefulButton>();

        public static UsefulButton GetActiveUsefulButton(string id)
        {
            if (!activeUsefulButtons.ContainsKey(id)) return null;
            return activeUsefulButtons[id];
        }

        static protected Texture2D LoadIcon(string assetLabel, Texture2D targetTex)
        {
            // helper
            if (targetTex != null) return targetTex;

            Texture2D tex = null;
            string[] guids = AssetDatabase.FindAssets(assetLabel, null);
            if (guids.Length > 0)
            {
                tex = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guids[0]), typeof(Texture2D)) as Texture2D;
            }
            if (tex == null) tex = Texture2D.redTexture;

            return tex;
        }
    }

A specific Button looks like this:

   [EditorToolbarElement(id, typeof(SceneView))]
    public class SpeedDownButton : UsefulButton
    {
        public const string id = "SpeedDownButton";

        static Texture2D iconOk;
        static Texture2D iconNotOk;

        SpeedDownButton()
        {
            activeUsefulButtons[id]= this;
            //VisualElement e = this;

            iconOk = LoadIcon("l:BetterEditorCamera.ArrowLeft", iconOk);
            iconNotOk = LoadIcon("l:BetterEditorCamera.RedArrowLeft", iconNotOk);
                          
            SetState();
            clicked += OnClick;
        }

        public void SetState()
        {
            bool minSpeedReached = SettingsWindow.MinSpeedReached();
            if (minSpeedReached)
            {
                icon = iconNotOk;
                SetEnabled(false);
            }
            else
            {
                icon = iconOk;
                SetEnabled(true);
            }
        }

        void OnClick()
        {
            SettingsWindow.ChangeSpeeds(-1);
            SetState();
            (GetActiveUsefulButton("SpeedUpButton") as SpeedUpButton).SetState();
            (GetActiveUsefulButton("SpeedLabel") as SpeedLabel).SetState();
        }

    }

So I ended up with lots of hard to read/hard to understand 183 lines of code and 5 classes with widely distributed OnClick()- logic to change some Icons on a few buttons. Code is ugly, hard to reuse and far too complex for the task.
And it took me 2 days when it should be 2 hours. Whatever. Hope someone takes sth out of it.
EDIT: yeah, and everything must know about everything. Just forced into terrible design.

3 Likes

ICreateHorizontalToolbar, ICreateVerticalToolbar are available in which version of Unity?

Thank you for sharing this. I hope this API will be improved in the future, as I can’t even access Overlay.rootVisualElement which is internal for some reason.

2 Likes

Yeah… but I have a bad feeling about this. Unity is dying. For quite some years now all dev in Unity goes the wrong way. We got useless features noone asked for just complicating things without bringing benefit. No quality of life features anymore. Noone is professionally using the engine. We got that horrible software design stuck in the MFC phase (early 90ies C++) not using any of great C# features… I don’t see it. Smells like the guys founding Unity and making it great are all living on their island now and the corporate apes took over… whatever.