You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Problem Description:
Currently, when using DragInt(), DragFloat(), or other DragScalar() based widgets, there is no built-in mechanism to easily cancel the drag operation and revert the value to its state before the drag started.
Pressing ESC often confirms the current value or closes a parent popup/window, rather than cancelling the drag.
Right-clicking during a drag operation currently has no default effect for cancelling the drag.
Motivation / Goal:
This forces developers who need this cancellation behavior (which is common in many editing applications, like Blender's property dragging) to implement manual state tracking around each DragScalar call, which adds boilerplate code. It is also inconvenient for users who accidentally start a drag or drag too far, as they have no simple way to abort other than potentially confirming an unwanted value and then manually undoing or correcting it. We need a simple, built-in way to cancel the drag and revert the value.
Proposed Solution:
I propose adding a built-in mechanism to cancel a DragScalar operation using a right-click while the drag is active (mouse button still held down).
The desired behavior would be:
User starts dragging a DragScalar widget (e.g., DragFloat).
While the left mouse button is still held down and the drag is active, the user right-clicks.
The value associated with the DragScalar widget immediately reverts to the value it had just before the drag operation started.
The drag operation is cancelled (effectively, ClearActiveID() should be called internally for the widget).
The change is not marked as an edit (no MarkItemEdited() call for the cancelled drag).
Alternatives Considered:
The current alternative is to manually implement this logic around every DragScalar call using IsItemActivated(), IsItemActive(), IsMouseClicked(ImGuiMouseButton_Right), storing the initial value, and restoring it. This works but adds significant boilerplate code, especially in interfaces with many draggable numeric fields. A built-in solution would be much cleaner and provide a consistent user experience.
Additional Context / Implementation Idea:
A possible implementation approach could involve modifying the DragScalar function internally:
Store the initial value when the drag interaction starts (SetActiveID is called for the drag).
Inside DragScalar, after DragBehavior is called, check if the widget is still active (g.ActiveId == id) and if IsMouseClicked(ImGuiMouseButton_Right) is true.
If so, restore the saved initial value, set the internal value_changed flag to false to prevent MarkItemEdited, and call ClearActiveID().
Temporary state should ideally be stored within ImGuiContext to avoid issues with multi-context setups, rather than using static variables.
This feature would significantly improve the usability of DragScalar widgets, particularly in editor-like applications.
Screenshots/Video:
Minimal, Complete and Verifiable Example code:
Here is my working example in imgui_widgets.cpp you can copy paste for testing it:
// Find the existing DragScalar function in the imgui_widgets.cpp file and replace it with the following// [MY MODIFICATION START] - Static variables for cancellation feature (Warning: Potential reentrancy/multi-context issues)
#include<imgui.h>// Added for ImGuiDataTypeStorage
#include"imgui_internal.h"// Added for ImGuiContext (to use GImGui)static ImGuiDataTypeStorage GDragScalarStartValue; // To store the drag start valuestatic ImGuiID GDragScalarActiveID = 0; // To track which DragScalar is active// [MY MODIFICATION END]// Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, p_min and p_max are optional.// Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly.boolImGui::DragScalar(constchar* label, ImGuiDataType data_type, void* p_data, float v_speed, constvoid* p_min, constvoid* p_max, constchar* format, ImGuiSliderFlags flags)
{
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
returnfalse;
ImGuiContext& g = *GImGui;
const ImGuiStyle& style = g.Style;
const ImGuiID id = window->GetID(label);
constfloat w = CalcItemWidth();
const ImVec2 label_size = CalcTextSize(label, NULL, true);
const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y + style.FramePadding.y * 2.0f));
const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? style.ItemInnerSpacing.x + label_size.x : 0.0f, 0.0f));
constbool temp_input_allowed = (flags & ImGuiSliderFlags_NoInput) == 0;
ItemSize(total_bb, style.FramePadding.y);
if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
returnfalse;
// Default format string when passing NULLif (format == NULL)
format = DataTypeGetInfo(data_type)->PrintFmt;
constbool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
if (!temp_input_is_active)
{
// Tabbing or CTRL-clicking on Drag turns it into an InputTextconstbool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
constbool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id));
constbool make_active = (clicked || double_clicked || g.NavActivateId == id);
if (make_active && (clicked || double_clicked))
SetKeyOwner(ImGuiKey_MouseLeft, id);
if (make_active && temp_input_allowed)
if ((clicked && g.IO.KeyCtrl) || double_clicked || (g.NavActivateId == id && (g.NavActivateFlags & ImGuiActivateFlags_PreferInput)))
temp_input_is_active = true;
// (Optional) simple click (without moving) turns Drag into an InputTextif (g.IO.ConfigDragClickToInputText && temp_input_allowed && !temp_input_is_active)
if (g.ActiveId == id && hovered && g.IO.MouseReleased[0] && !IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
{
g.NavActivateId = id;
g.NavActivateFlags = ImGuiActivateFlags_PreferInput;
temp_input_is_active = true;
}
// Store initial value (not used by main lib but available as a convenience but some mods e.g. to revert)// [MY MODIFICATION START] - Store initial value on activation for drag cancellationif (make_active)
{
memcpy(&g.ActiveIdValueOnActivation, p_data, DataTypeGetInfo(data_type)->Size); // Keep original backup too if needed elsewhere// Store value if activating drag (not text input)if (!temp_input_is_active)
{
memcpy(&GDragScalarStartValue, p_data, DataTypeGetInfo(data_type)->Size);
GDragScalarActiveID = id;
}
}
// [MY MODIFICATION END]if (make_active && !temp_input_is_active)
{
SetActiveID(id, window);
SetFocusID(id, window);
FocusWindow(window);
g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
}
}
if (temp_input_is_active)
{
// [MY MODIFICATION START] - Reset drag tracking if we switch to text inputif (GDragScalarActiveID == id)
GDragScalarActiveID = 0;
// [MY MODIFICATION END]// Only clamp CTRL+Click input when ImGuiSliderFlags_ClampOnInput is set (generally via ImGuiSliderFlags_AlwaysClamp)bool clamp_enabled = false;
if ((flags & ImGuiSliderFlags_ClampOnInput) && (p_min != NULL || p_max != NULL))
{
constint clamp_range_dir = (p_min != NULL && p_max != NULL) ? DataTypeCompare(data_type, p_min, p_max) : 0; // -1 when *p_min < *p_max, == 0 when *p_min == *p_maxif (p_min == NULL || p_max == NULL || clamp_range_dir < 0)
clamp_enabled = true;
elseif (clamp_range_dir == 0)
clamp_enabled = DataTypeIsZero(data_type, p_min) ? ((flags & ImGuiSliderFlags_ClampZeroRange) != 0) : true;
}
returnTempInputScalar(frame_bb, id, label, data_type, p_data, format, clamp_enabled ? p_min : NULL, clamp_enabled ? p_max : NULL);
}
// Draw frameconst ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
RenderNavCursor(frame_bb, id);
RenderFrame(frame_bb.Min, frame_bb.Max, frame_col, true, style.FrameRounding);
// Drag behaviorbool value_changed = DragBehavior(id, data_type, p_data, v_speed, p_min, p_max, format, flags);
// [MY MODIFICATION START] Right-click cancellation check// Check for cancellation *after* DragBehavior potentially modified the value this framebool cancelled = false;
if (GDragScalarActiveID == id && g.ActiveId == id) // If our specific drag scalar is active
{
// Use 'false' for the second parameter of IsMouseClicked to potentially catch click even if mouse moved slightly off widget.if (ImGui::IsMouseClicked(ImGuiMouseButton_Right, false))
{
memcpy(p_data, &GDragScalarStartValue, DataTypeGetInfo(data_type)->Size); // Restore valueClearActiveID(); // Cancel drag input processing
value_changed = false; // Ensure MarkItemEdited is not called
cancelled = true; // Flag that cancellation happened// GDragScalarActiveID will be reset below
}
}
// Reset tracking if drag naturally ends or was just cancelledif (GDragScalarActiveID == id && g.ActiveId != id)
{
GDragScalarActiveID = 0;
}
// [MY MODIFICATION END]if (value_changed) // This check now happens *after* potential cancellation overrideMarkItemEdited(id);
// Display value using user-provided display format so user can add prefix/suffix/decorations to the value.char value_buf[64];
constchar* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_data, format);
if (g.LogEnabled)
LogSetNextTextDecoration("{", "}");
RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
if (label_size.x > 0.0f)
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
return value_changed; // Return the potentially overridden value_changed
}
The text was updated successfully, but these errors were encountered:
Thanks for your suggestion.
The problem with adding right-click it that it could interfere with existing code using right-click for other things. Even though I agree it's not much likely to be used while item is active.
I will first add support for this mapped to the Escape key. I realize it's rather awkward, but this way we at least have the 99% code ready and we can more easily add right-click later if we figure out a design that can work or decide that adding right-click is fine.
Version/Branch of Dear ImGui:
Version 1.92, Branch: master/docking
Back-ends:
imgui_widgets.cpp
Compiler, OS:
All
Full config/build information:
No response
Details:
Problem Description:
Currently, when using
DragInt()
,DragFloat()
, or otherDragScalar()
based widgets, there is no built-in mechanism to easily cancel the drag operation and revert the value to its state before the drag started.ESC
often confirms the current value or closes a parent popup/window, rather than cancelling the drag.Motivation / Goal:
This forces developers who need this cancellation behavior (which is common in many editing applications, like Blender's property dragging) to implement manual state tracking around each
DragScalar
call, which adds boilerplate code. It is also inconvenient for users who accidentally start a drag or drag too far, as they have no simple way to abort other than potentially confirming an unwanted value and then manually undoing or correcting it. We need a simple, built-in way to cancel the drag and revert the value.Proposed Solution:
I propose adding a built-in mechanism to cancel a
DragScalar
operation using a right-click while the drag is active (mouse button still held down).The desired behavior would be:
DragScalar
widget (e.g.,DragFloat
).DragScalar
widget immediately reverts to the value it had just before the drag operation started.ClearActiveID()
should be called internally for the widget).MarkItemEdited()
call for the cancelled drag).Alternatives Considered:
The current alternative is to manually implement this logic around every
DragScalar
call usingIsItemActivated()
,IsItemActive()
,IsMouseClicked(ImGuiMouseButton_Right)
, storing the initial value, and restoring it. This works but adds significant boilerplate code, especially in interfaces with many draggable numeric fields. A built-in solution would be much cleaner and provide a consistent user experience.Additional Context / Implementation Idea:
A possible implementation approach could involve modifying the
DragScalar
function internally:SetActiveID
is called for the drag).DragScalar
, afterDragBehavior
is called, check if the widget is still active (g.ActiveId == id
) and ifIsMouseClicked(ImGuiMouseButton_Right)
is true.value_changed
flag tofalse
to preventMarkItemEdited
, and callClearActiveID()
.ImGuiContext
to avoid issues with multi-context setups, rather than using static variables.This feature would significantly improve the usability of
DragScalar
widgets, particularly in editor-like applications.Screenshots/Video:
Minimal, Complete and Verifiable Example code:
Here is my working example in imgui_widgets.cpp you can copy paste for testing it:
The text was updated successfully, but these errors were encountered: