imgui icon indicating copy to clipboard operation
imgui copied to clipboard

Modal Popups close when losing focus

Open ldsPatrick opened this issue 5 years ago • 4 comments

Version/Branch of Dear ImGui:

Version: 1.80 Branch: docking

Back-end/Renderer/Compiler/OS

Back-ends: Custom engine/back-end modeled after imgui_impl_Win32.cpp + imgui_impl_dx11.cpp Compiler: VS2017 (Issue is not compiler specific) Operating System: Win10

My Issue/Question:

We're seeing an issue where modal popups are getting closed when focus gets taken away from them. This happens in one of two scenarios:

  1. If any code not related to the modal popup calls ImGui::SetWindowFocus(). In our case, we call this to set the focus to NULL when our application loses active state in order to close non-modal popups such as combo dropdowns or context menus, and remove the visible focus highlight on items to reduce user confusion. It also ensures input device activity is not accidentally handled by any ImGui widget when the application is not in the foreground.
  2. When a new window appears. If a modal popup is active and some part of the code decides to display a new window (Due to some external event, such as a network message, or a file being done loading). This will internally call ImGui::SetWindowFocus().

The issue is happening because ImGui::SetWindowFocus() wind up calling ClosePopupsOverWindow(), which has no conditions for stopping at the first modal popup.

Setting focus to NULL should not affect modal popups, but should still close any other popup above that. Setting focus to another window not under the topmost modal popup should probably just do nothing.

Thanks,

Patrick.

ldsPatrick avatar Nov 12 '20 17:11 ldsPatrick

I've tried to simply stop ClosePopupsOverWindow from killing modal popups, and it's working well. Both scenarios are resolved.

In that function, I'm changing this part of the code:

// Don't close our own child popup windows.
int popup_count_to_keep = 0;
if (ref_window)
...

with

int popup_count_to_keep;
for (popup_count_to_keep = g.OpenPopupStack.Size - 1; popup_count_to_keep >= 0; popup_count_to_keep--)
{
	ImGuiPopupData& popup = g.OpenPopupStack[popup_count_to_keep];
	if (!popup.Window)
		continue;
	IM_ASSERT((popup.Window->Flags & ImGuiWindowFlags_Popup) != 0);
	if (popup.Window->Flags & ImGuiWindowFlags_Modal)
	{
		popup_count_to_keep++;
		break;
	}
}

popup_count_to_keep = ImMax(popup_count_to_keep, 0);
if (ref_window)
...

This effectively makes modal popups be unkillable by focus changes, so setting focus to null leaves it alone.

In the case of a new window opening while a modal is up, we also need to stop the new window from getting focus, or it will have nav control, and although mouse actions are properly blocked, keyboard actions are not. For this, I needed to add at the top of FocusWindow():

// Don't allow setting focus to a window that's below a modal popup.
ImGuiWindow *popup_window = GetTopMostPopupModal();
if ((popup_window != NULL) && (popup_window != window))
	return false;

FocusWindow() was made to return a bool, which is checked in ImGui::Begin and ImGui::BeginChildEx, to stop the calls to NavInitWindow().

Those changes seem to work out in all scenarios I've tried, but it's hard to tell if there are any other more arcane paths that would be broken.

ldsPatrick avatar Dec 04 '20 23:12 ldsPatrick

I found another workaround:

Before:

if (ImGui::BeginPopupModal("foo")) {
    ImGui::Text("bar");
    ImGui::EndPopup();
}

After:

while (!ImGui::BeginPopupModal("foo")) {
    ImGui::OpenPopup("foo");
}
ImGui::Text("bar");
ImGui::EndPopup();

This obviously works only when this code is only run when the popup should be open. So maybe you need another bool somewhere to track that state. The advantage of this solution is that you don't have to modify any ImGui source files. The disadvantage is that it's not a universal solution that works on every popup.

@ ImGui developers: Is the current behaviour expected or is it a bug?

bb1950328 avatar Aug 15 '21 20:08 bb1950328

I've been having a similar problem to 1., and for me the workaround is to use

ImGui::FocusWindow(nullptr, ImGuiFocusRequestFlags_UnlessBelowModal);

instead of

ImGui::SetWindowFocus()

Sadly it is part of imgui_internal.h and not the public API.

piernov avatar Aug 14 '24 16:08 piernov

I think I will aim to make those flags public over time, but I am not sure of the proper design yet.

ocornut avatar Aug 14 '24 18:08 ocornut