From 564ff2dfd379d40568879a5bc89e8cfea7e51d2f Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 20 Oct 2017 17:52:22 +0200 Subject: [PATCH] IsItemHovered(), IsWindowHovered(): added flags to enable various and more specific behavior. Will enable improvements for popups/context menus and drag'n drop. (relate ~#439, #1013, #143, #925) The legacy confusing IsItemRectHovered(), IsWindowRectHovered() can be completely removed now. Changed IsWindowHovered() behavior with default parameter: it now return false is the window is blocked by a popup. Demo: Added tests for those two functions. --- imgui.cpp | 51 +++++++++++++++++++++++++------------------------- imgui.h | 23 ++++++++++++++++------- imgui_demo.cpp | 28 +++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 33 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 463065033854..efb234f0fb53 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -216,11 +216,12 @@ Here is a change-log of API breaking changes, if you are using one of the functions listed, expect to have to fix some code. Also read releases logs https://github.com/ocornut/imgui/releases for more details. + - 2017/10/20 (1.52) - removed the IsItemRectHovered()/IsWindowRectHovered() names introduced in 1.51, the new flags available for IsItemHovered() and IsWindowHovered() are providing much more details/options, and those legacy entry points were confusing/misleading. - 2017/10/17 (1.52) - marked the old 5-parameters version of Begin() as obsolete (still available). Use SetNextWindowSize()+Begin() instead! - 2017/10/11 (1.52) - renamed AlignFirstTextHeightToWidgets() to AlignTextToFramePadding(). Kept inline redirection function (will obsolete). - 2017/09/25 (1.52) - removed SetNextWindowPosCenter() because SetNextWindowPos() now has the optional pivot information to do the same and more. Kept redirection function (will obsolete). - 2017/08/25 (1.52) - io.MousePos needs to be set to ImVec2(-FLT_MAX,-FLT_MAX) when mouse is unavailable/missing. Previously ImVec2(-1,-1) was enough but we now accept negative mouse coordinates. In your binding if you need to support unavailable mouse, make sure to replace "io.MousePos = ImVec2(-1,-1)" with "io.MousePos = ImVec2(-FLT_MAX,-FLT_MAX)". - - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). + - 2017/08/22 (1.51) - renamed IsItemHoveredRect() to IsItemRectHovered(). Kept inline redirection function (will obsolete). -> (1.52) use IsItemHovered(ImGuiHoveredFlags_RectOnly)! - renamed IsMouseHoveringAnyWindow() to IsAnyWindowHovered() for consistency. Kept inline redirection function (will obsolete). - renamed IsMouseHoveringWindow() to IsWindowRectHovered() for consistency. Kept inline redirection function (will obsolete). - 2017/08/20 (1.51) - renamed GetStyleColName() to GetStyleColorName() for consistency. @@ -1944,15 +1945,22 @@ void ImGui::KeepAliveID(ImGuiID id) g.ActiveIdIsAlive = true; } -static inline bool IsWindowContentHoverable(ImGuiWindow* window) +static inline bool IsWindowContentHoverable(ImGuiWindow* window, ImGuiHoveredFlags flags) { // An active popup disable hovering on other windows (apart from its own children) // FIXME-OPT: This could be cached/stored within the window. ImGuiContext& g = *GImGui; if (g.NavWindow) if (ImGuiWindow* focused_root_window = g.NavWindow->RootWindow) - if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) != 0 && focused_root_window->WasActive && focused_root_window != window->RootWindow) - return false; + if (focused_root_window->WasActive && focused_root_window != window->RootWindow) + { + // For the purpose of those flags we differentiate "standard popup" from "modal popup" + // NB: The order of those two tests is important because Modal windows are also Popups. + if (focused_root_window->Flags & ImGuiWindowFlags_Modal) + return false; + if ((focused_root_window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiHoveredFlags_AllowWhenBlockedByPopup)) + return false; + } return true; } @@ -2012,7 +2020,7 @@ bool ImGui::ItemAdd(const ImRect& bb, ImGuiID id) // This is roughly matching the behavior of internal-facing ItemHoverable() // - we allow hovering to be true when ActiveId==window->MoveID, so that clicking on non-interactive items such as a Text() item still returns true with IsItemHovered()) // - this should work even for non-interactive items that have no ID, so we cannot use LastItemId -bool ImGui::IsItemHovered() +bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) { ImGuiContext& g = *GImGui; @@ -2023,21 +2031,16 @@ bool ImGui::IsItemHovered() // Until a solution is found I believe reverting to the test from 2017/09/27 is safe since this was the test that has been running for a long while. //if (g.HoveredWindow != window) // return false; - if (g.HoveredRootWindow != window->RootWindow) - return false; - if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) + if (g.HoveredRootWindow != window->RootWindow && !(flags & ImGuiHoveredFlags_AllowWhenOverlapped)) return false; - if (!IsWindowContentHoverable(window)) + if (!(flags & ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) + if (g.ActiveId != 0 && g.ActiveId != window->DC.LastItemId && !g.ActiveIdAllowOverlap && g.ActiveId != window->MoveId) + return false; + if (!IsWindowContentHoverable(window, flags)) return false; return true; } -bool ImGui::IsItemRectHovered() -{ - ImGuiWindow* window = GetCurrentWindowRead(); - return window->DC.LastItemRectHoveredRect; -} - // Internal facing ItemHoverable() used when submitting widgets. Differs slightly from IsItemHovered(). bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) { @@ -2052,7 +2055,7 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) return false; if (!IsMouseHoveringRect(bb.Min, bb.Max)) return false; - if (!IsWindowContentHoverable(window)) + if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_Default)) return false; SetHoveredID(id); @@ -5086,16 +5089,11 @@ const char* ImGui::GetStyleColorName(ImGuiCol idx) return "Unknown"; } -bool ImGui::IsWindowHovered() -{ - ImGuiContext& g = *GImGui; - return g.HoveredWindow == g.CurrentWindow && IsWindowContentHoverable(g.HoveredRootWindow); -} - -bool ImGui::IsWindowRectHovered() +bool ImGui::IsWindowHovered(ImGuiHoveredFlags flags) { + IM_ASSERT((flags & (ImGuiHoveredFlags_AllowWhenOverlapped | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) == 0); // Flags not supported by this function ImGuiContext& g = *GImGui; - return g.HoveredWindow == g.CurrentWindow; + return g.HoveredWindow == g.CurrentWindow && IsWindowContentHoverable(g.HoveredRootWindow, flags); } bool ImGui::IsWindowFocused() @@ -5116,10 +5114,11 @@ bool ImGui::IsRootWindowOrAnyChildFocused() return g.NavWindow && g.NavWindow->RootWindow == g.CurrentWindow->RootWindow; } -bool ImGui::IsRootWindowOrAnyChildHovered() +bool ImGui::IsRootWindowOrAnyChildHovered(ImGuiHoveredFlags flags) { + IM_ASSERT((flags & (ImGuiHoveredFlags_AllowWhenOverlapped | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) == 0); // Flags not supported by this function ImGuiContext& g = *GImGui; - return g.HoveredRootWindow && (g.HoveredRootWindow == g.CurrentWindow->RootWindow) && IsWindowContentHoverable(g.HoveredRootWindow); + return g.HoveredRootWindow && (g.HoveredRootWindow == g.CurrentWindow->RootWindow) && IsWindowContentHoverable(g.HoveredRootWindow, flags); } float ImGui::GetWindowWidth() diff --git a/imgui.h b/imgui.h index 4771175e5177..3b72401d9e53 100644 --- a/imgui.h +++ b/imgui.h @@ -80,6 +80,7 @@ typedef int ImGuiColumnsFlags; // flags: for *Columns*() typedef int ImGuiInputTextFlags; // flags: for InputText*() // enum ImGuiInputTextFlags_ typedef int ImGuiSelectableFlags; // flags: for Selectable() // enum ImGuiSelectableFlags_ typedef int ImGuiTreeNodeFlags; // flags: for TreeNode*(), Collapsing*() // enum ImGuiTreeNodeFlags_ +typedef int ImGuiHoveredFlags; // flags: for IsItemHovered() // enum ImGuiHoveredFlags_ typedef int (*ImGuiTextEditCallback)(ImGuiTextEditCallbackData *data); typedef void (*ImGuiSizeConstraintCallback)(ImGuiSizeConstraintCallbackData* data); #ifdef _MSC_VER @@ -414,8 +415,7 @@ namespace ImGui IMGUI_API void StyleColorsClassic(ImGuiStyle* dst = NULL); // Utilities - IMGUI_API bool IsItemHovered(); // is the last item hovered by mouse (and usable)? - IMGUI_API bool IsItemRectHovered(); // is the last item hovered by mouse? even if another item is active or window is blocked by popup while we are hovering this + IMGUI_API bool IsItemHovered(ImGuiHoveredFlags flags = 0); // is the last item hovered by mouse (and usable)? IMGUI_API bool IsItemActive(); // is the last item active? (e.g. button being held, text field being edited- items that don't interact will always return false) IMGUI_API bool IsItemClicked(int mouse_button = 0); // is the last item clicked? (e.g. button/node just clicked on) IMGUI_API bool IsItemVisible(); // is the last item visible? (aka not out of sight due to clipping/scrolling.) @@ -426,11 +426,10 @@ namespace ImGui IMGUI_API ImVec2 GetItemRectSize(); // " IMGUI_API void SetItemAllowOverlap(); // allow last item to be overlapped by a subsequent item. sometimes useful with invisible buttons, selectables, etc. to catch unused area. IMGUI_API bool IsWindowFocused(); // is current window focused - IMGUI_API bool IsWindowHovered(); // is current window hovered and hoverable (not blocked by a popup) (differentiate child windows from each others) - IMGUI_API bool IsWindowRectHovered(); // is current window rectangle hovered, disregarding of any consideration of being blocked by a popup. (unlike IsWindowHovered() this will return true even if the window is blocked because of a popup) + IMGUI_API bool IsWindowHovered(ImGuiHoveredFlags flags = 0); // is current window hovered (and typically: not blocked by a popup/modal) IMGUI_API bool IsRootWindowFocused(); // is current root window focused (root = top-most parent of a child, otherwise self) IMGUI_API bool IsRootWindowOrAnyChildFocused(); // is current root window or any of its child (including current window) focused - IMGUI_API bool IsRootWindowOrAnyChildHovered(); // is current root window or any of its child (including current window) hovered and hoverable (not blocked by a popup) + IMGUI_API bool IsRootWindowOrAnyChildHovered(ImGuiHoveredFlags flags = 0); // is current root window or any of its child (including current window) hovered and hoverable (not blocked by a popup) IMGUI_API bool IsAnyWindowHovered(); // is mouse hovering any visible window IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle (of given size, starting from cursor position) is visible / not clipped. IMGUI_API bool IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max); // test if rectangle (in screen space) is visible / not clipped. to perform coarse clipping on user's side. @@ -569,6 +568,16 @@ enum ImGuiSelectableFlags_ ImGuiSelectableFlags_AllowDoubleClick = 1 << 2 // Generate press events on double clicks too }; +enum ImGuiHoveredFlags_ +{ + ImGuiHoveredFlags_Default = 0, // Return true if directly over the item/window, not obstructed by another window, not obstructed by an active popup or modal blocking inputs under them. + ImGuiHoveredFlags_AllowWhenBlockedByPopup = 1 << 0, // Return true even if a popup window is normally blocking access to this item/window + //ImGuiHoveredFlags_AllowWhenBlockedByModal = 1 << 1, // Return true even if a modal popup window is normally blocking access to this item/window. FIXME-TODO: Unavailable yet. + ImGuiHoveredFlags_AllowWhenBlockedByActiveItem = 1 << 2, // Return true even if an active item is blocking access to this item/window + ImGuiHoveredFlags_AllowWhenOverlapped = 1 << 3, // Return true even if the position is overlapped by another window + ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped +}; + // User fill ImGuiIO.KeyMap[] array with indices into the ImGuiIO.KeysDown[512] array enum ImGuiKey_ { @@ -881,10 +890,10 @@ namespace ImGui bool Begin(const char* name, bool* p_open, const ImVec2& size_on_first_use, float bg_alpha_override = -1.0f, ImGuiWindowFlags flags = 0); // OBSOLETE 1.52+. use SetNextWindowSize() instead if you want to set a window size. static inline void AlignFirstTextHeightToWidgets() { AlignTextToFramePadding(); } // OBSOLETE 1.52+ static inline void SetNextWindowPosCenter(ImGuiCond cond = 0) { SetNextWindowPos(ImVec2(GetIO().DisplaySize.x * 0.5f, GetIO().DisplaySize.y * 0.5f), cond, ImVec2(0.5f, 0.5f)); } // OBSOLETE 1.52+ - static inline bool IsItemHoveredRect() { return IsItemRectHovered(); } // OBSOLETE 1.51+ + static inline bool IsItemHoveredRect() { return IsItemHovered(ImGuiHoveredFlags_RectOnly); } // OBSOLETE 1.51+ static inline bool IsPosHoveringAnyWindow(const ImVec2&) { IM_ASSERT(0); return false; } // OBSOLETE 1.51+. This was partly broken. You probably wanted to use ImGui::GetIO().WantCaptureMouse instead. static inline bool IsMouseHoveringAnyWindow() { return IsAnyWindowHovered(); } // OBSOLETE 1.51+ - static inline bool IsMouseHoveringWindow() { return IsWindowRectHovered(); } // OBSOLETE 1.51+ + static inline bool IsMouseHoveringWindow() { return IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem); } // OBSOLETE 1.51+ static inline bool CollapsingHeader(const char* label, const char* str_id, bool framed = true, bool default_open = false) { (void)str_id; (void)framed; ImGuiTreeNodeFlags default_open_flags = 1 << 5; return CollapsingHeader(label, (default_open ? default_open_flags : 0)); } // OBSOLETE 1.49+ static inline ImFont* GetWindowFont() { return GetFont(); } // OBSOLETE 1.48+ static inline float GetWindowFontSize() { return GetFontSize(); } // OBSOLETE 1.48+ diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 7fafd9ef2551..d923dffebfc3 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1763,6 +1763,34 @@ void ImGui::ShowTestWindow(bool* p_open) ImGui::TreePop(); } + if (ImGui::TreeNode("Hovering")) + { + // Testing IsWindowHovered() function + ImGui::BulletText( + "IsWindowHovered() = %d\n" + "IsWindowHovered(_AllowWhenBlockedByPopup) = %d\n", + //"IsWindowHovered(_AllowWhenBlockedByActiveItem) = %d\n", + ImGui::IsWindowHovered(), + ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup)); + //ImGui::IsWindowHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)); + + // Testing IsItemHovered() function (because BulletText is an item itself and that would affect the output of IsItemHovered, we pass all lines in a single items to shorten the code) + ImGui::Button("ITEM"); + ImGui::BulletText( + "IsItemHovered() = %d\n" + "IsItemHovered(_AllowWhenBlockedByPopup) = %d\n" + "IsItemHovered(_AllowWhenBlockedByActiveItem) = %d\n" + "IsItemHovered(_AllowWhenOverlapped) = %d\n" + "IsItemhovered(_RectOnly) = %d\n", + ImGui::IsItemHovered(), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByPopup), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem), + ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenOverlapped), + ImGui::IsItemHovered(ImGuiHoveredFlags_RectOnly)); + + ImGui::TreePop(); + } + if (ImGui::TreeNode("Dragging")) { ImGui::TextWrapped("You can use ImGui::GetMouseDragDelta(0) to query for the dragged amount on any widget.");