Skip to content
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

IsItemDeactivated() doesn't trigger if the item gets deactivated by stealing the window focus #5904

Closed
GamingMinds-DanielC opened this issue Nov 21, 2022 · 6 comments

Comments

@GamingMinds-DanielC
Copy link
Contributor

Version/Branch of Dear ImGui:

Version: 1.89.1 WIP
Branch: docking (tested in docking, but issue should be in master branch)

Back-end/Renderer/Compiler/OS

Back-ends: imgui_impl_dx11.cpp + imgui_impl_win32.cpp
Compiler: Visual Studio 2019
Operating System: Windows 10 Pro

Those probably have nothing to do with the issue.

My Issue/Question:

In an asset viewer and editor, I need to take away focus from ImGui when I right-click into the viewport (I need keys for camera controls). When I do that by calling ImGui::SetWindowFocus( nullptr ), the currently active item gets deactivated (as expected), but in a way that prevents ImGui::IsItemDeactivated() from working. I use ImGui::IsItemDeactivatedAfterEdit() for the undo stack, but this one breaks because it uses IsItemDeactivated().

Standalone, minimal, complete and verifiable example:

static bool g_DeactivationTestRequestStealFocus = false;

// call before ImGui::NewFrame() or after ImGui::Render()
void DeactivationTestUpdate()
{
	if ( g_DeactivationTestRequestStealFocus )
	{
		ImGui::SetWindowFocus( nullptr );
		g_DeactivationTestRequestStealFocus = false;
	}
}

// call between ImGui::NewFrame() and ImGui::Render()
void DeactivationTestGui()
{
	if ( ImGui::Begin( "Deactivation Test" ) )
	{
		static float value                     = 0.0f;
		static int   countDeactivated          = 0;
		static int   countDeactivatedAfterEdit = 0;

		ImGui::DragFloat( "Value", &value );

		// these ones don't work if the focus has been stolen
		if ( ImGui::IsItemDeactivated() )
			countDeactivated++;
		if ( ImGui::IsItemDeactivatedAfterEdit() )
			countDeactivatedAfterEdit++;

		// displaying counters
		if ( ImGui::BeginTable( "counters", 2 ) )
		{
			ImGui::TableNextRow();
			ImGui::TableSetColumnIndex( 0 ); ImGui::TextUnformatted( "Deactivated:" );
			ImGui::TableSetColumnIndex( 1 ); ImGui::Text( "%d", countDeactivated );

			ImGui::TableNextRow();
			ImGui::TableSetColumnIndex( 0 ); ImGui::TextUnformatted( "Deactivated after Edit:" );
			ImGui::TableSetColumnIndex( 1 ); ImGui::Text( "%d", countDeactivatedAfterEdit );

			ImGui::EndTable();
		}

		ImGui::Separator();

		// interactive text to facilitate focus-stealing
		ImGui::Text(
		    "Ctrl-click or double-click the \"Value\" item above,\n"
		    "then edit it and right-click this text to remove focus.\n"
		    "The counters will not be increased as desired." );

		if ( ImGui::IsItemHovered() && ImGui::IsKeyPressed( ImGuiKey_MouseRight ) )
			g_DeactivationTestRequestStealFocus = true;
	}
	ImGui::End();
}
@ocornut
Copy link
Owner

ocornut commented Nov 24, 2022

Thank you for the useful repro.
Linking to #5184 seems essentially the same.

@GamingMinds-DanielC
Copy link
Contributor Author

GamingMinds-DanielC commented Aug 1, 2023

I stumbled upon this issue again (still happens in 1.89.8 WIP) and did a little more digging. Doesn't look like a simple bug anymore, more like an unfortunate side effect of the implementation.

What happened for me was:

  • item is being edited
  • right click into the viewport steals focus, sets active id to 0
  • new frame gets started, copies current active id (now 0) into last frames active id
  • ImGui::IsItemDeactivated() fails because the item (or rather the context) forgot it was active during the last frame

I found a workaround that fixes the problem for me. I just set a flag to defer focus stealing until right after the next call to ImGui::NewFrame(). Than the last active id is still valid and deactivation queries work as expected.

This workaround will not work for #5184 though. For that, a proper solution would most likely have to memorize all items that were active during a frame, not just the last one active at the end of it. And then on the new frame mark all of them as having been deactivated if they are no longer active.

@ocornut
Copy link
Owner

ocornut commented Jan 10, 2025

#5184 #5904 #6766 #8303 are all the same, and maybe #8004 too.
It only has to do with the relative timing in the frame of when active id was changed.
Will investigate.

Strangely although the solution to #4714 was meant to remove order reliance it didn't plumb everything correctly.

@ocornut
Copy link
Owner

ocornut commented Jan 10, 2025

Here's code to showcase how they are all the same thing:

{
    ImGui::Begin("Deactivate Test Bed");

    ImGui::ButtonEx("Right click (1)", ImVec2(0, 0), ImGuiButtonFlags_MouseButtonRight);
    ImGui::Text("F1 to ClearActiveID()");
    ImGui::Text("F2 to SetWindowFocus(NULL)");
    ImGui::Text("F3 to Open Modal");
    if (ImGui::Shortcut(ImGuiKey_F1, ImGuiInputFlags_RouteGlobal))
        ImGui::ClearActiveID();
    if (ImGui::Shortcut(ImGuiKey_F2, ImGuiInputFlags_RouteGlobal))
        ImGui::SetWindowFocus(NULL);
    if (ImGui::Shortcut(ImGuiKey_F3, ImGuiInputFlags_RouteGlobal))
        ImGui::OpenPopup("Modal 1");
    if (ImGui::BeginPopupModal("Modal 1"))
    {
        ImGui::Text("Interrupted!");
        if (ImGui::Shortcut(ImGuiKey_Escape))
            ImGui::CloseCurrentPopup();
        ImGui::EndPopup();
    }

    static char buf[128];
    ImGui::InputText("Input", buf, 128);
    bool is_item_activated = ImGui::IsItemActivated();
    bool is_item_deactivated = ImGui::IsItemDeactivated();
    bool is_item_deactivated_after_edit = ImGui::IsItemDeactivatedAfterEdit();
    if (is_item_deactivated)
        IMGUI_DEBUG_LOG("IsItemDeactivated()\n");
    if (is_item_deactivated_after_edit)
        IMGUI_DEBUG_LOG("IsItemDeactivatedAfterEdit()\n");

    ImGui::ButtonEx("Right click (2)", ImVec2(0, 0), ImGuiButtonFlags_MouseButtonRight);
    ImGui::Text("F5 to ClearActiveID()");
    ImGui::Text("F6 to SetWindowFocus(NULL)");
    ImGui::Text("F7 to Open Modal");
    if (ImGui::Shortcut(ImGuiKey_F5, ImGuiInputFlags_RouteGlobal))
        ImGui::ClearActiveID();
    if (ImGui::Shortcut(ImGuiKey_F6, ImGuiInputFlags_RouteGlobal))
        ImGui::SetWindowFocus(NULL);
    if (ImGui::Shortcut(ImGuiKey_F7, ImGuiInputFlags_RouteGlobal))
        ImGui::OpenPopup("Modal 2");
    if (ImGui::BeginPopupModal("Modal 2"))
    {
        ImGui::Text("Interrupted!");
        if (ImGui::Shortcut(ImGuiKey_Escape))
            ImGui::CloseCurrentPopup();
        ImGui::EndPopup();
    }

    ImGui::End();
}

@ocornut
Copy link
Owner

ocornut commented Jan 10, 2025

I have a possible solution for everything but will need to be testing further, and it doesn't seem wise to push it before the week-end, but I hope to maybe push it on Monday. There are also some issues with groups (even in existing code).

@ocornut
Copy link
Owner

ocornut commented Jan 13, 2025

This should now be fixed by a604d4f
Basic regression test: ocornut/imgui_test_engine@b85d832
Let me know if you run into any other issue or have related questions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants