Merge branch 'master' into docking

# Conflicts:
#	imgui.cpp
#	imgui.h
#	imgui_internal.h
This commit is contained in:
ocornut
2022-04-05 17:55:53 +02:00
10 changed files with 292 additions and 230 deletions

View File

@@ -2015,23 +2015,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)
@@ -2113,45 +2110,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
@@ -2160,10 +2129,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;
}
@@ -2267,8 +2234,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;
@@ -2864,8 +2831,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)
@@ -2883,8 +2850,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)
@@ -3227,6 +3194,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()
@@ -3290,6 +3259,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)
@@ -3336,6 +3356,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.
@@ -3348,7 +3376,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))
{
@@ -3358,7 +3387,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)
@@ -3391,12 +3420,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();
@@ -3823,7 +3852,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;
@@ -4209,16 +4238,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.
@@ -4229,7 +4257,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);
@@ -4245,19 +4273,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());
@@ -5464,7 +5491,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)
@@ -5472,11 +5499,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))
@@ -6949,7 +6973,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))
{
@@ -6960,18 +6984,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
{