Nested group hovering detection and group dragging? #479

Closed
opened 2016-01-09 22:51:55 +00:00 by unpacklo · 6 comments
unpacklo commented 2016-01-09 22:51:55 +00:00 (Migrated from github.com)

Hi Omar!

I was working on a GUI feature on my game where we could take array elements and click and drag them to reorder. I ran into a case where each array element was another array and the logic I was using involved groups to help detect the whole area used up by an element (rather than just each component widget).

The problem I ran into was that I couldn't determine correctly the nested group hovering. It would return to me the "top level" group as being hovered, despite the nested group being just finished.

Here's a test case:

        if (ImGui::Begin("Nested groups"))
        {
            static bool doGroupA = true;
            static bool doGroupB = true;
            ImGui::Checkbox("Do Group A", &doGroupA);
            ImGui::Checkbox("Do Group B", &doGroupB);

            if (doGroupA) ImGui::BeginGroup();
            ImGui::Button("Group A button1");
            ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing();

            {
                ImGui::Indent(); ImGui::Indent();
                if (doGroupB) ImGui::BeginGroup();
                ImGui::Button("Group B button 1");
                ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing();
                ImGui::Button("Group B button 2");
                if (doGroupB) ImGui::EndGroup();

                if (doGroupB && ImGui::IsItemHovered())
                {
                    ImGui::SetTooltip("Group B");
                }

                ImGui::Unindent(); ImGui::Unindent();
            }

            ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing();
            ImGui::Button("Group A button 2");
            if (doGroupA) ImGui::EndGroup();

            if (doGroupA && ImGui::IsItemHovered())
            {
                ImGui::SetTooltip("Group A");
            }
        }

        ImGui::End();

Should I just be using BeginChild()/EndChild() here? The thing I like about groups is that it just becomes the size of the space taken up by widgets between the begin/end calls. BeginChild() I have to specify a size ahead of time. Am I missing something?

The other thing I was wondering is if there's some easy way for me to work in clicking and dragging the group? What I want to do is basically treat the entire region encompassed by the group as something I can click and drag, but currently if you begin click and dragging in a non-widget area of the window, it just starts performing the window moving logic. Basically, I want to disable the window moving logic temporarily... or somehow let the group itself be an interactable "widget" as well.

-Dale Kim

Hi Omar! I was working on a GUI feature on my game where we could take array elements and click and drag them to reorder. I ran into a case where each array element was another array and the logic I was using involved groups to help detect the whole area used up by an element (rather than just each component widget). The problem I ran into was that I couldn't determine correctly the nested group hovering. It would return to me the "top level" group as being hovered, despite the nested group being just finished. Here's a test case: ``` if (ImGui::Begin("Nested groups")) { static bool doGroupA = true; static bool doGroupB = true; ImGui::Checkbox("Do Group A", &doGroupA); ImGui::Checkbox("Do Group B", &doGroupB); if (doGroupA) ImGui::BeginGroup(); ImGui::Button("Group A button1"); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); { ImGui::Indent(); ImGui::Indent(); if (doGroupB) ImGui::BeginGroup(); ImGui::Button("Group B button 1"); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Button("Group B button 2"); if (doGroupB) ImGui::EndGroup(); if (doGroupB && ImGui::IsItemHovered()) { ImGui::SetTooltip("Group B"); } ImGui::Unindent(); ImGui::Unindent(); } ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); ImGui::Button("Group A button 2"); if (doGroupA) ImGui::EndGroup(); if (doGroupA && ImGui::IsItemHovered()) { ImGui::SetTooltip("Group A"); } } ImGui::End(); ``` Should I just be using BeginChild()/EndChild() here? The thing I like about groups is that it just becomes the size of the space taken up by widgets between the begin/end calls. BeginChild() I have to specify a size ahead of time. Am I missing something? The other thing I was wondering is if there's some easy way for me to work in clicking and dragging the group? What I want to do is basically treat the entire region encompassed by the group as something I can click and drag, but currently if you begin click and dragging in a non-widget area of the window, it just starts performing the window moving logic. Basically, I want to disable the window moving logic temporarily... or somehow let the group itself be an interactable "widget" as well. -Dale Kim
ocornut commented 2016-01-09 23:24:06 +00:00 (Migrated from github.com)

Hi Dale,

Both groups would return true to IsItemHovered(). SetTooltip() overwrite existing tooltip if any so I am seeing "Group A" in this repro but if I comment out the SetTooltip("Group A") - or modify the feedback - it shows that "Group B" is hovered as well.

So based on this I believe the code in ImGui is correct? And it may be a case of re-organizing your code knowing that to consider the nested-most item only.

Some ideas toward your later question, you could possibly create an invisible button at the END of each group so the empty area can be caught. Or, you can create an invisible button at the beginning to cover the entire window and/or known group size, record the active/hovered state then call SetItemAllowOverlap() to also further visible items to be used. Bit awkward but it should work. If you use that sort of pattern and have an idea how they could be simplified let me know :)

Hi Dale, Both groups would return true to IsItemHovered(). SetTooltip() overwrite existing tooltip if any so I am seeing "Group A" in this repro but if I comment out the SetTooltip("Group A") - or modify the feedback - it shows that "Group B" is hovered as well. So based on this I believe the code in ImGui is correct? And it may be a case of re-organizing your code knowing that to consider the nested-most item only. Some ideas toward your later question, you could possibly create an invisible button at the END of each group so the empty area can be caught. Or, you can create an invisible button at the beginning to cover the entire window and/or known group size, record the active/hovered state then call SetItemAllowOverlap() to also further visible items to be used. Bit awkward but it should work. If you use that sort of pattern and have an idea how they could be simplified let me know :)
unpacklo commented 2016-01-10 00:03:01 +00:00 (Migrated from github.com)

Ah yes, you're correct on that! My expectations of IsItemHovered() was wrong. The query I want to be making is something along the lines of: "What item is the mouse currently hovering over?" and "If that item is the last item" do something. I'll try rearranging or modifying my code to detect the nested case.

With regard to the invisible button, I'll try that out and let you know how it goes! In fact, this might nullify my need to do this IsItemHovered() + groups hack at all...

Ah yes, you're correct on that! My expectations of IsItemHovered() was wrong. The query I want to be making is something along the lines of: "What item is the mouse currently hovering over?" and "If that item is the last item" do something. I'll try rearranging or modifying my code to detect the nested case. With regard to the invisible button, I'll try that out and let you know how it goes! In fact, this might nullify my need to do this IsItemHovered() + groups hack at all...
unpacklo commented 2016-01-27 04:37:16 +00:00 (Migrated from github.com)

Sorry, I totally forgot about this issue!

I found a good workaround for this by using your suggestion of an invisible button at the end of the "group":

https://www.youtube.com/watch?v=uWEOWp0zE_g&feature=youtu.be
https://www.youtube.com/watch?v=ice2oLxIqa8&feature=youtu.be

The code looks like this:

for (int i = 0; i < fa->size; ++i)
{
    ImVec2 cursorBegin = ImGui::GetCursorPos();
    ImGui::PushID(i);
    ImGui::Text("%s[%d]", faLabel, i); ImGui::SameLine();

    if (ImGui::Button("Remove"))
    {
        toRemove = i;
    }

    ImGui::SameLine();
    ImGuiDirtySetter(resource, ImGuiResourceSelectButton(gui, pack, (void **)(fa->array + i), "", types, numTypes));

    ImVec2 cursorEnd = ImGui::GetCursorPos();
    float invisWidth = ImGui::GetContentRegionAvailWidth();
    ImGui::SetCursorPos(cursorBegin);
    ImGui::InvisibleButton("drag area", ImVec2(ImGui::GetContentRegionAvailWidth(), cursorEnd.y - cursorBegin.y));

    if (ImGui::IsMouseClicked(0) && ImGui::IsItemActive())
    {
        ImGuiFixedArrayBeginDragReorder(gui, fa, i);
    }

    if (ImGui::IsItemHoveredRect())
    {
        itemHovered = i;
    }

    ImGui::SetCursorPos(cursorEnd);
    ImGui::Separator();

    ImGui::PopID();
}

Excuse the game specific code here, but it's very simple. Just remember the cursor position before doing the GUI elements. After GUI is done, find the current cursor position so you can create a button of the proper height. Reset cursor to the starting position before the GUI elements and create the button!

Everything falls nicely into place; input events are sent directly to the intended buttons when interacting with the normal GUI portion. The only thing that's iffy here is that it isn't obvious that you can drag things around or what the region actually is, but for us it's not a big deal. It's just understood that the label is draggable.

-Dale Kim

Sorry, I totally forgot about this issue! I found a good workaround for this by using your suggestion of an invisible button at the end of the "group": https://www.youtube.com/watch?v=uWEOWp0zE_g&feature=youtu.be https://www.youtube.com/watch?v=ice2oLxIqa8&feature=youtu.be The code looks like this: ``` for (int i = 0; i < fa->size; ++i) { ImVec2 cursorBegin = ImGui::GetCursorPos(); ImGui::PushID(i); ImGui::Text("%s[%d]", faLabel, i); ImGui::SameLine(); if (ImGui::Button("Remove")) { toRemove = i; } ImGui::SameLine(); ImGuiDirtySetter(resource, ImGuiResourceSelectButton(gui, pack, (void **)(fa->array + i), "", types, numTypes)); ImVec2 cursorEnd = ImGui::GetCursorPos(); float invisWidth = ImGui::GetContentRegionAvailWidth(); ImGui::SetCursorPos(cursorBegin); ImGui::InvisibleButton("drag area", ImVec2(ImGui::GetContentRegionAvailWidth(), cursorEnd.y - cursorBegin.y)); if (ImGui::IsMouseClicked(0) && ImGui::IsItemActive()) { ImGuiFixedArrayBeginDragReorder(gui, fa, i); } if (ImGui::IsItemHoveredRect()) { itemHovered = i; } ImGui::SetCursorPos(cursorEnd); ImGui::Separator(); ImGui::PopID(); } ``` Excuse the game specific code here, but it's very simple. Just remember the cursor position before doing the GUI elements. After GUI is done, find the current cursor position so you can create a button of the proper height. Reset cursor to the starting position before the GUI elements and create the button! Everything falls nicely into place; input events are sent directly to the intended buttons when interacting with the normal GUI portion. The only thing that's iffy here is that it isn't obvious that you can drag things around or what the region actually is, but for us it's not a big deal. It's just understood that the label is draggable. -Dale Kim
ocornut commented 2016-01-28 10:01:38 +00:00 (Migrated from github.com)

Good to hear!
(video with "visible" buttons looking very odd obviously)

You could display a rectangle outline or fill around the entire zone when hovered.
If you can bother using the ImDrawList::ChannelSplit api so you also draw out of the order if this is easier to your code and you want a filled rectangle, so you could possibly draw the background after the fact.

Good to hear! (video with "visible" buttons looking very odd obviously) You could display a rectangle outline or fill around the entire zone when hovered. If you can bother using the ImDrawList::ChannelSplit api so you also draw out of the order if this is easier to your code and you want a filled rectangle, so you could possibly draw the background after the fact.
ocornut commented 2016-04-03 13:49:01 +00:00 (Migrated from github.com)

Closing this afaik this is a closed topic?

Closing this afaik this is a closed topic?
unpacklo commented 2016-04-03 21:00:00 +00:00 (Migrated from github.com)

Yep, closed! Thanks Omar!

On Sunday, April 3, 2016, omar notifications@github.com wrote:

Closed #479 https://github.com/ocornut/imgui/issues/479.

—
You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub
https://github.com/ocornut/imgui/issues/479#event-612707995

Yep, closed! Thanks Omar! On Sunday, April 3, 2016, omar notifications@github.com wrote: > Closed #479 https://github.com/ocornut/imgui/issues/479. > > — > You are receiving this because you authored the thread. > Reply to this email directly or view it on GitHub > https://github.com/ocornut/imgui/issues/479#event-612707995
Sign in to join this conversation.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ocornut/imgui#479
No description provided.