Hi,
Is there a way to consume certain mouse events (e.g. button click, dragging)? Example pseudocode:
if( condition && IsMouseClicked( 0 ) )
{
ConsumeMouseClick( 0 );
}
if( IsMouseClicked( 0 ) )
{
// should not activate if 'condition' is true
}
The use case for this would be handling interaction with overlays, which should prevent clicks going to UI elements underneath the overlay.
So far I had limited success directly manipulating ImGui IO structure, but I don't know how robust it is and what assumptions are made about preservation of the state.
The last time I looked at this, my conclusion what that instead of a coming up with a concept of "consuming" we should instead have a system to claim "ownership" of an input, which seemed more flexible, as your actual consumer is likely to want to poll/use the mouse-down and mouse-release events coming after the click.
So in pseudo-code it would be like:
if( condition && IsMouseClicked( 0 ) )
SetMouseButtonInputOwner( 0, GetID("mytool"));
And then functions like IsMouseClicked() would take an optional owner in the form of an id.
#define ImGuiInputOwner_None ((ImGuiID)-1) // return input if it has no owner
#define ImGuiInputOwner_Any (0) // return input regardless of owner
The use case for this would be handling interaction with overlays, which should prevent clicks going to UI elements underneath the overlay.
There are many variations of this problem and I'm always interested in new examples to add to my checklist.
What you mention is often solved by using windows (if your overlay is in a window it would capture inputs).
Is the reason you can't use an input that you want things to pass-through when there's no item, as per issue #3368 (coincidentally adjacent to this issue) ?
The last time I looked at this, my conclusion what that instead of a coming up with a concept of "consuming" we should instead have a system to claim "ownership" of an input, which seemed more flexible, as your actual consumer is likely to want to poll/use the mouse-down and mouse-release events coming after the click.
This is not the case I am asking for. I need a way to stop further code within the current frame from reacting to given mouse events. As for what happens in next frames - it's up to my logic to handle.
What you mention is often solved by using windows (if your overlay is in a window it would capture inputs).
This part of the UI is drawn using ImDrawList.
Is the reason you can't use an input that you want things to pass-through when there's no item, as per issue #3368 (coincidentally adjacent to this issue) ?
No. The problem that I have is that there are two possible reactions to a mouse event in my code and I want to give one priority over the other. Essentially, a way of saying "this mouse event has been handled, don't do anything with it anymore".
To give this some context, clicking on a zone in Tracy will open the zone information window:

Dragging the left mouse button over the timeline will display a time range:

However, if you want to limit some statistics to a specific time during program execution, you will be presented with an overlay (the striped area). This overlay can be then adjusted by dragging its borders to fit the required time range. While such adjustment is performed, other possible reactions to mouse events should not happen.

I have decided to implement my own wrapper for mouse handling: https://github.com/wolfpld/tracy/blob/master/server/TracyMouse.cpp
While this issue was a partial motivation for this move, a more pressing problem had surfaced. I needed to hook up two actions to the same mouse button: panning the view and opening a context menu. While ImGui has facilities to set a threshold before dragging action is reported, it is not enough for my needs.
Handling the context menu opening (i.e. mouse click then mouse release, without mouse movement) has to have some leeway for random mouse jitter, so a drag threshold is needed.
Panning the view should be as smooth as possible and having threshold set introduces a jarring jump when dragging action activates for the first time.
These two problems have solutions that mutually exclude each other. However, the context menu is not always available to be opened, in which case the drag threshold can be disabled. Determining if this is the case can be easily done by checking if a click+release function was called during the frame (a rather badly named IsMouseClickReleased in the code above).
Hello,
This is not the case I am asking for. I need a way to stop further code within the current frame from reacting to given mouse events. As for what happens in next frames - it's up to my logic to handle.
The idea I presented above would work similarly, we'd just have to coin the right api/semantic to capture e.g. the button down state for the current frame. What I ommitted in my pseudo API paste above is also that decide that the capture is honored by default in all getters functions, rather than requesting an explicit parameter to honor capture.
So far I had limited success directly manipulating ImGui IO structure, but I don't know how robust it is and what assumptions are made about preservation of the state.
Your workaround will be OK for now. I presume you can technically clear the io flags directly as you mentioned, from my selfish pov it may be preferable as we may discover more edge cases this way, if any exists.
The behaviors you describe two posts above (with the pictures) are similar to what most high-level widgets already do by relying on HoveredID and ActiveID to claim ownership / take precedence. It might be easier to work at a slightly higher-level than polling mouse inputs sometimes. Just calling InvisibleButton() or ButtonBehavior() selectively sometimes may do the job.
These two problems have solutions that mutually exclude each other. However, the context menu is not always available to be opened, in which case the drag threshold can be disabled. Determining if this is the case can be easily done by checking if a click+release function was called during the frame (a rather badly named IsMouseClickReleased in the code above).
I think you can use IsMouseDragPastThreshold(), it is valid to use on a MouseReleased frame.
(It is poorly advertised but GetMouseDragDelta() return the last delta which it is valid on the mouse release frame (see #2419), and this is possible because we store io.MouseDragMaxDistanceSqr[] for each button and make it sure it is only cleared on click and not on release.)
If I am not misunderstanding your code, all the state you used is already there and can be queried using IsMouseReleased() && IsMouseDragPastThreshold(). The later is merely a wrapper to access io.MouseDragMaxDistanceSqr[].
The state is affected by whether IsMouseClickReleased() is or isn't called (essentially through mousePotentialClickRelease). This selects if drag threshold should be honored.
The state is affected by whether IsMouseClickReleased() is or isn't called (essentially through mousePotentialClickRelease). This selects if drag threshold should be honored.
I'm not sure I understand, I feel you could replace IsMouseClickReleased() with IsMouseRelease() && IsMouseDragPastThreshold() == false. If that code is public I'm curious to see how it goes. Are you doing this to avoid having to communication information from one part of UI code to the other?
I'm currently building code to replicate your case (and add in imgui_demo) and finding that using a zero threshold for immediate panning BUT showing the menu on a very small drag (smaller than the typical non-zero threshold) also feels like a good and consistent solution.
Are you doing this to avoid having to communication information from one part of UI code to the other?
Yes.
I'm currently building code to replicate your case (and add in imgui_demo) and finding that using a zero threshold for immediate panning BUT showing the menu on a very small drag (smaller than the typical non-zero threshold) also feels like a good and consistent solution.
I don't want any drag action to happen if menu is to be opened, as this feels much more solid to me. I may be skewed by previously designing for mobile, where you need to account for much larger inaccuracies with touch input.
The behaviors you describe two posts above (with the pictures) are similar to what most high-level widgets already do by relying on HoveredID and ActiveID to claim ownership / take precedence. It might be easier to work at a slightly higher-level than polling mouse inputs sometimes. Just calling InvisibleButton() or ButtonBehavior() selectively sometimes may do the job.
InvisibleButton() has a long standing flaw that it didn't give access to button flags, among which one of the most important is the possibility to activate on multiple mouse buttons. I exposed those flags now, making it possible to do:
ImGui::InvisibleButton("canvas", canvas_sz, ImGuiButtonFlags_MouseButtonLeft | ImGuiButtonFlags_MouseButtonRight);
const bool is_hovered = ImGui::IsItemHovered(); // Hovered
const bool is_active = ImGui::IsItemActive(); // Held
This makes quite a difference as this small defect was a recurrent reason for people from jump from simple public API to lower-level imgui_internal.h API (ItemAdd, etc.).
The Demo in "Custom Rendering"->"Canvas" also now showcase a little bit of dragging:

I'm using InvisibleButton() as a glorified ItemSize(), for layout purposes (i.e. the gray-background frames overview and the timeline, which is further split into the frames header and a vertically scrollable portion containing zones, etc.), but that's that.

In my case the context menu is only appearing when there's an appropriate item under the mouse cursor (frame header, zone, etc.). I wouldn't be comfortable having to handle this through ImGui widgets rather than items I draw myself.
The Demo in "Custom Rendering"->"Canvas" also now showcase a little bit of dragging:
Doesn't feel solid to me, sorry.
I'm using InvisibleButton() as a glorified ItemSize(), for layout purposes (i.e. the gray-background frames overview and the timeline, which is further split into the frames header and a vertically scrollable portion containing zones, etc.), but that's that.
What I meant is that you also may be able to use one or more layered InvisibleButton() to catch mouse inputs and block them from other layers. You may decide to only submit a wide button under the mouse based on the finer and custom hovering tests you are already doing, etc. It's a possible higher-level approach without having to create widgets for every fine items (which I agree wouldn't be wise here).
I'm mostly saying that for completeness (from my pov I want to reduce some cases of people adding and maintaining their wrappers).
The Demo in "Custom Rendering"->"Canvas" also now showcase a little bit of dragging:
Doesn't feel solid to me, sorry.
I thought I would be nice to demo it this way, but instead I reworked the code to be closer to your logic:
ImGui::Checkbox("Enable context menu", &opt_enable_context_menu);
[....]
// Pan (we use a zero mouse threshold when there's no context menu)
// You may decide to make that threshold dynamic based on whether the mouse is hovering something etc.
const float mouse_threshold_for_pan = opt_enable_context_menu ? -1.0f : 0.0f;
if (is_active && ImGui::IsMouseDragging(ImGuiMouseButton_Right, mouse_threshold_for_pan))
[...]
if (opt_enable_context_menu && ImGui::IsMouseReleased(ImGuiMouseButton_Right) && ....