|
|
@@ -167,7 +167,21 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
|
|
|
const ImVec2 text_pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
|
|
|
const float wrap_pos_x = window->DC.TextWrapPos;
|
|
|
const bool wrap_enabled = (wrap_pos_x >= 0.0f);
|
|
|
- if (text_end - text > 2000 && !wrap_enabled)
|
|
|
+ if (text_end - text <= 2000 || wrap_enabled)
|
|
|
+ {
|
|
|
+ // Common case
|
|
|
+ const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
|
|
|
+ const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
|
|
|
+
|
|
|
+ ImRect bb(text_pos, text_pos + text_size);
|
|
|
+ ItemSize(text_size, 0.0f);
|
|
|
+ if (!ItemAdd(bb, 0))
|
|
|
+ return;
|
|
|
+
|
|
|
+ // Render (we don't hide text after ## in this end-user function)
|
|
|
+ RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
|
|
|
+ }
|
|
|
+ else
|
|
|
{
|
|
|
// Long text!
|
|
|
// Perform manual coarse clipping to optimize for long multi-line text
|
|
|
@@ -240,19 +254,6 @@ void ImGui::TextEx(const char* text, const char* text_end, ImGuiTextFlags flags)
|
|
|
ItemSize(text_size, 0.0f);
|
|
|
ItemAdd(bb, 0);
|
|
|
}
|
|
|
- else
|
|
|
- {
|
|
|
- const float wrap_width = wrap_enabled ? CalcWrapWidthForPos(window->DC.CursorPos, wrap_pos_x) : 0.0f;
|
|
|
- const ImVec2 text_size = CalcTextSize(text_begin, text_end, false, wrap_width);
|
|
|
-
|
|
|
- ImRect bb(text_pos, text_pos + text_size);
|
|
|
- ItemSize(text_size, 0.0f);
|
|
|
- if (!ItemAdd(bb, 0))
|
|
|
- return;
|
|
|
-
|
|
|
- // Render (we don't hide text after ## in this end-user function)
|
|
|
- RenderTextWrapped(bb.Min, text_begin, text_end, wrap_width);
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
void ImGui::TextUnformatted(const char* text, const char* text_end)
|
|
|
@@ -547,13 +548,9 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
|
|
|
{
|
|
|
// Poll buttons
|
|
|
int mouse_button_clicked = -1;
|
|
|
- int mouse_button_released = -1;
|
|
|
if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseClicked[0]) { mouse_button_clicked = 0; }
|
|
|
else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseClicked[1]) { mouse_button_clicked = 1; }
|
|
|
else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseClicked[2]) { mouse_button_clicked = 2; }
|
|
|
- if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseReleased[0]) { mouse_button_released = 0; }
|
|
|
- else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseReleased[1]) { mouse_button_released = 1; }
|
|
|
- else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseReleased[2]) { mouse_button_released = 2; }
|
|
|
|
|
|
if (mouse_button_clicked != -1 && g.ActiveId != id)
|
|
|
{
|
|
|
@@ -578,15 +575,21 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
|
|
|
FocusWindow(window);
|
|
|
}
|
|
|
}
|
|
|
- if ((flags & ImGuiButtonFlags_PressedOnRelease) && mouse_button_released != -1)
|
|
|
+ if (flags & ImGuiButtonFlags_PressedOnRelease)
|
|
|
{
|
|
|
- // Repeat mode trumps on release behavior
|
|
|
- const bool has_repeated_at_least_once = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay;
|
|
|
- if (!has_repeated_at_least_once)
|
|
|
- pressed = true;
|
|
|
- if (!(flags & ImGuiButtonFlags_NoNavFocus))
|
|
|
- SetFocusID(id, window);
|
|
|
- ClearActiveID();
|
|
|
+ int mouse_button_released = -1;
|
|
|
+ if ((flags & ImGuiButtonFlags_MouseButtonLeft) && g.IO.MouseReleased[0]) { mouse_button_released = 0; }
|
|
|
+ else if ((flags & ImGuiButtonFlags_MouseButtonRight) && g.IO.MouseReleased[1]) { mouse_button_released = 1; }
|
|
|
+ else if ((flags & ImGuiButtonFlags_MouseButtonMiddle) && g.IO.MouseReleased[2]) { mouse_button_released = 2; }
|
|
|
+ if (mouse_button_released != -1)
|
|
|
+ {
|
|
|
+ const bool has_repeated_at_least_once = (flags & ImGuiButtonFlags_Repeat) && g.IO.MouseDownDurationPrev[mouse_button_released] >= g.IO.KeyRepeatDelay; // Repeat mode trumps on release behavior
|
|
|
+ if (!has_repeated_at_least_once)
|
|
|
+ pressed = true;
|
|
|
+ if (!(flags & ImGuiButtonFlags_NoNavFocus))
|
|
|
+ SetFocusID(id, window);
|
|
|
+ ClearActiveID();
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
// 'Repeat' mode acts when held regardless of _PressedOn flags (see table above).
|
|
|
@@ -608,7 +611,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
|
|
|
if (g.NavActivateDownId == id)
|
|
|
{
|
|
|
bool nav_activated_by_code = (g.NavActivateId == id);
|
|
|
- bool nav_activated_by_inputs = IsNavInputTest(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiInputReadMode_Repeat : ImGuiInputReadMode_Pressed);
|
|
|
+ bool nav_activated_by_inputs = IsNavInputTest(ImGuiNavInput_Activate, (flags & ImGuiButtonFlags_Repeat) ? ImGuiNavReadMode_Repeat : ImGuiNavReadMode_Pressed);
|
|
|
if (nav_activated_by_code || nav_activated_by_inputs)
|
|
|
{
|
|
|
// Set active id so it can be queried by user via IsItemActive(), equivalent of holding the mouse button.
|
|
|
@@ -843,9 +846,8 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
|
|
|
// Render
|
|
|
ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
|
|
|
ImU32 text_col = GetColorU32(ImGuiCol_Text);
|
|
|
- ImVec2 center = bb.GetCenter();
|
|
|
if (hovered || held)
|
|
|
- window->DrawList->AddCircleFilled(center/*+ ImVec2(0.0f, -0.5f)*/, g.FontSize * 0.5f + 1.0f, bg_col, 12);
|
|
|
+ window->DrawList->AddCircleFilled(bb.GetCenter()/*+ ImVec2(0.0f, -0.5f)*/, g.FontSize * 0.5f + 1.0f, bg_col, 12);
|
|
|
RenderArrow(window->DrawList, bb.Min + g.Style.FramePadding, text_col, window->Collapsed ? ImGuiDir_Right : ImGuiDir_Down, 1.0f);
|
|
|
|
|
|
// Switch to moving the window after mouse is moved beyond the initial drag threshold
|
|
|
@@ -857,7 +859,7 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos)
|
|
|
|
|
|
ImGuiID ImGui::GetWindowScrollbarID(ImGuiWindow* window, ImGuiAxis axis)
|
|
|
{
|
|
|
- return window->GetIDNoKeepAlive(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
|
|
|
+ return window->GetID(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY");
|
|
|
}
|
|
|
|
|
|
// Return scrollbar rectangle, must only be called for corresponding axis if window->ScrollbarX/Y is set.
|
|
|
@@ -1286,7 +1288,7 @@ void ImGui::Bullet()
|
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
const ImGuiStyle& style = g.Style;
|
|
|
- const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + g.Style.FramePadding.y * 2), g.FontSize);
|
|
|
+ const float line_height = ImMax(ImMin(window->DC.CurrLineSize.y, g.FontSize + style.FramePadding.y * 2), g.FontSize);
|
|
|
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(g.FontSize, line_height));
|
|
|
ItemSize(bb);
|
|
|
if (!ItemAdd(bb, 0))
|
|
|
@@ -1342,6 +1344,7 @@ void ImGui::NewLine()
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
const ImGuiLayoutType backup_layout_type = window->DC.LayoutType;
|
|
|
window->DC.LayoutType = ImGuiLayoutType_Vertical;
|
|
|
+ window->DC.IsSameLine = false;
|
|
|
if (window->DC.CurrLineSize.y > 0.0f) // In the event that we are on a line with items that is smaller that FontSize high, we will preserve its height.
|
|
|
ItemSize(ImVec2(0, 0));
|
|
|
else
|
|
|
@@ -1734,6 +1737,7 @@ bool ImGui::BeginComboPreview()
|
|
|
window->DC.CursorPos = preview_data->PreviewRect.Min + g.Style.FramePadding;
|
|
|
window->DC.CursorMaxPos = window->DC.CursorPos;
|
|
|
window->DC.LayoutType = ImGuiLayoutType_Horizontal;
|
|
|
+ window->DC.IsSameLine = false;
|
|
|
PushClipRect(preview_data->PreviewRect.Min, preview_data->PreviewRect.Max, true);
|
|
|
|
|
|
return true;
|
|
|
@@ -1759,6 +1763,7 @@ void ImGui::EndComboPreview()
|
|
|
window->DC.CursorPosPrevLine = preview_data->BackupCursorPosPrevLine;
|
|
|
window->DC.PrevLineTextBaseOffset = preview_data->BackupPrevLineTextBaseOffset;
|
|
|
window->DC.LayoutType = preview_data->BackupLayout;
|
|
|
+ window->DC.IsSameLine = false;
|
|
|
preview_data->PreviewRect = ImRect();
|
|
|
}
|
|
|
|
|
|
@@ -2008,23 +2013,20 @@ bool ImGui::DataTypeApplyFromText(const char* buf, ImGuiDataType data_type, void
|
|
|
ImGuiDataTypeTempStorage data_backup;
|
|
|
memcpy(&data_backup, p_data, type_info->Size);
|
|
|
|
|
|
- if (format == NULL)
|
|
|
+ // Sanitize format
|
|
|
+ // For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf
|
|
|
+ char format_sanitized[32];
|
|
|
+ if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
|
|
|
format = type_info->ScanFmt;
|
|
|
-
|
|
|
- if (data_type == ImGuiDataType_S32 || data_type == ImGuiDataType_U32 || data_type == ImGuiDataType_S64 || data_type == ImGuiDataType_U64 || data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
|
|
|
- {
|
|
|
- // For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf
|
|
|
- if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
|
|
|
- format = type_info->ScanFmt;
|
|
|
- if (sscanf(buf, format, p_data) < 1)
|
|
|
- return false;
|
|
|
- }
|
|
|
else
|
|
|
+ format = ImParseFormatSanitizeForScanning(format, format_sanitized, IM_ARRAYSIZE(format_sanitized));
|
|
|
+
|
|
|
+ // Small types need a 32-bit buffer to receive the result from scanf()
|
|
|
+ int v32 = 0;
|
|
|
+ if (sscanf(buf, format, type_info->Size >= 4 ? p_data : &v32) < 1)
|
|
|
+ return false;
|
|
|
+ if (type_info->Size < 4)
|
|
|
{
|
|
|
- // Small types need a 32-bit buffer to receive the result from scanf()
|
|
|
- int v32;
|
|
|
- if (sscanf(buf, format, &v32) < 1)
|
|
|
- return false;
|
|
|
if (data_type == ImGuiDataType_S8)
|
|
|
*(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX);
|
|
|
else if (data_type == ImGuiDataType_U8)
|
|
|
@@ -2106,45 +2108,17 @@ static float GetMinimumStepAtDecimalPrecision(int decimal_precision)
|
|
|
}
|
|
|
|
|
|
template<typename TYPE>
|
|
|
-static const char* ImAtoi(const char* src, TYPE* output)
|
|
|
-{
|
|
|
- int negative = 0;
|
|
|
- if (*src == '-') { negative = 1; src++; }
|
|
|
- if (*src == '+') { src++; }
|
|
|
- TYPE v = 0;
|
|
|
- while (*src >= '0' && *src <= '9')
|
|
|
- v = (v * 10) + (*src++ - '0');
|
|
|
- *output = negative ? -v : v;
|
|
|
- return src;
|
|
|
-}
|
|
|
-
|
|
|
-// Sanitize format
|
|
|
-// - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
|
|
|
-// - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
|
|
|
-static void SanitizeFormatString(const char* fmt, char* fmt_out, size_t fmt_out_size)
|
|
|
-{
|
|
|
- IM_UNUSED(fmt_out_size);
|
|
|
- const char* fmt_end = ImParseFormatFindEnd(fmt);
|
|
|
- IM_ASSERT((size_t)(fmt_end - fmt + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
|
|
|
- while (fmt < fmt_end)
|
|
|
- {
|
|
|
- char c = *(fmt++);
|
|
|
- if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
|
|
|
- *(fmt_out++) = c;
|
|
|
- }
|
|
|
- *fmt_out = 0; // Zero-terminate
|
|
|
-}
|
|
|
-
|
|
|
-template<typename TYPE, typename SIGNEDTYPE>
|
|
|
TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, TYPE v)
|
|
|
{
|
|
|
+ IM_UNUSED(data_type);
|
|
|
+ IM_ASSERT(data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double);
|
|
|
const char* fmt_start = ImParseFormatFindStart(format);
|
|
|
if (fmt_start[0] != '%' || fmt_start[1] == '%') // Don't apply if the value is not visible in the format string
|
|
|
return v;
|
|
|
|
|
|
// Sanitize format
|
|
|
char fmt_sanitized[32];
|
|
|
- SanitizeFormatString(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
|
|
|
+ ImParseFormatSanitizeForPrinting(fmt_start, fmt_sanitized, IM_ARRAYSIZE(fmt_sanitized));
|
|
|
fmt_start = fmt_sanitized;
|
|
|
|
|
|
// Format value with our rounding, and read back
|
|
|
@@ -2153,10 +2127,8 @@ TYPE ImGui::RoundScalarWithFormatT(const char* format, ImGuiDataType data_type,
|
|
|
const char* p = v_str;
|
|
|
while (*p == ' ')
|
|
|
p++;
|
|
|
- if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
|
|
|
- v = (TYPE)ImAtof(p);
|
|
|
- else
|
|
|
- ImAtoi(p, (SIGNEDTYPE*)&v);
|
|
|
+ v = (TYPE)ImAtof(p);
|
|
|
+
|
|
|
return v;
|
|
|
}
|
|
|
|
|
|
@@ -2206,7 +2178,7 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const
|
|
|
else if (g.ActiveIdSource == ImGuiInputSource_Nav)
|
|
|
{
|
|
|
const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
|
|
|
- adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis];
|
|
|
+ adjust_delta = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiNavReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis];
|
|
|
v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
|
|
|
}
|
|
|
adjust_delta *= v_speed;
|
|
|
@@ -2260,8 +2232,8 @@ bool ImGui::DragBehaviorT(ImGuiDataType data_type, TYPE* v, float v_speed, const
|
|
|
}
|
|
|
|
|
|
// Round to user desired precision based on format string
|
|
|
- if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
|
|
|
- v_cur = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_cur);
|
|
|
+ if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
|
|
|
+ v_cur = RoundScalarWithFormatT<TYPE>(format, data_type, v_cur);
|
|
|
|
|
|
// Preserve remainder after rounding has been applied. This also allow slow tweaking of values.
|
|
|
g.DragCurrentAccumDirty = false;
|
|
|
@@ -2754,7 +2726,7 @@ namespace ImGui
|
|
|
|
|
|
float RoundScalarWithFormatFloat(const char* format, ImGuiDataType data_type, float v)
|
|
|
{
|
|
|
- return RoundScalarWithFormatT<float, float>(format, data_type, v);
|
|
|
+ return RoundScalarWithFormatT<float>(format, data_type, v);
|
|
|
}
|
|
|
|
|
|
float SliderCalcRatioFromValueFloat(ImGuiDataType data_type, float v, float v_min, float v_max, float power, float linear_zero_pos)
|
|
|
@@ -2825,7 +2797,7 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ
|
|
|
g.SliderCurrentAccumDirty = false;
|
|
|
}
|
|
|
|
|
|
- const ImVec2 input_delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiInputReadMode_RepeatFast, 0.0f, 0.0f);
|
|
|
+ const ImVec2 input_delta2 = GetNavInputAmount2d(ImGuiNavDirSourceFlags_Keyboard | ImGuiNavDirSourceFlags_PadDPad, ImGuiNavReadMode_RepeatFast, 0.0f, 0.0f);
|
|
|
float input_delta = (axis == ImGuiAxis_X) ? input_delta2.x : -input_delta2.y;
|
|
|
if (input_delta != 0.0f)
|
|
|
{
|
|
|
@@ -2872,8 +2844,8 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ
|
|
|
|
|
|
// Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator
|
|
|
TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
|
|
|
- if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
|
|
|
- v_new = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_new);
|
|
|
+ if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
|
|
|
+ v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
|
|
|
float new_clicked_t = ScaleRatioFromValueT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, v_new, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
|
|
|
|
|
|
if (delta > 0)
|
|
|
@@ -2891,8 +2863,8 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ
|
|
|
TYPE v_new = ScaleValueFromRatioT<TYPE, SIGNEDTYPE, FLOATTYPE>(data_type, clicked_t, v_min, v_max, is_logarithmic, logarithmic_zero_epsilon, zero_deadzone_halfsize);
|
|
|
|
|
|
// Round to user desired precision based on format string
|
|
|
- if (!(flags & ImGuiSliderFlags_NoRoundToFormat))
|
|
|
- v_new = RoundScalarWithFormatT<TYPE, SIGNEDTYPE>(format, data_type, v_new);
|
|
|
+ if (is_floating_point && !(flags & ImGuiSliderFlags_NoRoundToFormat))
|
|
|
+ v_new = RoundScalarWithFormatT<TYPE>(format, data_type, v_new);
|
|
|
|
|
|
// Apply result
|
|
|
if (*v != v_new)
|
|
|
@@ -3235,6 +3207,8 @@ bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, i
|
|
|
// - ImParseFormatFindStart() [Internal]
|
|
|
// - ImParseFormatFindEnd() [Internal]
|
|
|
// - ImParseFormatTrimDecorations() [Internal]
|
|
|
+// - ImParseFormatSanitizeForPrinting() [Internal]
|
|
|
+// - ImParseFormatSanitizeForScanning() [Internal]
|
|
|
// - ImParseFormatPrecision() [Internal]
|
|
|
// - TempInputTextScalar() [Internal]
|
|
|
// - InputScalar()
|
|
|
@@ -3298,6 +3272,57 @@ const char* ImParseFormatTrimDecorations(const char* fmt, char* buf, size_t buf_
|
|
|
return buf;
|
|
|
}
|
|
|
|
|
|
+// Sanitize format
|
|
|
+// - Zero terminate so extra characters after format (e.g. "%f123") don't confuse atof/atoi
|
|
|
+// - stb_sprintf.h supports several new modifiers which format numbers in a way that also makes them incompatible atof/atoi.
|
|
|
+void ImParseFormatSanitizeForPrinting(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
|
|
|
+{
|
|
|
+ const char* fmt_end = ImParseFormatFindEnd(fmt_in);
|
|
|
+ IM_UNUSED(fmt_out_size);
|
|
|
+ IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
|
|
|
+ while (fmt_in < fmt_end)
|
|
|
+ {
|
|
|
+ char c = *fmt_in++;
|
|
|
+ if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
|
|
|
+ *(fmt_out++) = c;
|
|
|
+ }
|
|
|
+ *fmt_out = 0; // Zero-terminate
|
|
|
+}
|
|
|
+
|
|
|
+// - For scanning we need to remove all width and precision fields "%3.7f" -> "%f". BUT don't strip types like "%I64d" which includes digits. ! "%07I64d" -> "%I64d"
|
|
|
+const char* ImParseFormatSanitizeForScanning(const char* fmt_in, char* fmt_out, size_t fmt_out_size)
|
|
|
+{
|
|
|
+ const char* fmt_end = ImParseFormatFindEnd(fmt_in);
|
|
|
+ const char* fmt_out_begin = fmt_out;
|
|
|
+ IM_UNUSED(fmt_out_size);
|
|
|
+ IM_ASSERT((size_t)(fmt_end - fmt_in + 1) < fmt_out_size); // Format is too long, let us know if this happens to you!
|
|
|
+ bool has_type = false;
|
|
|
+ while (fmt_in < fmt_end)
|
|
|
+ {
|
|
|
+ char c = *fmt_in++;
|
|
|
+ if (!has_type && ((c >= '0' && c <= '9') || c == '.'))
|
|
|
+ continue;
|
|
|
+ has_type |= ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')); // Stop skipping digits
|
|
|
+ if (c != '\'' && c != '$' && c != '_') // Custom flags provided by stb_sprintf.h. POSIX 2008 also supports '.
|
|
|
+ *(fmt_out++) = c;
|
|
|
+ }
|
|
|
+ *fmt_out = 0; // Zero-terminate
|
|
|
+ return fmt_out_begin;
|
|
|
+}
|
|
|
+
|
|
|
+template<typename TYPE>
|
|
|
+static const char* ImAtoi(const char* src, TYPE* output)
|
|
|
+{
|
|
|
+ int negative = 0;
|
|
|
+ if (*src == '-') { negative = 1; src++; }
|
|
|
+ if (*src == '+') { src++; }
|
|
|
+ TYPE v = 0;
|
|
|
+ while (*src >= '0' && *src <= '9')
|
|
|
+ v = (v * 10) + (*src++ - '0');
|
|
|
+ *output = negative ? -v : v;
|
|
|
+ return src;
|
|
|
+}
|
|
|
+
|
|
|
// 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 ImParseFormatPrecision(const char* fmt, int default_precision)
|
|
|
@@ -3344,6 +3369,14 @@ bool ImGui::TempInputText(const ImRect& bb, ImGuiID id, const char* label, char*
|
|
|
return value_changed;
|
|
|
}
|
|
|
|
|
|
+static inline ImGuiInputTextFlags InputScalar_DefaultCharsFilter(ImGuiDataType data_type, const char* format)
|
|
|
+{
|
|
|
+ if (data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double)
|
|
|
+ return ImGuiInputTextFlags_CharsScientific;
|
|
|
+ const char format_last_char = format[0] ? format[strlen(format) - 1] : 0;
|
|
|
+ return (format_last_char == 'x' || format_last_char == 'X') ? ImGuiInputTextFlags_CharsHexadecimal : ImGuiInputTextFlags_CharsDecimal;
|
|
|
+}
|
|
|
+
|
|
|
// Note that Drag/Slider functions are only forwarding the min/max values clamping values if the ImGuiSliderFlags_AlwaysClamp flag is set!
|
|
|
// 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.
|
|
|
@@ -3356,7 +3389,8 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG
|
|
|
ImStrTrimBlanks(data_buf);
|
|
|
|
|
|
ImGuiInputTextFlags flags = ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited;
|
|
|
- flags |= ((data_type == ImGuiDataType_Float || data_type == ImGuiDataType_Double) ? ImGuiInputTextFlags_CharsScientific : ImGuiInputTextFlags_CharsDecimal);
|
|
|
+ flags |= InputScalar_DefaultCharsFilter(data_type, format);
|
|
|
+
|
|
|
bool value_changed = false;
|
|
|
if (TempInputText(bb, id, label, data_buf, IM_ARRAYSIZE(data_buf), flags))
|
|
|
{
|
|
|
@@ -3366,7 +3400,7 @@ bool ImGui::TempInputScalar(const ImRect& bb, ImGuiID id, const char* label, ImG
|
|
|
memcpy(&data_backup, p_data, data_type_size);
|
|
|
|
|
|
// Apply new value (or operations) then clamp
|
|
|
- DataTypeApplyFromText(data_buf, data_type, p_data, NULL);
|
|
|
+ DataTypeApplyFromText(data_buf, data_type, p_data, format);
|
|
|
if (p_clamp_min || p_clamp_max)
|
|
|
{
|
|
|
if (p_clamp_min && p_clamp_max && DataTypeCompare(data_type, p_clamp_min, p_clamp_max) > 0)
|
|
|
@@ -3399,12 +3433,12 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
|
|
|
char buf[64];
|
|
|
DataTypeFormatString(buf, IM_ARRAYSIZE(buf), data_type, p_data, format);
|
|
|
|
|
|
- bool value_changed = false;
|
|
|
- if ((flags & (ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
|
|
|
- flags |= ImGuiInputTextFlags_CharsDecimal;
|
|
|
- flags |= ImGuiInputTextFlags_AutoSelectAll;
|
|
|
- flags |= ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
|
|
|
+ // Testing ActiveId as a minor optimization as filtering is not needed until active
|
|
|
+ if (g.ActiveId == 0 && (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_CharsScientific)) == 0)
|
|
|
+ flags |= InputScalar_DefaultCharsFilter(data_type, format);
|
|
|
+ flags |= ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoMarkEdited; // We call MarkItemEdited() ourselves by comparing the actual data rather than the string.
|
|
|
|
|
|
+ bool value_changed = false;
|
|
|
if (p_step != NULL)
|
|
|
{
|
|
|
const float button_size = GetFrameHeight();
|
|
|
@@ -3831,7 +3865,7 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f
|
|
|
if (c < 0x20)
|
|
|
{
|
|
|
bool pass = false;
|
|
|
- pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline));
|
|
|
+ pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); // Note that an Enter KEY will emit \r and be ignored (we poll for KEY in InputText() code)
|
|
|
pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput));
|
|
|
if (!pass)
|
|
|
return false;
|
|
|
@@ -4217,16 +4251,15 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
if (state->SelectedAllMouseLock && !io.MouseDown[0])
|
|
|
state->SelectedAllMouseLock = false;
|
|
|
|
|
|
- // It is ill-defined whether the backend needs to send a \t character when pressing the TAB keys.
|
|
|
- // Win32 and GLFW naturally do it but not SDL.
|
|
|
+ // We except backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336)
|
|
|
+ // (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes)
|
|
|
const bool ignore_char_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper);
|
|
|
if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressed(ImGuiKey_Tab) && !ignore_char_inputs && !io.KeyShift && !is_readonly)
|
|
|
- if (!io.InputQueueCharacters.contains('\t'))
|
|
|
- {
|
|
|
- unsigned int c = '\t'; // Insert TAB
|
|
|
- if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
|
|
|
- state->OnKeyPressed((int)c);
|
|
|
- }
|
|
|
+ {
|
|
|
+ unsigned int c = '\t'; // Insert TAB
|
|
|
+ if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
|
|
|
+ state->OnKeyPressed((int)c);
|
|
|
+ }
|
|
|
|
|
|
// Process regular text input (before we check for Return because using some IME will effectively send a Return?)
|
|
|
// We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters.
|
|
|
@@ -4237,7 +4270,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
{
|
|
|
// Insert character if they pass filtering
|
|
|
unsigned int c = (unsigned int)io.InputQueueCharacters[n];
|
|
|
- if (c == '\t' && io.KeyShift)
|
|
|
+ if (c == '\t') // Skip Tab, see above.
|
|
|
continue;
|
|
|
if (InputTextFilterCharacter(&c, flags, callback, callback_user_data, ImGuiInputSource_Keyboard))
|
|
|
state->OnKeyPressed((int)c);
|
|
|
@@ -4253,19 +4286,18 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
|
|
|
{
|
|
|
IM_ASSERT(state != NULL);
|
|
|
- IM_ASSERT(io.KeyMods == GetMergedKeyModFlags() && "Mismatching io.KeyCtrl/io.KeyShift/io.KeyAlt/io.KeySuper vs io.KeyMods"); // We rarely do this check, but if anything let's do it here.
|
|
|
|
|
|
const int row_count_per_page = ImMax((int)((inner_size.y - style.FramePadding.y) / g.FontSize), 1);
|
|
|
state->Stb.row_count_per_page = row_count_per_page;
|
|
|
|
|
|
const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0);
|
|
|
const bool is_osx = io.ConfigMacOSXBehaviors;
|
|
|
- const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiKeyModFlags_Super | ImGuiKeyModFlags_Shift));
|
|
|
+ const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiModFlags_Super | ImGuiModFlags_Shift));
|
|
|
const bool is_wordmove_key_down = is_osx ? io.KeyAlt : io.KeyCtrl; // OS X style: Text editing cursor movement using Alt instead of Ctrl
|
|
|
const bool is_startend_key_down = is_osx && io.KeySuper && !io.KeyCtrl && !io.KeyAlt; // OS X style: Line/Text Start and End using Cmd+Arrows instead of Home/End
|
|
|
- const bool is_ctrl_key_only = (io.KeyMods == ImGuiKeyModFlags_Ctrl);
|
|
|
- const bool is_shift_key_only = (io.KeyMods == ImGuiKeyModFlags_Shift);
|
|
|
- const bool is_shortcut_key = g.IO.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiKeyModFlags_Super) : (io.KeyMods == ImGuiKeyModFlags_Ctrl);
|
|
|
+ const bool is_ctrl_key_only = (io.KeyMods == ImGuiModFlags_Ctrl);
|
|
|
+ const bool is_shift_key_only = (io.KeyMods == ImGuiModFlags_Shift);
|
|
|
+ const bool is_shortcut_key = g.IO.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiModFlags_Super) : (io.KeyMods == ImGuiModFlags_Ctrl);
|
|
|
|
|
|
const bool is_cut = ((is_shortcut_key && IsKeyPressed(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressed(ImGuiKey_Delete))) && !is_readonly && !is_password && (!is_multiline || state->HasSelection());
|
|
|
const bool is_copy = ((is_shortcut_key && IsKeyPressed(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressed(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection());
|
|
|
@@ -4275,8 +4307,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
|
|
|
|
|
|
// We allow validate/cancel with Nav source (gamepad) to makes it easier to undo an accidental NavInput press with no keyboard wired, but otherwise it isn't very useful.
|
|
|
const bool is_validate_enter = IsKeyPressed(ImGuiKey_Enter) || IsKeyPressed(ImGuiKey_KeypadEnter);
|
|
|
- const bool is_validate_nav = (IsNavInputTest(ImGuiNavInput_Activate, ImGuiInputReadMode_Pressed) && !IsKeyPressed(ImGuiKey_Space)) || IsNavInputTest(ImGuiNavInput_Input, ImGuiInputReadMode_Pressed);
|
|
|
- const bool is_cancel = IsKeyPressed(ImGuiKey_Escape) || IsNavInputTest(ImGuiNavInput_Cancel, ImGuiInputReadMode_Pressed);
|
|
|
+ const bool is_validate_nav = (IsNavInputTest(ImGuiNavInput_Activate, ImGuiNavReadMode_Pressed) && !IsKeyPressed(ImGuiKey_Space)) || IsNavInputTest(ImGuiNavInput_Input, ImGuiNavReadMode_Pressed);
|
|
|
+ const bool is_cancel = IsKeyPressed(ImGuiKey_Escape) || IsNavInputTest(ImGuiNavInput_Cancel, ImGuiNavReadMode_Pressed);
|
|
|
|
|
|
if (IsKeyPressed(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); }
|
|
|
else if (IsKeyPressed(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); }
|
|
|
@@ -5471,7 +5503,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl
|
|
|
// FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip.
|
|
|
// 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip.
|
|
|
// Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set.
|
|
|
-bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, ImVec2 size)
|
|
|
+bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags, const ImVec2& size_arg)
|
|
|
{
|
|
|
ImGuiWindow* window = GetCurrentWindow();
|
|
|
if (window->SkipItems)
|
|
|
@@ -5479,11 +5511,8 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl
|
|
|
|
|
|
ImGuiContext& g = *GImGui;
|
|
|
const ImGuiID id = window->GetID(desc_id);
|
|
|
- float default_size = GetFrameHeight();
|
|
|
- if (size.x == 0.0f)
|
|
|
- size.x = default_size;
|
|
|
- if (size.y == 0.0f)
|
|
|
- size.y = default_size;
|
|
|
+ const float default_size = GetFrameHeight();
|
|
|
+ const ImVec2 size(size_arg.x == 0.0f ? default_size : size_arg.x, size_arg.y == 0.0f ? default_size : size_arg.y);
|
|
|
const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size);
|
|
|
ItemSize(bb, (size.y >= default_size) ? g.Style.FramePadding.y : 0.0f);
|
|
|
if (!ItemAdd(bb, id))
|
|
|
@@ -6721,6 +6750,7 @@ bool ImGui::BeginMenuBar()
|
|
|
// We overwrite CursorMaxPos because BeginGroup sets it to CursorPos (essentially the .EmitItem hack in EndMenuBar() would need something analogous here, maybe a BeginGroupEx() with flags).
|
|
|
window->DC.CursorPos = window->DC.CursorMaxPos = ImVec2(bar_rect.Min.x + window->DC.MenuBarOffset.x, bar_rect.Min.y + window->DC.MenuBarOffset.y);
|
|
|
window->DC.LayoutType = ImGuiLayoutType_Horizontal;
|
|
|
+ window->DC.IsSameLine = false;
|
|
|
window->DC.NavLayerCurrent = ImGuiNavLayer_Menu;
|
|
|
window->DC.MenuBarAppending = true;
|
|
|
AlignTextToFramePadding();
|
|
|
@@ -6764,6 +6794,7 @@ void ImGui::EndMenuBar()
|
|
|
g.GroupStack.back().EmitItem = false;
|
|
|
EndGroup(); // Restore position on layer 0
|
|
|
window->DC.LayoutType = ImGuiLayoutType_Vertical;
|
|
|
+ window->DC.IsSameLine = false;
|
|
|
window->DC.NavLayerCurrent = ImGuiNavLayer_Main;
|
|
|
window->DC.MenuBarAppending = false;
|
|
|
}
|
|
|
@@ -6952,7 +6983,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
|
|
|
{
|
|
|
// Close menu when not hovering it anymore unless we are moving roughly in the direction of the menu
|
|
|
// Implement http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown to avoid using timers, so menus feels more reactive.
|
|
|
- bool moving_toward_other_child_menu = false;
|
|
|
+ bool moving_toward_child_menu = false;
|
|
|
ImGuiWindow* child_menu_window = (g.BeginPopupStack.Size < g.OpenPopupStack.Size && g.OpenPopupStack[g.BeginPopupStack.Size].SourceWindow == window) ? g.OpenPopupStack[g.BeginPopupStack.Size].Window : NULL;
|
|
|
if (g.HoveredWindow == window && child_menu_window != NULL && !(window->Flags & ImGuiWindowFlags_MenuBar))
|
|
|
{
|
|
|
@@ -6963,18 +6994,22 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
|
|
|
ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
|
|
|
float extra = ImClamp(ImFabs(ta.x - tb.x) * 0.30f, ref_unit * 0.5f, ref_unit * 2.5f); // add a bit of extra slack.
|
|
|
ta.x += (window->Pos.x < child_menu_window->Pos.x) ? -0.5f : +0.5f; // to avoid numerical issues (FIXME: ??)
|
|
|
- tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -ref_unit * 8.0f); // triangle is maximum 200 high to limit the slope and the bias toward large sub-menus // FIXME: Multiply by fb_scale?
|
|
|
+ tb.y = ta.y + ImMax((tb.y - extra) - ta.y, -ref_unit * 8.0f); // triangle has maximum height to limit the slope and the bias toward large sub-menus
|
|
|
tc.y = ta.y + ImMin((tc.y + extra) - ta.y, +ref_unit * 8.0f);
|
|
|
- moving_toward_other_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
|
|
|
+ moving_toward_child_menu = ImTriangleContainsPoint(ta, tb, tc, g.IO.MousePos);
|
|
|
//GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_other_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG]
|
|
|
}
|
|
|
- if (menu_is_open && !hovered && g.HoveredWindow == window && g.HoveredIdPreviousFrame != 0 && g.HoveredIdPreviousFrame != id && !moving_toward_other_child_menu)
|
|
|
+
|
|
|
+ // The 'HovereWindow == window' check creates an inconsistency (e.g. moving away from menu slowly tends to hit same window, whereas moving away fast does not)
|
|
|
+ // But we also need to not close the top-menu menu when moving over void. Perhaps we should extend the triangle check to a larger polygon.
|
|
|
+ // (Remember to test this on BeginPopup("A")->BeginMenu("B") sequence which behaves slightly differently as B isn't a Child of A and hovering isn't shared.)
|
|
|
+ if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu)
|
|
|
want_close = true;
|
|
|
|
|
|
// Open
|
|
|
if (!menu_is_open && pressed) // Click/activate to open
|
|
|
want_open = true;
|
|
|
- else if (!menu_is_open && hovered && !moving_toward_other_child_menu) // Hover to open
|
|
|
+ else if (!menu_is_open && hovered && !moving_toward_child_menu) // Hover to open
|
|
|
want_open = true;
|
|
|
if (g.NavId == id && g.NavMoveDir == ImGuiDir_Right) // Nav-Right to open
|
|
|
{
|