diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index f35ed4197..952ea0ae5 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -50,7 +50,12 @@ Other changes: where user may not be callinga constructor manually. (#5856) - Modals: In the case of nested modal, made sure that focused or appearing windows are moved below the lowest blocking modal (rather than the highest one). (#4317) -- IsItemHovered: Tweaked default value of style.HoverDelayNormal from 0.30 to 0.40, +- IsItemHovered: Added ImGuiHoveredFlags_ForTooltip as a shortcut for using _Stationary + and _DelayNormal flags. (#1485) +- IsItemHovered: Added ImGuiHoveredFlags_Stationary to add a stationary test on + hovering a new item. Added style.HoverStationaryDelay (default 0.15 sec). Once the mouse + has been stationary once the state is preserved. (#1485) +- IsItemHovered: Tweaked default value style.HoverDelayNormal from 0.30 to 0.40, Tweaked default value of style.HoverDelayShort from 0.10 to 0.15. (#1485) - Tooltips: Tweak default offset for non-drag and drop tooltips so underlying items isn't covered as much. (Match offset for drag and drop tooltips) @@ -562,7 +567,7 @@ Other Changes: - ColorEdit3: fixed id collision leading to an assertion. (#5707) - IsItemHovered: Added ImGuiHoveredFlags_DelayNormal and ImGuiHoveredFlags_DelayShort flags, allowing to introduce a shared delay for tooltip idioms. The delays are respectively - io.HoverDelayNormal (default to 0.30f) and io.HoverDelayFast (default to 0.10f). (#1485) + io.HoverDelayNormal (default to 0.30f) and io.HoverDelayShort (default to 0.10f). (#1485) - IsItemHovered: Added ImGuiHoveredFlags_NoSharedDelay to disable sharing delays between items, so moving from one item to a nearby one will requires delay to elapse again. (#1485) - Tables: activating an ID (e.g. clicking button inside) column doesn't prevent columns diff --git a/imgui.cpp b/imgui.cpp index 3ff6e7ee3..cd92c1596 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1164,6 +1164,7 @@ ImGuiStyle::ImGuiStyle() CircleTessellationMaxError = 0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. // Behaviors + HoverStationaryDelay = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_Stationary). Time required to consider mouse stationary. HoverDelayShort = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay. HoverDelayNormal = 0.40f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). " @@ -3992,12 +3993,19 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) delay = g.Style.HoverDelayNormal; else delay = 0.0f; - if (delay > 0.0f) + if (delay > 0.0f || (flags & ImGuiHoveredFlags_Stationary)) { ImGuiID hover_delay_id = (g.LastItemData.ID != 0) ? g.LastItemData.ID : window->GetIDFromRectangle(g.LastItemData.Rect); if ((flags & ImGuiHoveredFlags_NoSharedDelay) && (g.HoverItemDelayIdPreviousFrame != hover_delay_id)) g.HoverItemDelayTimer = 0.0f; g.HoverItemDelayId = hover_delay_id; + + // When changing hovered item we requires a bit of stationary delay before activating hover timer, + // but once unlocked on a given item we also moving. + //if (g.HoverDelayTimer >= delay && (g.HoverDelayTimer - g.IO.DeltaTime < delay || g.MouseStationaryTimer - g.IO.DeltaTime < g.Style.HoverStationaryDelay)) { IMGUI_DEBUG_LOG("HoverDelayTimer = %f/%f, MouseStationaryTimer = %f\n", g.HoverDelayTimer, delay, g.MouseStationaryTimer); } + if ((flags & ImGuiHoveredFlags_Stationary) != 0 && g.HoverItemUnlockedStationaryId != hover_delay_id) + return false; + if (g.HoverItemDelayTimer < delay) return false; } @@ -4543,11 +4551,18 @@ void ImGui::NewFrame() } #endif + // Record when we have been stationary as this state is preserved while over same item. + // FIXME: The way this is expressed means user cannot alter HoverStationaryDelay during the frame to use varying values. + // To allow this we should store HoverItemMaxStationaryTime+ID and perform the >= check in IsItemHovered() function. + if (g.HoverItemDelayId != 0 && g.MouseStationaryTimer >= g.Style.HoverStationaryDelay) + g.HoverItemUnlockedStationaryId = g.HoverItemDelayId; + else if (g.HoverItemDelayId == 0) + g.HoverItemUnlockedStationaryId = 0; + // Update hover delay for IsItemHovered() with delays and tooltips g.HoverItemDelayIdPreviousFrame = g.HoverItemDelayId; if (g.HoverItemDelayId != 0) { - //if (g.IO.MouseDelta.x == 0.0f && g.IO.MouseDelta.y == 0.0f) // Need design/flags g.HoverItemDelayTimer += g.IO.DeltaTime; g.HoverItemDelayClearTimer = 0.0f; g.HoverItemDelayId = 0; @@ -8535,9 +8550,17 @@ static void ImGui::UpdateMouseInputs() else io.MouseDelta = ImVec2(0.0f, 0.0f); + // Update stationary timer. Only reset on 2 successive moving frames. + // FIXME: May need to expose threshold or treat touch inputs differently. + const float mouse_stationary_threshold = (io.MouseSource == ImGuiMouseSource_Mouse) ? 2.0f : 3.0f; // Slightly higher threshold for ImGuiMouseSource_TouchScreen/ImGuiMouseSource_Pen, may need rework. + g.MouseMovingFrames = (ImLengthSqr(io.MouseDelta) >= mouse_stationary_threshold * mouse_stationary_threshold) ? (g.MouseMovingFrames + 1) : 0; + if (g.MouseMovingFrames == 0) + g.MouseStationaryTimer += io.DeltaTime; + else if (g.MouseMovingFrames > 1) + g.MouseStationaryTimer = 0.0f; + // If mouse moved we re-enable mouse hovering in case it was disabled by gamepad/keyboard. In theory should use a >0.0f threshold but would need to reset in everywhere we set this to true. - const bool is_stationary = (g.IO.MouseDelta.x == 0.0f && g.IO.MouseDelta.y == 0.0f); - if (!is_stationary) + if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) g.NavDisableMouseHover = false; io.MousePosPrev = io.MousePos; @@ -13901,6 +13924,7 @@ void ImGui::ShowMetricsWindow(bool* p_open) Text("Mouse clicked:"); for (int i = 0; i < count; i++) if (IsMouseClicked(i)) { SameLine(); Text("b%d (%d)", i, io.MouseClickedCount[i]); } Text("Mouse released:"); for (int i = 0; i < count; i++) if (IsMouseReleased(i)) { SameLine(); Text("b%d", i); } Text("Mouse wheel: %.1f", io.MouseWheel); + Text("MouseStationaryTimer: %.2f", g.MouseStationaryTimer); Text("Mouse source: %s", GetMouseSourceName(io.MouseSource)); Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused Unindent(); diff --git a/imgui.h b/imgui.h index fe6b51b2c..ed4928c88 100644 --- a/imgui.h +++ b/imgui.h @@ -1285,10 +1285,14 @@ enum ImGuiHoveredFlags_ ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped, ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows, - // Mouse Hovering delays (for tooltips) - ImGuiHoveredFlags_DelayShort = 1 << 11, // IsItemHovered() only: Return true after style.HoverDelayShort elapsed (~0.15 sec) - ImGuiHoveredFlags_DelayNormal = 1 << 12, // IsItemHovered() only: Return true after style.HoverDelayNormal elapsed (~0.40 sec) - ImGuiHoveredFlags_NoSharedDelay = 1 << 13, // IsItemHovered() only: Disable shared delay system where moving from one item to the next keeps the previous timer for a short time (standard for tooltips with long delays) + // Mouse Hovering delays (e.g. for tooltips) + // - for frequently actioned or hovered items providing a tooltip, you want may to use ImGuiHoveredFlags_ForTooltip (stationary + normal delay) so the tooltip doesn't show too often. + // - for items which main purpose is to be hovered for a tooltip, or items with low affordance, prefer no delay or shorter delay. + ImGuiHoveredFlags_Stationary = 1 << 11, // IsItemHovered() only: Require mouse to be stationary for style.HoverStationaryDelay (~0.15 sec) _at least one time_. After this, can move on same item. + ImGuiHoveredFlags_DelayShort = 1 << 13, // IsItemHovered() only: Return true after style.HoverDelayShort elapsed (~0.15 sec) (shared between items) + requires mouse to be stationary for style.HoverStationaryDelay (once per item). + ImGuiHoveredFlags_DelayNormal = 1 << 14, // IsItemHovered() only: Return true after style.HoverDelayNormal elapsed (~0.40 sec) (shared between items) + requires mouse to be stationary for style.HoverStationaryDelay (once per item). + ImGuiHoveredFlags_NoSharedDelay = 1 << 15, // IsItemHovered() only: Disable shared delay system where moving from one item to the next keeps the previous timer for a short time (standard for tooltips with long delays) + ImGuiHoveredFlags_ForTooltip = ImGuiHoveredFlags_Stationary | ImGuiHoveredFlags_DelayNormal, }; // Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload() @@ -1891,6 +1895,7 @@ struct ImGuiStyle ImVec4 Colors[ImGuiCol_COUNT]; // Behaviors + float HoverStationaryDelay; // Delay for IsItemHovered(ImGuiHoveredFlags_Stationary). Time required to consider mouse stationary. float HoverDelayShort; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay. float HoverDelayNormal; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). " diff --git a/imgui_demo.cpp b/imgui_demo.cpp index aae245207..5dbfb0fc3 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -629,16 +629,16 @@ static void ShowDemoWindowWidgets() { // Tooltips IMGUI_DEMO_MARKER("Widgets/Basic/Tooltips"); - //ImGui::AlignTextToFramePadding(); + ImGui::AlignTextToFramePadding(); ImGui::Text("Tooltips:"); ImGui::SameLine(); - ImGui::SmallButton("Basic"); + ImGui::Button("Basic"); if (ImGui::IsItemHovered()) ImGui::SetTooltip("I am a tooltip"); ImGui::SameLine(); - ImGui::SmallButton("Fancy"); + ImGui::Button("Fancy"); if (ImGui::IsItemHovered() && ImGui::BeginTooltip()) { ImGui::Text("I am a fancy tooltip"); @@ -648,11 +648,22 @@ static void ShowDemoWindowWidgets() ImGui::EndTooltip(); } + // Showcase use of ImGuiHoveredFlags_ForTooltip which is an alias for ImGuiHoveredFlags_DelayNormal + ImGuiHoveredFlags_Stationary. + // - ImGuiHoveredFlags_DelayNormal requires an hovering delay (default to 0.40 sec) + // - ImGuiHoveredFlags_Stationary requires mouse to be stationary (default to 0.15 sec) at least once on a new item. + // We show two items to showcase how the main delay is by default shared between items, + // so once in "tooltip mode" moving to another tooltip only requires the stationary delay. + ImGui::SameLine(); - ImGui::SmallButton("Delayed"); - if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) // With a delay + ImGui::Button("Delayed1"); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) ImGui::SetTooltip("I am a tooltip with a delay."); + ImGui::SameLine(); + ImGui::Button("Delayed2"); + if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip)) + ImGui::SetTooltip("I am another tooltip with a delay."); + ImGui::SameLine(); HelpMarker( "Tooltip are created by using the IsItemHovered() function over any kind of item."); @@ -2377,8 +2388,10 @@ static void ShowDemoWindowWidgets() if (item_type == 15){ const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", ¤t, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); } bool hovered_delay_none = ImGui::IsItemHovered(); + bool hovered_delay_stationary = ImGui::IsItemHovered(ImGuiHoveredFlags_Stationary); bool hovered_delay_short = ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort); bool hovered_delay_normal = ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal); + bool hovered_delay_tooltip = ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip); // = Normal + Stationary // Display the values of IsItemHovered() and other common item state functions. // Note that the ImGuiHoveredFlags_XXX flags can be combined. @@ -2425,7 +2438,13 @@ static void ShowDemoWindowWidgets() ImGui::GetItemRectSize().x, ImGui::GetItemRectSize().y ); ImGui::BulletText( - "w/ Hovering Delay: None = %d, Fast %d, Normal = %d", hovered_delay_none, hovered_delay_short, hovered_delay_normal); + "with Hovering Delay or Stationary test:\n" + "IsItemHovered() = = %d\n" + "IsItemHovered(_Stationary) = %d\n" + "IsItemHovered(_DelayShort) = %d\n" + "IsItemHovered(_DelayNormal) = %d\n" + "IsItemHovered(_Tooltip) = %d", + hovered_delay_none, hovered_delay_stationary, hovered_delay_short, hovered_delay_normal, hovered_delay_tooltip); if (item_disabled) ImGui::EndDisabled(); diff --git a/imgui_internal.h b/imgui_internal.h index 1eff91150..ba834124c 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1963,9 +1963,12 @@ struct ImGuiContext ImGuiID HoverItemDelayIdPreviousFrame; float HoverItemDelayTimer; // Currently used by IsItemHovered() float HoverItemDelayClearTimer; // Currently used by IsItemHovered(): grace time before g.TooltipHoverTimer gets cleared. + ImGuiID HoverItemUnlockedStationaryId; // Mouse state ImGuiMouseCursor MouseCursor; + int MouseMovingFrames; + float MouseStationaryTimer; ImVec2 MouseLastValidPos; // Widget state @@ -2164,10 +2167,12 @@ struct ImGuiContext TablesTempDataStacked = 0; CurrentTabBar = NULL; - HoverItemDelayId = HoverItemDelayIdPreviousFrame = 0; + HoverItemDelayId = HoverItemDelayIdPreviousFrame = HoverItemUnlockedStationaryId = 0; HoverItemDelayTimer = HoverItemDelayClearTimer = 0.0f; MouseCursor = ImGuiMouseCursor_Arrow; + MouseMovingFrames = 0; + MouseStationaryTimer = 0.0f; TempInputId = 0; ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_;