Added GetItemClickedCountWithSingleClickDelay(), io.MouseSingleClickDelay. (#8337)

This commit is contained in:
ocornut
2026-07-01 18:25:21 +02:00
parent 0edad6d097
commit 279d04f7a3
3 changed files with 80 additions and 4 deletions

View File

@@ -100,6 +100,15 @@ Other Changes:
instead of _NavEnableGamepad. (#9454, #8803, #9270) [@Clownacy]
- TreeNode:
- Fixed nav cursor rendering with rounding even though tree nodes don't have it. (#7589)
- Inputs:
- Added GetItemClickedCountWithSingleClickDelay() helper for easy disambiguation
between single-click and double-click for actions that needs single-click to do
something other than selection. (#8337)
- Returns 1 on single-click but delayed by io.MouseSingleClickDelay.
- Returns 2 on double-click, and 2+ on subsequent repeated cicks.
- Added io.MouseSingleClickDelay to configure default delayed single click delay when
using GetItemClickedCountWithSingleClickDelay() or IsMouseReleasedWithDelay(). (#8337)
Note that io.MouseSingleClickDelay is always > io.MouseDoubleClickTime.
- Style:
- Added style.MenuItemRounding, ImGuiStyleVar_MenuItemRounding. (#7589, #9375, #9453)
- Added style.SelectableRounding, ImGuiStyleVar_SelectableRounding. (#7589, #9375, #9453)

View File

@@ -1692,6 +1692,7 @@ ImGuiIO::ImGuiIO()
// Inputs Behaviors
MouseDoubleClickTime = 0.30f;
MouseDoubleClickMaxDist = 6.0f;
MouseSingleClickDelay = 0.50f;
MouseDragThreshold = 6.0f;
KeyRepeatDelay = 0.275f;
KeyRepeatRate = 0.050f;
@@ -9427,6 +9428,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE
// - IsMouseReleased()
// - IsMouseDoubleClicked()
// - GetMouseClickedCount()
// - GetItemClickedCountWithSingleClickDelay()
// - IsMouseHoveringRect() [Internal]
// - IsMouseDragPastThreshold() [Internal]
// - IsMouseDragging()
@@ -10006,6 +10008,8 @@ bool ImGui::IsMouseReleased(ImGuiMouseButton button, ImGuiID owner_id)
return g.IO.MouseReleased[button] && TestKeyOwner(MouseButtonToKey(button), owner_id); // Should be same as IsKeyReleased(MouseButtonToKey(button), owner_id)
}
// Prefer higher-level helper GetItemClickedCountWithSingleClickDelay()
// Use if you absolutely need to distinguish single-click from double-click by introducing a delay.
// Generally use with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount == 1' test.
// This is a very rarely used UI idiom, but some apps use this: e.g. MS Explorer single click on an icon to rename.
@@ -10013,8 +10017,12 @@ bool ImGui::IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay)
{
ImGuiContext& g = *GImGui;
IM_ASSERT(button >= 0 && button < IM_COUNTOF(g.IO.MouseDown));
if (IsMouseDown(button))
return false;
if (delay < 0.0f)
delay = g.IO.MouseSingleClickDelay;
const float time_since_release = (float)(g.Time - g.IO.MouseReleasedTime[button]);
return !IsMouseDown(button) && (time_since_release - g.IO.DeltaTime < delay) && (time_since_release >= delay);
return (time_since_release - g.IO.DeltaTime < delay) && (time_since_release >= delay);
}
bool ImGui::IsMouseDoubleClicked(ImGuiMouseButton button)
@@ -10038,6 +10046,62 @@ int ImGui::GetMouseClickedCount(ImGuiMouseButton button)
return g.IO.MouseClickedCount[button];
}
// FIXME: This is close to what BeginDragDropSource() is doing, maybe rework.
static ImGuiID LastItemOverlayButtonForNullId(ImGuiMouseButton mouse_button)
{
ImGuiContext& g = *GImGui;
IM_ASSERT(g.LastItemData.ID == 0);
ImGuiWindow* window = g.CurrentWindow;
ImGuiID id = window->GetIDFromRectangle(g.LastItemData.Rect);
if (g.IO.MouseClicked[mouse_button] && ImGui::ItemHoverable(g.LastItemData.Rect, id, g.LastItemData.ItemFlags))
{
ImGui::SetActiveID(id, window);
ImGui::FocusWindow(window);
}
else if (g.ActiveId == id)
{
ImGui::KeepAliveID(id);
if (!g.IO.MouseDown[mouse_button])
ImGui::ClearActiveID();
}
return id;
}
// [BETA] Building block for disambiguation between single-click and double-click.
// - Returns 1 on single-click but delayed by io.MouseSingleClickDelay (which is always > io.MouseDoubleClickTime) after mouse release.
// - Returns 2+ on double-click and subsequent repeated clicks.
// In order use that to replicate Windows Explorer's "click on label to rename after a delay",
// When the function returns 1 for a delayed single click, you can add with further tests:
// - If you want to test that the mouse position AT THE TIME of the click (before the delay): you can use the 'io.MouseClickedPos[mouse_button]' position.
// - If you want to test that the mouse position is still over the item at the end of delay: you can use '&& IsItemHovered()'.
// - If you want to test that the item was selected or the sole selection AT THE TIME of the click (before the delay): you can test for 'g.LastActiveIdWasSelected' or 'g.LastActiveIdWasSoleSelected'.
// e.g.
// int click_count = ImGui::GetItemClickedCountWithSingleClickDelay(mouse_button);
// if (click_count == 1 && ImGui::GetCurrentContext()->LastActiveIdWasSoleSelected)
// StartRename();
// if (click_count == 2)
// Launch();
int ImGui::GetItemClickedCountWithSingleClickDelay(ImGuiMouseButton mouse_button, float delay)
{
// Action: double-click and subsequent clicks
ImGuiContext& g = *GImGui;
if (g.IO.MouseClickedCount[mouse_button] >= 2 && IsItemClicked(mouse_button))
return g.IO.MouseClickedCount[mouse_button];
// Action: second click, delayed
ImGuiID id = g.LastItemData.ID;
if (id == 0)
id = LastItemOverlayButtonForNullId(mouse_button);
if (g.LastActiveId == id)
{
if (delay >= 0.0f)
delay = ImMax(delay, g.IO.MouseDoubleClickTime + 0.01f);
if (IsMouseReleasedWithDelay(mouse_button, delay) && g.IO.MouseClickedLastCount[mouse_button] == 1)
return 1;
}
return 0;
}
// Test if mouse cursor is hovering given rectangle
// NB- Rectangle is clipped by our current clip setting
// NB- Expand the rectangle to be generous on imprecise inputs systems (g.Style.TouchExtraPadding)
@@ -10985,6 +11049,7 @@ static void ImGui::ErrorCheckNewFrameSanityChecks()
IM_ASSERT(g.Style.WindowMenuButtonPosition == ImGuiDir_None || g.Style.WindowMenuButtonPosition == ImGuiDir_Left || g.Style.WindowMenuButtonPosition == ImGuiDir_Right);
IM_ASSERT(g.Style.ColorButtonPosition == ImGuiDir_Left || g.Style.ColorButtonPosition == ImGuiDir_Right);
IM_ASSERT(g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesNone || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesFull || g.Style.TreeLinesFlags == ImGuiTreeNodeFlags_DrawLinesToNodes);
IM_ASSERT(g.IO.MouseSingleClickDelay > g.IO.MouseDoubleClickTime);
// Error handling: we do not accept 100% silent recovery! Please contact me if you feel this is getting in your way.
if (g.IO.ConfigErrorRecovery)

View File

@@ -1026,6 +1026,7 @@ namespace ImGui
IMGUI_API ImVec2 GetItemRectMax(); // get lower-right bounding rectangle of the last item (screen space)
IMGUI_API ImVec2 GetItemRectSize(); // get size of last item
IMGUI_API ImGuiItemFlags GetItemFlags(); // get generic flags of last item
IMGUI_API int GetItemClickedCountWithSingleClickDelay(ImGuiMouseButton mouse_button = 0, float delay = -1.0f); // [BETA] building block for disambiguation between single-click and double-click. Returns 1 on single-click but delayed by io.MouseSingleClickDelay after mouse release. Returns 2+ on double-click or repeated clicks.
// Viewports
// - Currently represents the Platform Window created by the application which is hosting our Dear ImGui windows.
@@ -1108,7 +1109,7 @@ namespace ImGui
IMGUI_API bool IsMouseClicked(ImGuiMouseButton button, bool repeat = false); // did mouse button clicked? (went from !Down to Down). Same as GetMouseClickedCount() == 1.
IMGUI_API bool IsMouseReleased(ImGuiMouseButton button); // did mouse button released? (went from Down to !Down)
IMGUI_API bool IsMouseDoubleClicked(ImGuiMouseButton button); // did mouse button double-clicked? Same as GetMouseClickedCount() == 2. (note that a double-click will also report IsMouseClicked() == true)
IMGUI_API bool IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay); // delayed mouse release (use very sparingly!). Generally used with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount==1' test. This is a very rarely used UI idiom, but some apps use this: e.g. MS Explorer single click on an icon to rename.
IMGUI_API bool IsMouseReleasedWithDelay(ImGuiMouseButton button, float delay=-1.f);// delayed mouse release. Use sparingly. Prefer higher-level helper GetItemClickedCountWithSingleClickDelay(). Generally used with 'delay >= io.MouseDoubleClickTime' + combined with a 'io.MouseClickedLastCount==1' test.
IMGUI_API int GetMouseClickedCount(ImGuiMouseButton button); // return the number of successive mouse-clicks at the time where a click happen (otherwise 0).
IMGUI_API bool IsMouseHoveringRect(const ImVec2& r_min, const ImVec2& r_max, bool clip = true);// is mouse hovering given bounding rect (in screen space). clipped by current clipping settings, but disregarding of other consideration of focus/window ordering/popup-block.
IMGUI_API bool IsMousePosValid(const ImVec2* mouse_pos = NULL); // by convention we use (-FLT_MAX,-FLT_MAX) to denote that there is no mouse available
@@ -2451,8 +2452,9 @@ struct ImGuiIO
// Inputs Behaviors
// (other variables, ones which are expected to be tweaked within UI code, are exposed in ImGuiStyle)
float MouseDoubleClickTime; // = 0.30f // Time for a double-click, in seconds.
float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click, in pixels.
float MouseDoubleClickTime; // = 0.30f // Time for consecutive clicks to account as a double-click, in seconds.
float MouseDoubleClickMaxDist; // = 6.0f // Distance threshold to stay in to validate a double-click or multiple clicks, in pixels.
float MouseSingleClickDelay; // = 0.60f // Time for a delayed click when using GetItemClickedCountWithSingleClickDelay() or IsMouseReleasedWithDelay(), in seconds. Must be > io.MouseDoubleClickTime.
float MouseDragThreshold; // = 6.0f // Distance threshold before considering we are dragging.
float KeyRepeatDelay; // = 0.275f // When holding a key/button, time before it starts repeating, in seconds (for buttons in Repeat mode, etc.).
float KeyRepeatRate; // = 0.050f // When holding a key/button, rate at which it repeats, in seconds.