Seeking something like "IsAnyOtherItemHovered()" to fix interaction with multiple overlapping elements #3909

Closed
opened 2021-03-13 22:09:43 +00:00 by pixtur · 4 comments
pixtur commented 2021-03-13 22:09:43 +00:00 (Migrated from github.com)

Version/Branch of Dear ImGui:
Version: 1.77
Branch: master

Back-end/Renderer/Compiler/OS
Back-ends: imgui.net
Operating System: win10

My Issue/Question:
I know that overlapping interactive elements are tricky to implement with a single pass immediate UI.
And I learned, that it is possible to partly resolve the inversed draw/interaction sort order by channel splitting.

I normally try to work around or ignore these problems, but for my timeline editing this get kind of annoying.

I am wondering, if there is the possibility to prevent multiple click events by something like this...

            ImGui.PushId(item.id);
            var somethingElseWasHovered = ImGui.IsAnyItemHovered();
            var wasClicked = ImGui.InvisibleButton("item", bodySize);
            if(!somethingElseWasHovered  && wasClicked) {
                  // do stuff
            }
            ImGui.PopId();

For some reason however, somethingElseWasHovered becomes true even before the invisible button is being drawn. Obviously I don't understand what's going on (I guess some information from the previous frame must be used for this).

  • Is there a method to check, if another (overlapping) item is hovered besides the current item?
  • Are there any tutorials on how how IsAnyItemHovered() works? (search the documentation in imgui.h)

Screenshots/Video
https://user-images.githubusercontent.com/1732545/111050991-336d8e00-8450-11eb-8616-c325f622ccdc.mp4

**Version/Branch of Dear ImGui:** Version: 1.77 Branch: master **Back-end/Renderer/Compiler/OS** Back-ends: imgui.net Operating System: win10 **My Issue/Question:** I know that overlapping interactive elements are tricky to implement with a single pass immediate UI. And I learned, that it is possible to partly resolve the inversed draw/interaction sort order by channel splitting. I normally try to work around or ignore these problems, but for my timeline editing this get kind of annoying. I am wondering, if there is the possibility to prevent multiple click events by something like this... ``` ImGui.PushId(item.id); var somethingElseWasHovered = ImGui.IsAnyItemHovered(); var wasClicked = ImGui.InvisibleButton("item", bodySize); if(!somethingElseWasHovered && wasClicked) { // do stuff } ImGui.PopId(); ``` For some reason however, `somethingElseWasHovered` becomes true even before the invisible button is being drawn. Obviously I don't understand what's going on (I guess some information from the previous frame must be used for this). - Is there a method to check, if another (overlapping) item is hovered besides the current item? - Are there any tutorials on how how `IsAnyItemHovered()` works? (search the documentation in imgui.h) **Screenshots/Video** https://user-images.githubusercontent.com/1732545/111050991-336d8e00-8450-11eb-8616-c325f622ccdc.mp4
ocornut commented 2021-03-15 11:03:03 +00:00 (Migrated from github.com)

Obviously I don't understand what's going on (I guess some information from the previous frame must be used for this).

Yes it return true if any item has been hovered in the previous frame.

Are there any tutorials on how how IsAnyItemHovered() works? (search the documentation in imgui.h)

It's a 1 line function you can check the code.
The || g.Hovered != 0 part of the test always bothered me since that part is testing for element hovered this frame but only covers what has been submitted so far.

It's hard to understand what you are getting at with your question, I reckon you should be looking at SetItemAllowOverlap() which is a mechanism to allow overlapping hit-testing.

This is going to be confusing as relatively to half-implemented/designed API, but I'm still going to write it down:

ImGui::Begin("Overlap");

ImGui::SetCursorPos(ImVec2(50, 50));
bool button_1_pressed = ImGui::Button("11111", ImVec2(50, 50));
bool button_1_hovered = ImGui::IsItemHovered();
//ImGui::SetItemAllowOverlap();

ImGui::SetCursorPos(ImVec2(70, 70));
bool button_2_pressed = ImGui::Button("22222", ImVec2(50, 50));
bool button_2_hovered = ImGui::IsItemHovered();

ImGui::Text("Button 1: %s%s", button_1_hovered ? "hovered " : "", button_1_pressed ? "PRESSED " : "");
ImGui::Text("Button 2: %s%s", button_2_hovered ? "hovered " : "", button_2_pressed ? "PRESSED " : "");

ImGui::End();

image

When hovering in the intersection of both button:

(A) Without SetItemAllowOverlap() after Button 1

  • when clicking: button 1 is pressed,
  • only button 1 appears hovered
  • both button return true to IsItemHovered()

(B) With SetItemAllowOverlap() after Button 1

  • when clicking: button 2 is pressed
  • both buttons appears hovered
  • both button return true to IsItemHovered()

(C) With (internal) ImGuiButtonFlags_AllowItemOverlap flag on Button 1 + SetItemAllowOverlap() after Button 1

  • when clicking: button 2 is pressed
  • only button 2 appears appears hovered
  • both button return true to IsItemHovered()

Added records of 1.88 behavior on 2023/06:
(D) With (internal) ImGuiButtonFlags_AllowItemOverlap flag on Button 1 + WIITHOUT SetItemAllowOverlap() call.

  • when clicking: button 1 is pressed
  • only button 1 appears hovered
  • both button return true to IsItemHovered()

Notice issues:

  • Only difference between B and C is how Button 1 visibly react to hover. Both cases are useful depending on context, but C is likely to be more commonly useful.
  • Inconsistency between the return value of IsItemHovered() and what the button display, I think this should be fixed.
  • ImGuiButtonFlags_AllowItemOverlap is currently explicit, I've been considering switching to a hover model where it is implicit default, but it's going to be a lots of work. With this model we would only need to call SetItemAllowOverlap().
  • Either settings could be wired to shared item flags.

Added records of 1.88 behavior on 2023/06:

  • The only difference between (D) and not doing anything is: affected button will check g.HoveredIdPreviousFrame and defer initial hover reaction by 1 frame, but because SetItemAllowOverlap() is not called, other well-behaved items won't claim g.HoveredId.
  • As of today 1.89.7 WIP (18965) two places were using ImGuiButtonFlags_AllowItemOverlap WITHOUT SetItemAllowOverlap(): SplitterBehavior() and TableUpdateBorders(). Effectively this behavior was analogous to not doing anything, EXCEPT in the case of SplitterBehavior() is allows the caller to follow the call with SetItemAllowOverlap().

(This is a a bit of a mess. There are they two aspects: the ButtonBehavior() logic has the ImGuiButtonFlags_AllowItemOverlap flag + there's a `SetItemAllowOverlap() call which can be called afterwards.... hence I know this post is probably only opening the veil to a new world of confusion especially if you are stuck without access to internals..)

> Obviously I don't understand what's going on (I guess some information from the previous frame must be used for this). Yes it return true if any item has been hovered in the previous frame. > Are there any tutorials on how how IsAnyItemHovered() works? (search the documentation in imgui.h) It's a 1 line function you can check the code. The `|| g.Hovered != 0` part of the test always bothered me since that part is testing for element hovered this frame but only covers what has been submitted so far. It's hard to understand what you are getting at with your question, I reckon you should be looking at `SetItemAllowOverlap()` which is a mechanism to allow overlapping hit-testing. This is going to be confusing as relatively to half-implemented/designed API, but I'm still going to write it down: ```cpp ImGui::Begin("Overlap"); ImGui::SetCursorPos(ImVec2(50, 50)); bool button_1_pressed = ImGui::Button("11111", ImVec2(50, 50)); bool button_1_hovered = ImGui::IsItemHovered(); //ImGui::SetItemAllowOverlap(); ImGui::SetCursorPos(ImVec2(70, 70)); bool button_2_pressed = ImGui::Button("22222", ImVec2(50, 50)); bool button_2_hovered = ImGui::IsItemHovered(); ImGui::Text("Button 1: %s%s", button_1_hovered ? "hovered " : "", button_1_pressed ? "PRESSED " : ""); ImGui::Text("Button 2: %s%s", button_2_hovered ? "hovered " : "", button_2_pressed ? "PRESSED " : ""); ImGui::End(); ``` ![image](https://user-images.githubusercontent.com/8225057/111142005-7892dc80-8584-11eb-9c64-24b456f49852.png) When hovering in the intersection of both button: (A) Without SetItemAllowOverlap() after Button 1 - when clicking: button 1 is pressed, - only button 1 appears hovered - both button return true to IsItemHovered() (B) With SetItemAllowOverlap() after Button 1 - when clicking: button 2 is pressed - both buttons appears hovered - both button return true to IsItemHovered() (C) With (internal) ImGuiButtonFlags_AllowItemOverlap flag on Button 1 + SetItemAllowOverlap() after Button 1 - when clicking: button 2 is pressed - only button 2 appears appears hovered - both button return true to IsItemHovered() _Added records of 1.88 behavior on 2023/06:_ (D) With (internal) ImGuiButtonFlags_AllowItemOverlap flag on Button 1 + WIITHOUT SetItemAllowOverlap() call. - when clicking: button 1 is pressed - only button 1 appears hovered - both button return true to IsItemHovered() Notice issues: - Only difference between B and C is how Button 1 visibly react to hover. Both cases are useful depending on context, but C is likely to be more commonly useful. - Inconsistency between the return value of IsItemHovered() and what the button display, I think this should be fixed. - `ImGuiButtonFlags_AllowItemOverlap` is currently explicit, I've been considering switching to a hover model where it is implicit default, but it's going to be a lots of work. With this model we would only need to call `SetItemAllowOverlap()`. - Either settings could be wired to shared item flags. Added records of 1.88 behavior on 2023/06: - The only difference between (D) and not doing anything is: affected button will check g.HoveredIdPreviousFrame and defer initial hover reaction by 1 frame, but because SetItemAllowOverlap() is not called, other well-behaved items won't claim g.HoveredId. - As of today 1.89.7 WIP (18965) two places were using ImGuiButtonFlags_AllowItemOverlap WITHOUT SetItemAllowOverlap(): `SplitterBehavior()` and `TableUpdateBorders()`. Effectively this behavior was analogous to not doing anything, EXCEPT in the case of SplitterBehavior() is allows the caller to follow the call with `SetItemAllowOverlap()`. (This is a a bit of a mess. There are they two aspects: the `ButtonBehavior()` logic has the `ImGuiButtonFlags_AllowItemOverlap` flag + there's a `SetItemAllowOverlap() call which can be called afterwards.... hence I know this post is probably only opening the veil to a new world of confusion especially if you are stuck without access to internals..)
pixtur commented 2021-03-15 22:25:28 +00:00 (Migrated from github.com)

Thank you for this excellent explanation. I guess I have to think about this.

Some thoughts, though:

  • Just to verify that I understood this correctly: ImGui will make sure, that only one of overlapping elements is activated. And I don't need the above hack for detecting if something else was hovered to prevent simultaneous clicks on multiple elements.
  • I find your explanation above much clearer than then documentation of SetItemAllowOverlap()...

allow last item to be overlapped by a subsequent item. sometimes useful with invisible buttons, selectables, etc. to catch unused area.

  • I personally would prefer a version D: Only "Button 2" returns true to when calling with some fancy optional parameter IsItemHovered(wasNotOverlappedInPreviousFrame:true); so I could only react on clicks if this returns true.😄 Especially with node graphs and timeline elements, activating the overlapped element is probably worse than a slight delay.

But I'm sure, that you already thought about this problem. If there would be an easy solution, you would have done it already...

Thank you for this excellent explanation. I guess I have to think about this. Some thoughts, though: - Just to verify that I understood this correctly: ImGui will make sure, that only _one_ of overlapping elements is activated. And I don't need the above hack for detecting if something else was hovered to prevent simultaneous clicks on multiple elements. - I find your explanation above much clearer than then documentation of `SetItemAllowOverlap()`... > allow last item to be overlapped by a subsequent item. sometimes useful with invisible buttons, selectables, etc. to catch unused area. - I personally would prefer a version D: Only "Button 2" returns true to when calling with some fancy optional parameter `IsItemHovered(wasNotOverlappedInPreviousFrame:true);` so I could only react on clicks if this returns true.😄 Especially with node graphs and timeline elements, activating the overlapped element is probably worse than a slight delay. But I'm sure, that you already thought about this problem. If there would be an easy solution, you would have done it already...
ocornut commented 2023-06-12 12:33:55 +00:00 (Migrated from github.com)

Just to verify that I understood this correctly: ImGui will make sure, that only one of overlapping elements is activated. And I don't need the above hack for detecting if something else was hovered to prevent simultaneous clicks on multiple elements

Correct.

I find your explanation above much clearer than then documentation of SetItemAllowOverlap()...

This was never advertised much because of the subtleties between case B and C and the potential need for both.

However, plot twist discovered via #6512, case B simply doesn't work since 1.89 because of the introduction of the input ownership system.

Update of preceding report following changes in 1.89+. When hovering in the intersection of both button, case A and C are identical, but case B changes:

(B after 1.89) With SetItemAllowOverlap() after Button 1 but not using ImGuiButtonFlags_AllowItemOverlap in Button 1:

  • when clicking: button 1 is pressed --> This differ from pre 1.89
  • both buttons appears hovered
  • both button return true to IsItemHovered()

This is because Button 1 takes ownership for ImGuiKey_MouseLeft on mouse down, preventing Mouse 2 to access it.
The API SetItemAllowOverlap() cannot be modified to solve this.

I am going to work on a solution for this.

> Just to verify that I understood this correctly: ImGui will make sure, that only one of overlapping elements is activated. And I don't need the above hack for detecting if something else was hovered to prevent simultaneous clicks on multiple elements Correct. > I find your explanation above much clearer than then documentation of SetItemAllowOverlap()... This was never advertised much because of the subtleties between case B and C and the potential need for both. However, plot twist discovered via #6512, case B simply doesn't work since 1.89 because of the introduction of the input ownership system. Update of preceding report following changes in 1.89+. When hovering in the intersection of both button, case A and C are identical, but case B changes: (B after 1.89) With SetItemAllowOverlap() after Button 1 but not using ImGuiButtonFlags_AllowItemOverlap in Button 1: - when clicking: **button 1 is pressed** --> **This differ from pre 1.89** - both buttons appears hovered - both button return true to IsItemHovered() This is because Button 1 takes ownership for `ImGuiKey_MouseLeft` on mouse down, preventing Mouse 2 to access it. The API `SetItemAllowOverlap()` cannot be modified to solve this. I am going to work on a solution for this.
ocornut commented 2023-06-28 13:13:50 +00:00 (Migrated from github.com)

I have pushed a bunch of changes related to AllowOverlap mode.

See this comment for details: https://github.com/ocornut/imgui/issues/6512#issuecomment-1611369198

TL;DR;

  • SetItemAllowOverlap() couldn't work since 1.89 and was obsoleted.
  • Introduced SetNextItemAllowOverlap(). It doesn't need an internal button flags and should work across most/all widgets.
  • Made IsItemHovered() aware of this (previously it wasn't) with opt-out flag ImGuiHoveredFlags_AllowWhenOverlappedByItem.
  • Renamed various xxxAllowItemOverlap flags to xxxAllowOverlap.
  • Other things, see link above.
I have pushed a bunch of changes related to AllowOverlap mode. See this comment for details: https://github.com/ocornut/imgui/issues/6512#issuecomment-1611369198 TL;DR; - `SetItemAllowOverlap()` couldn't work since 1.89 and was obsoleted. - Introduced `SetNextItemAllowOverlap()`. It doesn't need an internal button flags and should work across most/all widgets. - Made `IsItemHovered()` aware of this (previously it wasn't) with opt-out flag `ImGuiHoveredFlags_AllowWhenOverlappedByItem`. - Renamed various `xxxAllowItemOverlap` flags to `xxxAllowOverlap`. - Other things, see link above.
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ocornut/imgui#3909
No description provided.