Skip to content

Commit 94d0184

Browse files
committed
SliderRange2: Address maintainer feedback
- Single format string API: removed format_max parameter, format can be "%.3f" (same for both) or "%.3f...%.3f" (different for min/max) - Accept any number of dots (.., ..., ....) as separator when parsing - Accept " - " as separator in addition to dots - Ctrl+Click shows "min...max" format for editing both values - Unknown state (-2) when handles overlap: drag direction determines which handle to use - Display uses " - " separator by default (customizable via format)
1 parent 5743047 commit 94d0184

3 files changed

Lines changed: 165 additions & 38 deletions

File tree

imgui.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -704,9 +704,9 @@ namespace ImGui
704704
IMGUI_API bool SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0);
705705
IMGUI_API bool SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format = NULL, ImGuiSliderFlags flags = 0);
706706
IMGUI_API bool SliderScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, const void* p_min, const void* p_max, const char* format = NULL, ImGuiSliderFlags flags = 0);
707-
IMGUI_API bool SliderScalarRange2(const char* label, ImGuiDataType data_type, void* p_v_min, void* p_v_max, const void* p_min, const void* p_max, const char* format = NULL, const char* format_max = NULL, ImGuiSliderFlags flags = 0, const void* p_step = NULL);
708-
IMGUI_API bool SliderFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_min = 0.0f, float v_max = 1.0f, const char* format = "%.3f", const char* format_max = NULL, ImGuiSliderFlags flags = 0, float step = 0.0f);
709-
IMGUI_API bool SliderIntRange2(const char* label, int* v_current_min, int* v_current_max, int v_min = 0, int v_max = 100, const char* format = "%d", const char* format_max = NULL, ImGuiSliderFlags flags = 0, int step = 0);
707+
IMGUI_API bool SliderScalarRange2(const char* label, ImGuiDataType data_type, void* p_v_min, void* p_v_max, const void* p_min, const void* p_max, const char* format = NULL, ImGuiSliderFlags flags = 0, const void* p_step = NULL);
708+
IMGUI_API bool SliderFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_min = 0.0f, float v_max = 1.0f, const char* format = "%.3f", ImGuiSliderFlags flags = 0, float step = 0.0f);
709+
IMGUI_API bool SliderIntRange2(const char* label, int* v_current_min, int* v_current_max, int v_min = 0, int v_max = 100, const char* format = "%d", ImGuiSliderFlags flags = 0, int step = 0);
710710
IMGUI_API bool VSliderFloat(const char* label, const ImVec2& size, float* v, float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0);
711711
IMGUI_API bool VSliderInt(const char* label, const ImVec2& size, int* v, int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0);
712712
IMGUI_API bool VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format = NULL, ImGuiSliderFlags flags = 0);

imgui_demo.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -979,8 +979,9 @@ static void DemoWindowWidgetsBasic()
979979
static float fmin = 0.25f, fmax = 0.75f;
980980
static int imin = 25, imax = 75;
981981
ImGui::SliderFloatRange2("range float", &fmin, &fmax, 0.0f, 1.0f);
982+
ImGui::SliderFloatRange2("range (size)", &fmin, &fmax, 0.0f, 1.0f, "%.2f...%.2f (%.2f)");
982983
ImGui::SliderIntRange2("range int", &imin, &imax, 0, 100);
983-
ImGui::SameLine(); HelpMarker("Click and drag handles individually, or drag the bar between them to move both.");
984+
ImGui::SameLine(); HelpMarker("Click and drag handles individually, or drag the bar between them to move both.\nCtrl+Click to input values. Use \"...\" or \" - \" as separator to set both.");
984985
}
985986

986987
ImGui::SeparatorText("Selectors/Pickers");

imgui_widgets.cpp

Lines changed: 160 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -3486,6 +3486,28 @@ bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const
34863486
// Forward declaration for TempInput function used by SliderScalarRange2
34873487
static bool TempInputScalarRange2(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data_min, void* p_data_max, const char* format, const char* format_max, const void* p_clamp_min, const void* p_clamp_max, int selected_handle);
34883488

3489+
// Helper to parse range format string "%.3f...%.3f" into separate min/max formats
3490+
// Returns pointer to start of max format (after dots), or NULL if no separator found
3491+
// Accepts any number of consecutive dots (2+) as separator
3492+
static const char* ParseRangeFormatSeparator(const char* format)
3493+
{
3494+
if (!format)
3495+
return NULL;
3496+
const char* p = format;
3497+
while (*p)
3498+
{
3499+
if (p[0] == '.' && p[1] == '.')
3500+
{
3501+
// Found at least "..", skip all consecutive dots
3502+
while (*p == '.')
3503+
p++;
3504+
return p; // Return start of max format
3505+
}
3506+
p++;
3507+
}
3508+
return NULL; // No separator found
3509+
}
3510+
34893511
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
34903512
static bool SliderBehaviorRangeT(const ImRect& bb, ImGuiID id, ImGuiDataType data_type, TYPE* v_min_val, TYPE* v_max_val, TYPE v_min, TYPE v_max, const char* format, const char* format_max, ImGuiSliderFlags flags, ImRect* out_grab_bb_min, ImRect* out_grab_bb_max, int* p_handle, TYPE step)
34913513
{
@@ -3554,8 +3576,11 @@ static bool SliderBehaviorRangeT(const ImRect& bb, ImGuiID id, ImGuiDataType dat
35543576

35553577
if (dist_min <= handle_threshold || dist_max <= handle_threshold)
35563578
{
3557-
// Click near a handle
3558-
*p_handle = (dist_min <= dist_max) ? 0 : 1;
3579+
// Click near a handle - if handles overlap, use undetermined state (-2)
3580+
if (ImAbs(grab_min_pos - grab_max_pos) < 1.0f)
3581+
*p_handle = -2; // Undetermined: will be resolved by drag direction
3582+
else
3583+
*p_handle = (dist_min <= dist_max) ? 0 : 1;
35593584
}
35603585
else if (click_pos > grab_min_pos && click_pos < grab_max_pos)
35613586
{
@@ -3613,6 +3638,19 @@ static bool SliderBehaviorRangeT(const ImRect& bb, ImGuiID id, ImGuiDataType dat
36133638
grab_max_pos = grab_pos_from_value(*v_max_val);
36143639
}
36153640
}
3641+
else if (*p_handle == -2)
3642+
{
3643+
// Undetermined state (handles overlapping): resolve based on drag direction
3644+
float delta = mouse_abs_pos - g.IO.MouseClickedPos[0][axis];
3645+
if (ImAbs(delta) > 1.0f)
3646+
{
3647+
// Resolved: positive delta = max handle, negative = min handle (for X axis)
3648+
*p_handle = (delta > 0.0f) ? 1 : 0;
3649+
if (axis == ImGuiAxis_Y)
3650+
*p_handle = 1 - *p_handle; // Invert for Y axis
3651+
}
3652+
// Don't set value until resolved
3653+
}
36163654
else
36173655
{
36183656
// Single handle dragging
@@ -3753,7 +3791,7 @@ static bool SliderBehaviorRange(const ImRect& bb, ImGuiID id, ImGuiDataType data
37533791
}
37543792
}
37553793

3756-
bool ImGui::SliderScalarRange2(const char* label, ImGuiDataType data_type, void* p_v_min, void* p_v_max, const void* p_min, const void* p_max, const char* format, const char* format_max, ImGuiSliderFlags flags, const void* p_step)
3794+
bool ImGui::SliderScalarRange2(const char* label, ImGuiDataType data_type, void* p_v_min, void* p_v_max, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags, const void* p_step)
37573795
{
37583796
ImGuiWindow* window = GetCurrentWindow();
37593797
if (window->SkipItems)
@@ -3773,11 +3811,49 @@ bool ImGui::SliderScalarRange2(const char* label, ImGuiDataType data_type, void*
37733811
if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
37743812
return false;
37753813

3776-
// Default format
3814+
// Default format and parse "min...max" format string
37773815
if (format == NULL)
37783816
format = DataTypeGetInfo(data_type)->PrintFmt;
3779-
if (format_max == NULL)
3780-
format_max = format;
3817+
3818+
// Parse format string: "%.3f...%.3f" or "%.3f...%.3f (%.3f)" for range size display
3819+
char format_min_buf[64];
3820+
char format_max_buf[64];
3821+
const char* format_min = format;
3822+
const char* format_max = format; // Default: same format for both
3823+
const char* format_range = NULL; // Optional range size format
3824+
const char* separator = ParseRangeFormatSeparator(format);
3825+
if (separator)
3826+
{
3827+
// Copy min format (everything before the dots)
3828+
const char* dots = format;
3829+
while (dots < separator && !(dots[0] == '.' && dots[1] == '.'))
3830+
dots++;
3831+
size_t min_len = ImMin((size_t)(dots - format), sizeof(format_min_buf) - 1);
3832+
memcpy(format_min_buf, format, min_len);
3833+
format_min_buf[min_len] = '\0';
3834+
format_min = format_min_buf;
3835+
3836+
// Check for range size format: look for " (" or " [" followed by "%"
3837+
const char* range_start = separator;
3838+
while (*range_start && *range_start != '(' && *range_start != '[')
3839+
range_start++;
3840+
if (*range_start && strchr(range_start, '%'))
3841+
{
3842+
// Found range size format - copy max format (between separator and range_start)
3843+
size_t max_len = ImMin((size_t)(range_start - separator), sizeof(format_max_buf) - 1);
3844+
// Trim trailing spaces
3845+
while (max_len > 0 && separator[max_len - 1] == ' ')
3846+
max_len--;
3847+
memcpy(format_max_buf, separator, max_len);
3848+
format_max_buf[max_len] = '\0';
3849+
format_max = format_max_buf;
3850+
format_range = range_start;
3851+
}
3852+
else
3853+
{
3854+
format_max = separator;
3855+
}
3856+
}
37813857

37823858
const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
37833859

@@ -3848,21 +3924,21 @@ bool ImGui::SliderScalarRange2(const char* label, ImGuiDataType data_type, void*
38483924

38493925
if (temp_input_is_active)
38503926
{
3851-
// Ctrl+Click text input: shows selected handle value, user can type "XXX...YYYY" to set both
3852-
return TempInputScalarRange2(frame_bb, id, label, data_type, p_v_min, p_v_max, format, format_max,
3927+
// Ctrl+Click text input: shows "min...max" format, user can type to set values
3928+
return TempInputScalarRange2(frame_bb, id, label, data_type, p_v_min, p_v_max, format_min, format_max,
38533929
(flags & ImGuiSliderFlags_AlwaysClamp) ? p_min : NULL,
38543930
(flags & ImGuiSliderFlags_AlwaysClamp) ? p_max : NULL,
38553931
*p_handle);
38563932
}
38573933

38583934
// Frame
3859-
RenderNavHighlight(frame_bb, id);
3935+
RenderNavCursor(frame_bb, id);
38603936
RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
38613937

38623938
// Slider behavior
38633939
ImRect grab_bb_min, grab_bb_max;
38643940
int handle_int = *p_handle;
3865-
const bool value_changed = SliderBehaviorRange(frame_bb, id, data_type, p_v_min, p_v_max, p_min, p_max, format, format_max, flags, &grab_bb_min, &grab_bb_max, &handle_int, p_step);
3941+
const bool value_changed = SliderBehaviorRange(frame_bb, id, data_type, p_v_min, p_v_max, p_min, p_max, format_min, format_max, flags, &grab_bb_min, &grab_bb_max, &handle_int, p_step);
38663942
*p_handle = (ImS8)handle_int;
38673943
if (value_changed)
38683944
MarkItemEdited(id);
@@ -3894,9 +3970,28 @@ bool ImGui::SliderScalarRange2(const char* label, ImGuiDataType data_type, void*
38943970

38953971
// Display value
38963972
char value_buf[128];
3897-
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_v_min, format);
3973+
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_v_min, format_min);
38983974
value_buf_end += ImFormatString((char*)value_buf_end, (size_t)(value_buf + IM_ARRAYSIZE(value_buf) - value_buf_end), " - ");
38993975
value_buf_end = value_buf_end + DataTypeFormatString((char*)value_buf_end, (size_t)(value_buf + IM_ARRAYSIZE(value_buf) - value_buf_end), data_type, p_v_max, format_max);
3976+
3977+
// Optionally display range size if format includes it
3978+
if (format_range)
3979+
{
3980+
// Calculate range size (max - min)
3981+
ImGuiDataTypeStorage range_size;
3982+
if (data_type == ImGuiDataType_Float)
3983+
{
3984+
float range = *(const float*)p_v_max - *(const float*)p_v_min;
3985+
memcpy(&range_size, &range, sizeof(float));
3986+
}
3987+
else if (data_type == ImGuiDataType_S32)
3988+
{
3989+
int range = *(const int*)p_v_max - *(const int*)p_v_min;
3990+
memcpy(&range_size, &range, sizeof(int));
3991+
}
3992+
value_buf_end += ImFormatString((char*)value_buf_end, (size_t)(value_buf + IM_ARRAYSIZE(value_buf) - value_buf_end), " ");
3993+
value_buf_end = value_buf_end + DataTypeFormatString((char*)value_buf_end, (size_t)(value_buf + IM_ARRAYSIZE(value_buf) - value_buf_end), data_type, &range_size, format_range);
3994+
}
39003995
RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
39013996

39023997
if (label_size.x > 0.0f)
@@ -3906,14 +4001,14 @@ bool ImGui::SliderScalarRange2(const char* label, ImGuiDataType data_type, void*
39064001
return value_changed;
39074002
}
39084003

3909-
bool ImGui::SliderFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_min, float v_max, const char* format, const char* format_max, ImGuiSliderFlags flags, float step)
4004+
bool ImGui::SliderFloatRange2(const char* label, float* v_current_min, float* v_current_max, float v_min, float v_max, const char* format, ImGuiSliderFlags flags, float step)
39104005
{
3911-
return SliderScalarRange2(label, ImGuiDataType_Float, v_current_min, v_current_max, &v_min, &v_max, format, format_max, flags, step > 0.0f ? &step : NULL);
4006+
return SliderScalarRange2(label, ImGuiDataType_Float, v_current_min, v_current_max, &v_min, &v_max, format, flags, step > 0.0f ? &step : NULL);
39124007
}
39134008

3914-
bool ImGui::SliderIntRange2(const char* label, int* v_current_min, int* v_current_max, int v_min, int v_max, const char* format, const char* format_max, ImGuiSliderFlags flags, int step)
4009+
bool ImGui::SliderIntRange2(const char* label, int* v_current_min, int* v_current_max, int v_min, int v_max, const char* format, ImGuiSliderFlags flags, int step)
39154010
{
3916-
return SliderScalarRange2(label, ImGuiDataType_S32, v_current_min, v_current_max, &v_min, &v_max, format, format_max, flags, step > 0 ? &step : NULL);
4011+
return SliderScalarRange2(label, ImGuiDataType_S32, v_current_min, v_current_max, &v_min, &v_max, format, flags, step > 0 ? &step : NULL);
39174012
}
39184013

39194014
bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, ImGuiSliderFlags flags)
@@ -4219,10 +4314,11 @@ static bool TempInputScalarRange2(const ImRect& bb, ImGuiID id, const char* labe
42194314
if (format_max[0] == 0)
42204315
format_max = format;
42214316

4222-
// Format initial value: just the selected handle's value (user can type "..." to edit both)
4223-
void* p_selected = (selected_handle == 0) ? p_data_min : p_data_max;
4224-
const char* selected_format = (selected_handle == 0) ? format : format_max;
4225-
ImGui::DataTypeFormatString(data_buf, IM_COUNTOF(data_buf), data_type, p_selected, selected_format);
4317+
// Format initial value: show "min...max" format by default
4318+
char* p = data_buf;
4319+
p += ImGui::DataTypeFormatString(p, IM_COUNTOF(data_buf), data_type, p_data_min, format);
4320+
p += ImFormatString(p, (size_t)(data_buf + IM_COUNTOF(data_buf) - p), "...");
4321+
p += ImGui::DataTypeFormatString(p, (size_t)(data_buf + IM_COUNTOF(data_buf) - p), data_type, p_data_max, format_max);
42264322
ImStrTrimBlanks(data_buf);
42274323

42284324
ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
@@ -4236,11 +4332,30 @@ static bool TempInputScalarRange2(const ImRect& bb, ImGuiID id, const char* labe
42364332
memcpy(&data_backup_min, p_data_min, data_type_size);
42374333
memcpy(&data_backup_max, p_data_max, data_type_size);
42384334

4239-
// Look for "..." separator - if present, update both values
4240-
const char* separator = strstr(data_buf, "...");
4335+
// Look for separator: ".." (any number of dots) or " - "
4336+
const char* separator = NULL;
4337+
const char* separator_end = NULL;
4338+
for (const char* s = data_buf; *s; s++)
4339+
{
4340+
if (s[0] == '.' && s[1] == '.')
4341+
{
4342+
separator = s;
4343+
separator_end = s;
4344+
while (*separator_end == '.')
4345+
separator_end++;
4346+
break;
4347+
}
4348+
if (s[0] == ' ' && s[1] == '-' && s[2] == ' ')
4349+
{
4350+
separator = s;
4351+
separator_end = s + 3;
4352+
break;
4353+
}
4354+
}
4355+
42414356
if (separator)
42424357
{
4243-
// Parse min value (before "...")
4358+
// Parse min value (before separator)
42444359
char min_buf[32];
42454360
size_t min_len = (size_t)(separator - data_buf);
42464361
if (min_len >= IM_COUNTOF(min_buf))
@@ -4249,10 +4364,9 @@ static bool TempInputScalarRange2(const ImRect& bb, ImGuiID id, const char* labe
42494364
min_buf[min_len] = 0;
42504365
ImStrTrimBlanks(min_buf);
42514366

4252-
// Parse max value (after "...")
4253-
const char* max_str = separator + 3;
4367+
// Parse max value (after separator)
42544368
char max_buf[32];
4255-
ImStrncpy(max_buf, max_str, IM_COUNTOF(max_buf));
4369+
ImStrncpy(max_buf, separator_end, IM_COUNTOF(max_buf));
42564370
ImStrTrimBlanks(max_buf);
42574371

42584372
// Apply values
@@ -4267,16 +4381,28 @@ static bool TempInputScalarRange2(const ImRect& bb, ImGuiID id, const char* labe
42674381
}
42684382
else
42694383
{
4270-
// No separator - update only the selected handle
4271-
ImGui::DataTypeApplyFromText(data_buf, data_type, p_selected, selected_format, NULL);
4272-
4273-
// Ensure min <= max after update
4274-
if (ImGui::DataTypeCompare(data_type, p_data_min, p_data_max) > 0)
4384+
// No separator - update selected handle only, or both if bar was selected
4385+
if (selected_handle == -1)
42754386
{
4276-
if (selected_handle == 0)
4277-
memcpy(p_data_min, p_data_max, data_type_size); // Clamp min to max
4278-
else
4279-
memcpy(p_data_max, p_data_min, data_type_size); // Clamp max to min
4387+
// Bar selected: set both to same value
4388+
ImGui::DataTypeApplyFromText(data_buf, data_type, p_data_min, format, NULL);
4389+
memcpy(p_data_max, p_data_min, data_type_size);
4390+
}
4391+
else
4392+
{
4393+
// Handle selected: update only that handle
4394+
void* p_target = (selected_handle == 0) ? p_data_min : p_data_max;
4395+
const char* target_format = (selected_handle == 0) ? format : format_max;
4396+
ImGui::DataTypeApplyFromText(data_buf, data_type, p_target, target_format, NULL);
4397+
4398+
// Ensure min <= max after update
4399+
if (ImGui::DataTypeCompare(data_type, p_data_min, p_data_max) > 0)
4400+
{
4401+
if (selected_handle == 0)
4402+
memcpy(p_data_min, p_data_max, data_type_size); // Clamp min to max
4403+
else
4404+
memcpy(p_data_max, p_data_min, data_type_size); // Clamp max to min
4405+
}
42804406
}
42814407
}
42824408

0 commit comments

Comments
 (0)