diff --git a/imgui.cpp b/imgui.cpp index 54a107ef..9cce2940 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8631,7 +8631,7 @@ bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const c char fmt_buf[32]; char data_buf[32]; - format = ParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf)); + format = ImParseFormatTrimDecorations(format, fmt_buf, IM_ARRAYSIZE(fmt_buf)); DataTypeFormatString(data_buf, IM_ARRAYSIZE(data_buf), data_type, data_ptr, format); ImStrTrimBlanks(data_buf); ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal); @@ -8647,7 +8647,8 @@ bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const c return false; } -const char* ImGui::ParseFormatTrimDecorationsLeading(const char* fmt) +// We don't use strchr() because our strings are usually very short and often start with '%' +const char* ImParseFormatFindStart(const char* fmt) { while (char c = fmt[0]) { @@ -8660,36 +8661,44 @@ const char* ImGui::ParseFormatTrimDecorationsLeading(const char* fmt) return fmt; } +const char* ImParseFormatFindEnd(const char* fmt) +{ + // Printf/scanf types modifiers: I/L/h/j/l/t/w/z. Other uppercase letters qualify as types aka end of the format. + IM_ASSERT(fmt[0] == '%'); + const unsigned int ignored_uppercase_mask = (1 << ('I'-'A')) | (1 << ('L'-'A')); + const unsigned int ignored_lowercase_mask = (1 << ('h'-'a')) | (1 << ('j'-'a')) | (1 << ('l'-'a')) | (1 << ('t'-'a')) | (1 << ('w'-'a')) | (1 << ('z'-'a')); + for (char c; (c = *fmt) != 0; fmt++) + { + if (c >= 'A' && c <= 'Z' && ((1 << (c - 'A')) & ignored_uppercase_mask) == 0) + return fmt + 1; + if (c >= 'a' && c <= 'z' && ((1 << (c - 'a')) & ignored_lowercase_mask) == 0) + return fmt + 1; + } + return fmt; +} + // Extract the format out of a format string with leading or trailing decorations // fmt = "blah blah" -> return fmt // fmt = "%.3f" -> return fmt // fmt = "hello %.3f" -> return fmt + 6 // fmt = "%.3f hello" -> return buf written with "%.3f" -const char* ImGui::ParseFormatTrimDecorations(const char* fmt, char* buf, int buf_size) +const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, int buf_size) { - // We don't use strchr() because our strings are usually very short and often start with '%' - const char* fmt_start = ParseFormatTrimDecorationsLeading(fmt); + const char* fmt_start = ImParseFormatFindStart(fmt); if (fmt_start[0] != '%') return fmt; - fmt = fmt_start; - while (char c = *fmt++) - { - if (c >= 'A' && c <= 'Z' && (c != 'L')) // L is a type modifier, other letters qualify as types aka end of the format - break; - if (c >= 'a' && c <= 'z' && (c != 'h' && c != 'j' && c != 'l' && c != 't' && c != 'w' && c != 'z')) // h/j/l/t/w/z are type modifiers, other letters qualify as types aka end of the format - break; - } - if (fmt[0] == 0) // If we only have leading decoration, we don't need to copy the data. + const char* fmt_end = ImParseFormatFindEnd(fmt_start); + if (fmt_end[0] == 0) // If we only have leading decoration, we don't need to copy the data. return fmt_start; - ImStrncpy(buf, fmt_start, ImMin((int)(fmt + 1 - fmt_start), buf_size)); + ImStrncpy(buf, fmt_start, ImMin((int)(fmt_end + 1 - fmt_start), buf_size)); return buf; } // Parse display precision back from the display format string // FIXME: This is still used by some navigation code path to infer a minimum tweak step, but we should aim to rework widgets so it isn't needed. -int ImGui::ParseFormatPrecision(const char* fmt, int default_precision) +int ImParseFormatPrecision(const char* fmt, int default_precision) { - fmt = ParseFormatTrimDecorationsLeading(fmt); + fmt = ImParseFormatFindStart(fmt); if (fmt[0] != '%') return default_precision; fmt++; @@ -8717,13 +8726,6 @@ static float GetMinimumStepAtDecimalPrecision(int decimal_precision) return (decimal_precision >= 0 && decimal_precision < 10) ? min_steps[decimal_precision] : powf(10.0f, (float)-decimal_precision); } -float ImGui::RoundScalarWithFormat(const char* format, float value) -{ - char buf[64]; - ImFormatString(buf, IM_ARRAYSIZE(buf), ParseFormatTrimDecorationsLeading(format), value); - return (float)atof(buf); -} - static inline float SliderBehaviorCalcRatioFromValue(float v, float v_min, float v_max, float power, float linear_zero_pos) { if (v_min == v_max) @@ -8762,7 +8764,7 @@ bool ImGui::SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v const bool is_non_linear = (power < 1.0f-0.00001f) || (power > 1.0f+0.00001f); const bool is_horizontal = (flags & ImGuiSliderFlags_Vertical) == 0; - const bool is_decimal = ParseFormatPrecision(format, 3) != 0; + const bool is_decimal = ImParseFormatPrecision(format, 3) != 0; const float grab_padding = 2.0f; const float slider_sz = is_horizontal ? (frame_bb.GetWidth() - grab_padding * 2.0f) : (frame_bb.GetHeight() - grab_padding * 2.0f); @@ -8877,7 +8879,9 @@ bool ImGui::SliderBehavior(const ImRect& frame_bb, ImGuiID id, float* v, float v } // Round past decimal precision - new_value = RoundScalarWithFormat(format, new_value); + char buf[64]; + ImFormatString(buf, IM_ARRAYSIZE(buf), ImParseFormatFindStart(format), new_value); + new_value = (float)atof(buf); if (*v != new_value) { *v = new_value; @@ -9155,7 +9159,7 @@ static bool ImGui::DragBehaviorT(ImGuiID id, ImGuiDataType data_type, TYPE* v, f } if (g.ActiveIdSource == ImGuiInputSource_Nav) { - int decimal_precision = ParseFormatPrecision(format, 3); + int decimal_precision = ImParseFormatPrecision(format, 3); adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard|ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f/10.0f, 10.0f).x; v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision)); } @@ -9198,7 +9202,7 @@ static bool ImGui::DragBehaviorT(ImGuiID id, ImGuiDataType data_type, TYPE* v, f // Round to user desired precision based on format string char v_str[64]; - ImFormatString(v_str, IM_ARRAYSIZE(v_str), ParseFormatTrimDecorationsLeading(format), v_cur); + ImFormatString(v_str, IM_ARRAYSIZE(v_str), ImParseFormatFindStart(format), v_cur); if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) v_cur = (TYPE)atof(v_str); else diff --git a/imgui_internal.h b/imgui_internal.h index f612c8af..3cd5cc1b 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -121,6 +121,10 @@ IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end IMGUI_API void ImStrTrimBlanks(char* str); IMGUI_API int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3); IMGUI_API int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) IM_FMTLIST(3); +IMGUI_API const char* ImParseFormatFindStart(const char* format); +IMGUI_API const char* ImParseFormatFindEnd(const char* format); +IMGUI_API const char* ImParseFormatTrimDecorations(const char* format, char* buf, int buf_size); +IMGUI_API int ImParseFormatPrecision(const char* format, int default_value); // Helpers: Math // We are keeping those not leaking to the user by default, in the case the user has implicit cast operators between ImVec2 and its own types (when IM_VEC2_CLASS_EXTRA is defined) @@ -1118,11 +1122,6 @@ namespace ImGui IMGUI_API void PlotEx(ImGuiPlotType plot_type, const char* label, float (*values_getter)(void* data, int idx), void* data, int values_count, int values_offset, const char* overlay_text, float scale_min, float scale_max, ImVec2 graph_size); - IMGUI_API const char* ParseFormatTrimDecorationsLeading(const char* format); - IMGUI_API const char* ParseFormatTrimDecorations(const char* format, char* buf, int buf_size); - IMGUI_API int ParseFormatPrecision(const char* format, int default_value); - IMGUI_API float RoundScalarWithFormat(const char* format, float value); - // Shade functions (write over already created vertices) IMGUI_API void ShadeVertsLinearColorGradientKeepAlpha(ImDrawVert* vert_start, ImDrawVert* vert_end, ImVec2 gradient_p0, ImVec2 gradient_p1, ImU32 col0, ImU32 col1); IMGUI_API void ShadeVertsLinearAlphaGradientForLeftToRightText(ImDrawVert* vert_start, ImDrawVert* vert_end, float gradient_p0_x, float gradient_p1_x);