[bug] EditorGUIUtility appears broken by UIToolkit (currentViewWidth gets corrupted)?

Creating a custom inspector (using “public override VisualElement CreateInspectorGUI()” in a CustomEditor):

I need to know the width of the render area so that I can size the VisualElements that are fed into UIToolkit (UIT will do most of the layout, but e.g. some of them need to maintain specific aspect-ratios, which requires some manual sizing)

  1. The traditional unity-provided method for this was: EditorGUIUtility.currentViewWidth
  2. …which has always been slightly broken in UnityEditor (it’s off by a fixed amount, and doesn’t correctly handle when the scrollbar appears/disappears) - but these are known issues and everyone has workarounds for them
  3. … … and there’s also the private internal method Unity’s own source code uses, which is usually more accuraet (but which sadly has never been made public): .contextWidth

Both of these properties work the first time an inspector is displayed.

But … as soon as you click elsewhere in the editor (select a different GO) and then click back again - both of them have corrupt data, and return 0 forever.

Is this a bug? Or by design?

(EditorGUIUtility was created for IMGUI, so maybe it’s an accident that it works on the first render?)

If it’s by design - how do we fetch the ACTUAL width of the Inspector when inside the “CreateInspectorGUI()” method?

Actually … reading the source code of Editor … suggests this a bug: Unity uses “contextWidth” themselves, so it’s supposed to be working.

I don’t know if it’s not working because of something else I’ve done (or some extra setup code that UIToolkit requires?) or if it’s a core bug in UIToolkit.

If I understand correctly you’re trying to resize some of your custom inspector VisualElements, is that so?
If yes, have you tried adding a GeometryChangedEvent callback to apply the size you want once the inspector is laid out in place? I haven’t tried it here right now but I suppose it should work like you want it to!

If I use the GeometryChangedEvent, then that means I have zero code in the main method itself: the method will be simply a wrapper to delay everything until the GCE has happened:

{
var root = new VisualElement();
root.RegisterCallback<GeometryChangedEvent( evt =>
{
// ALL the layout and UIToolkit code goes here!
} );
return root;
}

…is that correct?

What frame-delays are there for doing that?

Are there any problems this causes, that there’s just an empty shell of VisualElement graph until GCE gets sent?

In my case: the whole UI (if you like: which styles to use, what tree of VisualElements, etc), depends on the size it’s being laid-out in.

e.g. Unity uses this figure in IMGUI to decide “draw a wide inspector, or draw a narrow inspector?” which are different codepaths (you draw a different UI).

You can do that yes, but you’ll want it to be its own method that you’ll then use to unregister the callback when it runs. Otherwise you’ll get infinite GeometryChangedEvents as you modify the geometry over and over.
Do you have a specific concern in doing this? I tried it out here quickly with a custom inspector and it seemed fine.

1 Like

There are no docs saying what the timing is of the GCE - my assumption is that waiting for the GCE will create/cause timing bugs in our code somewhere.

(if not - why is this not the default? Every UI I ever write with UIToolkit for the next few years is going to require this exact same copy/pasta setup code. That seems very strange - why not have this built-in to the API itself? That makes me think there must be something wrong with it, otherwise it would already be built-in.)

(also: if this is the correct way of writing UIToolkit code, then it should be featured in the docs very prominently! :))

Two updates:

  1. GeometryChangeEvent breaks PropertyField. This is the kind of incompatibility I suspected/feared - tracked it down to literally: if you try to Add( PropertyField ) in a GCE callback: Unity corrupts the VisualElement tree … if you run the exact same code outside GCE callback: everything works as expected.

EDIT: I’ve submitted this to UnityQA as a bug with a very short reproduction

  1. Unity QA has accepted the bug I filed on EditorGUIUtility

Going forwards … I’m tempted to stop using UIToolkit until the EditorGUIUtility bug is fixed, because GCE is way too undocumented and (it seems) buggy and confusing. I’ll keep testing as long as I can and try to file more bugs against UIToolkit (currently got two accepted in my first two weeks with it - there are clearly a lot of outstanding bugs here), but with so little documentation and so much broken or changed it’s extremely difficult and I don’t konw how much more time I can afford. I’d rather wait for Unity to do the basics (DOCUMENTATION!!!)

I’m sorry you’ve been having so many issues but have you tried do add your PropertyField before the GCE and only adjust its size on the GCE? I would building the main UI regardless of window size and then adjusting the sizes wherever necessary if that makes sense.

Submitted as Case 1291478

For me to add the PropertyField first means:

  1. Writing lots of extra code – I will have to replace all local variables and vars with member variables
  2. Is not always possible: the PropertyField isn’t always needed, it depends on the state and size of the Inspector window.

I could probably workaround item 2 - I could create multiple fake PropertyField instances, one for each that might be needed, set them all to “display:none”, and then selectively display the ones I need based on circumstance.

But if I do that … it starts to become faster and more maintainable to write all inspectors in IMGUI, which does not have these bugs.

Let me try to respond to some of the comments and questions above.

It would help a lot if you provided some details about what you’re trying to achieve. Mainly, why is the UI require to so drastically change based on the width of the Inspector? What changes and at which width increments? And is it all dynamic or will it have discreet “pops” where the entire UI changes as I slowly resize the Inspector?

The only timing considerations with GCE is that it’s “delayed”. It’s usually 1 frame but may be a few more. However, in the vast majority of cases you should not have to worry about how long the delay is.

It is also very much not the default. UI Toolkit is designed to avoid having to worry about reading widths and heights of other elements and computing layouts manually. Whenever GCE is involved, you are stepping outside the norm and into “advanced workflows”. Yes, this event is being abused somewhat in the absence of a true “first layout” event, which is why you have to be aware of it’s double meaning and unregister it as soon as you get it the first time.

I’ve read your comments on the bug you submitted. I think the comment from QA was spot on. Basically, this not a bug. It’s a quirk around how bindings work and not actually specific to PropertyField. PropertyField is just affect more than other fields because it’s not actually a field. It’s an empty container that creates UI when it gets bound to a SerializedObject. So if binding fails, you see nothing in the Inspector because nothing was created. You can also bind regular fields like IntegerField, same as you bind a PropertyField, in which case when binding fails the field still gets created - it’s just not bound.

I want to clear up some of the mystery around Bindings. Bindings are not black magic. You manually create bindings by:

  1. registering for ValueChangeEvent on your field element where you update your object state
  2. poll the object state and when the value changes, update the UI

When you say: myInspectorRootElement.Bind(mySerializedObject)
it’s basically a shorthand for doing 1. and 2. above for each field under myInspectorRootElement (recursively), using their bindingPath attribute as the name of the Object member to “bind” to.

The Inspector assumes everyone will do this for their fields and with the selected GameObject/Component that it just calls Bind() on the entire Inspector when it’s done being created. If you missed this boat (ie. did not create your UI in CreateInspectorGUI()), you need to call Bind() yourself.

This pinned thread at the top of this forum has a video that goes into excessive detail around creating custom inspectors and how UI Toolkit is different from IMGUI:
https://discussions.unity.com/t/723268

A much shorter but more dated video that walks through the manually creating Bindings and then using the Binding system to not have to manually do it:

https://www.youtube.com/watch?v=sVEmJ5-dr5E

repo: https://github.com/Unity-Technologies/UIElementsExamples/blob/master/Assets/QuickIntro/Editor/BasicsDemoWindow.cs

We also recently published this video that covers similar subjects:

https://www.youtube.com/watch?v=mTjYA3gC1hA

So coming back to what I said at the very top of this post, I’d like to know what you’re trying to achieve as a final goal that implies such requirements. Why does everything have to change and why is flex not enough?

2 Likes

OK, I’ll give some cases, but … those questions feel a bit unfair. I want an API that works correctly and allows me to draw the inspector/UI I want, not a limited set of cases that have been pre-approved.

(especially: As someone who writes assets, where there’s a 12-18 month delay on us being able to use many of the fixes that Unity makes, because of the LTS policy etc (I report a lot of bugs, a lot get fixed, but I think maybe one in 50 get backported to 2018, even though that’s still a live supported version). So I have to “hope” they get fixed in 2019, and some get classed as non-critical and are only fixed in 2020 - which won’t be production ready for at least another 6-12 months).

I’m not criticising the LTS / support / etc policy here - merely saying it’s a constraint we have to work within.

But I don’t want to spend the next few years having to come back to Unity to say: “Oh, yeah, that’s still broken, and now here’s another case that shows another example. I told you it was broken last year, but you said it didn’t count until we had a live game that showed that particular case. Now I have one … I’ll stop work for a year until it gets fixed”)

Example: Unity’s default inspectors are not intended to be easy to use or rich or powerful - they are basic and functional. So for any complex game we write our own inspectors. We write them to be easy to use, powerful, highly visual, etc.

  • If there’s only 100 px of space I’ll probably show summary info.

  • If there’s 200-300px of space I’ll try to show some graphical info + full text info (including extra “info” mode HelpBox’s in the old IMGUI inspectors, context-sensitive advice + help – which was missing in the narrow version because it would force a stupdily long vertical scroll and if someone chose to have a tiny inspector we can almost guarantee they don’t want detail)

  • If there’s 400+px of space I’ll show a purely graphical layout - e.g. my 4k monitor (cost: barely $200! Most devs have these) has almost 4,000 pixels horizontally: I want to use that space!). I’ll be using colored boxes, backgrounds, large drag/drop zones (where before I probably only had a default object picker), etc. Even: drag/drop zones that preview what will happen when the current drag-action drops in place.

It’s a lot more work, but it makes for 10x faster and more pleasurable editing of game data than to do things in a plain text-heavy Unity Inspector.

Graphic-heavy inspectors require lots of heuristic UX work - I assume this is why Unity originally invented the “wide vs narrow” inspector code, for the same reason: a narrow inspector needs to show different information / show it differently than a wide inspector. e.g. VectorField lays out differently based on screen width.

(Flexbox supports that one case naturally, which is great - but it’s literally the most trivial example of improving the UX for an inspector.)

I have not seen this advice anywhere in the forums or documentation so far - this feels like an important detail! (both that it’s OK to unregister after the first call - I mean: without guidance, it would not be safe to assume that’s an OK thing to do - but also the warning/advice that this is worth doing (because not doing it can cause other issues).

That feels like a very artificial argument: you’re giving a justification for why the system behaves unpredictably and surprisingly for users, based on the fact that internally the code-implementation makes sense. I appreciate the explanation - it’s interesting and I like knowing how this stuff works behind the scenes - but it doesn’t prevent it being a bug.

I don’t see how you can decide what’s a bug using only the code-writer’s perspective: bugs are something primarily detected / experienced by the users/API-consumers, and they have to be classified that way (from the user perspective), otherwise the system will never be user-friendly - it will be hard to use and the perception by users will be that it is “very buggy” (fairly or not).

There are a couple of problems here:

  1. Videos aren’t googleable. They don’t come up in search results. The information in them is hidden, opaque, unreferenceable. I can’t share it with colleagues (the best I can do is tell them "oh, and. … you have to jump tio 13.54s, and then watch the next 20 s, but skip the minute after, and then watch another 8 seconds … " - in practice: this is rarely done, it’s too much effort).

  2. Vides are a highly unreliable source for core docs, so they are rarely the thing developers use or reference (last time I asked around my colleagues in a game studio I think the number of programmers saying they would watch a video for getting info was less than 5%. The number saying they’d go to StackOverflow was over 90%). They (usually) don’t get updated when info changes (e.g. YouTube doesn’t even allow them to be updated! They’re immutable). Unity-related documentation videos have a very long history of being wrong and promoting wrong approaches - because they were originally correct! … but then the API/implemetnation/context changed over time, things got fixed or replaced or obsoleted, etc. They are also extremely expensive to update or re-record. So: they don’t get updated, they don’t get replaced (and even when they do: the old versions usually can’t be fixed). This is no-one’s fault.

  3. Videos are an exceptionally slow and bloated way of getting high-detail info. They work fine for low-detail things, but have very low signal-to-noise ratio. e.g. I can skimread an API doc in seconds - with a video I have to wait for minutes (60 times slower) to get the same data.

TL;DR: Thanks for pointing this out, but … in all the people I’ve asked about these issues, no-one was aware of these videos containing the answers or referenced them, and I think only one person suggested they might help (without being sure - they just threw it out as a random suggestion - but the one they pointed to did not appear to answer the question, but it’s hard to tell without slowly listening to the whole thing)

So I think more is needed. Text would be cheaper to maintain, many times faster to read, and much easier for developers to share and reference.

1 Like

I’ve been writing Unity Editor Scripts since approximately 2011 (Unity 3.x), and I’ve gone deep into the internals of the editor, hacking it to do many things that weren’t officially possible until years later.

A video titled “Getting Started with Unity Editor Scripting” is one I’m always going to ignore; if you hadn’t specifically referenced it here (I trust your advice!) based on the title (in giant letters) there is no way I could see that being anything but a waste of my time. If it contains core new content about ways that editor scripting has changed it really needs a different title - or better: to be split out into separate videos with more targetted titles.

The Inspector window has an enforced min-width of 275px. So I would just merge these 2 options. Everything else would be broken in the Inspector if it was 100px wide anyway. Why spend the time optimizing for that?

Again, unless you’re in a project where only your custom inspectors are used and no other Inspectors (like Transform) are used, optimizing for 400px+ Inspector width is also a bit of a waste. Most Editors will just look awful at 400px+. Almost no Editor tries to take advantage of a super wade Inspector window.

For what you describe, I would go with a custom EditorWindow instead of trying to embed all of this inside the Inspector. As a user of Unity, I have a strong reflex to assume the Inspector window will only be so wide and never dock it to take up my entire wide-screen monitor. Even if one or two CustomEditors have fancy UI that can take advantage of my giant screen, most other Editors will not. So I just won’t do it. But with a custom EditorWindow that is made to use the current selection and inspect certain types of GameObjects and Components - that I can definitely see being docked full-screen on a second monitor.

But that’s just my thought on the new details you’ve provided. We can definitely make it work in the Inspector if that’s what you wish to do. Just understand that no tool is optimal for all tasks. You are swimming against the river here. Things will need to be hacked and twisted and will never “just work” when you’re trying to go off road.

Here’s what I suggest you try:

  • Create 2 UI creation functions: ThinUI() and WideUI() - these create the actual elements and parent them to a root element container (and also cleanup any previously created hierarchy)

  • Create a post-UI creation function: PostCreation() - inside it, call Bind(serializedObject) on the container created in 3. below

  • In the CreateInspectorGUI(), create a container VisualElement onto which you register the GCE on

  • In the GCE callback, which now becomes your main “detect Inspector width change callback” (so don’t unregister it), choose which of the two UI creation functions you need to use depending on the new GeometryChangedEvent.newRect, and ends with a call to PostCreation()

Note that you should never be in a state where ThinUI() or WideUI() is called every frame, or every update to your object, or basically more than once per GameObject selection or Inspector width transition between width ranges (ie. going from 200-300px range to the 400px+ range). You can do many things with UI events to avoid have to recreate your UI with any frequency. Something like a “Redraw” or “ForceRedraw” does not make sense in UI Toolkit. The whole point is that only what needs to change is redrawn and you just have to tell the system what needs to change by updating the hierarchy or the layout/styles.

As you see in my post above, it’s not always the case you need to unregister. The event name is pretty self-explanatory. It’s an event that fires every time the element is resized, including the very first time it gets resized (from nothingness). We sometimes use that “very first time” case as a marker for when the element is fully realized and don’t actually want to know about future size changes. That’s when it makes sense to unregister from this event. Other cases, like the one above, do not require un-registration.

Regarding your “Videos are not docs” post. Yes. We know all of that. I never claimed that our docs are just fine because we have videos. We know our docs need work and we’re working on them. I linked those videos because that’s what we have today and I know the answers you’re looking for are in them. The time spent talking about how we don’t have docs could be spent watching those videos and getting the answers you’re looking for.

Agreed on the name not being ideal. I’ve passed the message to the team.

1 year later: hitting the same bug again :(.

Reading the bug report data from Unity: this bug was confirmed in 2019.4 LTS (so: 2019, 2020, 2021) and fixed in Unity 2021.1 - but not fixed in any of the LTS versions.

i.e. only uses of the Unity betas have access to the bugfix. Currently that’s a small minority.

I’m going to email QA and ask if they can provide a workaround – it’s been over a year, I think it’s reasonable that this should have been fixed or else a workaround provided.