@@ -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
34873487static 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+
34893511template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
34903512static 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
39194014bool 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