ocornut/imgui

Pinned Tooltip Windows

mikesart opened this issue · 12 comments

I would like windows with the following properties:

  • Render on top of a window, but don't take focus and don't activate.
  • When I mouse click on them and drag, they move.
  • All other events (mouse hovering, right clicking, etc go to window underneath).

I'm planning on using them for "pinned tooltip" behavior like in the below picture. Hover over some event, hit a hotkey, that tooltip is now a "pinned tooltip" and you can view / compare to other events in the main tooltip. I also think these will be useful for displaying floating information about other things.

I've got this working using the code below (which pretty much abuses tooltips). I tried using popups and a few other techniques and couldn't get anything working.

The only issue I've got is the z-ordering, but I think I can get everything else working like I want without imgui changes.

My question would be: this ok? Is there a better way to accomplish this behavior?

Thanks!

image

void imgui_set_tooltip( const char *name, const ImVec2 pos, const char *str, rect_t *rc )
{
    ImGuiIO& io = ImGui::GetIO();
    const ImVec2 mousepos_orig = io.MousePos;

    ImGuiWindowFlags flags = ImGuiWindowFlags_Tooltip |
            ImGuiWindowFlags_NoTitleBar |
            ImGuiWindowFlags_NoMove |
            ImGuiWindowFlags_NoResize |
            ImGuiWindowFlags_NoSavedSettings |
            ImGuiWindowFlags_AlwaysAutoResize;

    io.MousePos = pos;

    ImGui::Begin( name, NULL, flags );
    ImGui::Text( "%s", str );

    if ( rc )
    {
        ImVec2 wpos = ImGui::GetWindowPos();
        ImVec2 size = ImGui::GetWindowSize();

        // Return window dimensions so higher level can handle click / moving window
        *rc = { wpos.x, wpos.y, size.x, size.y };
    }

    ImGui::End();

    io.MousePos = mousepos_orig;
}

I have this code checked into my project.

imgui_set_tooltip() in gpuvis_utils.cpp
TraceWin::graph_render() has window moving code: gpuvis_graph.cpp

Going to generalize it to handle more windows, but otherwise it's working great. Only issue I'm seeing is window z-order. I'll take a look at that at some point, but it's not that big deal right now.

Mike,

It looks like the property you need the most is to keep that window over the parent window at all times (even if the parent window is interacted with).

But do you actually need inputs to go through? "All other events (mouse hovering, right clicking, etc go to window underneath).". I am surprised you want/need that, I'd find it odd to have a mouse click go through.

Some thoughts..

A) It may be possible to introduce a specific type of window for this, e.g. using Child+Tooltip flag might be appropriate.

B) A more generic solution, if we can ignore the inputs constraint, may boils down to the requirement that we want some form of finer control over the z-order. I've been reluctant to expose this just using absolute Z values because I think it'd be putting a lot of burden on the application. But perhaps expressed as a series of constraint (e.g. Window B always above Window A, or Window B stays between Z0 and Z1) it would be more flexible and appropriate. I haven't thought about it further than that for now.

I'll try to mess around to see if A) is doable without too much trouble.

(Linking to #983 #1328 for later ref)

It seems to work the hacky proof-of-concept patch below.

Use in the context of the parent window.

ImGui::Begin("Child Tooltip?", NULL, ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_ChildWindow | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_AlwaysUseWindowPadding);
ImGui::Text("I am a child tooltip");
ImGui::EndChild();

If you want to give it a test.
I'm pretty sure there will be other issues, now and later, and depending on them I'm not yet sure this is something I would want to maintain. But I suggest you try this locally and if it seems to work without too much complication, I don't mind committing the changes in imgui.cpp (I'll add extra comments + fixes to allow the "Child Tooltip" to have its own childs, and won't expose that particular Child+Tooltip combination publicly for now).

Let me know!

patch.zip

diff --git a/imgui.cpp b/imgui.cpp
index 849a05f..72be861 100644
--- a/imgui.cpp
+++ b/imgui.cpp
@@ -2264,9 +2264,11 @@ void ImGui::NewFrame()
         IM_ASSERT(g.MovedWindow->MoveId == g.MovedWindowMoveId);
         if (g.IO.MouseDown[0])
         {
-            g.MovedWindow->RootWindow->PosFloat += g.IO.MouseDelta;
+            // FIXME: Won't work if the tooltip itself has child.
+            ImGuiWindow* actual_moved_window = ((g.MovedWindow->Flags & ImGuiWindowFlags_ChildWindow) && (g.MovedWindow->Flags & ImGuiWindowFlags_Tooltip)) ? g.MovedWindow : g.MovedWindow->RootWindow;
+            actual_moved_window->PosFloat += g.IO.MouseDelta;
             if (g.IO.MouseDelta.x != 0.0f || g.IO.MouseDelta.y != 0.0f)
-                MarkIniSettingsDirty(g.MovedWindow->RootWindow);
+                MarkIniSettingsDirty(actual_moved_window);
             FocusWindow(g.MovedWindow);
         }
         else
@@ -4115,7 +4117,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us
         window->DrawList->Clear();
         window->DrawList->PushTextureID(g.Font->ContainerAtlas->TexID);
         ImRect fullscreen_rect(GetVisibleRect());
-        if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_ComboBox|ImGuiWindowFlags_Popup)))
+        if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_ComboBox|ImGuiWindowFlags_Popup|ImGuiWindowFlags_Tooltip)))
             PushClipRect(parent_window->ClipRect.Min, parent_window->ClipRect.Max, true);
         else
             PushClipRect(fullscreen_rect.Min, fullscreen_rect.Max, true);
@@ -4208,7 +4210,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us
             window->OrderWithinParent = parent_window->DC.ChildWindows.Size;
             parent_window->DC.ChildWindows.push_back(window);
         }
-        if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & ImGuiWindowFlags_Popup))
+        if ((flags & ImGuiWindowFlags_ChildWindow) && !(flags & (ImGuiWindowFlags_Popup|ImGuiWindowFlags_Tooltip)))
         {
             window->Pos = window->PosFloat = parent_window->DC.CursorPos;
             window->Size = window->SizeFull = size_on_first_use; // NB: argument name 'size_on_first_use' misleading here, it's really just 'size' as provided by user passed via BeginChild()->Begin().
@@ -4240,7 +4242,7 @@ bool ImGui::Begin(const char* name, bool* p_open, const ImVec2& size_on_first_us
         }
 
         // Position tooltip (always follows mouse)
-        if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api)
+        if ((flags & ImGuiWindowFlags_Tooltip) != 0 && !window_pos_set_by_api && !(flags & ImGuiWindowFlags_ChildWindow))
         {
             ImVec2 ref_pos = g.IO.MousePos;
             ImRect rect_to_avoid(ref_pos.x - 16, ref_pos.y - 8, ref_pos.x + 24, ref_pos.y + 24); // FIXME: Completely hard-coded. Perhaps center on cursor hit-point instead?

I tried your patch and it worked well but there are issues. One is the layout - appears to still be making room for the window. So if I draw graph, draw the "child tooltip", then draw the event list the tooltip shows up on top of the graph but there is space between graph and event list equal to height of tooltip. Also for my graph I'm checking if the mouse is over the window, has focus, and mouse click for panning, and it looks like I'd still need code to disable that cause right now clicking + moving tooltip window also pans the graph.

Also played around with this feature a bit and I think you're right, I probably don't need inputs to go through to the below window. It's quite easy for me to get either behavior with the current tooltip windows though.

B) A more generic solution, if we can ignore the inputs constraint, may boils down to the requirement that we want some form of finer control over the z-order. I've been reluctant to expose this just using absolute Z values because I think it'd be putting a lot of burden on the application. But perhaps expressed as a series of constraint (e.g. Window B always above Window A, or Window B stays between Z0 and Z1) it would be more flexible and appropriate. I haven't thought about it further than that for now.

I think this is the real issue I need solved. I want the "real" tooltip to show up on top of the pinned tooltips, and I'd like the tooltip that's being moved to be on top, and both of these are at the whim of that qsort function.

Maybe you could come up with what you think a good imgui quality API for this might look like and I could write some code for it and we iterate a bit? If you think that's useful, otherwise I'll hack something small in on my side. :)

Thanks a ton Omar!

I got everything working with your patch and pushed it out. I don't have to worry about z-order right now because I only have one tip window and your child+tip windows show up under that. Seems like everything is working great. Thank you very much!

screenshot_2017-10-02_13-45-18

FYI I realize this is essentially the behavior Win32 refer to as "Tool window".
Should eventually integrate that as a main line feature.

Yeah, this is really useful. Couple issues I think would need solving before it's a main line feature are z-order and widgets don't work. I wound up writing my own close button. Working great in gpuvis to display text right now though.

FYI posting this as it is related (but different from the "pinned" tooltips), if people want multiple tooltips that you want to manually position it is possible to do:

ImGui::Begin("Test Multi Tooltip");
ImGui::Button("Hello");
if (ImGui::IsItemHovered())
{
    ImVec2 m = ImGui::GetIO().MousePos;
    ImGui::SetNextWindowPos(ImVec2(m.x - 10, m.y));
    ImGui::Begin("1", NULL, ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar);
    ImGui::Text("FIRST TOOLTIP");
    ImGui::End();

    ImGui::SetNextWindowPos(ImVec2(m.x + 100, m.y));
    ImGui::Begin("2", NULL, ImGuiWindowFlags_Tooltip | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoTitleBar);
    ImGui::Text("SECOND TOOLTIP");
    ImGui::End();
}
ImGui::End(); 

The ImGuiWindowFlags_Tooltip flag is in theory marked as private but it'll probably keep working for a long while.

I have applied the patch discussed here as an undocumented feature. I think the full feature is desirable down the line but the patch has severe limitation (namely that you won't have any z-order control if you have multiple windows with tooltip+child flags.).

Got it merged into gpuvis. The only odd behavior is after I click on a pinned tooltip and move it, other tooltips don't pop up when I hover over the event list, etc. Kinda like the pinned tooltip took focus? Not a big deal though - I'm definitely gonna use your code to avoid the merge hassles. Thank you very much for getting this in Omar!

As alternative, you can set mouse position, than restore it.
`

	//Save Mouse Position
	ImVec2 m = ImGui::GetIO().MousePos;
	//Set Mouse Position
	ImGui::GetIO().MousePos.x = 100;
	ImGui::GetIO().MousePos.y = 100;	
	ImGui::BeginTooltip();
	ImGui::Text("Fixed Tool Tip");
	ImGui::End();
	//Restore Mouse Position
	ImGui::GetIO().MousePos = m;

`

@rafaelkrause Please note you can use SetNextWindowPos(ImVec2(100,100)) for the same effect as what your code snippet does. This is however different from Mike's discussion as the "pinned tooltip" pattern also those windows to be moved.