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

@@ -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)