Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/wolfpld/tracy.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'imgui/imgui_widgets.cpp')
-rw-r--r--imgui/imgui_widgets.cpp422
1 files changed, 217 insertions, 205 deletions
diff --git a/imgui/imgui_widgets.cpp b/imgui/imgui_widgets.cpp
index 7cb93667..ffe294dc 100644
--- a/imgui/imgui_widgets.cpp
+++ b/imgui/imgui_widgets.cpp
@@ -1,4 +1,4 @@
-// dear imgui, v1.88
+// dear imgui, v1.89 WIP
// (widgets code)
/*
@@ -611,7 +611,15 @@ 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) ? ImGuiNavReadMode_Repeat : ImGuiNavReadMode_Pressed);
+ bool nav_activated_by_inputs = (g.NavActivatePressedId == id);
+ if (!nav_activated_by_inputs && (flags & ImGuiButtonFlags_Repeat))
+ {
+ // Avoid pressing both keys from triggering double amount of repeat events
+ const ImGuiKeyData* key1 = GetKeyData(ImGuiKey_Space);
+ const ImGuiKeyData* key2 = GetKeyData(ImGuiKey_NavGamepadActivate);
+ const float t1 = ImMax(key1->DownDuration, key2->DownDuration);
+ nav_activated_by_inputs = CalcTypematicRepeatAmount(t1 - g.IO.DeltaTime, t1, g.IO.KeyRepeatDelay, g.IO.KeyRepeatRate) > 0;
+ }
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.
@@ -1036,14 +1044,15 @@ void ImGui::Image(ImTextureID user_texture_id, const ImVec2& size, const ImVec2&
// ImageButton() is flawed as 'id' is always derived from 'texture_id' (see #2464 #1390)
// We provide this internal helper to write your own variant while we figure out how to redesign the public ImageButton() API.
-bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec2& padding, const ImVec4& bg_col, const ImVec4& tint_col)
+bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
return false;
- const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2);
+ const ImVec2 padding = g.Style.FramePadding;
+ const ImRect bb(window->DC.CursorPos, window->DC.CursorPos + size + padding * 2.0f);
ItemSize(bb);
if (!ItemAdd(bb, id))
return false;
@@ -1062,9 +1071,21 @@ bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID texture_id, const ImVec2& size
return pressed;
}
-// frame_padding < 0: uses FramePadding from style (default)
-// frame_padding = 0: no framing
-// frame_padding > 0: set framing size
+bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiWindow* window = g.CurrentWindow;
+ if (window->SkipItems)
+ return false;
+
+ return ImageButtonEx(window->GetID(str_id), user_texture_id, size, uv0, uv1, bg_col, tint_col);
+}
+
+#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+// Legacy API obsoleted in 1.89. Two differences with new ImageButton()
+// - new ImageButton() requires an explicit 'const char* str_id' Old ImageButton() used opaque imTextureId (created issue with: multiple buttons with same image, transient texture id values, opaque computation of ID)
+// - new ImageButton() always use style.FramePadding Old ImageButton() had an override argument.
+// If you need to change padding with new ImageButton() you can use PushStyleVar(ImGuiStyleVar_FramePadding, value), consistent with other Button functions.
bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const ImVec2& uv0, const ImVec2& uv1, int frame_padding, const ImVec4& bg_col, const ImVec4& tint_col)
{
ImGuiContext& g = *GImGui;
@@ -1077,9 +1098,14 @@ bool ImGui::ImageButton(ImTextureID user_texture_id, const ImVec2& size, const I
const ImGuiID id = window->GetID("#image");
PopID();
- const ImVec2 padding = (frame_padding >= 0) ? ImVec2((float)frame_padding, (float)frame_padding) : g.Style.FramePadding;
- return ImageButtonEx(id, user_texture_id, size, uv0, uv1, padding, bg_col, tint_col);
+ if (frame_padding >= 0)
+ PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2((float)frame_padding, (float)frame_padding));
+ bool ret = ImageButtonEx(id, user_texture_id, size, uv0, uv1, bg_col, tint_col);
+ if (frame_padding >= 0)
+ PopStyleVar();
+ return ret;
}
+#endif // #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
bool ImGui::Checkbox(const char* label, bool* v)
{
@@ -1558,12 +1584,12 @@ void ImGui::ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_exc
items[n].Width = width_rounded;
}
while (width_excess > 0.0f)
- for (int n = 0; n < count; n++)
- if (items[n].Width + 1.0f <= items[n].InitialWidth)
- {
- items[n].Width += 1.0f;
- width_excess -= 1.0f;
- }
+ for (int n = 0; n < count && width_excess > 0.0f; n++)
+ {
+ float width_to_add = ImMin(items[n].InitialWidth - items[n].Width, 1.0f);
+ items[n].Width += width_to_add;
+ width_excess -= width_to_add;
+ }
}
//-------------------------------------------------------------------------
@@ -1877,11 +1903,11 @@ bool ImGui::Combo(const char* label, int* current_item, const char* items_separa
//-------------------------------------------------------------------------
// [SECTION] Data Type and Data Formatting Helpers [Internal]
//-------------------------------------------------------------------------
-// - PatchFormatStringFloatToInt()
// - DataTypeGetInfo()
// - DataTypeFormatString()
// - DataTypeApplyOp()
// - DataTypeApplyOpFromText()
+// - DataTypeCompare()
// - DataTypeClamp()
// - GetMinimumStepAtDecimalPrecision
// - RoundScalarWithFormat<>()
@@ -1907,30 +1933,6 @@ static const ImGuiDataTypeInfo GDataTypeInfo[] =
};
IM_STATIC_ASSERT(IM_ARRAYSIZE(GDataTypeInfo) == ImGuiDataType_COUNT);
-// FIXME-LEGACY: Prior to 1.61 our DragInt() function internally used floats and because of this the compile-time default value for format was "%.0f".
-// Even though we changed the compile-time default, we expect users to have carried %f around, which would break the display of DragInt() calls.
-// To honor backward compatibility we are rewriting the format string, unless IMGUI_DISABLE_OBSOLETE_FUNCTIONS is enabled. What could possibly go wrong?!
-static const char* PatchFormatStringFloatToInt(const char* fmt)
-{
- if (fmt[0] == '%' && fmt[1] == '.' && fmt[2] == '0' && fmt[3] == 'f' && fmt[4] == 0) // Fast legacy path for "%.0f" which is expected to be the most common case.
- return "%d";
- const char* fmt_start = ImParseFormatFindStart(fmt); // Find % (if any, and ignore %%)
- const char* fmt_end = ImParseFormatFindEnd(fmt_start); // Find end of format specifier, which itself is an exercise of confidence/recklessness (because snprintf is dependent on libc or user).
- if (fmt_end > fmt_start && fmt_end[-1] == 'f')
- {
-#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
- if (fmt_start == fmt && fmt_end[0] == 0)
- return "%d";
- const char* tmp_format;
- ImFormatStringToTempBuffer(&tmp_format, NULL, "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision.
- return tmp_format;
-#else
- IM_ASSERT(0 && "DragInt(): Invalid format string!"); // Old versions used a default parameter of "%.0f", please replace with e.g. "%d"
-#endif
- }
- return fmt;
-}
-
const ImGuiDataTypeInfo* ImGui::DataTypeGetInfo(ImGuiDataType data_type)
{
IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
@@ -2189,7 +2191,10 @@ 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, ImGuiNavReadMode_RepeatFast, 1.0f / 10.0f, 10.0f)[axis];
+ const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
+ const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
+ const float tweak_factor = tweak_slow ? 1.0f / 1.0f : tweak_fast ? 10.0f : 1.0f;
+ adjust_delta = GetNavTweakPressedAmount(axis) * tweak_factor;
v_speed = ImMax(v_speed, GetMinimumStepAtDecimalPrecision(decimal_precision));
}
adjust_delta *= v_speed;
@@ -2287,6 +2292,7 @@ bool ImGui::DragBehavior(ImGuiID id, ImGuiDataType data_type, void* p_v, float v
ImGuiContext& g = *GImGui;
if (g.ActiveId == id)
{
+ // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation.
if (g.ActiveIdSource == ImGuiInputSource_Mouse && !g.IO.MouseDown[0])
ClearActiveID();
else if (g.ActiveIdSource == ImGuiInputSource_Nav && g.NavActivatePressedId == id && !g.ActiveIdIsJustActivated)
@@ -2340,8 +2346,6 @@ bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data,
// Default format string when passing NULL
if (format == NULL)
format = DataTypeGetInfo(data_type)->PrintFmt;
- else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
- format = PatchFormatStringFloatToInt(format);
const bool hovered = ItemHoverable(frame_bb, id);
bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
@@ -2547,35 +2551,6 @@ bool ImGui::DragIntRange2(const char* label, int* v_current_min, int* v_current_
return value_changed;
}
-#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
-
-// Obsolete versions with power parameter. See https://github.com/ocornut/imgui/issues/3361 for details.
-bool ImGui::DragScalar(const char* label, ImGuiDataType data_type, void* p_data, float v_speed, const void* p_min, const void* p_max, const char* format, float power)
-{
- ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None;
- if (power != 1.0f)
- {
- IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
- IM_ASSERT(p_min != NULL && p_max != NULL); // When using a power curve the drag needs to have known bounds
- drag_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths
- }
- return DragScalar(label, data_type, p_data, v_speed, p_min, p_max, format, drag_flags);
-}
-
-bool ImGui::DragScalarN(const char* label, ImGuiDataType data_type, void* p_data, int components, float v_speed, const void* p_min, const void* p_max, const char* format, float power)
-{
- ImGuiSliderFlags drag_flags = ImGuiSliderFlags_None;
- if (power != 1.0f)
- {
- IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
- IM_ASSERT(p_min != NULL && p_max != NULL); // When using a power curve the drag needs to have known bounds
- drag_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths
- }
- return DragScalarN(label, data_type, p_data, components, v_speed, p_min, p_max, format, drag_flags);
-}
-
-#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
-
//-------------------------------------------------------------------------
// [SECTION] Widgets: SliderScalar, SliderFloat, SliderInt, etc.
//-------------------------------------------------------------------------
@@ -2795,25 +2770,26 @@ bool ImGui::SliderBehaviorT(const ImRect& bb, ImGuiID id, ImGuiDataType data_typ
g.SliderCurrentAccumDirty = false;
}
- 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;
+ float input_delta = (axis == ImGuiAxis_X) ? GetNavTweakPressedAmount(axis) : -GetNavTweakPressedAmount(axis);
if (input_delta != 0.0f)
{
+ const bool tweak_slow = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakSlow : ImGuiKey_NavKeyboardTweakSlow);
+ const bool tweak_fast = IsKeyDown((g.NavInputSource == ImGuiInputSource_Gamepad) ? ImGuiKey_NavGamepadTweakFast : ImGuiKey_NavKeyboardTweakFast);
const int decimal_precision = is_floating_point ? ImParseFormatPrecision(format, 3) : 0;
if (decimal_precision > 0)
{
input_delta /= 100.0f; // Gamepad/keyboard tweak speeds in % of slider bounds
- if (IsNavInputDown(ImGuiNavInput_TweakSlow))
+ if (tweak_slow)
input_delta /= 10.0f;
}
else
{
- if ((v_range >= -100.0f && v_range <= 100.0f) || IsNavInputDown(ImGuiNavInput_TweakSlow))
+ if ((v_range >= -100.0f && v_range <= 100.0f) || tweak_slow)
input_delta = ((input_delta < 0.0f) ? -1.0f : +1.0f) / (float)v_range; // Gamepad/keyboard tweak speeds in integer steps
else
input_delta /= 100.0f;
}
- if (IsNavInputDown(ImGuiNavInput_TweakFast))
+ if (tweak_fast)
input_delta *= 10.0f;
g.SliderCurrentAccum += input_delta;
@@ -2901,6 +2877,7 @@ bool ImGui::SliderBehavior(const ImRect& bb, ImGuiID id, ImGuiDataType data_type
// Read imgui.cpp "API BREAKING CHANGES" section for 1.78 if you hit this assert.
IM_ASSERT((flags == 1 || (flags & ImGuiSliderFlags_InvalidMask_) == 0) && "Invalid ImGuiSliderFlags flag! Has the 'float power' argument been mistakenly cast to flags? Call function with ImGuiSliderFlags_Logarithmic flags instead.");
+ // Those are the things we can do easily outside the SliderBehaviorT<> template, saves code generation.
ImGuiContext& g = *GImGui;
if ((g.LastItemData.InFlags & ImGuiItemFlags_ReadOnly) || (flags & ImGuiSliderFlags_ReadOnly))
return false;
@@ -2960,8 +2937,6 @@ bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_dat
// Default format string when passing NULL
if (format == NULL)
format = DataTypeGetInfo(data_type)->PrintFmt;
- else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
- format = PatchFormatStringFloatToInt(format);
const bool hovered = ItemHoverable(frame_bb, id);
bool temp_input_is_active = temp_input_allowed && TempInputIsActive(id);
@@ -3127,8 +3102,6 @@ bool ImGui::VSliderScalar(const char* label, const ImVec2& size, ImGuiDataType d
// Default format string when passing NULL
if (format == NULL)
format = DataTypeGetInfo(data_type)->PrintFmt;
- else if (data_type == ImGuiDataType_S32 && strcmp(format, "%d") != 0) // (FIXME-LEGACY: Patch old "%.0f" format string to use "%d", read function more details.)
- format = PatchFormatStringFloatToInt(format);
const bool hovered = ItemHoverable(frame_bb, id);
if ((hovered && g.IO.MouseClicked[0]) || g.NavActivateId == id || g.NavActivateInputId == id)
@@ -3175,33 +3148,6 @@ bool ImGui::VSliderInt(const char* label, const ImVec2& size, int* v, int v_min,
return VSliderScalar(label, size, ImGuiDataType_S32, v, &v_min, &v_max, format, flags);
}
-#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
-
-// Obsolete versions with power parameter. See https://github.com/ocornut/imgui/issues/3361 for details.
-bool ImGui::SliderScalar(const char* label, ImGuiDataType data_type, void* p_data, const void* p_min, const void* p_max, const char* format, float power)
-{
- ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None;
- if (power != 1.0f)
- {
- IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
- slider_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths
- }
- return SliderScalar(label, data_type, p_data, p_min, p_max, format, slider_flags);
-}
-
-bool ImGui::SliderScalarN(const char* label, ImGuiDataType data_type, void* v, int components, const void* v_min, const void* v_max, const char* format, float power)
-{
- ImGuiSliderFlags slider_flags = ImGuiSliderFlags_None;
- if (power != 1.0f)
- {
- IM_ASSERT(power == 1.0f && "Call function with ImGuiSliderFlags_Logarithmic flags instead of using the old 'float power' function!");
- slider_flags |= ImGuiSliderFlags_Logarithmic; // Fallback for non-asserting paths
- }
- return SliderScalarN(label, data_type, v, components, v_min, v_max, format, slider_flags);
-}
-
-#endif // IMGUI_DISABLE_OBSOLETE_FUNCTIONS
-
//-------------------------------------------------------------------------
// [SECTION] Widgets: InputScalar, InputFloat, InputInt, etc.
//-------------------------------------------------------------------------
@@ -3449,6 +3395,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* p_data
SetNextItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2));
if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view
value_changed = DataTypeApplyFromText(buf, data_type, p_data, format);
+ IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags);
// Step buttons
const ImVec2 backup_frame_padding = style.FramePadding;
@@ -3603,7 +3550,7 @@ bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, co
bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
{
- IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline()
+ IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() or InputTextEx() manually if you need multi-line + hint.
return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
}
@@ -3694,13 +3641,10 @@ static int is_word_boundary_from_right(ImGuiInputTextState* obj, int idx)
static int is_word_boundary_from_left(ImGuiInputTextState* obj, int idx) { if (obj->Flags & ImGuiInputTextFlags_Password) return 0; return idx > 0 ? (!is_separator(obj->TextW[idx - 1]) && is_separator(obj->TextW[idx])) : 1; }
static int STB_TEXTEDIT_MOVEWORDLEFT_IMPL(ImGuiInputTextState* obj, int idx) { idx--; while (idx >= 0 && !is_word_boundary_from_right(obj, idx)) idx--; return idx < 0 ? 0 : idx; }
static int STB_TEXTEDIT_MOVEWORDRIGHT_MAC(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_left(obj, idx)) idx++; return idx > len ? len : idx; }
-#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
-#ifdef __APPLE__ // FIXME: Move setting to IO structure
-#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_MAC
-#else
static int STB_TEXTEDIT_MOVEWORDRIGHT_WIN(ImGuiInputTextState* obj, int idx) { idx++; int len = obj->CurLenW; while (idx < len && !is_word_boundary_from_right(obj, idx)) idx++; return idx > len ? len : idx; }
-#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_WIN
-#endif
+static int STB_TEXTEDIT_MOVEWORDRIGHT_IMPL(ImGuiInputTextState* obj, int idx) { if (ImGui::GetIO().ConfigMacOSXBehaviors) return STB_TEXTEDIT_MOVEWORDRIGHT_MAC(obj, idx); else return STB_TEXTEDIT_MOVEWORDRIGHT_WIN(obj, idx); }
+#define STB_TEXTEDIT_MOVEWORDLEFT STB_TEXTEDIT_MOVEWORDLEFT_IMPL // They need to be #define for stb_textedit.h
+#define STB_TEXTEDIT_MOVEWORDRIGHT STB_TEXTEDIT_MOVEWORDRIGHT_IMPL
static void STB_TEXTEDIT_DELETECHARS(ImGuiInputTextState* obj, int pos, int n)
{
@@ -3778,11 +3722,12 @@ static void stb_textedit_replace(ImGuiInputTextState* str, STB_TexteditState* st
{
stb_text_makeundo_replace(str, state, 0, str->CurLenW, text_len);
ImStb::STB_TEXTEDIT_DELETECHARS(str, 0, str->CurLenW);
+ state->cursor = state->select_start = state->select_end = 0;
if (text_len <= 0)
return;
if (ImStb::STB_TEXTEDIT_INSERTCHARS(str, 0, text, text_len))
{
- state->cursor = text_len;
+ state->cursor = state->select_start = state->select_end = text_len;
state->has_preferred_x = 0;
return;
}
@@ -3901,6 +3846,13 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f
ImGuiContext& g = *GImGui;
const unsigned c_decimal_point = (unsigned int)g.PlatformLocaleDecimalPoint;
+ // Full-width -> half-width conversion for numeric fields (https://en.wikipedia.org/wiki/Halfwidth_and_Fullwidth_Forms_(Unicode_block)
+ // While this is mostly convenient, this has the side-effect for uninformed users accidentally inputting full-width characters that they may
+ // scratch their head as to why it works in numerical fields vs in generic text fields it would require support in the font.
+ if (flags & (ImGuiInputTextFlags_CharsDecimal | ImGuiInputTextFlags_CharsScientific | ImGuiInputTextFlags_CharsHexadecimal))
+ if (c >= 0xFF01 && c <= 0xFF5E)
+ c = c - 0xFF01 + 0x21;
+
// Allow 0-9 . - + * /
if (flags & ImGuiInputTextFlags_CharsDecimal)
if (!(c >= '0' && c <= '9') && (c != c_decimal_point) && (c != '-') && (c != '+') && (c != '*') && (c != '/'))
@@ -3919,11 +3871,13 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f
// Turn a-z into A-Z
if (flags & ImGuiInputTextFlags_CharsUppercase)
if (c >= 'a' && c <= 'z')
- *p_char = (c += (unsigned int)('A' - 'a'));
+ c += (unsigned int)('A' - 'a');
if (flags & ImGuiInputTextFlags_CharsNoBlank)
if (ImCharIsBlankW(c))
return false;
+
+ *p_char = c;
}
// Custom callback filter
@@ -4149,11 +4103,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
FocusWindow(window);
// Declare our inputs
- IM_ASSERT(ImGuiNavInput_COUNT < 32);
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Left) | (1 << ImGuiDir_Right);
if (is_multiline || (flags & ImGuiInputTextFlags_CallbackHistory))
g.ActiveIdUsingNavDirMask |= (1 << ImGuiDir_Up) | (1 << ImGuiDir_Down);
- g.ActiveIdUsingNavInputMask |= (1 << ImGuiNavInput_Cancel);
+ SetActiveIdUsingKey(ImGuiKey_Escape);
+ SetActiveIdUsingKey(ImGuiKey_NavGamepadCancel);
SetActiveIdUsingKey(ImGuiKey_Home);
SetActiveIdUsingKey(ImGuiKey_End);
if (is_multiline)
@@ -4176,10 +4130,10 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
clear_active_id = true;
// Lock the decision of whether we are going to take the path displaying the cursor or selection
- const bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
+ bool render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
bool render_selection = state && (state->HasSelection() || select_all) && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
bool value_changed = false;
- bool enter_pressed = false;
+ bool validated = false;
// When read-only we always use the live data passed to the function
// FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :(
@@ -4275,10 +4229,12 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
}
else if (io.MouseClicked[0] && !state->SelectedAllMouseLock)
{
- // FIXME: unselect on late click could be done release?
if (hovered)
{
- stb_textedit_click(state, &state->Stb, mouse_x, mouse_y);
+ if (io.KeyShift)
+ stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y);
+ else
+ stb_textedit_click(state, &state->Stb, mouse_x, mouse_y);
state->CursorAnimReset();
}
}
@@ -4291,7 +4247,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (state->SelectedAllMouseLock && !io.MouseDown[0])
state->SelectedAllMouseLock = false;
- // We except backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336)
+ // We expect 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)
@@ -4322,7 +4278,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
}
// Process other shortcuts/key-presses
- bool cancel_edit = false;
+ bool revert_edit = false;
if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id)
{
IM_ASSERT(state != NULL);
@@ -4332,23 +4288,25 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
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 == (ImGuiModFlags_Super | ImGuiModFlags_Shift));
+ const bool is_osx_shift_shortcut = is_osx && (io.KeyMods == (ImGuiMod_Super | ImGuiMod_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 == 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_ctrl_key_only = (io.KeyMods == ImGuiMod_Ctrl);
+ const bool is_shift_key_only = (io.KeyMods == ImGuiMod_Shift);
+ const bool is_shortcut_key = g.IO.ConfigMacOSXBehaviors ? (io.KeyMods == ImGuiMod_Super) : (io.KeyMods == ImGuiMod_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());
const bool is_paste = ((is_shortcut_key && IsKeyPressed(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressed(ImGuiKey_Insert))) && !is_readonly;
const bool is_undo = ((is_shortcut_key && IsKeyPressed(ImGuiKey_Z)) && !is_readonly && is_undoable);
const bool is_redo = ((is_shortcut_key && IsKeyPressed(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressed(ImGuiKey_Z))) && !is_readonly && is_undoable;
+ const bool is_select_all = is_shortcut_key && IsKeyPressed(ImGuiKey_A);
// 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, ImGuiNavReadMode_Pressed) && !IsKeyPressed(ImGuiKey_Space)) || IsNavInputTest(ImGuiNavInput_Input, ImGuiNavReadMode_Pressed);
- const bool is_cancel = IsKeyPressed(ImGuiKey_Escape) || IsNavInputTest(ImGuiNavInput_Cancel, ImGuiNavReadMode_Pressed);
+ const bool nav_gamepad_active = (io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) != 0 && (io.BackendFlags & ImGuiBackendFlags_HasGamepad) != 0;
+ const bool is_enter_pressed = IsKeyPressed(ImGuiKey_Enter, true) || IsKeyPressed(ImGuiKey_KeypadEnter, true);
+ const bool is_gamepad_validate = nav_gamepad_active && (IsKeyPressed(ImGuiKey_NavGamepadActivate, false) || IsKeyPressed(ImGuiKey_NavGamepadInput, false));
+ const bool is_cancel = IsKeyPressed(ImGuiKey_Escape, false) || (nav_gamepad_active && IsKeyPressed(ImGuiKey_NavGamepadCancel, false));
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); }
@@ -4370,12 +4328,17 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
}
state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask);
}
- else if (is_validate_enter)
+ else if (is_enter_pressed || is_gamepad_validate)
{
+ // Determine if we turn Enter into a \n character
bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0;
- if (!is_multiline || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
+ if (!is_multiline || is_gamepad_validate || (ctrl_enter_for_new_line && !io.KeyCtrl) || (!ctrl_enter_for_new_line && io.KeyCtrl))
{
- enter_pressed = clear_active_id = true;
+ validated = true;
+ if (io.ConfigInputTextEnterKeepActive && !is_multiline)
+ state->SelectAll(); // No need to scroll
+ else
+ clear_active_id = true;
}
else if (!is_readonly)
{
@@ -4384,21 +4347,32 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
state->OnKeyPressed((int)c);
}
}
- else if (is_validate_nav)
- {
- IM_ASSERT(!is_validate_enter);
- enter_pressed = clear_active_id = true;
- }
else if (is_cancel)
{
- clear_active_id = cancel_edit = true;
+ if (flags & ImGuiInputTextFlags_EscapeClearsAll)
+ {
+ if (state->CurLenA > 0)
+ {
+ revert_edit = true;
+ }
+ else
+ {
+ render_cursor = render_selection = false;
+ clear_active_id = true;
+ }
+ }
+ else
+ {
+ clear_active_id = revert_edit = true;
+ render_cursor = render_selection = false;
+ }
}
else if (is_undo || is_redo)
{
state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO);
state->ClearSelection();
}
- else if (is_shortcut_key && IsKeyPressed(ImGuiKey_A))
+ else if (is_select_all)
{
state->SelectAll();
state->CursorFollow = true;
@@ -4462,11 +4436,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (g.ActiveId == id)
{
IM_ASSERT(state != NULL);
- if (cancel_edit)
+ if (revert_edit && !is_readonly)
{
- // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
- if (!is_readonly && strcmp(buf, state->InitialTextA.Data) != 0)
+ if (flags & ImGuiInputTextFlags_EscapeClearsAll)
+ {
+ // Clear input
+ apply_new_text = "";
+ apply_new_text_length = 0;
+ STB_TEXTEDIT_CHARTYPE empty_string;
+ stb_textedit_replace(state, &state->Stb, &empty_string, 0);
+ }
+ else if (strcmp(buf, state->InitialTextA.Data) != 0)
{
+ // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents.
// Push records into the undo stack so we can CTRL+Z the revert operation itself
apply_new_text = state->InitialTextA.Data;
apply_new_text_length = state->InitialTextA.Size - 1;
@@ -4491,7 +4473,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// When using 'ImGuiInputTextFlags_EnterReturnsTrue' as a special case we reapply the live buffer back to the input buffer before clearing ActiveId, even though strictly speaking it wasn't modified on this frame.
// If we didn't do that, code like InputInt() with ImGuiInputTextFlags_EnterReturnsTrue would fail.
// This also allows the user to use InputText() with ImGuiInputTextFlags_EnterReturnsTrue without maintaining any user-side storage (please note that if you use this property along ImGuiInputTextFlags_CallbackResize you can end up with your temporary string object unnecessarily allocating once a frame, either store your string data, either if you don't then don't use ImGuiInputTextFlags_CallbackResize).
- const bool apply_edit_back_to_user_buffer = !cancel_edit || (enter_pressed && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
+ const bool apply_edit_back_to_user_buffer = !revert_edit || (validated && (flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0);
if (apply_edit_back_to_user_buffer)
{
// Apply new value immediately - copy modified buffer back
@@ -4566,6 +4548,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (callback_data.SelectionEnd != utf8_selection_end || buf_dirty) { state->Stb.select_end = (callback_data.SelectionEnd == callback_data.SelectionStart) ? state->Stb.select_start : ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); }
if (buf_dirty)
{
+ IM_ASSERT((flags & ImGuiInputTextFlags_ReadOnly) == 0);
IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text!
InputTextReconcileUndoStateAfterUserCallback(state, callback_data.Buf, callback_data.BufTextLen); // FIXME: Move the rest of this block inside function and rename to InputTextReconcileStateAfterUserCallback() ?
if (callback_data.BufTextLen > backup_current_text_length && is_resizable)
@@ -4584,9 +4567,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
apply_new_text_length = state->CurLenA;
}
}
-
- // Clear temporary user storage
- state->Flags = ImGuiInputTextFlags_None;
}
// Copy result to user buffer. This can currently only happen when (g.ActiveId == id)
@@ -4868,7 +4848,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.LastItemData.StatusFlags);
if ((flags & ImGuiInputTextFlags_EnterReturnsTrue) != 0)
- return enter_pressed;
+ return validated;
else
return value_changed;
}
@@ -5109,16 +5089,19 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
if (BeginPopup("picker"))
{
- picker_active_window = g.CurrentWindow;
- if (label != label_display_end)
+ if (g.CurrentWindow->BeginCount == 1)
{
- TextEx(label, label_display_end);
- Spacing();
+ picker_active_window = g.CurrentWindow;
+ if (label != label_display_end)
+ {
+ TextEx(label, label_display_end);
+ Spacing();
+ }
+ ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
+ ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
+ SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
+ value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
}
- ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags_DataTypeMask_ | ImGuiColorEditFlags_PickerMask_ | ImGuiColorEditFlags_InputMask_ | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
- ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags_DisplayMask_ | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
- SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
- value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
EndPopup();
}
}
@@ -5162,7 +5145,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
bool accepted_drag_drop = false;
if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_3F))
{
- memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512
+ memcpy((float*)col, payload->Data, sizeof(float) * 3); // Preserve alpha if any //-V512 //-V1086
value_changed = accepted_drag_drop = true;
}
if (const ImGuiPayload* payload = AcceptDragDropPayload(IMGUI_PAYLOAD_TYPE_COLOR_4F))
@@ -5181,7 +5164,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
if (picker_active_window && g.ActiveId != 0 && g.ActiveIdWindow == picker_active_window)
g.LastItemData.ID = g.ActiveId;
- if (value_changed)
+ if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId
MarkItemEdited(g.LastItemData.ID);
return value_changed;
@@ -5569,7 +5552,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl
if (value_changed && memcmp(backup_initial_col, col, components * sizeof(float)) == 0)
value_changed = false;
- if (value_changed)
+ if (value_changed && g.LastItemData.ID != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId
MarkItemEdited(g.LastItemData.ID);
PopID();
@@ -5904,7 +5887,14 @@ bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char
return TreeNodeBehavior(window->GetID(ptr_id), flags, label, label_end);
}
-bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
+void ImGui::TreeNodeSetOpen(ImGuiID id, bool open)
+{
+ ImGuiContext& g = *GImGui;
+ ImGuiStorage* storage = g.CurrentWindow->DC.StateStorage;
+ storage->SetInt(id, open ? 1 : 0);
+}
+
+bool ImGui::TreeNodeUpdateNextOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
{
if (flags & ImGuiTreeNodeFlags_Leaf)
return true;
@@ -5920,7 +5910,7 @@ bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
if (g.NextItemData.OpenCond & ImGuiCond_Always)
{
is_open = g.NextItemData.OpenVal;
- storage->SetInt(id, is_open);
+ TreeNodeSetOpen(id, is_open);
}
else
{
@@ -5929,7 +5919,7 @@ bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags)
if (stored_value == -1)
{
is_open = g.NextItemData.OpenVal;
- storage->SetInt(id, is_open);
+ TreeNodeSetOpen(id, is_open);
}
else
{
@@ -5995,7 +5985,7 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
// For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
// This is currently only support 32 level deep and we are fine with (1 << Depth) overflowing into a zero.
const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
- bool is_open = TreeNodeBehaviorIsOpen(id, flags);
+ bool is_open = TreeNodeUpdateNextOpen(id, flags);
if (is_open && !g.NavIdIsAlive && (flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
window->DC.TreeJumpToParentOnPopMask |= (1 << window->DC.TreeDepth);
@@ -7010,10 +7000,10 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
ImVec2 label_size = CalcTextSize(label, NULL, true);
// Odd hack to allow hovering across menus of a same menu-set (otherwise we wouldn't be able to hover parent without always being a Child window)
+ // This is only done for items for the menu set and not the full parent window.
const bool menuset_is_open = IsRootOfOpenMenuSet();
- ImGuiWindow* backed_nav_window = g.NavWindow;
if (menuset_is_open)
- g.NavWindow = window;
+ PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
// The reference position stored in popup_pos will be used by Begin() to find a suitable position for the child menu,
// However the final position is going to be different! It is chosen by FindBestWindowPosForPopup().
@@ -7062,7 +7052,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
const bool hovered = (g.HoveredId == id) && enabled && !g.NavDisableMouseHover;
if (menuset_is_open)
- g.NavWindow = backed_nav_window;
+ PopItemFlag();
bool want_open = false;
bool want_close = false;
@@ -7071,26 +7061,30 @@ 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_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))
+ ImGuiPopupData* child_popup = (g.BeginPopupStack.Size < g.OpenPopupStack.Size) ? &g.OpenPopupStack[g.BeginPopupStack.Size] : NULL; // Popup candidate (testing below)
+ ImGuiWindow* child_menu_window = (child_popup && child_popup->Window && child_popup->Window->ParentWindow == window) ? child_popup->Window : NULL;
+ if (g.HoveredWindow == window && child_menu_window != NULL)
{
float ref_unit = g.FontSize; // FIXME-DPI
+ float child_dir = (window->Pos.x < child_menu_window->Pos.x) ? 1.0f : -1.0f;
ImRect next_window_rect = child_menu_window->Rect();
ImVec2 ta = (g.IO.MousePos - g.IO.MouseDelta);
- ImVec2 tb = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetTL() : next_window_rect.GetTR();
- ImVec2 tc = (window->Pos.x < child_menu_window->Pos.x) ? next_window_rect.GetBL() : next_window_rect.GetBR();
+ ImVec2 tb = (child_dir > 0.0f) ? next_window_rect.GetTL() : next_window_rect.GetTR();
+ ImVec2 tc = (child_dir > 0.0f) ? 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: ??)
+ ta.x += child_dir * -0.5f;
+ tb.x += child_dir * ref_unit;
+ tc.x += child_dir * ref_unit;
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_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]
+ //GetForegroundDrawList()->AddTriangleFilled(ta, tb, tc, moving_toward_child_menu ? IM_COL32(0,128,0,128) : IM_COL32(128,0,0,128)); // [DEBUG]
}
// 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)
+ if (menu_is_open && !hovered && g.HoveredWindow == window && !moving_toward_child_menu && !g.NavDisableMouseHover)
want_close = true;
// Open
@@ -7144,10 +7138,19 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled)
if (menu_is_open)
{
- SetNextWindowPos(popup_pos, ImGuiCond_Always); // Note: this is super misleading! The value will serve as reference for FindBestWindowPosForPopup(), not actual pos.
+ ImGuiLastItemData last_item_in_parent = g.LastItemData;
+ SetNextWindowPos(popup_pos, ImGuiCond_Always); // Note: misleading: the value will serve as reference for FindBestWindowPosForPopup(), not actual pos.
PushStyleVar(ImGuiStyleVar_ChildRounding, style.PopupRounding); // First level will use _PopupRounding, subsequent will use _ChildRounding
- menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
+ menu_is_open = BeginPopupEx(id, flags); // menu_is_open can be 'false' when the popup is completely clipped (e.g. zero size display)
PopStyleVar();
+ if (menu_is_open)
+ {
+ // Restore LastItemData so IsItemXXXX functions can work after BeginMenu()/EndMenu()
+ // (This fixes using IsItemClicked() and IsItemHovered(), but IsItemHovered() also relies on its support for ImGuiItemFlags_NoWindowHoverableCheck)
+ g.LastItemData = last_item_in_parent;
+ if (g.HoveredWindow == window)
+ g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HoveredWindow;
+ }
}
else
{
@@ -7164,17 +7167,18 @@ bool ImGui::BeginMenu(const char* label, bool enabled)
void ImGui::EndMenu()
{
- // Nav: When a left move request _within our child menu_ failed, close ourselves (the _parent_ menu).
- // A menu doesn't close itself because EndMenuBar() wants the catch the last Left<>Right inputs.
- // However, it means that with the current code, a BeginMenu() from outside another menu or a menu-bar won't be closable with the Left direction.
+ // Nav: When a left move request our menu failed, close ourselves.
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
- if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet() && window->DC.LayoutType == ImGuiLayoutType_Vertical)
- if (g.NavWindow && (g.NavWindow->RootWindowForNav->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->RootWindowForNav->ParentWindow == window)
- {
- ClosePopupToLevel(g.BeginPopupStack.Size, true);
- NavMoveRequestCancel();
- }
+ IM_ASSERT(window->Flags & ImGuiWindowFlags_Popup); // Mismatched BeginMenu()/EndMenu() calls
+ ImGuiWindow* parent_window = window->ParentWindow; // Should always be != NULL is we passed assert.
+ if (window->BeginCount == window->BeginCountPreviousFrame)
+ if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet())
+ if (g.NavWindow && (g.NavWindow->RootWindowForNav == window) && parent_window->DC.LayoutType == ImGuiLayoutType_Vertical)
+ {
+ ClosePopupToLevel(g.BeginPopupStack.Size - 1, true);
+ NavMoveRequestCancel();
+ }
EndPopup();
}
@@ -7190,10 +7194,10 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
ImVec2 pos = window->DC.CursorPos;
ImVec2 label_size = CalcTextSize(label, NULL, true);
+ // See BeginMenuEx() for comments about this.
const bool menuset_is_open = IsRootOfOpenMenuSet();
- ImGuiWindow* backed_nav_window = g.NavWindow;
if (menuset_is_open)
- g.NavWindow = window;
+ PushItemFlag(ImGuiItemFlags_NoWindowHoverableCheck, true);
// We've been using the equivalent of ImGuiSelectableFlags_SetNavIdOnHover on all Selectable() since early Nav system days (commit 43ee5d73),
// but I am unsure whether this should be kept at all. For now moved it to be an opt-in feature used by menus only.
@@ -7245,7 +7249,7 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut
EndDisabled();
PopID();
if (menuset_is_open)
- g.NavWindow = backed_nav_window;
+ PopItemFlag();
return pressed;
}
@@ -7572,8 +7576,8 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
// Additionally, when using TabBarAddTab() to manipulate tab bar order we occasionally insert new tabs that don't have a width yet,
// and we cannot wait for the next BeginTabItem() call. We cannot compute this width within TabBarAddTab() because font size depends on the active window.
const char* tab_name = tab_bar->GetTabName(tab);
- const bool has_close_button = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) ? false : true;
- tab->ContentWidth = (tab->RequestedWidth > 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button).x;
+ const bool has_close_button_or_unsaved_marker = (tab->Flags & ImGuiTabItemFlags_NoCloseButton) == 0 || (tab->Flags & ImGuiTabItemFlags_UnsavedDocument);
+ tab->ContentWidth = (tab->RequestedWidth >= 0.0f) ? tab->RequestedWidth : TabItemCalcSize(tab_name, has_close_button_or_unsaved_marker).x;
int section_n = TabItemGetSectionIdx(tab);
ImGuiTabBarSection* section = &sections[section_n];
@@ -7585,9 +7589,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
ImGuiShrinkWidthItem* shrink_width_item = &g.ShrinkWidthBuffer[shrink_buffer_indexes[section_n]++];
shrink_width_item->Index = tab_n;
shrink_width_item->Width = shrink_width_item->InitialWidth = tab->ContentWidth;
-
- IM_ASSERT(tab->ContentWidth > 0.0f);
- tab->Width = tab->ContentWidth;
+ tab->Width = ImMax(tab->ContentWidth, 1.0f);
}
// Compute total ideal width (used for e.g. auto-resizing a window)
@@ -7617,7 +7619,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
width_excess = (section_0_w + section_2_w) - tab_bar->BarRect.GetWidth(); // Excess used to shrink leading/trailing section
// With ImGuiTabBarFlags_FittingPolicyScroll policy, we will only shrink leading/trailing if the central section is not visible anymore
- if (width_excess > 0.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible))
+ if (width_excess >= 1.0f && ((tab_bar->Flags & ImGuiTabBarFlags_FittingPolicyResizeDown) || !central_section_is_visible))
{
int shrink_data_count = (central_section_is_visible ? sections[1].TabCount : sections[0].TabCount + sections[2].TabCount);
int shrink_data_offset = (central_section_is_visible ? sections[0].TabCount + sections[2].TabCount : 0);
@@ -7631,6 +7633,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
if (shrinked_width < 0.0f)
continue;
+ shrinked_width = ImMax(1.0f, shrinked_width);
int section_n = TabItemGetSectionIdx(tab);
sections[section_n].Width -= (tab->Width - shrinked_width);
tab->Width = shrinked_width;
@@ -7674,7 +7677,7 @@ static void ImGui::TabBarLayout(ImGuiTabBar* tab_bar)
// CTRL+TAB can override visible tab temporarily
if (g.NavWindowingTarget != NULL && g.NavWindowingTarget->DockNode && g.NavWindowingTarget->DockNode->TabBar == tab_bar)
- tab_bar->VisibleTabId = scroll_to_tab_id = g.NavWindowingTarget->ID;
+ tab_bar->VisibleTabId = scroll_to_tab_id = g.NavWindowingTarget->TabId;
// Update scrolling
if (scroll_to_tab_id != 0)
@@ -7785,7 +7788,9 @@ void ImGui::TabBarRemoveTab(ImGuiTabBar* tab_bar, ImGuiID tab_id)
// Called on manual closure attempt
void ImGui::TabBarCloseTab(ImGuiTabBar* tab_bar, ImGuiTabItem* tab)
{
- IM_ASSERT(!(tab->Flags & ImGuiTabItemFlags_Button));
+ if (tab->Flags & ImGuiTabItemFlags_Button)
+ return; // A button appended with TabItemButton().
+
if (!(tab->Flags & ImGuiTabItemFlags_UnsavedDocument))
{
// This will remove a frame of lag for selecting another tab on closure.
@@ -8140,18 +8145,19 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
tab_bar->LastTabItemIdx = (ImS16)tab_bar->Tabs.index_from_ptr(tab);
// Calculate tab contents size
- ImVec2 size = TabItemCalcSize(label, p_open != NULL);
+ ImVec2 size = TabItemCalcSize(label, (p_open != NULL) || (flags & ImGuiTabItemFlags_UnsavedDocument));
tab->RequestedWidth = -1.0f;
if (g.NextItemData.Flags & ImGuiNextItemDataFlags_HasWidth)
size.x = tab->RequestedWidth = g.NextItemData.Width;
if (tab_is_new)
- tab->Width = size.x;
+ tab->Width = ImMax(1.0f, size.x);
tab->ContentWidth = size.x;
tab->BeginOrder = tab_bar->TabsActiveCount++;
const bool tab_bar_appearing = (tab_bar->PrevFrameVisible + 1 < g.FrameCount);
const bool tab_bar_focused = (tab_bar->Flags & ImGuiTabBarFlags_IsFocused) != 0;
const bool tab_appearing = (tab->LastFrameVisible + 1 < g.FrameCount);
+ const bool tab_just_unsaved = (flags & ImGuiTabItemFlags_UnsavedDocument) && !(tab->Flags & ImGuiTabItemFlags_UnsavedDocument);
const bool is_tab_button = (flags & ImGuiTabItemFlags_Button) != 0;
tab->LastFrameVisible = g.FrameCount;
tab->Flags = flags;
@@ -8312,7 +8318,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
SetActiveID(g.MovingWindow->MoveId, g.MovingWindow);
g.ActiveIdClickOffset -= g.MovingWindow->Pos - bb.Min;
g.ActiveIdNoClearOnFocusLoss = true;
- SetActiveIdUsingNavAndKeys();
+ SetActiveIdUsingAllKeyboardKeys();
}
}
}
@@ -8346,7 +8352,7 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
const ImGuiID close_button_id = p_open ? GetIDWithSeed("#CLOSE", NULL, docked_window ? docked_window->ID : id) : 0;
bool just_closed;
bool text_clipped;
- TabItemLabelAndCloseButton(display_draw_list, bb, flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped);
+ TabItemLabelAndCloseButton(display_draw_list, bb, tab_just_unsaved ? (flags & ~ImGuiTabItemFlags_UnsavedDocument) : flags, tab_bar->FramePadding, label, id, close_button_id, tab_contents_visible, &just_closed, &text_clipped);
if (just_closed && p_open != NULL)
{
*p_open = false;
@@ -8368,9 +8374,10 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open,
// (We test IsItemHovered() to discard e.g. when another item is active or drag and drop over the tab bar, which g.HoveredId ignores)
// FIXME: This is a mess.
// FIXME: We may want disabled tab to still display the tooltip?
- if (text_clipped && g.HoveredId == id && !held && g.HoveredIdNotActiveTimer > g.TooltipSlowDelay && IsItemHovered())
+ if (text_clipped && g.HoveredId == id && !held)
if (!(tab_bar->Flags & ImGuiTabBarFlags_NoTooltip) && !(tab->Flags & ImGuiTabItemFlags_NoTooltip))
- SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
+ if (IsItemHovered(ImGuiHoveredFlags_DelayNormal))
+ SetTooltip("%.*s", (int)(FindRenderedTextEnd(label) - label), label);
IM_ASSERT(!is_tab_button || !(tab_bar->SelectedTabId == tab->ID && is_tab_button)); // TabItemButton should not be selected
if (is_tab_button)
@@ -8404,18 +8411,23 @@ void ImGui::SetTabItemClosed(const char* label)
}
}
-ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button)
+ImVec2 ImGui::TabItemCalcSize(const char* label, bool has_close_button_or_unsaved_marker)
{
ImGuiContext& g = *GImGui;
ImVec2 label_size = CalcTextSize(label, NULL, true);
ImVec2 size = ImVec2(label_size.x + g.Style.FramePadding.x, label_size.y + g.Style.FramePadding.y * 2.0f);
- if (has_close_button)
+ if (has_close_button_or_unsaved_marker)
size.x += g.Style.FramePadding.x + (g.Style.ItemInnerSpacing.x + g.FontSize); // We use Y intentionally to fit the close button circle.
else
size.x += g.Style.FramePadding.x + 1.0f;
return ImVec2(ImMin(size.x, TabBarCalcMaxTabWidth()), size.y);
}
+ImVec2 ImGui::TabItemCalcSize(ImGuiWindow* window)
+{
+ return TabItemCalcSize(window->Name, window->HasCloseButton || (window->Flags & ImGuiWindowFlags_UnsavedDocument));
+}
+
void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabItemFlags flags, ImU32 col)
{
// While rendering tabs, we trim 1 pixel off the top of our bounding box so they can fit within a regular frame height while looking "detached" from it.