Drags, Sliders: Added ImGuiDragFlags_ClampOnInput/ImGuiSliderFlags_ClampOnInput flags to force clamping value when using CTRL+Click to type in a value manually. (#1829, #3209)

This commit is contained in:
omar 2020-07-23 17:39:22 +02:00 committed by ocornut
parent 7607aea018
commit 170d02bd99
4 changed files with 70 additions and 36 deletions

View File

@ -74,6 +74,8 @@ Other Changes:
- Drag, Slider: Added ImGuiDragFlags_Logarithmic/ImGuiSliderFlags_Logarithmic flags to - Drag, Slider: Added ImGuiDragFlags_Logarithmic/ImGuiSliderFlags_Logarithmic flags to
enable logarithmic editing (generally more precision around zero), as a replacement to the old enable logarithmic editing (generally more precision around zero), as a replacement to the old
'float power' parameter which was obsoleted. (#1823, #1316, #642) [@Shironekoben, @AndrewBelt] 'float power' parameter which was obsoleted. (#1823, #1316, #642) [@Shironekoben, @AndrewBelt]
- Drag, Slider: Added ImGuiDragFlags_ClampOnInput/ImGuiSliderFlags_ClampOnInput flags to force
clamping value when using CTRL+Click to type in a value manually. (#1829, #3209, #946, #413).
- DragFloatRange2, DragIntRange2: Fixed an issue allowing to drag out of bounds when both - DragFloatRange2, DragIntRange2: Fixed an issue allowing to drag out of bounds when both
min and max value are on the same value. (#1441) min and max value are on the same value. (#1441)
- InputText, ImDrawList: Fixed assert triggering when drawing single line of text with more - InputText, ImDrawList: Fixed assert triggering when drawing single line of text with more

View File

@ -464,6 +464,7 @@ namespace ImGui
// - CTRL+Click on any drag box to turn them into an input box. Manually input values aren't clamped and can go off-bounds. // - CTRL+Click on any drag box to turn them into an input box. Manually input values aren't clamped and can go off-bounds.
// - For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every functions, note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x // - For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every functions, note that a 'float v[X]' function argument is the same as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible. You can pass address of your first element out of a contiguous set, e.g. &myvector.x
// - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc.
// - Format string may also be set to NULL or use the default format ("%f" or "%d").
// - Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For gamepad/keyboard navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision). // - Speed are per-pixel of mouse movement (v_speed=0.2f: mouse needs to move by 5 pixels to increase value by 1). For gamepad/keyboard navigation, minimum speed is Max(v_speed, minimum_step_at_given_precision).
// - Use v_min < v_max to clamp edits to given limits. Note that CTRL+Click manual input can override those limits. // - Use v_min < v_max to clamp edits to given limits. Note that CTRL+Click manual input can override those limits.
// - Use v_max = FLT_MAX / INT_MAX etc to avoid clamping to a maximum, same with v_min = -FLT_MAX / INT_MIN to avoid clamping to a minimum. // - Use v_max = FLT_MAX / INT_MAX etc to avoid clamping to a maximum, same with v_min = -FLT_MAX / INT_MIN to avoid clamping to a minimum.
@ -485,6 +486,7 @@ namespace ImGui
// Widgets: Sliders // Widgets: Sliders
// - CTRL+Click on any slider to turn them into an input box. Manually input values aren't clamped and can go off-bounds. // - CTRL+Click on any slider to turn them into an input box. Manually input values aren't clamped and can go off-bounds.
// - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc.
// - Format string may also be set to NULL or use the default format ("%f" or "%d").
// - Legacy: Pre-1.78 there are SliderXXX() function signatures that takes a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument. // - Legacy: Pre-1.78 there are SliderXXX() function signatures that takes a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument.
// If you get a warning converting a float to ImGuiSliderFlags, read https://github.com/ocornut/imgui/issues/3361 // If you get a warning converting a float to ImGuiSliderFlags, read https://github.com/ocornut/imgui/issues/3361
IMGUI_API bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0); // adjust format to decorate the value with a prefix or a suffix for in-slider labels or unit display. IMGUI_API bool SliderFloat(const char* label, float* v, float v_min, float v_max, const char* format = "%.3f", ImGuiSliderFlags flags = 0); // adjust format to decorate the value with a prefix or a suffix for in-slider labels or unit display.
@ -1293,7 +1295,8 @@ enum ImGuiDragFlags_
{ {
ImGuiDragFlags_None = 0, ImGuiDragFlags_None = 0,
ImGuiDragFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed. ImGuiDragFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed.
ImGuiDragFlags_Logarithmic = 1 << 4 // Should this widget be logarithmic? (linear otherwise) ImGuiDragFlags_ClampOnInput = 1 << 4, // Clamp value to min/max bounds (if any) when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds.
ImGuiDragFlags_Logarithmic = 1 << 5 // Should this widget be logarithmic? (linear otherwise)
}; };
// Flags for SliderFloat(), SliderInt() etc. // Flags for SliderFloat(), SliderInt() etc.
@ -1301,7 +1304,8 @@ enum ImGuiSliderFlags_
{ {
ImGuiSliderFlags_None = 0, ImGuiSliderFlags_None = 0,
ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed. ImGuiSliderFlags_InvalidMask_ = 0x7000000F, // [Internal] We treat using those bits as being potentially a 'float power' argument from the previous API that has got miscast to this enum, and will trigger an assert if needed.
ImGuiSliderFlags_Logarithmic = 1 << 4 // Should this widget be logarithmic? (linear otherwise) ImGuiSliderFlags_ClampOnInput = 1 << 4, // Clamp value to min/max bounds when input manually with CTRL+Click. By default CTRL+Click allows going out of bounds.
ImGuiSliderFlags_Logarithmic = 1 << 5 // Should this widget be logarithmic? (linear otherwise)
}; };
// Identify a mouse button. // Identify a mouse button.

View File

@ -1488,6 +1488,38 @@ static void ShowDemoWindowWidgets()
ImGui::TreePop(); ImGui::TreePop();
} }
if (ImGui::TreeNode("Drag/Slider Flags"))
{
// Demonstrate using advanced flags for DragXXX functions
static ImGuiDragFlags drag_flags = ImGuiDragFlags_None;
ImGui::CheckboxFlags("ImGuiDragFlags_ClampOnInput", (unsigned int*)&drag_flags, ImGuiDragFlags_ClampOnInput);
ImGui::SameLine(); HelpMarker("Always clamp value to min/max bounds (if any) when input manually with CTRL+Click.");
ImGui::CheckboxFlags("ImGuiDragFlags_Logarithmic", (unsigned int*)&drag_flags, ImGuiDragFlags_Logarithmic);
ImGui::SameLine(); HelpMarker("Enable logarithmic editing (more precision for small values).");
static float drag_f = 0.5f;
static int drag_i = 50;
ImGui::DragFloat("DragFloat (0 -> 1)", &drag_f, 0.005f, 0.0f, 1.0f, "%f", drag_flags);
ImGui::DragFloat("DragFloat (0 -> +inf)", &drag_f, 0.005f, 0.0f, FLT_MAX, "%f", drag_flags);
ImGui::DragFloat("DragFloat (-inf -> 1)", &drag_f, 0.005f, -FLT_MAX, 1.0f, "%f", drag_flags);
ImGui::DragFloat("DragFloat (-inf -> +inf)", &drag_f, 0.005f, -FLT_MAX, +FLT_MAX, "%f", drag_flags);
ImGui::DragInt("DragInt (0 -> 100)", &drag_i, 0.5f, 0, 100, "%d", drag_flags);
// Demonstrate using advanced flags for SliderXXX functions
static ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None;
ImGui::CheckboxFlags("ImGuiSliderFlags_ClampOnInput", (unsigned int*)&slider_flags, ImGuiSliderFlags_ClampOnInput);
ImGui::SameLine(); HelpMarker("Always clamp value to min/max bounds (if any) when input manually with CTRL+Click.");
ImGui::CheckboxFlags("ImGuiSliderFlags_Logarithmic", (unsigned int*)&slider_flags, ImGuiSliderFlags_Logarithmic);
ImGui::SameLine(); HelpMarker("Enable logarithmic editing (more precision for small values).");
static float slider_f = 0.5f;
static int slider_i = 50;
ImGui::SliderFloat("SliderFloat (0 -> 1)", &slider_f, 0.0f, 1.0f, NULL, slider_flags);
ImGui::SliderInt("SliderInt (0 -> 100)", &slider_i, 0, 100, NULL, slider_flags);
ImGui::TreePop();
}
if (ImGui::TreeNode("Range Widgets")) if (ImGui::TreeNode("Range Widgets"))
{ {
static float begin = 10, end = 90; static float begin = 10, end = 90;
@ -3853,10 +3885,9 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
"rebuild the font atlas, and call style.ScaleAllSizes() on a reference ImGuiStyle structure.\n" "rebuild the font atlas, and call style.ScaleAllSizes() on a reference ImGuiStyle structure.\n"
"Using those settings here will give you poor quality results."); "Using those settings here will give you poor quality results.");
static float window_scale = 1.0f; static float window_scale = 1.0f;
if (ImGui::DragFloat("window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f")) // Scale only this window if (ImGui::DragFloat("window scale", &window_scale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiDragFlags_ClampOnInput)) // Scale only this window
ImGui::SetWindowFontScale(IM_MAX(window_scale, MIN_SCALE)); ImGui::SetWindowFontScale(window_scale);
if (ImGui::DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f")) // Scale everything ImGui::DragFloat("global scale", &io.FontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", ImGuiDragFlags_ClampOnInput); // Scale everything
io.FontGlobalScale = IM_MAX(io.FontGlobalScale, MIN_SCALE);
ImGui::PopItemWidth(); ImGui::PopItemWidth();
ImGui::EndTabItem(); ImGui::EndTabItem();

View File

@ -1910,10 +1910,11 @@ bool ImGui::DataTypeApplyOpFromText(const char* buf, const char* initial_value_b
} }
template<typename T> template<typename T>
static bool ClampBehaviorT(T* v, T v_min, T v_max) static bool ClampBehaviorT(T* v, const T* v_min, const T* v_max)
{ {
if (*v < v_min) { *v = v_min; return true; } // Clamp, both sides are optional
if (*v > v_max) { *v = v_max; return true; } if (v_min && *v < *v_min) { *v = *v_min; return true; }
if (v_max && *v > *v_max) { *v = *v_max; return true; }
return false; return false;
} }
@ -1921,16 +1922,16 @@ bool ImGui::DataTypeClamp(ImGuiDataType data_type, void* p_data, const void* p_m
{ {
switch (data_type) switch (data_type)
{ {
case ImGuiDataType_S8: return ClampBehaviorT<ImS8 >((ImS8* )p_data, *(const ImS8* )p_min, *(const ImS8* )p_max); case ImGuiDataType_S8: return ClampBehaviorT<ImS8 >((ImS8* )p_data, (const ImS8* )p_min, (const ImS8* )p_max);
case ImGuiDataType_U8: return ClampBehaviorT<ImU8 >((ImU8* )p_data, *(const ImU8* )p_min, *(const ImU8* )p_max); case ImGuiDataType_U8: return ClampBehaviorT<ImU8 >((ImU8* )p_data, (const ImU8* )p_min, (const ImU8* )p_max);
case ImGuiDataType_S16: return ClampBehaviorT<ImS16 >((ImS16* )p_data, *(const ImS16* )p_min, *(const ImS16* )p_max); case ImGuiDataType_S16: return ClampBehaviorT<ImS16 >((ImS16* )p_data, (const ImS16* )p_min, (const ImS16* )p_max);
case ImGuiDataType_U16: return ClampBehaviorT<ImU16 >((ImU16* )p_data, *(const ImU16* )p_min, *(const ImU16* )p_max); case ImGuiDataType_U16: return ClampBehaviorT<ImU16 >((ImU16* )p_data, (const ImU16* )p_min, (const ImU16* )p_max);
case ImGuiDataType_S32: return ClampBehaviorT<ImS32 >((ImS32* )p_data, *(const ImS32* )p_min, *(const ImS32* )p_max); case ImGuiDataType_S32: return ClampBehaviorT<ImS32 >((ImS32* )p_data, (const ImS32* )p_min, (const ImS32* )p_max);
case ImGuiDataType_U32: return ClampBehaviorT<ImU32 >((ImU32* )p_data, *(const ImU32* )p_min, *(const ImU32* )p_max); case ImGuiDataType_U32: return ClampBehaviorT<ImU32 >((ImU32* )p_data, (const ImU32* )p_min, (const ImU32* )p_max);
case ImGuiDataType_S64: return ClampBehaviorT<ImS64 >((ImS64* )p_data, *(const ImS64* )p_min, *(const ImS64* )p_max); case ImGuiDataType_S64: return ClampBehaviorT<ImS64 >((ImS64* )p_data, (const ImS64* )p_min, (const ImS64* )p_max);
case ImGuiDataType_U64: return ClampBehaviorT<ImU64 >((ImU64* )p_data, *(const ImU64* )p_min, *(const ImU64* )p_max); case ImGuiDataType_U64: return ClampBehaviorT<ImU64 >((ImU64* )p_data, (const ImU64* )p_min, (const ImU64* )p_max);
case ImGuiDataType_Float: return ClampBehaviorT<float >((float* )p_data, *(const float* )p_min, *(const float* )p_max); case ImGuiDataType_Float: return ClampBehaviorT<float >((float* )p_data, (const float* )p_min, (const float* )p_max);
case ImGuiDataType_Double: return ClampBehaviorT<double>((double*)p_data, *(const double*)p_min, *(const double*)p_max); case ImGuiDataType_Double: return ClampBehaviorT<double>((double*)p_data, (const double*)p_min, (const double*)p_max);
case ImGuiDataType_COUNT: break; case ImGuiDataType_COUNT: break;
} }
IM_ASSERT(0); IM_ASSERT(0);
@ -2197,9 +2198,12 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
} }
} }
// Our current specs do NOT clamp when using CTRL+Click manual input, but we should eventually add a flag for that..
if (temp_input_is_active) if (temp_input_is_active)
return TempInputScalar(frame_bb, id, label, data_type, p_data, format);// , p_min, p_max); {
// Only clamp CTRL+Click input when ImGuiDragFlags_ClampInput is set
const bool is_clamp_input = (flags & ImGuiDragFlags_ClampOnInput) != 0;
return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
}
// Draw frame // Draw frame
const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
@ -2753,9 +2757,12 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
} }
} }
// Our current specs do NOT clamp when using CTRL+Click manual input, but we should eventually add a flag for that..
if (temp_input_is_active) if (temp_input_is_active)
return TempInputScalar(frame_bb, id, label, data_type, p_data, format);// , p_min, p_max); {
// Only clamp CTRL+Click input when ImGuiSliderFlags_ClampInput is set
const bool is_clamp_input = (flags & ImGuiSliderFlags_ClampOnInput) != 0;
return TempInputScalar(frame_bb, id, label, data_type, p_data, format, is_clamp_input ? p_min : NULL, is_clamp_input ? p_max : NULL);
}
// Draw frame // Draw frame
const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg); const ImU32 frame_col = GetColorU32(g.ActiveId == id ? ImGuiCol_FrameBgActive : g.HoveredId == id ? ImGuiCol_FrameBgHovered : ImGuiCol_FrameBg);
@ -3081,20 +3088,10 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char*
return value_changed; return value_changed;
} }
// Note that Drag/Slider functions are currently NOT forwarding the min/max values clamping values! // Note that Drag/Slider functions are only forwarding the min/max values clamping values if the
// ImGuiDragFlags_ClampOnInput / ImGuiSliderFlags_ClampOnInput flag is set!
// This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility. // This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility.
// However this may not be ideal for all uses, as some user code may break on out of bound values. // However this may not be ideal for all uses, as some user code may break on out of bound values.
// In the future we should add flags to Slider/Drag to specify how to enforce min/max values with CTRL+Click.
// See GitHub issues #1829 and #3209
// In the meanwhile, you can easily "wrap" those functions to enforce clamping, using wrapper functions, e.g.
// bool SliderFloatClamp(const char* label, float* v, float v_min, float v_max)
// {
// float v_backup = *v;
// if (!SliderFloat(label, v, v_min, v_max))
// return false;
// *v = ImClamp(*v, v_min, v_max);
// return v_backup != *v;
// }
bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min, const void* p_clamp_max) bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* p_data, const char* format, const void* p_clamp_min, const void* p_clamp_max)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
@ -3117,7 +3114,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG
// Apply new value (or operations) then clamp // Apply new value (or operations) then clamp
DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialTextA.Data, data_type, p_data, NULL); DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialTextA.Data, data_type, p_data, NULL);
if (p_clamp_min && p_clamp_max) if (p_clamp_min || p_clamp_max)
DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max); DataTypeClamp(data_type, p_data, p_clamp_min, p_clamp_max);
// Only mark as edited if new value is different // Only mark as edited if new value is different