From 43df7eb5ea1fe744e2bcb4aa24c3f2df28388321 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 20 Oct 2017 18:56:31 +0200 Subject: [PATCH 1/4] Added OpenPopupOnItemClick(); helper which mimic BeginPopupContextItem() but doesn't do the open. Made the color popups uses this standard function so they handle reopening a context menu. --- imgui.cpp | 51 +++++++++++++++++++++++++++++---------------------- imgui.h | 3 ++- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 385b7d22..189fa669 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3446,7 +3446,7 @@ bool ImGui::IsItemActive() bool ImGui::IsItemClicked(int mouse_button) { - return IsMouseClicked(mouse_button) && IsItemHovered(); + return IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_Default); } bool ImGui::IsAnyItemHovered() @@ -3746,22 +3746,29 @@ void ImGui::EndPopup() PopStyleVar(); } +bool ImGui::OpenPopupOnItemClick(const char* str_id, int mouse_button) +{ + ImGuiWindow* window = GImGui->CurrentWindow; + if (IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + { + ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! + IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) + OpenPopupEx(id, true); + return true; + } + return false; +} + // This is a helper to handle the simplest case of associating one named popup to one given widget. -// 1. If you have many possible popups (for different "instances" of a same widget, or for wholly different widgets), you may be better off handling -// this yourself so you can store data relative to the widget that opened the popup instead of choosing different popup identifiers. -// 2. If you want right-clicking on the same item to reopen the popup at new location, use the same code replacing IsItemHovered() with IsItemRectHovered() -// and passing true to the OpenPopupEx(). -// This is because hovering an item in a window below the popup won't work. IsItemRectHovered() skips this test. -// The pattern of ignoring the fact that the item can be interacted with (because it is blocked by the active popup) may useful in some situation -// when e.g. large canvas where the content of menu driven by click position. +// You may want to handle this on user side if you have specific needs (e.g. tweaking IsItemHovered() parameters). +// You can pass a NULL str_id to use the identifier of the last item. bool ImGui::BeginPopupContextItem(const char* str_id, int mouse_button) { ImGuiWindow* window = GImGui->CurrentWindow; ImGuiID id = str_id ? window->GetID(str_id) : window->DC.LastItemId; // If user hasn't passed an ID, we can use the LastItemID. Using LastItemID as a Popup ID won't conflict! IM_ASSERT(id != 0); // However, you cannot pass a NULL str_id if the last item has no identifier (e.g. a Text() item) - if (IsMouseClicked(mouse_button)) - if (IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) - OpenPopupEx(id, true); + if (IsMouseClicked(mouse_button) && IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + OpenPopupEx(id, true); return BeginPopupEx(id, ImGuiWindowFlags_ShowBorders | ImGuiWindowFlags_AlwaysAutoResize); } @@ -9575,8 +9582,8 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag value_changed |= value_changed_as_float |= DragFloat(ids[n], &f[n], 1.0f/255.0f, 0.0f, hdr ? 0.0f : 1.0f, fmt_table_float[fmt_idx][n]); else value_changed |= DragInt(ids[n], &i[n], 1.0f, 0, hdr ? 0 : 255, fmt_table_int[fmt_idx][n]); - if (!(flags & ImGuiColorEditFlags_NoOptions) && IsItemHovered() && IsMouseClicked(1)) - OpenPopup("context"); + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); } PopItemWidth(); PopItemWidth(); @@ -9602,8 +9609,8 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag else sscanf(p, "%02X%02X%02X", (unsigned int*)&i[0], (unsigned int*)&i[1], (unsigned int*)&i[2]); } - if (!(flags & ImGuiColorEditFlags_NoOptions) && IsItemHovered() && IsMouseClicked(1)) - OpenPopup("context"); + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); PopItemWidth(); } @@ -9624,9 +9631,9 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag SetNextWindowPos(window->DC.LastItemRect.GetBL() + ImVec2(-1,style.ItemSpacing.y)); } } - if (!(flags & ImGuiColorEditFlags_NoOptions) && IsItemHovered() && IsMouseClicked(1)) - OpenPopup("context"); - + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); + if (BeginPopup("picker")) { picker_active = true; @@ -9791,8 +9798,8 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl value_changed = value_changed_sv = true; } } - if (!(flags & ImGuiColorEditFlags_NoOptions) && IsItemHovered() && IsMouseClicked(1)) - OpenPopup("context"); + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); } else if (flags & ImGuiColorEditFlags_PickerHueBar) { @@ -9804,8 +9811,8 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size-1)); value_changed = value_changed_sv = true; } - if (!(flags & ImGuiColorEditFlags_NoOptions) && IsItemHovered() && IsMouseClicked(1)) - OpenPopup("context"); + if (!(flags & ImGuiColorEditFlags_NoOptions)) + OpenPopupOnItemClick("context"); // Hue bar logic SetCursorScreenPos(ImVec2(bar0_pos_x, picker_pos.y)); diff --git a/imgui.h b/imgui.h index 3b72401d..e281b4f1 100644 --- a/imgui.h +++ b/imgui.h @@ -390,11 +390,12 @@ namespace ImGui // Popups IMGUI_API void OpenPopup(const char* str_id); // call to mark popup as open (don't call every frame!). popups are closed when user click outside, or if CloseCurrentPopup() is called within a BeginPopup()/EndPopup() block. By default, Selectable()/MenuItem() are calling CloseCurrentPopup(). Popup identifiers are relative to the current ID-stack (so OpenPopup and BeginPopup needs to be at the same level). + IMGUI_API bool OpenPopupOnItemClick(const char* str_id = NULL, int mouse_button = 1); // helper to open popup when clicked on last item. return true when just opened. IMGUI_API bool BeginPopup(const char* str_id); // return true if the popup is open, and you can start outputting to it. only call EndPopup() if BeginPopup() returned true! IMGUI_API bool BeginPopupModal(const char* name, bool* p_open = NULL, ImGuiWindowFlags extra_flags = 0); // modal dialog (block interactions behind the modal window, can't close the modal window by clicking outside) IMGUI_API bool BeginPopupContextItem(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked on last item. if you can pass a NULL str_id only if the previous item had an id. If you want to use that on a non-interactive item such as Text() you need to pass in an explicit ID here. read comments in .cpp! IMGUI_API bool BeginPopupContextWindow(const char* str_id = NULL, int mouse_button = 1, bool also_over_items = true); // helper to open and begin popup when clicked on current window. - IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked in void (no window). + IMGUI_API bool BeginPopupContextVoid(const char* str_id = NULL, int mouse_button = 1); // helper to open and begin popup when clicked in void (where there are no imgui windows). IMGUI_API void EndPopup(); IMGUI_API bool IsPopupOpen(const char* str_id); // return true if the popup is open IMGUI_API void CloseCurrentPopup(); // close the popup we have begin-ed into. clicking on a MenuItem or Selectable automatically close the current popup. From 59a89774d79ee5d39c38a0f0721702e2322e7bf5 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 20 Oct 2017 19:24:12 +0200 Subject: [PATCH 2/4] Popups: fixed CloseInactivePopups() so that right-clicking to close one level of popups in a popups stack won't close the whole stack. This is done by properly refocusing the lower level popup. Fixes 87ae40843c0b5c526edd9cdb62b072cd0bcc49ff (~#439) --- imgui.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 189fa669..d40bacde 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2802,9 +2802,7 @@ void ImGui::EndFrame() // With right mouse button we close popups without changing focus // (The left mouse button path calls FocusWindow which will lead NewFrame->CloseInactivePopups to trigger) if (g.IO.MouseClicked[1]) - { CloseInactivePopups(g.HoveredWindow); - } } } @@ -3605,8 +3603,8 @@ static void CloseInactivePopups(ImGuiWindow* ref_window) break; } } - if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a useful breakpoint on the line below - g.OpenPopupStack.resize(n); + if (n < g.OpenPopupStack.Size) // This test is not required but it allows to set a convenient breakpoint on the block below + ClosePopupToLevel(n); } static ImGuiWindow* GetFrontMostModalRootWindow() From aca23fd3f0eb1b6fb109840f5fc942c49312d096 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 20 Oct 2017 19:25:34 +0200 Subject: [PATCH 3/4] Popups: similarly to previous commit, we fix reopening a popup within a popup stack from truncating the whole stack. This is done by properly refocusing the lower level popup. (~#439) --- imgui.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/imgui.cpp b/imgui.cpp index d40bacde..8849021f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3567,6 +3567,11 @@ void ImGui::OpenPopupEx(ImGuiID id, bool reopen_existing) { g.OpenPopupStack.resize(current_stack_size+1); g.OpenPopupStack[current_stack_size] = popup_ref; + + // When reopening a popup we first refocus its parent, otherwise if its parent is itself a popup it would get closed by CloseInactivePopups(). + // This is equivalent to what ClosePopupToLevel() does. + if (g.OpenPopupStack[current_stack_size].PopupId == id) + FocusWindow(window); } } From 6ab737a4bb288d553f4de3e3841e393ef96d32a0 Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 20 Oct 2017 20:31:01 +0200 Subject: [PATCH 4/4] Popups: Fixed right-click to close popups not handling modal windows properly. (~#439) --- imgui.cpp | 27 ++++++++++++++++++++++----- imgui_demo.cpp | 2 ++ 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 8849021f..b1593c26 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2802,7 +2802,23 @@ void ImGui::EndFrame() // With right mouse button we close popups without changing focus // (The left mouse button path calls FocusWindow which will lead NewFrame->CloseInactivePopups to trigger) if (g.IO.MouseClicked[1]) - CloseInactivePopups(g.HoveredWindow); + { + // Find the top-most window between HoveredWindow and the front most Modal Window. + // This is where we can trim the popup stack. + ImGuiWindow* modal = GetFrontMostModalRootWindow(); + bool hovered_window_above_modal = false; + if (modal == NULL) + hovered_window_above_modal = true; + for (int i = g.Windows.Size - 1; i >= 0 && hovered_window_above_modal == false; i--) + { + ImGuiWindow* window = g.Windows[i]; + if (window == modal) + break; + if (window == g.HoveredWindow) + hovered_window_above_modal = true; + } + CloseInactivePopups(hovered_window_above_modal ? g.HoveredWindow : modal); + } } } @@ -3588,7 +3604,7 @@ static void CloseInactivePopups(ImGuiWindow* ref_window) return; // When popups are stacked, clicking on a lower level popups puts focus back to it and close popups above it. - // Don't close our own child popup windows + // Don't close our own child popup windows. int n = 0; if (ref_window) { @@ -3601,6 +3617,7 @@ static void CloseInactivePopups(ImGuiWindow* ref_window) if (popup.Window->Flags & ImGuiWindowFlags_ChildWindow) continue; + // Trim the stack if popups are not direct descendant of the reference window (which is often the NavWindow) bool has_focus = false; for (int m = n; m < g.OpenPopupStack.Size && !has_focus; m++) has_focus = (g.OpenPopupStack[m].Window && g.OpenPopupStack[m].Window->RootWindow == ref_window->RootWindow); @@ -3616,9 +3633,9 @@ static ImGuiWindow* GetFrontMostModalRootWindow() { ImGuiContext& g = *GImGui; for (int n = g.OpenPopupStack.Size-1; n >= 0; n--) - if (ImGuiWindow* front_most_popup = g.OpenPopupStack.Data[n].Window) - if (front_most_popup->Flags & ImGuiWindowFlags_Modal) - return front_most_popup; + if (ImGuiWindow* popup = g.OpenPopupStack.Data[n].Window) + if (popup->Flags & ImGuiWindowFlags_Modal) + return popup; return NULL; } diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 242decae..404dcbdb 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1453,6 +1453,8 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::Text("Hello from Stacked The First\nUsing style.Colors[ImGuiCol_ModalWindowDarkening] for darkening."); static int item = 1; ImGui::Combo("Combo", &item, "aaaa\0bbbb\0cccc\0dddd\0eeee\0\0"); + static float color[4] = { 0.4f,0.7f,0.0f,0.5f }; + ImGui::ColorEdit4("color", color); // This is to test behavior of stacked regular popups over a modal if (ImGui::Button("Add another modal..")) ImGui::OpenPopup("Stacked 2");