Skip to content

Instantly share code, notes, and snippets.

@niklas-ourmachinery
Last active January 24, 2023 06:09
Show Gist options
  • Save niklas-ourmachinery/9e37bdcad5bacaaf09ad4f5bb93ecfaf to your computer and use it in GitHub Desktop.
Save niklas-ourmachinery/9e37bdcad5bacaaf09ad4f5bb93ecfaf to your computer and use it in GitHub Desktop.
Identifying controls in an IMGUI

Identifying controls in an IMGUI

In an IMGUI we need a way to identify which control is active or hot. For example, when we press a key, which text box does the text go to.

Possible options:

  1. Sequential number (Unity)

    Each control drawn gets a sequentially increasing number.

    If controls dynamically pop in and out of existence (e.g. animation), the IDs of subsequent controls will change. This will screw up interaction with those controls. Changing the draw order -- for example bringing a graph node "to front" will also mess this up.

    Can maybe be fixed with "checkpoints" -- assigning fixed IDs at certain points and then number sequentially from there.

  2. Hash of string (Dear Imgui)

    Control ID is a hash of the button label. To support buttons with same label, ID can be appended "OK##ID1" "OK##ID2". Contexts can be added with PushId(), PopId(). Only labels that are the same within the same context are a problem.

    "OK##ID1" s not very elegant. Context will need to be thread-local, which is not super nice.

  3. Use address of data that control manipulates

    I.e., bool * for checkbox.

    Not all controls have data (push buttons). Can maybe introduce pseudo-pointers for such controls. What if multiple controls use the same data? I.e. multiple property windows that display data for the same object.

  4. Preprocessor __FILE__ and __LINE__

    Only works well for UIs generated in code, not so much for data-driven UIs. Needs additional info when controls are created in a loop.

  5. Stateless (Nuklear)

    Avoid concept of a hot controller. Instead just check "is the mouse in this controller's rect?"

    Doesn't allow for all kinds of interaction. For example, with a slider you must keep the mouse within the slider's rect while dragging it, whereas standard UI behavior is to "lock" the mouse to interacting with the slider once you click it -- even if the mouse goes outside the rect (i.e. below the slider), it will still affect it.

  6. Stateless 2

    Instead of storing hot control ID, store history of where mouse was pressed. Determine if we are hot by checking if mouse was pressed on us. Longer history can be stored to determine double-clicking, etc.

    Elegant in a way, but breaks down if UI elements start to move around (i.e. dragging nodes in a graph).

  7. User assigned ID

    Works, but cumbersome for user to come up with IDs.

1 or 3 seems most promising, with 7 as a fallback for breaking ties.

@nem0
Copy link

nem0 commented Jun 27, 2017

  1. wont work with locals
bool b = flags & (1 << SOME_FLAG);
if(checkbox("flag x", &b)) ...

@aardappel
Copy link

https://github.com/google/flatui uses #2. The string to hash can be derived from the label, but can also be something else.

Something like #6 is used in https://github.com/aardappel/lobster. It additionally tracks the size of object that was hit last frame, which must match. This won't work with animated sizes, but seems generally useful.

I really like #4, since the point of an IMGUI is usually to use it in code. Can be used in combination with #2?

@ocornut
Copy link

ocornut commented Jun 27, 2017

EDITED

#2 having id that are non-opaque and externally computable has other benefits, you can compute or derive the ID of "other" elements. Although dear imgui doesn't fully empower right now it it has its uses, eg you can check if the "OK" button is hovered outside the context/code that produced the button, you can explicitly close a node given an id you computed, in the future you could remotely activate a widget RemoteActivate("AnotherWindow/OK"),
I would like to explore those ideas in future versions, currently a few specific components add piling semi-opaque information on the id stack, but the idea of building unique pathname to identify any widget could yield lots of potential features.

I don't understand the thread-local comment in #2, most ui code and most of those solutions described require a context somehow.

With this system, explicit id manipulator such as "OK##1" are also the exception rather than the norm, so the burden of those fugly construct is quite lightweight.

Being unable to reorder/add/remove elements would be a deal breaker for me: preserving node open/closed state for a list of object (#2 allow to identify with the object pointer). Gamepad/keyboard navigation also requires keeping the "cursor" somewhere somehow. If you were to lose your keyboard position because some ui changed (esp #1) it'd be not good?

Typical authoring tool ui are fairly static vs ui that allows to visualize in-engine data typically less stable.

@ocornut
Copy link

ocornut commented Jun 27, 2017

Both #3 and #4 would make the system less friendly to porting to variety of non-native languages, but are exploitable for C/C++.

#2 allows / is a superset of #7

#6 interesting to explore. fancy version could record widget pos/shape along with the inputs events to solve some cases. Not sure how far that madness could be taken.

I still think #2 has the best pros and less cons but obviously biased :D

More precise definition is: hash of a stack of data, the data being generally strings, but we add indices and pointers into the stack as well. When storing persistent state (eg: tree node) there are legit/desirable uses for all three cases: identifying by indices or by string or by pointer.

@niklas-ourmachinery
Copy link
Author

@nem0 Yes, #3 breaks down with locals -- which is not very nice.

@aardappel For #4 someone pointed out on twitter that you can use preprocessor stringification and string concatenation to create a unique string concatenating FILE and LINE -- pretty convenient... but it's not a good use case for me, because I think in the end most of the UI will be data-generated (property viewers, etc) rather than written in code.

Someone suggested hashing the item rect's too -- which is pretty good since gui items typically don't coincide or move around. Yet, there might be special situations when they do (dragging a graph node perfectly on top of another graph node) that needs to be handled.

@ocornut I can definitely see the value of having an unique element path. Regarding thread-local -- I was thinking about generating UI elements on multiple threads. I.e. -- for a big graph, split the node generation over multiple threads. But there are lots of other issues with doing that (ordering, etc) so it's not really fair to count it against #2.

I'm a bit concerned about computing all these hashes... but it's probably not that big a deal given everything else that the UI needs to do. Hmm.

I'm not sure how Unity deals with reordering... I've started an implementation based on #1 and it requires assigning "checkpoint" IDs to everything that can be reordered (graph nodes get IDs 1000, 2000, 3000, etc and then the subcontrols get sequential values). It kind of works, but it's a bit painful having to keep track of these assigned "checkpoint" IDs and making sure they don't collide.

@maxmcguire
Copy link

We found that user specified ids wasn't a big deal. We made the ids hierarchical, so that a button "ok" inside a panel "confirm" would have its id computed as something like "confirm/ok". In addition to string constants for ids, we also allowed integers as ids to make it easy to put controls inside of loops. For containers, we used a pattern like BeginPanel("confirm") ... EndPanel("confirm"), so having the explicit ids also made it easy to check that we had matching pairs. There's a little bit of overhead for resolving the ids, but we never found it to be a significant concern.

@rgriege
Copy link

rgriege commented Jul 26, 2017

Someone suggested hashing the item rect's too -- which is pretty good since gui items typically don't coincide or move around. Yet, there might be special situations when they do (dragging a graph node perfectly on top of another graph node) that needs to be handled.

Late to the party, but I've found that using a hash of the control's position and window/panel as the control's id like @niklas-ourmachinery mentioned works pretty well. I've never encountered a situation when I wanted a control to move while I was interacting with it. The only exception is dragging, which still works since the UI itself is manipulating the control's position and can thus re-assign the id.

The only aspect I haven't solved yet is dragging objects within the same panel/window to the same position. Since this hasn't been a big problem in my main project, I've been able to ignore it for the time being, but it did come up when I was trying to drag objects on a grid for a quick side project. My only (untested) idea was to combine this with method #3 for drag controls only, since I couldn't think of a scenario when I'd want to modify local x/y variables with a drag-able control.

As a caveat, my UI library is still relatively young, so I may not have encountered the wrong situations for this kind of design. However, I can't imagine a situation (again, other than dragging) where a control is moving while being hot that still provides a good user experience.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment