mirror of
https://github.com/ocornut/imgui.git
synced 2026-09-27 00:13:29 +03:00
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)
This commit is contained in:
parent
57430472af
commit
94d0184527
3 changed files with 165 additions and 38 deletions
6
imgui.h
6
imgui.h
|
|
@ -704,9 +704,9 @@ namespace ImGui
|
|||
IMGUI_API bool SliderInt4(const char* label, int v[4], int v_min, int v_max, const char* format = "%d", ImGuiSliderFlags flags = 0);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -979,8 +979,9 @@ static void DemoWindowWidgetsBasic()
|
|||
static float fmin = 0.25f, fmax = 0.75f;
|
||||
static int imin = 25, imax = 75;
|
||||
ImGui::SliderFloatRange2("range float", &fmin, &fmax, 0.0f, 1.0f);
|
||||
ImGui::SliderFloatRange2("range (size)", &fmin, &fmax, 0.0f, 1.0f, "%.2f...%.2f (%.2f)");
|
||||
ImGui::SliderIntRange2("range int", &imin, &imax, 0, 100);
|
||||
ImGui::SameLine(); HelpMarker("Click and drag handles individually, or drag the bar between them to move both.");
|
||||
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.");
|
||||
}
|
||||
|
||||
ImGui::SeparatorText("Selectors/Pickers");
|
||||
|
|
|
|||
|
|
@ -3486,6 +3486,28 @@ bool ImGui::SliderInt4(const char* label, int v[4], int v_min, int v_max, const
|
|||
// Forward declaration for TempInput function used by SliderScalarRange2
|
||||
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);
|
||||
|
||||
// Helper to parse range format string "%.3f...%.3f" into separate min/max formats
|
||||
// Returns pointer to start of max format (after dots), or NULL if no separator found
|
||||
// Accepts any number of consecutive dots (2+) as separator
|
||||
static const char* ParseRangeFormatSeparator(const char* format)
|
||||
{
|
||||
if (!format)
|
||||
return NULL;
|
||||
const char* p = format;
|
||||
while (*p)
|
||||
{
|
||||
if (p[0] == '.' && p[1] == '.')
|
||||
{
|
||||
// Found at least "..", skip all consecutive dots
|
||||
while (*p == '.')
|
||||
p++;
|
||||
return p; // Return start of max format
|
||||
}
|
||||
p++;
|
||||
}
|
||||
return NULL; // No separator found
|
||||
}
|
||||
|
||||
template<typename TYPE, typename SIGNEDTYPE, typename FLOATTYPE>
|
||||
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)
|
||||
{
|
||||
|
|
@ -3554,8 +3576,11 @@ static bool SliderBehaviorRangeT(const ImRect& bb, ImGuiID id, ImGuiDataType dat
|
|||
|
||||
if (dist_min <= handle_threshold || dist_max <= handle_threshold)
|
||||
{
|
||||
// Click near a handle
|
||||
*p_handle = (dist_min <= dist_max) ? 0 : 1;
|
||||
// Click near a handle - if handles overlap, use undetermined state (-2)
|
||||
if (ImAbs(grab_min_pos - grab_max_pos) < 1.0f)
|
||||
*p_handle = -2; // Undetermined: will be resolved by drag direction
|
||||
else
|
||||
*p_handle = (dist_min <= dist_max) ? 0 : 1;
|
||||
}
|
||||
else if (click_pos > grab_min_pos && click_pos < grab_max_pos)
|
||||
{
|
||||
|
|
@ -3613,6 +3638,19 @@ static bool SliderBehaviorRangeT(const ImRect& bb, ImGuiID id, ImGuiDataType dat
|
|||
grab_max_pos = grab_pos_from_value(*v_max_val);
|
||||
}
|
||||
}
|
||||
else if (*p_handle == -2)
|
||||
{
|
||||
// Undetermined state (handles overlapping): resolve based on drag direction
|
||||
float delta = mouse_abs_pos - g.IO.MouseClickedPos[0][axis];
|
||||
if (ImAbs(delta) > 1.0f)
|
||||
{
|
||||
// Resolved: positive delta = max handle, negative = min handle (for X axis)
|
||||
*p_handle = (delta > 0.0f) ? 1 : 0;
|
||||
if (axis == ImGuiAxis_Y)
|
||||
*p_handle = 1 - *p_handle; // Invert for Y axis
|
||||
}
|
||||
// Don't set value until resolved
|
||||
}
|
||||
else
|
||||
{
|
||||
// Single handle dragging
|
||||
|
|
@ -3753,7 +3791,7 @@ static bool SliderBehaviorRange(const ImRect& bb, ImGuiID id, ImGuiDataType data
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
{
|
||||
ImGuiWindow* window = GetCurrentWindow();
|
||||
if (window->SkipItems)
|
||||
|
|
@ -3773,11 +3811,49 @@ bool ImGui::SliderScalarRange2(const char* label, ImGuiDataType data_type, void*
|
|||
if (!ItemAdd(total_bb, id, &frame_bb, temp_input_allowed ? ImGuiItemFlags_Inputable : 0))
|
||||
return false;
|
||||
|
||||
// Default format
|
||||
// Default format and parse "min...max" format string
|
||||
if (format == NULL)
|
||||
format = DataTypeGetInfo(data_type)->PrintFmt;
|
||||
if (format_max == NULL)
|
||||
format_max = format;
|
||||
|
||||
// Parse format string: "%.3f...%.3f" or "%.3f...%.3f (%.3f)" for range size display
|
||||
char format_min_buf[64];
|
||||
char format_max_buf[64];
|
||||
const char* format_min = format;
|
||||
const char* format_max = format; // Default: same format for both
|
||||
const char* format_range = NULL; // Optional range size format
|
||||
const char* separator = ParseRangeFormatSeparator(format);
|
||||
if (separator)
|
||||
{
|
||||
// Copy min format (everything before the dots)
|
||||
const char* dots = format;
|
||||
while (dots < separator && !(dots[0] == '.' && dots[1] == '.'))
|
||||
dots++;
|
||||
size_t min_len = ImMin((size_t)(dots - format), sizeof(format_min_buf) - 1);
|
||||
memcpy(format_min_buf, format, min_len);
|
||||
format_min_buf[min_len] = '\0';
|
||||
format_min = format_min_buf;
|
||||
|
||||
// Check for range size format: look for " (" or " [" followed by "%"
|
||||
const char* range_start = separator;
|
||||
while (*range_start && *range_start != '(' && *range_start != '[')
|
||||
range_start++;
|
||||
if (*range_start && strchr(range_start, '%'))
|
||||
{
|
||||
// Found range size format - copy max format (between separator and range_start)
|
||||
size_t max_len = ImMin((size_t)(range_start - separator), sizeof(format_max_buf) - 1);
|
||||
// Trim trailing spaces
|
||||
while (max_len > 0 && separator[max_len - 1] == ' ')
|
||||
max_len--;
|
||||
memcpy(format_max_buf, separator, max_len);
|
||||
format_max_buf[max_len] = '\0';
|
||||
format_max = format_max_buf;
|
||||
format_range = range_start;
|
||||
}
|
||||
else
|
||||
{
|
||||
format_max = separator;
|
||||
}
|
||||
}
|
||||
|
||||
const bool hovered = ItemHoverable(frame_bb, id, g.LastItemData.ItemFlags);
|
||||
|
||||
|
|
@ -3848,21 +3924,21 @@ bool ImGui::SliderScalarRange2(const char* label, ImGuiDataType data_type, void*
|
|||
|
||||
if (temp_input_is_active)
|
||||
{
|
||||
// Ctrl+Click text input: shows selected handle value, user can type "XXX...YYYY" to set both
|
||||
return TempInputScalarRange2(frame_bb, id, label, data_type, p_v_min, p_v_max, format, format_max,
|
||||
// Ctrl+Click text input: shows "min...max" format, user can type to set values
|
||||
return TempInputScalarRange2(frame_bb, id, label, data_type, p_v_min, p_v_max, format_min, format_max,
|
||||
(flags & ImGuiSliderFlags_AlwaysClamp) ? p_min : NULL,
|
||||
(flags & ImGuiSliderFlags_AlwaysClamp) ? p_max : NULL,
|
||||
*p_handle);
|
||||
}
|
||||
|
||||
// Frame
|
||||
RenderNavHighlight(frame_bb, id);
|
||||
RenderNavCursor(frame_bb, id);
|
||||
RenderFrame(frame_bb.Min, frame_bb.Max, GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : hovered ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg), true, style.FrameRounding);
|
||||
|
||||
// Slider behavior
|
||||
ImRect grab_bb_min, grab_bb_max;
|
||||
int handle_int = *p_handle;
|
||||
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);
|
||||
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);
|
||||
*p_handle = (ImS8)handle_int;
|
||||
if (value_changed)
|
||||
MarkItemEdited(id);
|
||||
|
|
@ -3894,9 +3970,28 @@ bool ImGui::SliderScalarRange2(const char* label, ImGuiDataType data_type, void*
|
|||
|
||||
// Display value
|
||||
char value_buf[128];
|
||||
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_v_min, format);
|
||||
const char* value_buf_end = value_buf + DataTypeFormatString(value_buf, IM_ARRAYSIZE(value_buf), data_type, p_v_min, format_min);
|
||||
value_buf_end += ImFormatString((char*)value_buf_end, (size_t)(value_buf + IM_ARRAYSIZE(value_buf) - value_buf_end), " - ");
|
||||
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);
|
||||
|
||||
// Optionally display range size if format includes it
|
||||
if (format_range)
|
||||
{
|
||||
// Calculate range size (max - min)
|
||||
ImGuiDataTypeStorage range_size;
|
||||
if (data_type == ImGuiDataType_Float)
|
||||
{
|
||||
float range = *(const float*)p_v_max - *(const float*)p_v_min;
|
||||
memcpy(&range_size, &range, sizeof(float));
|
||||
}
|
||||
else if (data_type == ImGuiDataType_S32)
|
||||
{
|
||||
int range = *(const int*)p_v_max - *(const int*)p_v_min;
|
||||
memcpy(&range_size, &range, sizeof(int));
|
||||
}
|
||||
value_buf_end += ImFormatString((char*)value_buf_end, (size_t)(value_buf + IM_ARRAYSIZE(value_buf) - value_buf_end), " ");
|
||||
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);
|
||||
}
|
||||
RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImVec2(0.5f, 0.5f));
|
||||
|
||||
if (label_size.x > 0.0f)
|
||||
|
|
@ -3906,14 +4001,14 @@ bool ImGui::SliderScalarRange2(const char* label, ImGuiDataType data_type, void*
|
|||
return value_changed;
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
{
|
||||
return SliderScalarRange2(label, ImGuiDataType_Float, v_current_min, v_current_max, &v_min, &v_max, format, format_max, flags, step > 0.0f ? &step : NULL);
|
||||
return SliderScalarRange2(label, ImGuiDataType_Float, v_current_min, v_current_max, &v_min, &v_max, format, flags, step > 0.0f ? &step : NULL);
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
{
|
||||
return SliderScalarRange2(label, ImGuiDataType_S32, v_current_min, v_current_max, &v_min, &v_max, format, format_max, flags, step > 0 ? &step : NULL);
|
||||
return SliderScalarRange2(label, ImGuiDataType_S32, v_current_min, v_current_max, &v_min, &v_max, format, flags, step > 0 ? &step : NULL);
|
||||
}
|
||||
|
||||
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
|
|||
if (format_max[0] == 0)
|
||||
format_max = format;
|
||||
|
||||
// Format initial value: just the selected handle's value (user can type "..." to edit both)
|
||||
void* p_selected = (selected_handle == 0) ? p_data_min : p_data_max;
|
||||
const char* selected_format = (selected_handle == 0) ? format : format_max;
|
||||
ImGui::DataTypeFormatString(data_buf, IM_COUNTOF(data_buf), data_type, p_selected, selected_format);
|
||||
// Format initial value: show "min...max" format by default
|
||||
char* p = data_buf;
|
||||
p += ImGui::DataTypeFormatString(p, IM_COUNTOF(data_buf), data_type, p_data_min, format);
|
||||
p += ImFormatString(p, (size_t)(data_buf + IM_COUNTOF(data_buf) - p), "...");
|
||||
p += ImGui::DataTypeFormatString(p, (size_t)(data_buf + IM_COUNTOF(data_buf) - p), data_type, p_data_max, format_max);
|
||||
ImStrTrimBlanks(data_buf);
|
||||
|
||||
ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | (ImGuiInputTextFlags)ImGuiInputTextFlags_LocalizeDecimalPoint;
|
||||
|
|
@ -4236,11 +4332,30 @@ static bool TempInputScalarRange2(const ImRect& bb, ImGuiID id, const char* labe
|
|||
memcpy(&data_backup_min, p_data_min, data_type_size);
|
||||
memcpy(&data_backup_max, p_data_max, data_type_size);
|
||||
|
||||
// Look for "..." separator - if present, update both values
|
||||
const char* separator = strstr(data_buf, "...");
|
||||
// Look for separator: ".." (any number of dots) or " - "
|
||||
const char* separator = NULL;
|
||||
const char* separator_end = NULL;
|
||||
for (const char* s = data_buf; *s; s++)
|
||||
{
|
||||
if (s[0] == '.' && s[1] == '.')
|
||||
{
|
||||
separator = s;
|
||||
separator_end = s;
|
||||
while (*separator_end == '.')
|
||||
separator_end++;
|
||||
break;
|
||||
}
|
||||
if (s[0] == ' ' && s[1] == '-' && s[2] == ' ')
|
||||
{
|
||||
separator = s;
|
||||
separator_end = s + 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (separator)
|
||||
{
|
||||
// Parse min value (before "...")
|
||||
// Parse min value (before separator)
|
||||
char min_buf[32];
|
||||
size_t min_len = (size_t)(separator - data_buf);
|
||||
if (min_len >= IM_COUNTOF(min_buf))
|
||||
|
|
@ -4249,10 +4364,9 @@ static bool TempInputScalarRange2(const ImRect& bb, ImGuiID id, const char* labe
|
|||
min_buf[min_len] = 0;
|
||||
ImStrTrimBlanks(min_buf);
|
||||
|
||||
// Parse max value (after "...")
|
||||
const char* max_str = separator + 3;
|
||||
// Parse max value (after separator)
|
||||
char max_buf[32];
|
||||
ImStrncpy(max_buf, max_str, IM_COUNTOF(max_buf));
|
||||
ImStrncpy(max_buf, separator_end, IM_COUNTOF(max_buf));
|
||||
ImStrTrimBlanks(max_buf);
|
||||
|
||||
// Apply values
|
||||
|
|
@ -4267,16 +4381,28 @@ static bool TempInputScalarRange2(const ImRect& bb, ImGuiID id, const char* labe
|
|||
}
|
||||
else
|
||||
{
|
||||
// No separator - update only the selected handle
|
||||
ImGui::DataTypeApplyFromText(data_buf, data_type, p_selected, selected_format, NULL);
|
||||
|
||||
// Ensure min <= max after update
|
||||
if (ImGui::DataTypeCompare(data_type, p_data_min, p_data_max) > 0)
|
||||
// No separator - update selected handle only, or both if bar was selected
|
||||
if (selected_handle == -1)
|
||||
{
|
||||
if (selected_handle == 0)
|
||||
memcpy(p_data_min, p_data_max, data_type_size); // Clamp min to max
|
||||
else
|
||||
memcpy(p_data_max, p_data_min, data_type_size); // Clamp max to min
|
||||
// Bar selected: set both to same value
|
||||
ImGui::DataTypeApplyFromText(data_buf, data_type, p_data_min, format, NULL);
|
||||
memcpy(p_data_max, p_data_min, data_type_size);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Handle selected: update only that handle
|
||||
void* p_target = (selected_handle == 0) ? p_data_min : p_data_max;
|
||||
const char* target_format = (selected_handle == 0) ? format : format_max;
|
||||
ImGui::DataTypeApplyFromText(data_buf, data_type, p_target, target_format, NULL);
|
||||
|
||||
// Ensure min <= max after update
|
||||
if (ImGui::DataTypeCompare(data_type, p_data_min, p_data_max) > 0)
|
||||
{
|
||||
if (selected_handle == 0)
|
||||
memcpy(p_data_min, p_data_max, data_type_size); // Clamp min to max
|
||||
else
|
||||
memcpy(p_data_max, p_data_min, data_type_size); // Clamp max to min
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue