ocornut / imgui

Dear ImGui: Bloat-free Graphical User interface for C++ with minimal dependencies
MIT License
61.16k stars 10.31k forks source link

Trigger widgets (per default) with the click that closes a popup #7681

Open alektron opened 5 months ago

alektron commented 5 months ago

Version/Branch of Dear ImGui:

Version 1.90.5, Branch: docking

Back-ends:

Tested with win32_opengl3 and custom

Compiler, OS:

MSVC 2022

Full config/build information:

No response

Details:

Related to #3154 , however in my case, I would like it not just for IsWindowHovered calls (where it can be done with a flag) but as a default behavior for all (or most) widgets.

Current behavior: When a non-modal popup is open, clicking out of that popup closes it. While the popup is open, the windows behind it do not receive hover 'events', e.g. buttons do not get highlighted on mouse over. This is to be expected and in fact the same behavior as in most applications I have tested (Visual Studio, Chrome).

Expected/Wanted behavior: The difference to other applications is, that despite the hover effect not showing, the click will still close the popup AND trigger the widget that is below the mouse cursor. For example, in Chrome if I open a context menu via right click on a web page and then click on a different tab, that single click will close the context menu and switch to that tab.

Is there already some configuration flag or similar that would replicate this behavior? As far as I can tell, the fact that the background widgets can not go into the hovered state causes them to also not go into the active state on click. So this might be a problem because they can not be in a state where they are hovered but do not look like they are. Is that a correct assumption?

At first I did not think, this behavior would cause any problems but I have now repeatedly encountered "less experienced" users of our software, that do not realize that a click did not work because there was still a popup on screen that got closed first. Ok, so this is just a bit of a bad user experience ("why did that click not work? Weird, guess I'll click again"). But in a recent extreme case, a user was trying to switch to another tab, whose content looked quite similar to the one that was currently open. That tab switch did not work because of an open popup and the user did not notice that and continued to erroneously work in the same tab he was already in. The active tab is distinctly colored different to the other tabs (light blue vs. dark gray/black) and I also made the popup background color brighter than other windows but some users are just resistant to visual cues.

Screenshots/Video:

No response

Minimal, Complete and Verifiable Example code:

No response

ocornut commented 5 months ago

Expected/Wanted behavior: The difference to other applications is, that despite the hover effect not showing, the click will still close the popup AND trigger the widget that is below the mouse cursor. For example, in Chrome if I open a context menu via right click on a web page and then click on a different tab, that single click will close the context menu and switch to that tab.

For right-click reopening of context-menu, this tends to be handled by dear imgui because the right-mouse down event is closing popup and the right-mouse up event opens one. Therefore a right-click does both. This is why BeginPopupContextItem() and other similar functions are using IsMouseReleased().

However you are right that many systems behave in the way you described, do not display highlight but react on click. I don't yet know how easy or difficult it would be to implement this.

I know that it would be simple to make the whole feature an opt-out in BeginPopup(), e..g ImGuiPopupFlags_NoBlockHoveringBehind, which might solve/help in some situation but not exactly provide the discussed behavior.

ocornut commented 5 months ago

The way this is implemented:

Actual filter is in IsWindowContentHoverable().

That function is roughly called from three spots: IsItemHovered(), ItemHoverable() and IsWindowHovered() ItemHoverable() is roughly in-widget-code specialized version of what would be needed for IsItemHovered(), so they have similar stuff.

Very broadly, making ItemHoverable() return false while still calling SetHoveredId() would be the direction to go, and we already have systems doing this, and it would probably get us 80% of the way. But the remaining 80% would require checking/considering every possible path, also the feature match and extra flags for IsItemHovered().

alektron commented 5 months ago

I'm still looking into the code trying to get a bit of an overview over all the hovering internals. However I have some trouble understanding and got some follow up questions.

I know that it would be simple to make the whole feature an opt-out in BeginPopup(), e..g ImGuiPopupFlags_NoBlockHoveringBehind, which might solve/help in some situation but not exactly provide the discussed behavior.

Would that not be an opt-IN? By passing that flag I would get the "new" behavior of not blocking the hover of windows behind the popup. Or are you saying if the new behavior were the default, one could opt back out into the old behavior via that flag. But then it seems to me it would be called ImGuiPopupFlags_BlockHoveringBehind without the "No".

Would that really be ImGuiPopupFlags_NoBlockHoveringBehind or ImGuiWindowFlags_NoBlockHoveringBehind? Since BeginPopup takes WindowFlags. Also contrary to OpenPopup (which would take PopupFlags), only BeginPopup seems to store the flags, which would be necessary for other windows to even check for that. Correct?


I've also been playing around a bit and while this is of course not the solution, I hardcoded an ImGuiHoveredFlags_AllowWhenBlockedByPopup into the call of IsWindowContentHoverable in ItemHoverable, just to see what happens. Like this I can already use widgets in the back. However when clicking on one, the popup stays open. The reason seems to be that UpdateMouseMovingWindowEndFrame, which would focus the window and close the popup down the line exits early because ActiveId and/or HoveredId is now set.

Also, we do of course get the normal hover effect. I am wondering however if this is even that bad. Why should one not want the hover effect when I can in fact click the widget? Tbh I feel like the no-hover-but-clickable convention in other applications could just as well be unintended and an artifact of default Windows window behavior. Something that we are just stuck with now due to historical reasons. Just something to think about.

ocornut commented 5 months ago

know that it would be simple to make the whole feature an opt-out in BeginPopup(), e..g Would that not be an opt-IN?

I meant the BLOCKING which is the current default, could be opted-out on a per popup basis.

Would that really be ImGuiPopupFlags_NoBlockHoveringBehind or ImGuiWindowFlags_NoBlockHoveringBehind? Since BeginPopup takes WindowFlags.

I'd rather move popup specific stuff to PopupFlags. But, dang, I forgot that some of those entry points don't have PopupFlags already exposed :( it's a little annoying because it means we would need to use BeginPopup(const char* str_id, ImGuiWindowFlags flags = 0, ImGuiPopupFlags = 0) which is inconsistent with e.g. BeginChild().

, only BeginPopup seems to store the flags, which would be necessary for other windows to even check for that. Correct?

We would likely store a ImGuiPopupFlags in ImGuiWindow just like we now store ImGuiChildFlags.

Tbh I feel like the no-hover-but-clickable convention in other applications could just as well be unintended and an artifact of default Windows window behavior. Something that we are just stuck with now due to historical reasons. Just something to think about.

Yes, to be honest I was actually surprised yesterday to find how many things allowed that click even though there's no hover feedback. I wonder if things evolved over the last decade or so tending toward the current state, because it my mind it wasn't even possible most of the time. I don't know where I got this idea from.