IsItemHovered, Tooltips: Added ImGuiHoveredFlags_ForTooltip, ImGuiHoveredFlags_Stationary. (#1485)

Update demo accordingly.
This commit is contained in:
ocornut 2023-06-15 15:51:00 +02:00
parent d4b94bd65b
commit b3b8cbd001
5 changed files with 75 additions and 17 deletions

View File

@ -50,7 +50,12 @@ Other changes:
where user may not be callinga constructor manually. (#5856) where user may not be callinga constructor manually. (#5856)
- Modals: In the case of nested modal, made sure that focused or appearing windows are - 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) 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) 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 - 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) 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) - ColorEdit3: fixed id collision leading to an assertion. (#5707)
- IsItemHovered: Added ImGuiHoveredFlags_DelayNormal and ImGuiHoveredFlags_DelayShort flags, - IsItemHovered: Added ImGuiHoveredFlags_DelayNormal and ImGuiHoveredFlags_DelayShort flags,
allowing to introduce a shared delay for tooltip idioms. The delays are respectively 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, - 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) 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 - Tables: activating an ID (e.g. clicking button inside) column doesn't prevent columns

View File

@ -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. 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 // 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. HoverDelayShort = 0.15f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay.
HoverDelayNormal = 0.40f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). " HoverDelayNormal = 0.40f; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). "
@ -3992,12 +3993,19 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags)
delay = g.Style.HoverDelayNormal; delay = g.Style.HoverDelayNormal;
else else
delay = 0.0f; 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); 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)) if ((flags & ImGuiHoveredFlags_NoSharedDelay) && (g.HoverItemDelayIdPreviousFrame != hover_delay_id))
g.HoverItemDelayTimer = 0.0f; g.HoverItemDelayTimer = 0.0f;
g.HoverItemDelayId = hover_delay_id; 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) if (g.HoverItemDelayTimer < delay)
return false; return false;
} }
@ -4543,11 +4551,18 @@ void ImGui::NewFrame()
} }
#endif #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 // Update hover delay for IsItemHovered() with delays and tooltips
g.HoverItemDelayIdPreviousFrame = g.HoverItemDelayId; g.HoverItemDelayIdPreviousFrame = g.HoverItemDelayId;
if (g.HoverItemDelayId != 0) 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.HoverItemDelayTimer += g.IO.DeltaTime;
g.HoverItemDelayClearTimer = 0.0f; g.HoverItemDelayClearTimer = 0.0f;
g.HoverItemDelayId = 0; g.HoverItemDelayId = 0;
@ -8535,9 +8550,17 @@ static void ImGui::UpdateMouseInputs()
else else
io.MouseDelta = ImVec2(0.0f, 0.0f); 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. // 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 (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)
if (!is_stationary)
g.NavDisableMouseHover = false; g.NavDisableMouseHover = false;
io.MousePosPrev = io.MousePos; 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 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 released:"); for (int i = 0; i < count; i++) if (IsMouseReleased(i)) { SameLine(); Text("b%d", i); }
Text("Mouse wheel: %.1f", io.MouseWheel); Text("Mouse wheel: %.1f", io.MouseWheel);
Text("MouseStationaryTimer: %.2f", g.MouseStationaryTimer);
Text("Mouse source: %s", GetMouseSourceName(io.MouseSource)); Text("Mouse source: %s", GetMouseSourceName(io.MouseSource));
Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused Text("Pen Pressure: %.1f", io.PenPressure); // Note: currently unused
Unindent(); Unindent();

13
imgui.h
View File

@ -1285,10 +1285,14 @@ enum ImGuiHoveredFlags_
ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped, ImGuiHoveredFlags_RectOnly = ImGuiHoveredFlags_AllowWhenBlockedByPopup | ImGuiHoveredFlags_AllowWhenBlockedByActiveItem | ImGuiHoveredFlags_AllowWhenOverlapped,
ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows, ImGuiHoveredFlags_RootAndChildWindows = ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows,
// Mouse Hovering delays (for tooltips) // Mouse Hovering delays (e.g. for tooltips)
ImGuiHoveredFlags_DelayShort = 1 << 11, // IsItemHovered() only: Return true after style.HoverDelayShort elapsed (~0.15 sec) // - 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.
ImGuiHoveredFlags_DelayNormal = 1 << 12, // IsItemHovered() only: Return true after style.HoverDelayNormal elapsed (~0.40 sec) // - for items which main purpose is to be hovered for a tooltip, or items with low affordance, prefer no delay or shorter delay.
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) 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() // Flags for ImGui::BeginDragDropSource(), ImGui::AcceptDragDropPayload()
@ -1891,6 +1895,7 @@ struct ImGuiStyle
ImVec4 Colors[ImGuiCol_COUNT]; ImVec4 Colors[ImGuiCol_COUNT];
// Behaviors // 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 HoverDelayShort; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayShort). Usually used along with HoverStationaryDelay.
float HoverDelayNormal; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). " float HoverDelayNormal; // Delay for IsItemHovered(ImGuiHoveredFlags_DelayNormal). "

View File

@ -629,16 +629,16 @@ static void ShowDemoWindowWidgets()
{ {
// Tooltips // Tooltips
IMGUI_DEMO_MARKER("Widgets/Basic/Tooltips"); IMGUI_DEMO_MARKER("Widgets/Basic/Tooltips");
//ImGui::AlignTextToFramePadding(); ImGui::AlignTextToFramePadding();
ImGui::Text("Tooltips:"); ImGui::Text("Tooltips:");
ImGui::SameLine(); ImGui::SameLine();
ImGui::SmallButton("Basic"); ImGui::Button("Basic");
if (ImGui::IsItemHovered()) if (ImGui::IsItemHovered())
ImGui::SetTooltip("I am a tooltip"); ImGui::SetTooltip("I am a tooltip");
ImGui::SameLine(); ImGui::SameLine();
ImGui::SmallButton("Fancy"); ImGui::Button("Fancy");
if (ImGui::IsItemHovered() && ImGui::BeginTooltip()) if (ImGui::IsItemHovered() && ImGui::BeginTooltip())
{ {
ImGui::Text("I am a fancy tooltip"); ImGui::Text("I am a fancy tooltip");
@ -648,11 +648,22 @@ static void ShowDemoWindowWidgets()
ImGui::EndTooltip(); 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::SameLine();
ImGui::SmallButton("Delayed"); ImGui::Button("Delayed1");
if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal)) // With a delay if (ImGui::IsItemHovered(ImGuiHoveredFlags_ForTooltip))
ImGui::SetTooltip("I am a tooltip with a delay."); 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(); ImGui::SameLine();
HelpMarker( HelpMarker(
"Tooltip are created by using the IsItemHovered() function over any kind of item."); "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", &current, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); } if (item_type == 15){ const char* items[] = { "Apple", "Banana", "Cherry", "Kiwi" }; static int current = 1; ret = ImGui::ListBox("ITEM: ListBox", &current, items, IM_ARRAYSIZE(items), IM_ARRAYSIZE(items)); }
bool hovered_delay_none = ImGui::IsItemHovered(); 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_short = ImGui::IsItemHovered(ImGuiHoveredFlags_DelayShort);
bool hovered_delay_normal = ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal); 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. // Display the values of IsItemHovered() and other common item state functions.
// Note that the ImGuiHoveredFlags_XXX flags can be combined. // Note that the ImGuiHoveredFlags_XXX flags can be combined.
@ -2425,7 +2438,13 @@ static void ShowDemoWindowWidgets()
ImGui::GetItemRectSize().x, ImGui::GetItemRectSize().y ImGui::GetItemRectSize().x, ImGui::GetItemRectSize().y
); );
ImGui::BulletText( 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) if (item_disabled)
ImGui::EndDisabled(); ImGui::EndDisabled();

View File

@ -1963,9 +1963,12 @@ struct ImGuiContext
ImGuiID HoverItemDelayIdPreviousFrame; ImGuiID HoverItemDelayIdPreviousFrame;
float HoverItemDelayTimer; // Currently used by IsItemHovered() float HoverItemDelayTimer; // Currently used by IsItemHovered()
float HoverItemDelayClearTimer; // Currently used by IsItemHovered(): grace time before g.TooltipHoverTimer gets cleared. float HoverItemDelayClearTimer; // Currently used by IsItemHovered(): grace time before g.TooltipHoverTimer gets cleared.
ImGuiID HoverItemUnlockedStationaryId;
// Mouse state // Mouse state
ImGuiMouseCursor MouseCursor; ImGuiMouseCursor MouseCursor;
int MouseMovingFrames;
float MouseStationaryTimer;
ImVec2 MouseLastValidPos; ImVec2 MouseLastValidPos;
// Widget state // Widget state
@ -2164,10 +2167,12 @@ struct ImGuiContext
TablesTempDataStacked = 0; TablesTempDataStacked = 0;
CurrentTabBar = NULL; CurrentTabBar = NULL;
HoverItemDelayId = HoverItemDelayIdPreviousFrame = 0; HoverItemDelayId = HoverItemDelayIdPreviousFrame = HoverItemUnlockedStationaryId = 0;
HoverItemDelayTimer = HoverItemDelayClearTimer = 0.0f; HoverItemDelayTimer = HoverItemDelayClearTimer = 0.0f;
MouseCursor = ImGuiMouseCursor_Arrow; MouseCursor = ImGuiMouseCursor_Arrow;
MouseMovingFrames = 0;
MouseStationaryTimer = 0.0f;
TempInputId = 0; TempInputId = 0;
ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_; ColorEditOptions = ImGuiColorEditFlags_DefaultOptions_;