mirror of
https://github.com/ocornut/imgui.git
synced 2026-09-27 00:13:29 +03:00
Drags: added value ladder: stepped value editing with explicit magnitude selection (Houdini-style) (#9463)
Hold middle mouse button over a DragXXX widget to open a vertical ladder of step magnitudes: drag vertically to select the step magnitude, horizontally to add/subtract steps of that size. Release middle mouse button to confirm, press Escape to cancel and revert to the initial value. - Enable globally with io.ConfigDragValueLadder or per-widget with ImGuiSliderFlags_ValueLadder. - Rungs are powers of ten, from the smallest step representable with the display format (1 for integers) up to 10000. For bounded widgets, rungs covering the whole range or more are dropped. - Keyboard/gamepad navigation support: activating the widget (Space) opens the ladder, Up/Down select the step magnitude, Left/Right add/subtract steps, Space confirms, Escape cancels and reverts. - The ladder is disabled when it would yield fewer than 3 rungs. - Unlike regular drag speed tweaks (Alt/Shift), the ladder makes the current step magnitude visible and exact, and any size of change is reachable without a range. - Min/max clamping, wrap-around and display format rounding behave the same as regular mouse drags (reuses the DragBehaviorT pipeline). - Multi-component widgets (DragFloat3 etc.) get a ladder per component. - Overlay is drawn on the viewport foreground draw list, so it cannot interfere with inputs and may spill outside the host window. The overlay grows (monotonically, staying centered and clamped into the viewport) whenever the formatted value is wider than the box. - Not supported together with ImGuiSliderFlags_Logarithmic. - Demo: added dedicated Widgets->Value Ladder section, checkboxes in Configuration->Widgets and Widgets->Drag/Slider Flags. - Added DataTypeAsDouble() to the DataTypeXXX API (replacing the local ValueLadderGetValueAsDouble() helper). Range span is computed with DataTypeApplyOp() subtraction followed by DataTypeAsDouble(), which also clamps integer overflow. Addresses feature request #9463. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
545d1a884a
commit
0cc7e51da2
6 changed files with 313 additions and 5 deletions
|
|
@ -207,6 +207,17 @@ Other Changes:
|
|||
default value of each `ImGuiItemFlags_LiveEditXXX`, because this is not expected
|
||||
to be a user preference but a programmer/widget preferences.
|
||||
And we strive to make the toolkit consistent.
|
||||
- Drags: added value ladder (inspired by Houdini): hold middle mouse button over
|
||||
a DragXXX widget to open a vertical ladder of step magnitudes: drag vertically
|
||||
to select the step magnitude, horizontally to add/subtract steps of that size.
|
||||
Release middle mouse button to confirm, press Escape to cancel and revert.
|
||||
Also works with keyboard/gamepad navigation: activating the widget (Space)
|
||||
opens the ladder, Up/Down select the step magnitude, Left/Right add/subtract
|
||||
steps, Space confirms, Escape cancels and reverts.
|
||||
Enable globally with `io.ConfigDragValueLadder` or per-widget with
|
||||
`ImGuiSliderFlags_ValueLadder`. Not supported with ImGuiSliderFlags_Logarithmic.
|
||||
Disabled when the widget would yield fewer than 3 rungs (e.g. small integer
|
||||
ranges). (#9463)
|
||||
- Windows:
|
||||
- Clicking on a window's empty-space to move/focus a window checks
|
||||
for lack of mouse button ownership. This gives an additional opportunity
|
||||
|
|
|
|||
|
|
@ -1743,6 +1743,7 @@ ImGuiIO::ImGuiIO()
|
|||
ConfigInputTextCursorBlink = true;
|
||||
ConfigInputTextEnterKeepActive = false;
|
||||
ConfigDragClickToInputText = false;
|
||||
ConfigDragValueLadder = false;
|
||||
ConfigColorEditFlags = ImGuiColorEditFlags_DefaultOptions_; // Current settings for ColorEdit/ColorPicker widgets. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions.
|
||||
ConfigWindowsResizeFromEdges = true;
|
||||
ConfigWindowsMoveFromTitleBarOnly = false;
|
||||
|
|
|
|||
2
imgui.h
2
imgui.h
|
|
@ -2075,6 +2075,7 @@ enum ImGuiSliderFlags_
|
|||
ImGuiSliderFlags_ClampZeroRange = 1 << 10, // Clamp even if min==max==0.0f. Otherwise due to legacy reason DragXXX functions don't clamp with those values. When your clamping limits are dynamic you almost always want to use it.
|
||||
ImGuiSliderFlags_NoSpeedTweaks = 1 << 11, // Disable keyboard modifiers altering tweak speed. Useful if you want to alter tweak speed yourself based on your own logic.
|
||||
ImGuiSliderFlags_ColorMarkers = 1 << 12, // DragScalarN(), SliderScalarN(): Draw R/G/B/A color markers on each component.
|
||||
ImGuiSliderFlags_ValueLadder = 1 << 13, // DragXXX() only: hold middle mouse button over the widget (or activate it with keyboard/gamepad nav) to open a value ladder: drag vertically/Up/Down to select step magnitude, horizontally/Left/Right to add/subtract steps. (Shared behavior flag: io.ConfigDragValueLadder)
|
||||
ImGuiSliderFlags_AlwaysClamp = ImGuiSliderFlags_ClampOnInput | ImGuiSliderFlags_ClampZeroRange,
|
||||
//ImGuiSliderFlags_LiveEditOnInput = 1 << 13, // Shortcut to enable LiveEdit for this field.
|
||||
//ImGuiSliderFlags_NoLiveEditOnInput= 1 << 14, // Shortcut to disable LiveEdit for this field.
|
||||
|
|
@ -2595,6 +2596,7 @@ struct ImGuiIO
|
|||
bool ConfigInputTextEnterKeepActive; // = false // [BETA] Pressing Enter will reactivate item and select all text (single-line only). Ctrl+Enter or Shift+Enter will deactivate normally.
|
||||
ImGuiColorEditFlags ConfigColorEditFlags; // = <defaults> // Current settings for ColorEdit/ColorPicker widgets. Must have one bit of ImGuiColorEditFlags_DisplayMask_, one bit of ImGuiColorEditFlags_DataTypeMask_, one bit of ImGuiColorEditFlags_PickerMask_, one bit of ImGuiColorEditFlags_InputMask_. Defaults to ImGuiColorEditFlags_DefaultOptions_. May be further edited by users, unless you also set ImGuiColorEditFlags_NoOptions.
|
||||
bool ConfigDragClickToInputText; // = false // [BETA] Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving). Not desirable on devices without a keyboard.
|
||||
bool ConfigDragValueLadder; // = false // [BETA] Enable value ladder on DragXXX widgets: hold middle mouse button (or activate with keyboard/gamepad nav) to open it, drag vertically/Up/Down to select step magnitude, horizontally/Left/Right to add/subtract steps. (Also available per-widget via ImGuiSliderFlags_ValueLadder.)
|
||||
bool ConfigWindowsResizeFromEdges; // = true // Enable resizing of windows from their edges and from the lower-left corner. This requires ImGuiBackendFlags_HasMouseCursors for better mouse cursor feedback. (This used to be a per-window ImGuiWindowFlags_ResizeFromAnySide flag)
|
||||
bool ConfigWindowsMoveFromTitleBarOnly; // = false // Enable allowing to move windows only when clicking on their title bar. Does not apply to windows without a title bar.
|
||||
bool ConfigWindowsCopyContentsWithCtrlC; // = false // [EXPERIMENTAL] Ctrl+C copy the contents of focused window into the clipboard. Experimental because: (1) has known issues with nested Begin/End pairs (2) text output quality varies (3) text output is in submission order rather than spatial order.
|
||||
|
|
|
|||
|
|
@ -602,6 +602,8 @@ void ImGui::ShowDemoWindow(bool* p_open)
|
|||
ImGui::SameLine(); HelpMarker("Pressing Enter will reactivate item and select all text (single-line only).");
|
||||
ImGui::Checkbox("io.ConfigDragClickToInputText", &io.ConfigDragClickToInputText);
|
||||
ImGui::SameLine(); HelpMarker("Enable turning DragXXX widgets into text input with a simple mouse click-release (without moving).");
|
||||
ImGui::Checkbox("io.ConfigDragValueLadder", &io.ConfigDragValueLadder);
|
||||
ImGui::SameLine(); HelpMarker("Enable holding middle mouse button over DragXXX widgets to open a value ladder: drag vertically to select step magnitude, horizontally to add/subtract steps. Release to confirm, press Escape to cancel.");
|
||||
ImGui::Checkbox("io.ConfigMacOSXBehaviors", &io.ConfigMacOSXBehaviors);
|
||||
ImGui::SameLine(); HelpMarker("Swap Cmd<>Ctrl keys, enable various MacOS style behaviors.");
|
||||
ImGui::Text("Also see Style->Rendering for rendering options.");
|
||||
|
|
@ -1892,6 +1894,8 @@ static void DemoWindowWidgetsDragsAndSliders()
|
|||
ImGui::CheckboxFlags("ImGuiSliderFlags_WrapAround", &flags, ImGuiSliderFlags_WrapAround);
|
||||
ImGui::SameLine(); HelpMarker("Enable wrapping around from max to min and from min to max (only supported by DragXXX() functions)");
|
||||
ImGui::CheckboxFlags("ImGuiSliderFlags_ColorMarkers", &flags, ImGuiSliderFlags_ColorMarkers);
|
||||
ImGui::CheckboxFlags("ImGuiSliderFlags_ValueLadder", &flags, ImGuiSliderFlags_ValueLadder);
|
||||
ImGui::SameLine(); HelpMarker("Hold middle mouse button over the widget to open a value ladder: drag vertically to select step magnitude, horizontally to add/subtract steps. Release to confirm, press Escape to cancel. (only supported by DragXXX() functions)");
|
||||
|
||||
// Drags
|
||||
static float drag_f = 0.5f;
|
||||
|
|
@ -1919,6 +1923,44 @@ static void DemoWindowWidgetsDragsAndSliders()
|
|||
|
||||
ImGui::TreePop();
|
||||
}
|
||||
|
||||
if (ImGui::TreeNode("Value Ladder"))
|
||||
{
|
||||
IMGUI_DEMO_MARKER("Widgets/Value Ladder");
|
||||
// Demonstrate the value ladder: stepped value editing with explicit magnitude selection, inspired by Houdini.
|
||||
// Enabled here per-widget with ImGuiSliderFlags_ValueLadder. Use io.ConfigDragValueLadder to enable it on all DragXXX widgets.
|
||||
ImGui::TextWrapped("Hold middle mouse button over a widget to open a value ladder: drag vertically to select step magnitude, horizontally to add/subtract steps. Release to confirm, press Escape to cancel. With keyboard/gamepad navigation: activate the widget (Space) to open the ladder, Up/Down to select step magnitude, Left/Right to add/subtract steps.");
|
||||
ImGui::Checkbox("io.ConfigDragValueLadder (enable on all DragXXX widgets)", &ImGui::GetIO().ConfigDragValueLadder);
|
||||
ImGui::Spacing();
|
||||
|
||||
const ImGuiSliderFlags flags = ImGuiSliderFlags_ValueLadder;
|
||||
static float pos = 0.0f;
|
||||
static float angle = 0.0f;
|
||||
static float precise = 0.5f;
|
||||
static float clamped = 0.5f;
|
||||
static int count = 50;
|
||||
static double d_value = 1000.0;
|
||||
static float f3[3] = { 1.0f, 2.0f, 3.0f };
|
||||
ImGui::DragFloat("Unbounded float", &pos, 1.0f, 0.0f, 0.0f, "%.2f", flags);
|
||||
ImGui::SameLine(); HelpMarker("No min/max: the ladder shines here as any size of change is reachable, from 0.01 to 10000.");
|
||||
ImGui::DragFloat("Angle (wrap 0 -> 360)", &angle, 1.0f, 0.0f, 360.0f, "%.1f deg", flags | ImGuiSliderFlags_WrapAround);
|
||||
ImGui::SameLine(); HelpMarker("v_speed=1 preselects the '1' rung. Move up to '10' or '100' for coarse rotation.");
|
||||
static float angle2 = 0.0f;
|
||||
ImGui::DragFloat("Angle (%.2f precision)", &angle2, 1.0f, 0.0f, 360.0f, "%.2f deg", flags | ImGuiSliderFlags_WrapAround);
|
||||
ImGui::SameLine(); HelpMarker("Same as above but displaying two digits after the decimal point, adding a 0.01 rung.");
|
||||
ImGui::DragFloat("High precision", &precise, 0.001f, 0.0f, 0.0f, "%.4f", flags);
|
||||
ImGui::SameLine(); HelpMarker("The smallest rung comes from the display format: %.4f offers steps down to 0.0001.");
|
||||
ImGui::DragFloat("Clamped (0 -> 1)", &clamped, 0.005f, 0.0f, 1.0f, "%.3f", flags);
|
||||
ImGui::DragInt("Integer (0 -> 1000)", &count, 1.0f, 0, 1000, "%d", flags);
|
||||
ImGui::SameLine(); HelpMarker("Integer types offer no rung smaller than 1.");
|
||||
static int small_count = 5;
|
||||
ImGui::DragInt("Integer (0 -> 100)", &small_count, 1.0f, 0, 100, "%d", flags);
|
||||
ImGui::SameLine(); HelpMarker("No ladder here: this range yields fewer than 3 rungs, so the ladder is disabled (it would provide little to no value).");
|
||||
ImGui::DragScalar("Double (unbounded)", ImGuiDataType_Double, &d_value, 1.0f, NULL, NULL, "%.3f", flags);
|
||||
ImGui::DragFloat3("DragFloat3", f3, 0.01f, 0.0f, 0.0f, "%.2f", flags);
|
||||
ImGui::SameLine(); HelpMarker("Multi-component widgets get a ladder on each component.");
|
||||
ImGui::TreePop();
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@ struct ImGuiTableColumnsSettings; // Storage for a column .ini settings
|
|||
struct ImGuiTreeNodeStackData; // Temporary storage for TreeNode().
|
||||
struct ImGuiTypingSelectState; // Storage for GetTypingSelectRequest()
|
||||
struct ImGuiTypingSelectRequest; // Storage for GetTypingSelectRequest() (aimed to be public)
|
||||
struct ImGuiValueLadderState; // Storage for value ladder (middle mouse button drag on DragXXX widgets)
|
||||
struct ImGuiWindow; // Storage for one window
|
||||
struct ImGuiWindowDockStyle; // Storage for window-style data which needs to be stored for docking purpose
|
||||
struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window)
|
||||
|
|
@ -1876,6 +1877,27 @@ struct IMGUI_API ImGuiTypingSelectState
|
|||
void Clear() { SearchBuffer[0] = 0; SingleCharModeLock = false; } // We preserve remaining data for easier debugging
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// [SECTION] Value ladder support
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
// Storage for value ladder: stepped value editing with explicit magnitude selection, activated by holding
|
||||
// middle mouse button over a DragXXX widget. See io.ConfigDragValueLadder and ImGuiSliderFlags_ValueLadder.
|
||||
struct IMGUI_API ImGuiValueLadderState
|
||||
{
|
||||
ImGuiID SourceId; // Widget owning the ladder (0 when inactive)
|
||||
int RungCount;
|
||||
int RungIndex; // Currently selected magnitude (index into RungSteps[])
|
||||
double RungSteps[16]; // Step value of each rung, ascending (displayed with largest at top)
|
||||
ImVec2 Pos; // Top-left corner of the ladder overlay, in screen space
|
||||
ImVec2 Size;
|
||||
float RowHeight;
|
||||
float PendingAccumX; // Horizontal mouse movement not yet converted into steps
|
||||
float StepDeltaToApply; // Computed by ValueLadderUpdate(), consumed by DragBehaviorT()
|
||||
|
||||
ImGuiValueLadderState() { memset((void*)this, 0, sizeof(*this)); }
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// [SECTION] Columns support
|
||||
//-----------------------------------------------------------------------------
|
||||
|
|
@ -2772,6 +2794,7 @@ struct ImGuiContext
|
|||
ImVector<char> ClipboardHandlerData; // If no custom clipboard handler is defined
|
||||
ImVector<ImGuiID> MenusIdSubmittedThisFrame; // A list of menu IDs that were rendered at least once
|
||||
ImGuiTypingSelectState TypingSelectState; // State for GetTypingSelectRequest()
|
||||
ImGuiValueLadderState ValueLadderState; // State for value ladder (middle mouse button drag on DragXXX widgets)
|
||||
|
||||
// Platform support
|
||||
ImGuiPlatformImeData PlatformImeData; // Data updated by current frame. Will be applied at end of the frame. For some backends, this is required to have WantVisible=true in order to receive text message.
|
||||
|
|
@ -4096,6 +4119,7 @@ namespace ImGui
|
|||
IMGUI_API const ImGuiDataTypeInfo* DataTypeGetInfo(ImGuiDataType data_type);
|
||||
IMGUI_API int DataTypeFormatString(char* buf, int buf_size, ImGuiDataType data_type, const void* p_data, const char* format);
|
||||
IMGUI_API void DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const void* arg_1, const void* arg_2);
|
||||
IMGUI_API double DataTypeAsDouble(ImGuiDataType data_type, const void* p_data);
|
||||
IMGUI_API bool DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty = NULL);
|
||||
IMGUI_API int DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2);
|
||||
IMGUI_API bool DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max);
|
||||
|
|
|
|||
|
|
@ -2263,6 +2263,7 @@ bool ImGui::Combo(const char* label, int* current_item, const char* items_separa
|
|||
// - DataTypeGetInfo()
|
||||
// - DataTypeFormatString()
|
||||
// - DataTypeApplyOp()
|
||||
// - DataTypeAsDouble()
|
||||
// - DataTypeApplyFromText()
|
||||
// - DataTypeCompare()
|
||||
// - DataTypeClamp()
|
||||
|
|
@ -2376,6 +2377,26 @@ void ImGui::DataTypeApplyOp(ImGuiDataType data_type, int op, void* output, const
|
|||
IM_ASSERT(0);
|
||||
}
|
||||
|
||||
double ImGui::DataTypeAsDouble(ImGuiDataType data_type, const void* p_data)
|
||||
{
|
||||
switch (data_type)
|
||||
{
|
||||
case ImGuiDataType_S8: return (double)*(const ImS8*)p_data;
|
||||
case ImGuiDataType_U8: return (double)*(const ImU8*)p_data;
|
||||
case ImGuiDataType_S16: return (double)*(const ImS16*)p_data;
|
||||
case ImGuiDataType_U16: return (double)*(const ImU16*)p_data;
|
||||
case ImGuiDataType_S32: return (double)*(const ImS32*)p_data;
|
||||
case ImGuiDataType_U32: return (double)*(const ImU32*)p_data;
|
||||
case ImGuiDataType_S64: return (double)*(const ImS64*)p_data;
|
||||
case ImGuiDataType_U64: return (double)*(const ImU64*)p_data;
|
||||
case ImGuiDataType_Float: return (double)*(const float*)p_data;
|
||||
case ImGuiDataType_Double: return *(const double*)p_data;
|
||||
case ImGuiDataType_COUNT: break;
|
||||
}
|
||||
IM_ASSERT(0);
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
// User can input math operators (e.g. +100) to edit a numerical values.
|
||||
// NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess..
|
||||
bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void* p_data, const char* format, void* p_data_when_empty)
|
||||
|
|
@ -2546,6 +2567,164 @@ TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type,
|
|||
// - DragInt4()
|
||||
// - DragIntRange2()
|
||||
//-------------------------------------------------------------------------
|
||||
// Value ladder (hold middle mouse button over a Drag widget): [Internal]
|
||||
// - ValueLadderCalcRange()
|
||||
// - ValueLadderCalcRungs()
|
||||
// - ValueLadderStart()
|
||||
// - ValueLadderUpdate()
|
||||
// - ValueLadderRender()
|
||||
//-------------------------------------------------------------------------
|
||||
|
||||
// Compute (max - min) as double. Integer overflow is clamped by DataTypeApplyOp().
|
||||
static double ValueLadderCalcRange(ImGuiDataType data_type, const void* p_min, const void* p_max)
|
||||
{
|
||||
ImGuiDataTypeStorage range_storage;
|
||||
ImGui::DataTypeApplyOp(data_type, '-', &range_storage, p_max, p_min);
|
||||
return ImGui::DataTypeAsDouble(data_type, &range_storage);
|
||||
}
|
||||
|
||||
// Compute the rung magnitudes as a range of powers of ten and return the rung count.
|
||||
// Rungs go from the smallest step representable with the display format (1 for integers) up to 10000.
|
||||
// For a bounded widget, rungs covering the whole range or more are dropped: capped at the largest power of ten smaller than the range span.
|
||||
static int ValueLadderCalcRungs(ImGuiDataType data_type, const void* p_min, const void* p_max, const char* format, int* out_min_exp)
|
||||
{
|
||||
const bool is_floating_point = (data_type == ImGuiDataType_Float) || (data_type == ImGuiDataType_Double);
|
||||
const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
|
||||
int min_exp = -ImClamp(decimal_precision, 0, 9);
|
||||
int max_exp = 4;
|
||||
if (p_min != NULL && p_max != NULL)
|
||||
{
|
||||
const double range = ValueLadderCalcRange(data_type, p_min, p_max);
|
||||
if (range > 0.0 && range < (double)FLT_MAX)
|
||||
{
|
||||
int range_exp = (int)ImFloor(ImLog(range) / ImLog(10.0));
|
||||
if (ImPow(10.0, (double)range_exp) >= range)
|
||||
range_exp--;
|
||||
max_exp = ImClamp(range_exp, is_floating_point ? -9 : 0, max_exp);
|
||||
if (max_exp < min_exp)
|
||||
min_exp = max_exp; // Add steps smaller than the display format rather than ending up with no rungs at all.
|
||||
}
|
||||
}
|
||||
*out_min_exp = min_exp;
|
||||
return ImMin(max_exp - min_exp + 1, (int)IM_COUNTOF(GImGui->ValueLadderState.RungSteps));
|
||||
}
|
||||
|
||||
// Initiate a value ladder for the given widget: build magnitude rungs and lay out the overlay so the selected rung is centered on anchor_pos.
|
||||
static void ValueLadderStart(ImGuiWindow* window, ImGuiID id, ImGuiDataType data_type, const void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, ImVec2 anchor_pos)
|
||||
{
|
||||
using namespace ImGui;
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiValueLadderState* ladder = &g.ValueLadderState;
|
||||
ladder->SourceId = id;
|
||||
ladder->PendingAccumX = 0.0f;
|
||||
ladder->StepDeltaToApply = 0.0f;
|
||||
|
||||
int min_exp;
|
||||
ladder->RungCount = ValueLadderCalcRungs(data_type, p_min, p_max, format, &min_exp);
|
||||
for (int n = 0; n < ladder->RungCount; n++)
|
||||
ladder->RungSteps[n] = ImPow(10.0, (double)(min_exp + n));
|
||||
|
||||
// Start on the rung nearest to the widget's tweak speed (1 when unspecified).
|
||||
int rung_index = -min_exp;
|
||||
if (v_speed > 0.0f)
|
||||
rung_index = (int)ImFloor(ImLog(v_speed) / ImLog(10.0f) + 0.5f) - min_exp;
|
||||
ladder->RungIndex = ImClamp(rung_index, 0, ladder->RungCount - 1);
|
||||
|
||||
// Lay out a vertical stack with the largest step at top and the selected rung centered on the anchor, clamped into the viewport.
|
||||
// Each rung is two text lines tall so the selected rung can display the step value above the current widget value.
|
||||
float max_label_width = 0.0f;
|
||||
for (int n = 0; n < ladder->RungCount; n++)
|
||||
{
|
||||
char label[32];
|
||||
ImFormatString(label, IM_COUNTOF(label), "%g", ladder->RungSteps[n]);
|
||||
max_label_width = ImMax(max_label_width, CalcTextSize(label).x);
|
||||
}
|
||||
char value_buf[64];
|
||||
const int value_buf_len = DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format);
|
||||
max_label_width = ImMax(max_label_width, CalcTextSize(value_buf, value_buf + value_buf_len).x);
|
||||
ladder->RowHeight = g.FontSize * 3.0f + g.Style.FramePadding.y * 2.0f;
|
||||
ladder->Size = ImVec2(max_label_width + g.Style.FramePadding.x * 4.0f, ladder->RowHeight * ladder->RungCount);
|
||||
ladder->Pos.x = anchor_pos.x - ladder->Size.x * 0.5f;
|
||||
ladder->Pos.y = anchor_pos.y - ladder->RowHeight * 0.5f - (float)(ladder->RungCount - 1 - ladder->RungIndex) * ladder->RowHeight;
|
||||
const ImRect viewport_rect = window->Viewport->GetMainRect();
|
||||
ladder->Pos = ImClamp(ladder->Pos, viewport_rect.Min, ImMax(viewport_rect.Min, viewport_rect.Max - ladder->Size));
|
||||
}
|
||||
|
||||
// Update rung selection and convert inputs into steps.
|
||||
// Mouse: vertical position selects the magnitude, horizontal movement adds/subtracts steps.
|
||||
// Keyboard/Gamepad: Up/Down select the magnitude, Left/Right add/subtract steps.
|
||||
static void ValueLadderUpdate()
|
||||
{
|
||||
using namespace ImGui;
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiValueLadderState* ladder = &g.ValueLadderState;
|
||||
ladder->StepDeltaToApply = 0.0f;
|
||||
if (g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad)
|
||||
{
|
||||
const int rung_delta = -(int)GetNavTweakPressedAmount(ImGuiAxis_Y); // Up = larger steps
|
||||
ladder->RungIndex = ImClamp(ladder->RungIndex + rung_delta, 0, ladder->RungCount - 1);
|
||||
ladder->StepDeltaToApply = (float)((double)GetNavTweakPressedAmount(ImGuiAxis_X) * ladder->RungSteps[ladder->RungIndex]);
|
||||
return;
|
||||
}
|
||||
if (!IsMousePosValid())
|
||||
return;
|
||||
|
||||
// Vertical position selects the magnitude (largest step at top).
|
||||
const int display_row = ImClamp((int)((g.IO.MousePos.y - ladder->Pos.y) / ladder->RowHeight), 0, ladder->RungCount - 1);
|
||||
ladder->RungIndex = ladder->RungCount - 1 - display_row;
|
||||
|
||||
// Horizontal movement adds/subtracts one step of the selected magnitude per threshold-worth of pixels.
|
||||
ladder->PendingAccumX += g.IO.MouseDelta.x;
|
||||
const float pixels_per_step = ImMax(4.0f, ImFloor(g.FontSize * 0.75f));
|
||||
const int steps = (int)(ladder->PendingAccumX / pixels_per_step);
|
||||
ladder->PendingAccumX -= (float)steps * pixels_per_step;
|
||||
ladder->StepDeltaToApply = (float)((double)steps * ladder->RungSteps[ladder->RungIndex]);
|
||||
}
|
||||
|
||||
// Draw the ladder overlay and current value on the viewport foreground, so it cannot interfere with inputs.
|
||||
static void ValueLadderRender(ImGuiWindow* window, ImGuiDataType data_type, const void* p_data, const char* format)
|
||||
{
|
||||
using namespace ImGui;
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiValueLadderState* ladder = &g.ValueLadderState;
|
||||
ImDrawList* draw_list = GetForegroundDrawList(window->Viewport);
|
||||
|
||||
// Widen the ladder when the current value text outgrows it (never shrink to avoid jittering), keeping it centered.
|
||||
char value_buf[64];
|
||||
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_COUNTOF(value_buf), data_type, p_data, format);
|
||||
const ImVec2 value_size = CalcTextSize(value_buf, value_buf_end);
|
||||
const float min_width = value_size.x + g.Style.FramePadding.x * 4.0f;
|
||||
if (ladder->Size.x < min_width)
|
||||
{
|
||||
ladder->Pos.x -= (min_width - ladder->Size.x) * 0.5f;
|
||||
ladder->Size.x = min_width;
|
||||
const ImRect viewport_rect = window->Viewport->GetMainRect();
|
||||
ladder->Pos = ImClamp(ladder->Pos, viewport_rect.Min, ImMax(viewport_rect.Min, viewport_rect.Max - ladder->Size));
|
||||
}
|
||||
|
||||
const ImRect bb(ladder->Pos, ladder->Pos + ladder->Size);
|
||||
draw_list->AddRectFilled(bb.Min, bb.Max, GetColorU32(ImGuiCol_PopupBg), g.Style.PopupRounding);
|
||||
for (int n = 0; n < ladder->RungCount; n++)
|
||||
{
|
||||
const int rung_n = ladder->RungCount - 1 - n; // Largest step at top
|
||||
const bool is_selected = (rung_n == ladder->RungIndex);
|
||||
const ImVec2 row_min(bb.Min.x, bb.Min.y + (float)n * ladder->RowHeight);
|
||||
const ImVec2 row_max(bb.Max.x, row_min.y + ladder->RowHeight);
|
||||
if (is_selected)
|
||||
draw_list->AddRectFilled(row_min, row_max, GetColorU32(ImGuiCol_FrameBgActive), g.Style.PopupRounding);
|
||||
if (n > 0)
|
||||
draw_list->AddLine(row_min, ImVec2(row_max.x, row_min.y), GetColorU32(ImGuiCol_Separator));
|
||||
char label[32];
|
||||
ImFormatString(label, IM_COUNTOF(label), "%g", ladder->RungSteps[rung_n]);
|
||||
const ImVec2 label_size = CalcTextSize(label);
|
||||
// Selected rung shows step value above and current widget value below; others show the step value vertically centered.
|
||||
const float label_y = is_selected ? (row_min.y + ((row_max.y - row_min.y) - g.FontSize * 2.0f) * 0.5f) : (row_min.y + ((row_max.y - row_min.y) - g.FontSize) * 0.5f);
|
||||
draw_list->AddText(ImVec2(row_min.x + ((row_max.x - row_min.x) - label_size.x) * 0.5f, label_y), GetColorU32(ImGuiCol_Text), label);
|
||||
if (is_selected)
|
||||
draw_list->AddText(ImVec2(row_min.x + ((row_max.x - row_min.x) - value_size.x) * 0.5f, label_y + g.FontSize), GetColorU32(ImGuiCol_Text), value_buf, value_buf_end);
|
||||
}
|
||||
draw_list->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), g.Style.PopupRounding);
|
||||
}
|
||||
|
||||
// This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls)
|
||||
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
|
||||
|
|
@ -2564,7 +2743,14 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const
|
|||
|
||||
// Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings
|
||||
float adjust_delta = 0.0f;
|
||||
if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
|
||||
const bool is_ladder = (g.ValueLadderState.SourceId != 0 && g.ValueLadderState.SourceId == g.ActiveId);
|
||||
if (is_ladder)
|
||||
{
|
||||
// Value ladder: delta computed by ValueLadderUpdate() with an explicit magnitude, bypass v_speed and speed tweaks.
|
||||
adjust_delta = g.ValueLadderState.StepDeltaToApply;
|
||||
g.ValueLadderState.StepDeltaToApply = 0.0f;
|
||||
}
|
||||
else if (g.ActiveIdSource == ImGuiInputSource_Mouse && IsMousePosValid() && IsMouseDragPastThreshold(0, g.IO.MouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR))
|
||||
{
|
||||
adjust_delta = g.IO.MouseDelta[axis];
|
||||
if (g.IO.KeyAlt && !(flags & ImGuiSliderFlags_NoSpeedTweaks))
|
||||
|
|
@ -2581,10 +2767,11 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const
|
|||
adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;
|
||||
v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
|
||||
}
|
||||
adjust_delta *= v_speed;
|
||||
if (!is_ladder)
|
||||
adjust_delta *= v_speed;
|
||||
|
||||
// For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter.
|
||||
if (axis == ImGuiAxis_Y)
|
||||
if (axis == ImGuiAxis_Y && !is_ladder)
|
||||
adjust_delta = -adjust_delta;
|
||||
|
||||
// For logarithmic use our range is effectively 0..1 so scale the delta into that range
|
||||
|
|
@ -2697,9 +2884,20 @@ bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v
|
|||
|
||||
// Things we can do easily outside the DragBehaviorT<> template, saves code generation.
|
||||
ImGuiContext& g = *GImGui;
|
||||
ImGuiValueLadderState* ladder = (id != 0 && g.ValueLadderState.SourceId == id) ? &g.ValueLadderState : NULL;
|
||||
if (g.ActiveId == id)
|
||||
{
|
||||
if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
|
||||
if (ladder != NULL && Shortcut(ImGuiKey_Escape, ImGuiInputFlags_None, id))
|
||||
{
|
||||
// Escape cancels the value ladder and reverts to the initial value.
|
||||
const bool value_changed = (DataTypeCompare(data_type, p_v, g.ActiveIdValueOnActivation.Data) != 0);
|
||||
memcpy(p_v, g.ActiveIdValueOnActivation.Data, DataTypeGetInfo(data_type)->Size);
|
||||
ClearActiveID();
|
||||
ladder->SourceId = 0;
|
||||
return value_changed;
|
||||
}
|
||||
// Value ladder driven by mouse is held with the middle mouse button.
|
||||
if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[(ladder != NULL) ? ImGuiMouseButton_Middle : ImGuiMouseButton_Left])
|
||||
ClearActiveID();
|
||||
else if ((g.ActiveIdSource == ImGuiInputSource_Keyboard || g.ActiveIdSource == ImGuiInputSource_Gamepad) && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
|
||||
ClearActiveID();
|
||||
|
|
@ -2714,9 +2912,15 @@ bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v
|
|||
}
|
||||
}
|
||||
if (g.ActiveId != id)
|
||||
{
|
||||
if (ladder != NULL)
|
||||
ladder->SourceId = 0;
|
||||
return false;
|
||||
}
|
||||
if ((g.LastItemData.ItemFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
|
||||
return false;
|
||||
if (ladder != NULL)
|
||||
ValueLadderUpdate();
|
||||
|
||||
switch (data_type)
|
||||
{
|
||||
|
|
@ -2785,9 +2989,21 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
|
|||
// Tabbing or Ctrl+Click on Drag turns it into an InputText
|
||||
const bool clicked = hovered && IsMouseClicked(0, ImGuiInputFlags_None, id);
|
||||
const bool double_clicked = (hovered && g.IO.MouseClickedCount[0] == 2 && TestKeyOwner(ImGuiKey_MouseLeft, id));
|
||||
const bool make_active = (clicked || double_clicked || g.NavActivateId == id);
|
||||
bool ladder_enabled = ((flags & ImGuiSliderFlags_ValueLadder) || g.IO.ConfigDragValueLadder) && !(flags & ImGuiSliderFlags_Logarithmic);
|
||||
const bool ladder_requested = ladder_enabled && ((hovered && IsMouseClicked(ImGuiMouseButton_Middle, ImGuiInputFlags_None, id)) || g.NavActivateId == id);
|
||||
if (ladder_requested)
|
||||
{
|
||||
// The ladder provides little to no value when only very few magnitudes are reachable (e.g. small integer ranges).
|
||||
int rung_min_exp;
|
||||
if (ValueLadderCalcRungs(data_type, p_min, p_max, format, &rung_min_exp) < 3)
|
||||
ladder_enabled = false;
|
||||
}
|
||||
const bool ladder_clicked = ladder_enabled && hovered && IsMouseClicked(ImGuiMouseButton_Middle, ImGuiInputFlags_None, id);
|
||||
const bool make_active = (clicked || double_clicked || ladder_clicked || g.NavActivateId == id);
|
||||
if (make_active && (clicked || double_clicked))
|
||||
SetKeyOwner(ImGuiKey_MouseLeft, id);
|
||||
if (make_active && ladder_clicked)
|
||||
SetKeyOwner(ImGuiKey_MouseMiddle, 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;
|
||||
|
|
@ -2811,6 +3027,14 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
|
|||
SetFocusID(id, window);
|
||||
FocusWindow(window);
|
||||
g.ActiveIdUsingNavDirMask = (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
|
||||
if (ladder_clicked)
|
||||
ValueLadderStart(window, id, data_type, p_data, v_speed, p_min, p_max, format, g.IO.MousePos);
|
||||
else if (ladder_enabled && g.NavActivateId == id)
|
||||
{
|
||||
// Keyboard/Gamepad activation (Space): open the ladder over the widget, claim Up/Down for magnitude selection.
|
||||
ValueLadderStart(window, id, data_type, p_data, v_speed, p_min, p_max, format, frame_bb.GetCenter());
|
||||
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2848,6 +3072,10 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
|
|||
if (label_size.x > 0.0f)
|
||||
RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label, label_end, false);
|
||||
|
||||
// Value ladder overlay (drawn on the viewport foreground so it may spill outside the window)
|
||||
if (g.ActiveId == id && g.ValueLadderState.SourceId == id)
|
||||
ValueLadderRender(window, data_type, p_data, format);
|
||||
|
||||
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags | (temp_input_allowed ? ImGuiItemStatusFlags_Inputable : 0));
|
||||
return value_changed;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue