-
-
Notifications
You must be signed in to change notification settings - Fork 10.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow click-through of transparent windows, but not contents #8360
Comments
And to be clear, in terms of implementation what I have in mind wouldn't be a pixel mask or something like that, but rather just the existing widget hit zones. So, even if a button inside this window has a transparent background, it should still be clickable. |
Hello Riho, Unfortunately I am not sure it is possible to implement in a sane/optimal manner. We latch the Internally we allow for one hole for hit-testing (window->HitTestHoleSize/HitTestHoleOffset) but that's unlikely useful to you. Barring that, I came up with (non-perfect) workarounds last year, but they are only useful if you want the passthrough you reach your background app, rather than another ImGui window: // V1
// Call just before EndFrame()/Render(), e.g. MakeWindowInputPassthrough("Dear ImGui Demo")
// FIXME: Will incorrectly clear WantCaptureMouse when hovering said window with a popup open.
#include "imgui_internal.h"
void MakeWindowInputPassthrough(const char* name)
{
if (ImGui::IsAnyItemHovered())
return;
// We don't even need to do a FindWindowByName()
ImGuiContext& g = *GImGui;
if (g.HoveredWindow == NULL || g.HoveredWindow->RootWindow->ID != ImHashStr(name))
return;
if (g.ActiveId == g.HoveredWindow->MoveId) // Undo clicking on window taking ActiveId (even if ImGuiWindowFlags_NoMove is set)
ImGui::ClearActiveID();
if (ImGui::IsAnyItemActive())
return;
// FIXME: maybe directly write to io.WantCaptureMouse = false so it's available in your input handler earlier?
ImGui::SetNextFrameWantCaptureMouse(false);
}
// V2
// Call before EndFrame()/Render(), e.g. MakeWindowInputPassthrough("Dear ImGui Demo")
// FIXME: Will incorrectly clear WantCaptureMouse when hovering said window with a popup open.
#include "imgui_internal.h"
void MakeWindowInputPassthrough(const char* name)
{
if (ImGui::IsAnyItemHovered())
return;
ImGuiWindow* window = ImGui::FindWindowByName(name);
ImGuiContext& g = *GImGui;
if (ImGui::IsAnyItemActive())
{
if (g.ActiveId != window->MoveId)
return;
}
else
{
if (g.HoveredWindow && g.HoveredWindow->RootWindow != window)
return;
}
// FIXME: maybe directly write to io.WantCaptureMouse = false so it's available in your input handler earlier?
ImGui::SetNextFrameWantCaptureMouse(false);
} That wasn't tested/polished much but I won't mind looking into this further if you think that would be of use. |
I'm not sure I can find satisfying generic answer to it, but if you wish to explain your situation and use case in a little more details (here or privately in e.g. a call) i could try to find if there's a solution that works better. |
Yea maybe we could hop on a call tomorrow to have a look at my case. The clickthrough to the rest of the app might work if I make sure this particular window is the first window drawn maybe? |
Following our call, here's an improved version of the code. // Usage:
// - call just before EndFrame()/Render(), e.g. MakeWindowVoidInputPassthrough("Dear ImGui Demo").
// Purpose:
// - Allow mouse hover/click in the empty space (void) of window to be passed down underlying game/app.
// - This works by clearing the io.WantCaptureMouse flag, so low-level input handler can keep dispatching mouse to underlying game/app.
// - This doesn't handle multiple overlapped imgui windows, so it is generally expected you use this on a single window that is in the background,
// or on multiple non-overlapping windows. Calling this on overlapping windows will erroneously clear io.WantCaptureMouse when hovering one,
// regardless of another one that may sit behind. This is possible to fix with some work.
// - Similarly, handle focusing the window.
// Discussions at https://github.com/ocornut/imgui/issues/8360
// FIXME: What about keyboard?
void MakeWindowVoidInputPassthrough(const char* name);
static bool MakeWindowVoidInputPassthroughTestMouse(ImGuiWindow* window)
{
ImGuiContext& g = *GImGui;
if (ImGui::IsAnyItemHovered())
return false;
// Any active item is assumed to take inputs unless g.ActiveIdAllowOverlap is set
// (the variable is historically a bit misnamed, but it allows e.g. InputText() to be active while allowing hovering other items)
if (ImGui::IsAnyItemActive() && !g.ActiveIdAllowOverlap)
{
// Unless we're clicking/dragging in the window's void itself
// When clicking on window's void we allow it to take the ActiveId.
if (g.ActiveId != window->MoveId)
return false;
}
else
{
if (g.HoveredWindow && g.HoveredWindow->RootWindow != window)
return false;
}
// If a popup is open it eats the click on void
// FIXME: This could be an optional thing?
// (low-level input handler may still distinguish io.WantCaptureMouse from ioWantCaptureMouseUnlessPopupClose if they need to)
if (ImGui::IsPopupOpen(nullptr, ImGuiPopupFlags_AnyPopup))
return false;
return true;
}
static bool MakeWindowVoidInputPassthroughTestKeyboard(ImGuiWindow* window)
{
ImGuiContext& g = *GImGui;
if (g.NavWindow == nullptr || g.NavWindow->RootWindow != window->RootWindow)
return false;
// If any item is active in the window (e.g. an InputText, or a Button) we keep keyboard to imgui
if (g.ActiveId != 0 && g.ActiveId != window->MoveId)
if (g.ActiveIdWindow->RootWindow == window->RootWindow)
return false;
// Allow navigation to work, tho it is likely you'd want to use ImGuiWindowFlags_NoNav on the window.
if (g.NavCursorVisible && g.NavId != 0)
return false;
// When navigation cursor is cleared, we disable nav on this window for the frame,
// preventing e.g. arrow keys from resuming navigation while underlying game/app is using them.
// Next frame's Begin() will clear the flag again.
// This means navigation on this window may only be activated via:
// - Ctrl+Tabbing into the window.
// - Using arrow key while an item is currently active (typically InputText, but it will also
// work while holding mouse button over any item even tho that's a low affordance behavior).
// - And pressing Escape will deactivate navigation and relinquish keyboard underlying game/app.
// This seems like the best design as we want e.g. clicking a button to end up stealing keyboard.
window->Flags |= ImGuiWindowFlags_NoNav;
return true;
}
void MakeWindowVoidInputPassthrough(const char* name)
{
ImGuiIO& io = ImGui::GetIO();
ImGuiWindow* window = ImGui::FindWindowByName(name);
if (window == nullptr)
return;
if (MakeWindowVoidInputPassthroughTestMouse(window))
{
// Write to io.WantCaptureMouse directly, so it is available in e.g. low-level input handler _before_ the next NewFrame().
// Technically we don't need to call SetNextFrameWantCaptureMouse(): what matter is that io.WantCaptureMouse is cleared
// at the time of low-level input handlers applying their filter.
io.WantCaptureMouse = false;
ImGui::SetNextFrameWantCaptureMouse(false);
}
if (MakeWindowVoidInputPassthroughTestKeyboard(window))
{
io.WantCaptureKeyboard = false;
ImGui::SetNextFrameWantCaptureKeyboard(false);
}
} Test bed: static bool g_WantCaptureMouseBeforeNewFrame = false;
void TestWindow()
{
ImGuiIO& io = ImGui::GetIO();
ImGuiViewport* viewport = ImGui::GetMainViewport();
ImGui::SetNextWindowPos(viewport->Pos);
ImGui::SetNextWindowSize(ImVec2(viewport->Size.x / 2, viewport->Size.y / 2));
ImGui::SetNextWindowBgAlpha(0.1f);
ImGui::Begin("(Background Overlay)", NULL, // Use a friendly name for Ctrl+Tab
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDecoration
| (0*ImGuiWindowFlags_NoBackground)
| ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoBringToFrontOnFocus
//| ImGuiWindowFlags_NoNav
);
ImGui::Button("Some button");
ImGui::Indent(50.0f);
static int counter = 0;
if (ImGui::Button("Another button"))
counter++;
ImGui::SameLine();
ImGui::Text("%d", counter);
static char text[128]; // To test active item
ImGui::InputText("Text", text, 128);
static ImVec4 color(1.0f, 0.0f, 0.0f, 1.0f); // To test popup
ImGui::ColorEdit4("Color", &color.x);
ImGui::Text("io.WantCaptureMouse now %d before_new_frame %d", io.WantCaptureMouse, g_WantCaptureMouseBeforeNewFrame);
ImGui::Text("io.WantCaptureKeyboard %d", io.WantCaptureKeyboard);
ImGui::Text("g.ActiveID = 0x%08X", ImGui::GetActiveID());
ImGui::DebugLocateItemOnHover(ImGui::GetActiveID());
ImGui::End();
} g_WantCaptureMouseBeforeNewFrame = ImGui::GetIO().WantCaptureMouse;
ImGui::NewFrame();
TestWindow();
[...]
MakeWindowVoidInputPassthrough("(Background Overlay)");
ImGui::EndFrame(); |
Version/Branch of Dear ImGui:
Not relevant
Back-ends:
Not relevant
Compiler, OS:
Not relevant
Full config/build information:
No response
Details:
Unless I'm missing something obvious somewhere, I don't think it's currently possible to have a transparent window (ImGuiWindowFlags_NoBackground) that allows you to click-through the transparent parts. This is quite common need for overlay type windows, where you'd want the window to not capture any mouse input unless you're actually hovering over an interactive element. There's some hacks I've seen to work around this, but I think a built-in flag for this would be quite helpful.
We've used ImGuiWindowFlags_NoInputs flag on the main window, and then create child windows for individual elements and then try to detect IsWindowHovered on those, but this is quite hacky and makes it very difficult to write nice widget code.
I guess what I'm looking for is a ImGuiWindowFlags_NoTransparentInputs flag, that would ignore inputs on the window itself, but still allow inputs on all child widgets / elements.
Thanks for considering,
Riho
Screenshots/Video:
No response
Minimal, Complete and Verifiable Example code:
The text was updated successfully, but these errors were encountered: