From f58bd817e2998489bace1b2dff49884eac790efb Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Tue, 31 May 2022 14:37:57 +0300 Subject: [PATCH 01/12] Tables: Fix drawcall merging of last column. (#4843, #4844) Amend 83d22f4e --- docs/CHANGELOG.txt | 2 ++ imgui.h | 2 +- imgui_tables.cpp | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 5211bf5c198f..cfadb88b3e05 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -88,6 +88,8 @@ Other Changes: - Tables: Fixed incorrect border height used for logic when resizing one of several synchronized instance of a same table ID, when instances have a different height. (#3955). - Tables: Fixed incorrect auto-fit of parent windows when using non-resizable weighted columns. (#5276) +- Tables: Fixed drawcall merging of last column. Depending on some unrelated settings (e.g. BorderH) + merging drawcall of the last column didn't always work (regression since 1.87). (#4843, #4844) [@rokups] - Inputs: Fixed IsMouseClicked() repeat mode rate being half of keyboard repeat rate. - ColorEdit: Fixed text baseline alignment after a SameLine() after a ColorEdit() with visible label. - Menus: Adjusted BeginMenu() closing logic so hovering void or non-MenuItem() in parent window diff --git a/imgui.h b/imgui.h index d0d9390968cc..488b5d1fd0a0 100644 --- a/imgui.h +++ b/imgui.h @@ -65,7 +65,7 @@ Index of this file: // Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens) #define IMGUI_VERSION "1.88 WIP" -#define IMGUI_VERSION_NUM 18724 +#define IMGUI_VERSION_NUM 18725 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) #define IMGUI_HAS_TABLE diff --git a/imgui_tables.cpp b/imgui_tables.cpp index bf04f44b5486..6757999cb705 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2370,7 +2370,7 @@ void ImGui::TableMergeDrawChannels(ImGuiTable* table) // Don't attempt to merge if there are multiple draw calls within the column ImDrawChannel* src_channel = &splitter->_Channels[channel_no]; - if (src_channel->_CmdBuffer.Size > 0 && src_channel->_CmdBuffer.back().ElemCount == 0 && src_channel->_CmdBuffer.back().UserCallback != NULL) // Equivalent of PopUnusedDrawCmd() + if (src_channel->_CmdBuffer.Size > 0 && src_channel->_CmdBuffer.back().ElemCount == 0 && src_channel->_CmdBuffer.back().UserCallback == NULL) // Equivalent of PopUnusedDrawCmd() src_channel->_CmdBuffer.pop_back(); if (src_channel->_CmdBuffer.Size != 1) continue; From 74f7ac04a166c77ef1cbbbebff51e5bfc4fcfa5d Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Tue, 31 May 2022 12:25:28 +0300 Subject: [PATCH 02/12] Misc: Fix font compressor utility warnings. (#5359) --- misc/fonts/binary_to_compressed_c.cpp | 28 +++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/misc/fonts/binary_to_compressed_c.cpp b/misc/fonts/binary_to_compressed_c.cpp index 284f039a2ffd..196c503461ca 100644 --- a/misc/fonts/binary_to_compressed_c.cpp +++ b/misc/fonts/binary_to_compressed_c.cpp @@ -66,7 +66,7 @@ int main(int argc, char** argv) char Encode85Byte(unsigned int x) { x = (x % 85) + 35; - return (x >= '\\') ? x + 1 : x; + return (char)((x >= '\\') ? x + 1 : x); } bool binary_to_compressed_c(const char* filename, const char* symbol, bool use_base85_encoding, bool use_compression, bool use_static) @@ -263,17 +263,17 @@ static int stb_compress_chunk(stb_uchar *history, int best = 2, dist=0; if (q+65536 > end) - match_max = end-q; + match_max = (stb_uint)(end-q); else match_max = 65536; -#define stb__nc(b,d) ((d) <= window && ((b) > 9 || stb_not_crap(b,d))) +#define stb__nc(b,d) ((d) <= window && ((b) > 9 || stb_not_crap((int)(b),(int)(d)))) #define STB__TRY(t,p) /* avoid retrying a match we already tried */ \ - if (p ? dist != q-t : 1) \ + if (p ? dist != (int)(q-t) : 1) \ if ((m = stb_matchlen(t, q, match_max)) > best) \ if (stb__nc(m,q-(t))) \ - best = m, dist = q - (t) + best = m, dist = (int)(q - (t)) // rather than search for all matches, only try 4 candidate locations, // chosen based on 4 different hash functions of different lengths. @@ -299,24 +299,24 @@ static int stb_compress_chunk(stb_uchar *history, if (best < 3) { // fast path literals ++q; } else if (best > 2 && best <= 0x80 && dist <= 0x100) { - outliterals(lit_start, q-lit_start); lit_start = (q += best); + outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best); stb_out(0x80 + best-1); stb_out(dist-1); } else if (best > 5 && best <= 0x100 && dist <= 0x4000) { - outliterals(lit_start, q-lit_start); lit_start = (q += best); + outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best); stb_out2(0x4000 + dist-1); stb_out(best-1); } else if (best > 7 && best <= 0x100 && dist <= 0x80000) { - outliterals(lit_start, q-lit_start); lit_start = (q += best); + outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best); stb_out3(0x180000 + dist-1); stb_out(best-1); } else if (best > 8 && best <= 0x10000 && dist <= 0x80000) { - outliterals(lit_start, q-lit_start); lit_start = (q += best); + outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best); stb_out3(0x100000 + dist-1); stb_out2(best-1); } else if (best > 9 && dist <= 0x1000000) { if (best > 65536) best = 65536; - outliterals(lit_start, q-lit_start); lit_start = (q += best); + outliterals(lit_start, (int)(q-lit_start)); lit_start = (q += best); if (best <= 0x100) { stb_out(0x06); stb_out3(dist-1); @@ -336,10 +336,10 @@ static int stb_compress_chunk(stb_uchar *history, q = start+length; // the literals are everything from lit_start to q - *pending_literals = (q - lit_start); + *pending_literals = (int)(q - lit_start); - stb__running_adler = stb_adler32(stb__running_adler, start, q - start); - return q - start; + stb__running_adler = stb_adler32(stb__running_adler, start, (stb_uint)(q - start)); + return (int)(q - start); } static int stb_compress_inner(stb_uchar *input, stb_uint length) @@ -384,5 +384,5 @@ stb_uint stb_compress(stb_uchar *out, stb_uchar *input, stb_uint length) stb_compress_inner(input, length); - return stb__out - out; + return (stb_uint)(stb__out - out); } From 7354009a73a39ace4d7b1c3ef75403fb968812c4 Mon Sep 17 00:00:00 2001 From: Warren Moore Date: Tue, 31 May 2022 15:06:42 -0700 Subject: [PATCH 03/12] Backends: Metal: Fixed null dereference inside command buffer completion handler. (#5363, #5365) --- backends/imgui_impl_metal.mm | 8 ++++++-- docs/CHANGELOG.txt | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 7023f43ff9b6..199a4c3a228b 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -12,6 +12,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2022-06-01: Metal: Fixed null dereference on exit inside command buffer completion handler. // 2022-04-27: Misc: Store backend data in a per-context struct, allowing to use this backend with multiple contexts. // 2022-01-03: Metal: Ignore ImDrawCmd where ElemCount == 0 (very rare but can technically be manufactured by user code). // 2021-12-30: Metal: Added Metal C++ support. Enable with '#define IMGUI_IMPL_METAL_CPP' in your imconfig.h file. @@ -293,8 +294,11 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id c { dispatch_async(dispatch_get_main_queue(), ^{ ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); - [bd->SharedMetalContext.bufferCache addObject:vertexBuffer]; - [bd->SharedMetalContext.bufferCache addObject:indexBuffer]; + if (bd != NULL) + { + [bd->SharedMetalContext.bufferCache addObject:vertexBuffer]; + [bd->SharedMetalContext.bufferCache addObject:indexBuffer]; + } }); }]; } diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index cfadb88b3e05..ed227d2aa431 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -126,6 +126,7 @@ Other Changes: - Backends: OSX: Monitor NSKeyUp events to catch missing keyUp for key when user press Cmd + key (#5128) [@thedmd] - Backends: OSX, Metal: Store backend data in a per-context struct, allowing to use these backends with multiple contexts. (#5203, #5221, #4141) [@noisewuwei] +- Backends: Metal: Fixed null dereference on exit inside command buffer completion handler. (#5363, #5365) [@warrenm] - Backends: OpenGL3: Partially revert 1.86 change of using glBufferSubData(): now only done on Windows and Intel GPU, based on querying glGetString(GL_VENDOR). Essentially we got report of accumulating leaks on Intel with multi-viewports when using simple glBufferData() without orphaning, and report of corruptions on other From 17b8c3b6eaeffe5d9f2be01f829df1e1a0e4a350 Mon Sep 17 00:00:00 2001 From: JJCUBER <34446698+JJCUBER@users.noreply.github.com> Date: Thu, 2 Jun 2022 08:32:43 -0400 Subject: [PATCH 04/12] Demo: Removed redundant window flag (#5367) --- imgui_demo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 743d3dcbbcf7..edf3ff4cff30 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -7358,7 +7358,7 @@ static void ShowExampleAppSimpleOverlay(bool* p_open) static void ShowExampleAppFullscreen(bool* p_open) { static bool use_work_area = true; - static ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings; + static ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoSavedSettings; // We demonstrate using the full viewport area or the work area (without menu-bars, task-bars etc.) // Based on your use case you may want one of the other. From 65c4c0a490f64e7929a63c43be05748b34ec6228 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 2 Jun 2022 16:55:13 +0200 Subject: [PATCH 05/12] IO: Added SetAppAcceptingEvents() function (#4921, #4858) --- docs/CHANGELOG.txt | 3 +++ imgui.cpp | 23 +++++++++++++++++++---- imgui.h | 8 +++++--- 3 files changed, 27 insertions(+), 7 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index ed227d2aa431..155a85bbada6 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -65,6 +65,9 @@ Other Changes: or interacting with a game/3D view). - IO: Fixed input queue trickling of mouse wheel events: multiple wheel events are merged, while a mouse pos followed by a mouse wheel are now trickled. (#4921, #4821) +- IO: Added io.SetAppAcceptingEvents() to set a master flag for accepting key/mouse/characters + events (default to true). Useful if you have native dialog boxes that are interrupting your + application loop/refresh, and you want to disable events being queued while your app is frozen. - Windows: Fixed first-time windows appearing in negative coordinates from being initialized with a wrong size. This would most often be noticeable in multi-viewport mode (docking branch) when spawning a window in a monitor with negative coordinates. (#5215, #3414) [@DimaKoltun] diff --git a/imgui.cpp b/imgui.cpp index eb2eb18f8f5a..4af42dd45e93 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1169,6 +1169,7 @@ ImGuiIO::ImGuiIO() for (int i = 0; i < IM_ARRAYSIZE(MouseDownDuration); i++) MouseDownDuration[i] = MouseDownDurationPrev[i] = -1.0f; for (int i = 0; i < IM_ARRAYSIZE(KeysData); i++) { KeysData[i].DownDuration = KeysData[i].DownDurationPrev = -1.0f; } for (int i = 0; i < IM_ARRAYSIZE(NavInputsDownDuration); i++) NavInputsDownDuration[i] = -1.0f; + AppAcceptingEvents = true; BackendUsingLegacyKeyArrays = (ImS8)-1; BackendUsingLegacyNavInputArray = true; // assume using legacy array until proven wrong } @@ -1181,7 +1182,7 @@ void ImGuiIO::AddInputCharacter(unsigned int c) { ImGuiContext& g = *GImGui; IM_ASSERT(&g.IO == this && "Can only add events to current context."); - if (c == 0) + if (c == 0 || !AppAcceptingEvents) return; ImGuiInputEvent e; @@ -1195,7 +1196,7 @@ void ImGuiIO::AddInputCharacter(unsigned int c) // we should save the high surrogate. void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c) { - if (c == 0 && InputQueueSurrogate == 0) + if ((c == 0 && InputQueueSurrogate == 0) || !AppAcceptingEvents) return; if ((c & 0xFC00) == 0xD800) // High surrogate, must save @@ -1229,6 +1230,8 @@ void ImGuiIO::AddInputCharacterUTF16(ImWchar16 c) void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) { + if (!AppAcceptingEvents) + return; while (*utf8_chars != 0) { unsigned int c = 0; @@ -1267,7 +1270,7 @@ void ImGuiIO::ClearInputKeys() void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) { //if (e->Down) { IMGUI_DEBUG_LOG("AddKeyEvent() Key='%s' %d, NativeKeycode = %d, NativeScancode = %d\n", ImGui::GetKeyName(e->Key), e->Down, e->NativeKeycode, e->NativeScancode); } - if (key == ImGuiKey_None) + if (key == ImGuiKey_None || !AppAcceptingEvents) return; ImGuiContext& g = *GImGui; IM_ASSERT(&g.IO == this && "Can only add events to current context."); @@ -1308,6 +1311,8 @@ void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) void ImGuiIO::AddKeyEvent(ImGuiKey key, bool down) { + if (!AppAcceptingEvents) + return; AddKeyAnalogEvent(key, down, down ? 1.0f : 0.0f); } @@ -1336,11 +1341,19 @@ void ImGuiIO::SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native #endif } +// Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen. +void ImGuiIO::SetAppAcceptingEvents(bool accepting_events) +{ + AppAcceptingEvents = accepting_events; +} + // Queue a mouse move event void ImGuiIO::AddMousePosEvent(float x, float y) { ImGuiContext& g = *GImGui; IM_ASSERT(&g.IO == this && "Can only add events to current context."); + if (!AppAcceptingEvents) + return; ImGuiInputEvent e; e.Type = ImGuiInputEventType_MousePos; @@ -1355,6 +1368,8 @@ void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down) ImGuiContext& g = *GImGui; IM_ASSERT(&g.IO == this && "Can only add events to current context."); IM_ASSERT(mouse_button >= 0 && mouse_button < ImGuiMouseButton_COUNT); + if (!AppAcceptingEvents) + return; ImGuiInputEvent e; e.Type = ImGuiInputEventType_MouseButton; @@ -1369,7 +1384,7 @@ void ImGuiIO::AddMouseWheelEvent(float wheel_x, float wheel_y) { ImGuiContext& g = *GImGui; IM_ASSERT(&g.IO == this && "Can only add events to current context."); - if (wheel_x == 0.0f && wheel_y == 0.0f) + if ((wheel_x == 0.0f && wheel_y == 0.0f) || !AppAcceptingEvents) return; ImGuiInputEvent e; diff --git a/imgui.h b/imgui.h index 488b5d1fd0a0..25854867a2e2 100644 --- a/imgui.h +++ b/imgui.h @@ -65,7 +65,7 @@ Index of this file: // Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens) #define IMGUI_VERSION "1.88 WIP" -#define IMGUI_VERSION_NUM 18725 +#define IMGUI_VERSION_NUM 18726 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) #define IMGUI_HAS_TABLE @@ -1975,9 +1975,10 @@ struct ImGuiIO IMGUI_API void AddInputCharacterUTF16(ImWchar16 c); // Queue a new character input from an UTF-16 character, it can be a surrogate IMGUI_API void AddInputCharactersUTF8(const char* str); // Queue a new characters input from an UTF-8 string + IMGUI_API void SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native_scancode, int native_legacy_index = -1); // [Optional] Specify index for legacy <1.87 IsKeyXXX() functions with native indices + specify native keycode, scancode. + IMGUI_API void SetAppAcceptingEvents(bool accepting_events); // Set master flag for accepting key/mouse/text events (default to true). Useful if you have native dialog boxes that are interrupting your application loop/refresh, and you want to disable events being queued while your app is frozen. IMGUI_API void ClearInputCharacters(); // [Internal] Clear the text input buffer manually IMGUI_API void ClearInputKeys(); // [Internal] Release all keys - IMGUI_API void SetKeyEventNativeData(ImGuiKey key, int native_keycode, int native_scancode, int native_legacy_index = -1); // [Optional] Specify index for legacy <1.87 IsKeyXXX() functions with native indices + specify native keycode, scancode. //------------------------------------------------------------------ // Output - Updated by NewFrame() or EndFrame()/Render() @@ -2044,7 +2045,8 @@ struct ImGuiIO float NavInputsDownDuration[ImGuiNavInput_COUNT]; float NavInputsDownDurationPrev[ImGuiNavInput_COUNT]; float PenPressure; // Touch/Pen pressure (0.0f to 1.0f, should be >0.0f only when MouseDown[0] == true). Helper storage currently unused by Dear ImGui. - bool AppFocusLost; + bool AppFocusLost; // Only modify via AddFocusEvent() + bool AppAcceptingEvents; // Only modify via SetAppAcceptingEvents() ImS8 BackendUsingLegacyKeyArrays; // -1: unknown, 0: using AddKeyEvent(), 1: using legacy io.KeysDown[] bool BackendUsingLegacyNavInputArray; // 0: using AddKeyAnalogEvent(), 1: writing to legacy io.NavInputs[] directly ImWchar16 InputQueueSurrogate; // For AddInputCharacterUTF16() From 74f02703e601c3c74b014ecd7b8952bd45265590 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jun 2022 11:38:43 +0200 Subject: [PATCH 06/12] Misc comments + Demo: use IsItemVisible() when using direct ImDrawList calls. --- imgui.h | 15 +++++---- imgui_demo.cpp | 78 +++++++++++++++++++++++++---------------------- imgui_widgets.cpp | 2 +- 3 files changed, 51 insertions(+), 44 deletions(-) diff --git a/imgui.h b/imgui.h index 25854867a2e2..f89cb0252f44 100644 --- a/imgui.h +++ b/imgui.h @@ -1340,6 +1340,8 @@ enum ImGuiSortDirection_ ImGuiSortDirection_Descending = 2 // Descending = 9->0, Z->A etc. }; +// Keys value 0 to 511 are left unused as legacy native/opaque key values (< 1.87) +// Keys value >= 512 are named keys (>= 1.87) enum ImGuiKey_ { // Keyboard @@ -1440,10 +1442,10 @@ enum ImGuiKey_ ImGuiKey_NamedKey_COUNT = ImGuiKey_NamedKey_END - ImGuiKey_NamedKey_BEGIN, #ifdef IMGUI_DISABLE_OBSOLETE_KEYIO ImGuiKey_KeysData_SIZE = ImGuiKey_NamedKey_COUNT, // Size of KeysData[]: only hold named keys - ImGuiKey_KeysData_OFFSET = ImGuiKey_NamedKey_BEGIN // First key stored in KeysData[0] + ImGuiKey_KeysData_OFFSET = ImGuiKey_NamedKey_BEGIN // First key stored in io.KeysData[0]. Accesses to io.KeysData[] must use (key - ImGuiKey_KeysData_OFFSET). #else ImGuiKey_KeysData_SIZE = ImGuiKey_COUNT, // Size of KeysData[]: hold legacy 0..512 keycodes + named keys - ImGuiKey_KeysData_OFFSET = 0 // First key stored in KeysData[0] + ImGuiKey_KeysData_OFFSET = 0 // First key stored in io.KeysData[0]. Accesses to io.KeysData[] must use (key - ImGuiKey_KeysData_OFFSET). #endif #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -1462,6 +1464,7 @@ enum ImGuiModFlags_ }; // Gamepad/Keyboard navigation +// Since >= 1.87 backends you generally don't need to care about this enum since io.NavInputs[] is setup automatically. This might become private/internal some day. // Keyboard: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard to enable. NewFrame() will automatically fill io.NavInputs[] based on your io.AddKeyEvent() calls. // Gamepad: Set io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad to enable. Backend: set ImGuiBackendFlags_HasGamepad and fill the io.NavInputs[] fields before calling NewFrame(). Note that io.NavInputs[] is cleared by EndFrame(). // Read instructions in imgui.cpp for more details. Download PNG/PSD at http://dearimgui.org/controls_sheets. @@ -1505,7 +1508,7 @@ enum ImGuiConfigFlags_ ImGuiConfigFlags_NoMouse = 1 << 4, // Instruct imgui to clear mouse position/buttons in NewFrame(). This allows ignoring the mouse information set by the backend. ImGuiConfigFlags_NoMouseCursorChange = 1 << 5, // Instruct backend to not alter mouse cursor shape and visibility. Use if the backend cursor changes are interfering with yours and you don't want to use SetMouseCursor() to change mouse cursor. You may want to honor requests from imgui by reading GetMouseCursor() yourself instead. - // User storage (to allow your backend/engine to communicate to code that may be shared between multiple projects. Those flags are not used by core Dear ImGui) + // User storage (to allow your backend/engine to communicate to code that may be shared between multiple projects. Those flags are NOT used by core Dear ImGui) ImGuiConfigFlags_IsSRGB = 1 << 20, // Application is SRGB-aware. ImGuiConfigFlags_IsTouchScreen = 1 << 21 // Application is using a touch screen instead of a mouse. }; @@ -1553,10 +1556,10 @@ enum ImGuiCol_ ImGuiCol_Separator, ImGuiCol_SeparatorHovered, ImGuiCol_SeparatorActive, - ImGuiCol_ResizeGrip, + ImGuiCol_ResizeGrip, // Resize grip in lower-right and lower-left corners of windows. ImGuiCol_ResizeGripHovered, ImGuiCol_ResizeGripActive, - ImGuiCol_Tab, + ImGuiCol_Tab, // TabItem in a TabBar ImGuiCol_TabHovered, ImGuiCol_TabActive, ImGuiCol_TabUnfocused, @@ -1571,7 +1574,7 @@ enum ImGuiCol_ ImGuiCol_TableRowBg, // Table row background (even rows) ImGuiCol_TableRowBgAlt, // Table row background (odd rows) ImGuiCol_TextSelectedBg, - ImGuiCol_DragDropTarget, + ImGuiCol_DragDropTarget, // Rectangle highlighting a drop target ImGuiCol_NavHighlight, // Gamepad/keyboard: current highlighted item ImGuiCol_NavWindowingHighlight, // Highlight window when using CTRL+TAB ImGuiCol_NavWindowingDimBg, // Darken/colorize entire screen behind the CTRL+TAB window list, when active diff --git a/imgui_demo.cpp b/imgui_demo.cpp index edf3ff4cff30..46fe4f2943f6 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3202,59 +3202,58 @@ static void ShowDemoWindowLayout() ImGui::DragFloat2("size", (float*)&size, 0.5f, 1.0f, 200.0f, "%.0f"); ImGui::TextWrapped("(Click and drag to scroll)"); + HelpMarker( + "(Left) Using ImGui::PushClipRect():\n" + "Will alter ImGui hit-testing logic + ImDrawList rendering.\n" + "(use this if you want your clipping rectangle to affect interactions)\n\n" + "(Center) Using ImDrawList::PushClipRect():\n" + "Will alter ImDrawList rendering only.\n" + "(use this as a shortcut if you are only using ImDrawList calls)\n\n" + "(Right) Using ImDrawList::AddText() with a fine ClipRect:\n" + "Will alter only this specific ImDrawList::AddText() rendering.\n" + "This is often used internally to avoid altering the clipping rectangle and minimize draw calls."); + for (int n = 0; n < 3; n++) { if (n > 0) ImGui::SameLine(); - ImGui::PushID(n); - ImGui::BeginGroup(); // Lock X position - ImGui::InvisibleButton("##empty", size); + ImGui::PushID(n); + ImGui::InvisibleButton("##canvas", size); if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { offset.x += ImGui::GetIO().MouseDelta.x; offset.y += ImGui::GetIO().MouseDelta.y; } + ImGui::PopID(); + if (!ImGui::IsItemVisible()) // Skip rendering as ImDrawList elements are not clipped. + continue; + const ImVec2 p0 = ImGui::GetItemRectMin(); const ImVec2 p1 = ImGui::GetItemRectMax(); const char* text_str = "Line 1 hello\nLine 2 clip me!"; const ImVec2 text_pos = ImVec2(p0.x + offset.x, p0.y + offset.y); ImDrawList* draw_list = ImGui::GetWindowDrawList(); - switch (n) { case 0: - HelpMarker( - "Using ImGui::PushClipRect():\n" - "Will alter ImGui hit-testing logic + ImDrawList rendering.\n" - "(use this if you want your clipping rectangle to affect interactions)"); ImGui::PushClipRect(p0, p1, true); draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255)); draw_list->AddText(text_pos, IM_COL32_WHITE, text_str); ImGui::PopClipRect(); break; case 1: - HelpMarker( - "Using ImDrawList::PushClipRect():\n" - "Will alter ImDrawList rendering only.\n" - "(use this as a shortcut if you are only using ImDrawList calls)"); draw_list->PushClipRect(p0, p1, true); draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255)); draw_list->AddText(text_pos, IM_COL32_WHITE, text_str); draw_list->PopClipRect(); break; case 2: - HelpMarker( - "Using ImDrawList::AddText() with a fine ClipRect:\n" - "Will alter only this specific ImDrawList::AddText() rendering.\n" - "(this is often used internally to avoid altering the clipping rectangle and minimize draw calls)"); ImVec4 clip_rect(p0.x, p0.y, p1.x, p1.y); // AddText() takes a ImVec4* here so let's convert. draw_list->AddRectFilled(p0, p1, IM_COL32(90, 90, 120, 255)); draw_list->AddText(ImGui::GetFont(), ImGui::GetFontSize(), text_pos, IM_COL32_WHITE, text_str, NULL, 0.0f, &clip_rect); break; } - ImGui::EndGroup(); - ImGui::PopID(); } ImGui::TreePop(); @@ -4841,7 +4840,7 @@ static void ShowDemoWindowTables() ImGui::TableSetColumnIndex(1); ImGui::SliderFloat("float1", &dummy_f, 0.0f, 1.0f); ImGui::TableSetColumnIndex(2); - ImGui::SliderFloat("float2", &dummy_f, 0.0f, 1.0f); + ImGui::SliderFloat("##float2", &dummy_f, 0.0f, 1.0f); // No visible label since right-aligned ImGui::PopID(); } ImGui::EndTable(); @@ -5755,26 +5754,31 @@ static void ShowDemoWindowMisc() { 2, 0, "", ImGuiKey_LeftShift },{ 2, 1, "Z", ImGuiKey_Z }, { 2, 2, "X", ImGuiKey_X }, { 2, 3, "C", ImGuiKey_C }, { 2, 4, "V", ImGuiKey_V } }; - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - draw_list->PushClipRect(board_min, board_max, true); - for (int n = 0; n < IM_ARRAYSIZE(keys_to_display); n++) + // Elements rendered manually via ImDrawList API are not clipped automatically. + // While not strictly necessary, here IsItemVisible() is used to avoid rendering these shapes when they are out of view. + ImGui::Dummy(ImVec2(board_max.x - board_min.x, board_max.y - board_min.y)); + if (ImGui::IsItemVisible()) { - const KeyLayoutData* key_data = &keys_to_display[n]; - ImVec2 key_min = ImVec2(start_pos.x + key_data->Col * key_step.x + key_data->Row * key_row_offset, start_pos.y + key_data->Row * key_step.y); - ImVec2 key_max = ImVec2(key_min.x + key_size.x, key_min.y + key_size.y); - draw_list->AddRectFilled(key_min, key_max, IM_COL32(204, 204, 204, 255), key_rounding); - draw_list->AddRect(key_min, key_max, IM_COL32(24, 24, 24, 255), key_rounding); - ImVec2 face_min = ImVec2(key_min.x + key_face_pos.x, key_min.y + key_face_pos.y); - ImVec2 face_max = ImVec2(face_min.x + key_face_size.x, face_min.y + key_face_size.y); - draw_list->AddRect(face_min, face_max, IM_COL32(193, 193, 193, 255), key_face_rounding, ImDrawFlags_None, 2.0f); - draw_list->AddRectFilled(face_min, face_max, IM_COL32(252, 252, 252, 255), key_face_rounding); - ImVec2 label_min = ImVec2(key_min.x + key_label_pos.x, key_min.y + key_label_pos.y); - draw_list->AddText(label_min, IM_COL32(64, 64, 64, 255), key_data->Label); - if (ImGui::IsKeyDown(key_data->Key)) - draw_list->AddRectFilled(key_min, key_max, IM_COL32(255, 0, 0, 128), key_rounding); + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + draw_list->PushClipRect(board_min, board_max, true); + for (int n = 0; n < IM_ARRAYSIZE(keys_to_display); n++) + { + const KeyLayoutData* key_data = &keys_to_display[n]; + ImVec2 key_min = ImVec2(start_pos.x + key_data->Col * key_step.x + key_data->Row * key_row_offset, start_pos.y + key_data->Row * key_step.y); + ImVec2 key_max = ImVec2(key_min.x + key_size.x, key_min.y + key_size.y); + draw_list->AddRectFilled(key_min, key_max, IM_COL32(204, 204, 204, 255), key_rounding); + draw_list->AddRect(key_min, key_max, IM_COL32(24, 24, 24, 255), key_rounding); + ImVec2 face_min = ImVec2(key_min.x + key_face_pos.x, key_min.y + key_face_pos.y); + ImVec2 face_max = ImVec2(face_min.x + key_face_size.x, face_min.y + key_face_size.y); + draw_list->AddRect(face_min, face_max, IM_COL32(193, 193, 193, 255), key_face_rounding, ImDrawFlags_None, 2.0f); + draw_list->AddRectFilled(face_min, face_max, IM_COL32(252, 252, 252, 255), key_face_rounding); + ImVec2 label_min = ImVec2(key_min.x + key_label_pos.x, key_min.y + key_label_pos.y); + draw_list->AddText(label_min, IM_COL32(64, 64, 64, 255), key_data->Label); + if (ImGui::IsKeyDown(key_data->Key)) + draw_list->AddRectFilled(key_min, key_max, IM_COL32(255, 0, 0, 128), key_rounding); + } + draw_list->PopClipRect(); } - draw_list->PopClipRect(); - ImGui::Dummy(ImVec2(board_max.x - board_min.x, board_max.y - board_min.y)); } ImGui::TreePop(); } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 58f0d3724730..78b2c9271f5e 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6887,7 +6887,7 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) bool menu_is_open = IsPopupOpen(id, ImGuiPopupFlags_None); // Sub-menus are ChildWindow so that mouse can be hovering across them (otherwise top-most popup menu would steal focus and not allow hovering on parent menu) - // The first menu in a hierarchy isn't so hovering doesn't get accross (otherwise e.g. resizing borders with ImGuiButtonFlags_FlattenChildren would react), but top-most BeginMenu() will bypass that limitation. + // The first menu in a hierarchy isn't so hovering doesn't get across (otherwise e.g. resizing borders with ImGuiButtonFlags_FlattenChildren would react), but top-most BeginMenu() will bypass that limitation. ImGuiWindowFlags flags = ImGuiWindowFlags_ChildMenu | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoNavFocus; if (window->Flags & ImGuiWindowFlags_ChildMenu) flags |= ImGuiWindowFlags_ChildWindow; From 64d6c30562e1e866cd3be6db6e5b9c9529f1f3f1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jun 2022 15:24:52 +0200 Subject: [PATCH 07/12] InputText: Fixed an undo-state corruption issue when editing buffer before reactivating item. (#4947) + Metrics: Added "InputText" section. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 7 +++++++ imgui.h | 2 +- imgui_internal.h | 1 + imgui_widgets.cpp | 46 ++++++++++++++++++++++++++++++++++++++++++---- 5 files changed, 53 insertions(+), 5 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 155a85bbada6..7a59c214d7c8 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -88,6 +88,7 @@ Other Changes: trickled with the new input queue (happened on some backends only). (#2467, #1336) - InputText: Fixed a one-frame display glitch where pressing Escape to revert after a deletion would lead to small garbage being displayed for one frame. Curiously a rather old bug! (#3008) +- InputText: Fixed an undo-state corruption issue when editing buffer before reactivating item. (#4947) - Tables: Fixed incorrect border height used for logic when resizing one of several synchronized instance of a same table ID, when instances have a different height. (#3955). - Tables: Fixed incorrect auto-fit of parent windows when using non-resizable weighted columns. (#5276) @@ -108,6 +109,7 @@ Other Changes: you have a UTF-8 text encoding issue or a font loading issue. [@LaMarche05, @ocornut] - Demo: Add better demo of how to use SetNextFrameWantCaptureMouse()/SetNextFrameWantCaptureKeyboard(). - Metrics: Added a "UTF-8 Encoding Viewer" section using the aforementioned DebugTextEncoding() function. +- Metrics: Added "InputText" section to visualize internal state (#4947, #4949). - Misc: Fixed calling GetID("label") _before_ a widget emitting this item inside a group (such as InputInt()) from causing an assertion when closing the group. (#5181). - Misc: Fixed IsAnyItemHovered() returning false when using navigation. diff --git a/imgui.cpp b/imgui.cpp index 4af42dd45e93..a9d027c3b0c8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -12480,6 +12480,13 @@ void ImGui::ShowMetricsWindow(bool* p_open) TreePop(); } + // Details for InputText + if (TreeNode("InputText")) + { + DebugNodeInputTextState(&g.InputTextState); + TreePop(); + } + // Details for Docking #ifdef IMGUI_HAS_DOCK if (TreeNode("Docking")) diff --git a/imgui.h b/imgui.h index f89cb0252f44..c9bdb2d7cd04 100644 --- a/imgui.h +++ b/imgui.h @@ -65,7 +65,7 @@ Index of this file: // Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens) #define IMGUI_VERSION "1.88 WIP" -#define IMGUI_VERSION_NUM 18726 +#define IMGUI_VERSION_NUM 18727 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) #define IMGUI_HAS_TABLE diff --git a/imgui_internal.h b/imgui_internal.h index 69277540ebf7..95f316d63e9d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2878,6 +2878,7 @@ namespace ImGui IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTable(ImGuiTable* table); IMGUI_API void DebugNodeTableSettings(ImGuiTableSettings* settings); + IMGUI_API void DebugNodeInputTextState(ImGuiInputTextState* state); IMGUI_API void DebugNodeWindow(ImGuiWindow* window, const char* label); IMGUI_API void DebugNodeWindowSettings(ImGuiWindowSettings* settings); IMGUI_API void DebugNodeWindowsList(ImVector* windows, const char* label); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 78b2c9271f5e..94de60ae9f63 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3571,6 +3571,7 @@ bool ImGui::InputDouble(const char* label, double* v, double step, double step_f // - InputTextReindexLines() [Internal] // - InputTextReindexLinesRange() [Internal] // - InputTextEx() [Internal] +// - DebugNodeInputTextState() [Internal] //------------------------------------------------------------------------- bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data) @@ -4048,17 +4049,21 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. memcpy(state->InitialTextA.Data, buf, buf_len + 1); + // Preserve cursor position and undo/redo stack if we come back to same widget + // FIXME: Since we reworked this on 2022/06, may want to differenciate recycle_cursor vs recycle_undostate? + bool recycle_state = (state->ID == id && !init_changed_specs); + if (recycle_state && (state->CurLenA != buf_len || (state->TextAIsValid && strncmp(state->TextA.Data, buf, buf_len) != 0))) + recycle_state = false; + // Start edition const char* buf_end = NULL; + state->ID = id; state->TextW.resize(buf_size + 1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string. state->TextA.resize(0); state->TextAIsValid = false; // TextA is not valid yet (we will display buf until then) state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end); state->CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. - // Preserve cursor position and undo/redo stack if we come back to same widget - // FIXME: For non-readonly widgets we might be able to require that TextAIsValid && TextA == buf ? (untested) and discard undo stack if user buffer has changed. - const bool recycle_state = (state->ID == id && !init_changed_specs); if (recycle_state) { // Recycle existing cursor/selection/undo stack but clamp position @@ -4067,7 +4072,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ } else { - state->ID = id; state->ScrollX = 0.0f; stb_textedit_initialize_state(&state->Stb, !is_multiline); } @@ -4816,6 +4820,40 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ return value_changed; } +void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state) +{ +#ifndef IMGUI_DISABLE_METRICS_WINDOW + ImGuiContext& g = *GImGui; + ImStb::STB_TexteditState* stb_state = &state->Stb; + ImStb::StbUndoState* undo_state = &stb_state->undostate; + Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId); + Text("CurLenW: %d, CurLenA: %d, Cursor: %d, Selection: %d..%d", state->CurLenA, state->CurLenW, stb_state->cursor, stb_state->select_start, stb_state->select_end); + Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point); + if (BeginChild("undopoints", ImVec2(0.0f, GetTextLineHeight() * 15), true)) // Visualize undo state + { + PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + for (int n = 0; n < STB_TEXTEDIT_UNDOSTATECOUNT; n++) + { + ImStb::StbUndoRecord* undo_rec = &undo_state->undo_rec[n]; + const char undo_rec_type = (n < undo_state->undo_point) ? 'u' : (n >= undo_state->redo_point) ? 'r' : ' '; + if (undo_rec_type == ' ') + BeginDisabled(); + char buf[64] = ""; + if (undo_rec_type != ' ' && undo_rec->char_storage != -1) + ImTextStrToUtf8(buf, IM_ARRAYSIZE(buf), undo_state->undo_char + undo_rec->char_storage, undo_state->undo_char + undo_rec->char_storage + undo_rec->insert_length); + Text("%c [%02d] where %03d, insert %03d, delete %03d, char_storage %03d \"%s\"", + undo_rec_type, n, undo_rec->where, undo_rec->insert_length, undo_rec->delete_length, undo_rec->char_storage, buf); + if (undo_rec_type == ' ') + EndDisabled(); + } + PopStyleVar(); + } + EndChild(); +#else + IM_UNUSED(state); +#endif +} + //------------------------------------------------------------------------- // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. //------------------------------------------------------------------------- From 23a785aeb3f65d07d69aaad61a9194c03272286c Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jun 2022 19:16:00 +0200 Subject: [PATCH 08/12] Internals: wrapped used of g.TempBuffer into ImFormatStringToTempBuffer/ImFormatStringToTempBufferV helpers. This leaves us room for growing the buffer if needed, and gives us a resizable buffer available for other work. --- imgui.cpp | 37 +++++++++++++++++++++++++++++-------- imgui_internal.h | 5 +++-- imgui_widgets.cpp | 32 ++++++++++++++++---------------- 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index a9d027c3b0c8..89204387231b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1704,6 +1704,25 @@ int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) } #endif // #ifdef IMGUI_DISABLE_DEFAULT_FORMAT_FUNCTIONS +void ImFormatStringToTempBuffer(const char** out_buf, const char** out_buf_end, const char* fmt, ...) +{ + ImGuiContext& g = *GImGui; + va_list args; + va_start(args, fmt); + int buf_len = ImFormatStringV(g.TempBuffer.Data, g.TempBuffer.Size, fmt, args); + *out_buf = g.TempBuffer.Data; + if (out_buf_end) { *out_buf_end = g.TempBuffer.Data + buf_len; } + va_end(args); +} + +void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, const char* fmt, va_list args) +{ + ImGuiContext& g = *GImGui; + int buf_len = ImFormatStringV(g.TempBuffer.Data, g.TempBuffer.Size, fmt, args); + *out_buf = g.TempBuffer.Data; + if (out_buf_end) { *out_buf_end = g.TempBuffer.Data + buf_len; } +} + // CRC32 needs a 1KB lookup table (not cache friendly) // Although the code to generate the table is simple and shorter than the table itself, using a const table allows us to easily: // - avoid an unnecessary branch/memory tap, - keep the ImHashXXX functions usable by static constructors, - make it thread-safe. @@ -4482,6 +4501,7 @@ void ImGui::Initialize() // Create default viewport ImGuiViewportP* viewport = IM_NEW(ImGuiViewportP)(); g.Viewports.push_back(viewport); + g.TempBuffer.resize(1024 * 3 + 1, 0); #ifdef IMGUI_HAS_DOCK #endif @@ -5178,15 +5198,16 @@ bool ImGui::BeginChildEx(const char* name, ImGuiID id, const ImVec2& size_arg, b SetNextWindowSize(size); // Build up name. If you need to append to a same child from multiple location in the ID stack, use BeginChild(ImGuiID id) with a stable value. + const char* temp_window_name; if (name) - ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%s/%s_%08X", parent_window->Name, name, id); + ImFormatStringToTempBuffer(&temp_window_name, NULL, "%s/%s_%08X", parent_window->Name, name, id); else - ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%s/%08X", parent_window->Name, id); + ImFormatStringToTempBuffer(&temp_window_name, NULL, "%s/%08X", parent_window->Name, id); const float backup_border_size = g.Style.ChildBorderSize; if (!border) g.Style.ChildBorderSize = 0.0f; - bool ret = Begin(g.TempBuffer, NULL, flags); + bool ret = Begin(temp_window_name, NULL, flags); g.Style.ChildBorderSize = backup_border_size; ImGuiWindow* child_window = g.CurrentWindow; @@ -13219,8 +13240,8 @@ void ImGui::ShowStackToolWindow(bool* p_open) if (tool->CopyToClipboardOnCtrlC && IsKeyDown(ImGuiKey_ModCtrl) && IsKeyPressed(ImGuiKey_C)) { tool->CopyToClipboardLastTime = (float)g.Time; - char* p = g.TempBuffer; - char* p_end = p + IM_ARRAYSIZE(g.TempBuffer); + char* p = g.TempBuffer.Data; + char* p_end = p + g.TempBuffer.Size; for (int stack_n = 0; stack_n < tool->Results.Size && p + 3 < p_end; stack_n++) { *p++ = '/'; @@ -13234,7 +13255,7 @@ void ImGui::ShowStackToolWindow(bool* p_open) } } *p = '\0'; - SetClipboardText(g.TempBuffer); + SetClipboardText(g.TempBuffer.Data); } // Display decorated stack @@ -13252,8 +13273,8 @@ void ImGui::ShowStackToolWindow(bool* p_open) TableNextColumn(); Text("0x%08X", (n > 0) ? tool->Results[n - 1].ID : 0); TableNextColumn(); - StackToolFormatLevelInfo(tool, n, true, g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer)); - TextUnformatted(g.TempBuffer); + StackToolFormatLevelInfo(tool, n, true, g.TempBuffer.Data, g.TempBuffer.Size); + TextUnformatted(g.TempBuffer.Data); TableNextColumn(); Text("0x%08X", info->ID); if (n == tool->Results.Size - 1) diff --git a/imgui_internal.h b/imgui_internal.h index 95f316d63e9d..3f7cc5f82f82 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -331,6 +331,8 @@ static inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c = // Helpers: Formatting IMGUI_API int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3); IMGUI_API int ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args) IM_FMTLIST(3); +IMGUI_API void ImFormatStringToTempBuffer(const char** out_buf, const char** out_buf_end, const char* fmt, ...) IM_FMTARGS(3); +IMGUI_API void ImFormatStringToTempBufferV(const char** out_buf, const char** out_buf_end, const char* fmt, va_list args) IM_FMTLIST(3); IMGUI_API const char* ImParseFormatFindStart(const char* format); IMGUI_API const char* ImParseFormatFindEnd(const char* format); IMGUI_API const char* ImParseFormatTrimDecorations(const char* format, char* buf, size_t buf_size); @@ -1809,7 +1811,7 @@ struct ImGuiContext int WantCaptureMouseNextFrame; // Explicit capture override via SetNextFrameWantCaptureMouse()/SetNextFrameWantCaptureKeyboard(). Default to -1. int WantCaptureKeyboardNextFrame; // " int WantTextInputNextFrame; - char TempBuffer[1024 * 3 + 1]; // Temporary text buffer + ImVector TempBuffer; // Temporary text buffer ImGuiContext(ImFontAtlas* shared_font_atlas) { @@ -1958,7 +1960,6 @@ struct ImGuiContext FramerateSecPerFrameIdx = FramerateSecPerFrameCount = 0; FramerateSecPerFrameAccum = 0.0f; WantCaptureMouseNextFrame = WantCaptureKeyboardNextFrame = WantTextInputNextFrame = -1; - memset(TempBuffer, 0, sizeof(TempBuffer)); } }; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 94de60ae9f63..707d06fd0c1b 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -276,9 +276,9 @@ void ImGui::TextV(const char* fmt, va_list args) return; // FIXME-OPT: Handle the %s shortcut? - ImGuiContext& g = *GImGui; - const char* text_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); - TextEx(g.TempBuffer, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); + const char* text, *text_end; + ImFormatStringToTempBufferV(&text, &text_end, fmt, args); + TextEx(text, text_end, ImGuiTextFlags_NoWidthForLargeClippedText); } void ImGui::TextColored(const ImVec4& col, const char* fmt, ...) @@ -359,8 +359,8 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) const ImGuiStyle& style = g.Style; const float w = CalcItemWidth(); - const char* value_text_begin = &g.TempBuffer[0]; - const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + const char* value_text_begin, *value_text_end; + ImFormatStringToTempBufferV(&value_text_begin, &value_text_end, fmt, args); const ImVec2 value_size = CalcTextSize(value_text_begin, value_text_end, false); const ImVec2 label_size = CalcTextSize(label, NULL, true); @@ -395,8 +395,8 @@ void ImGui::BulletTextV(const char* fmt, va_list args) ImGuiContext& g = *GImGui; const ImGuiStyle& style = g.Style; - const char* text_begin = g.TempBuffer; - const char* text_end = text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); + const char* text_begin, *text_end; + ImFormatStringToTempBufferV(&text_begin, &text_end, fmt, args); const ImVec2 label_size = CalcTextSize(text_begin, text_end, false); const ImVec2 total_size = ImVec2(g.FontSize + (label_size.x > 0.0f ? (label_size.x + style.FramePadding.x * 2) : 0.0f), label_size.y); // Empty text doesn't add padding ImVec2 pos = window->DC.CursorPos; @@ -1910,9 +1910,9 @@ static const char* PatchFormatStringFloatToInt(const char* fmt) #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS if (fmt_start == fmt && fmt_end[0] == 0) return "%d"; - ImGuiContext& g = *GImGui; - ImFormatString(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), "%.*s%%d%s", (int)(fmt_start - fmt), fmt, fmt_end); // Honor leading and trailing decorations, but lose alignment/precision. - return g.TempBuffer; + 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 @@ -5835,9 +5835,9 @@ bool ImGui::TreeNodeExV(const char* str_id, ImGuiTreeNodeFlags flags, const char if (window->SkipItems) return false; - ImGuiContext& g = *GImGui; - const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); - return TreeNodeBehavior(window->GetID(str_id), flags, g.TempBuffer, label_end); + const char* label, *label_end; + ImFormatStringToTempBufferV(&label, &label_end, fmt, args); + return TreeNodeBehavior(window->GetID(str_id), flags, label, label_end); } bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char* fmt, va_list args) @@ -5846,9 +5846,9 @@ bool ImGui::TreeNodeExV(const void* ptr_id, ImGuiTreeNodeFlags flags, const char if (window->SkipItems) return false; - ImGuiContext& g = *GImGui; - const char* label_end = g.TempBuffer + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); - return TreeNodeBehavior(window->GetID(ptr_id), flags, g.TempBuffer, label_end); + const char* label, *label_end; + ImFormatStringToTempBufferV(&label, &label_end, fmt, args); + return TreeNodeBehavior(window->GetID(ptr_id), flags, label, label_end); } bool ImGui::TreeNodeBehaviorIsOpen(ImGuiID id, ImGuiTreeNodeFlags flags) From 530332dfbc77cafdf6b488d8b9da5e21fa171111 Mon Sep 17 00:00:00 2001 From: Joshua Webb Date: Thu, 27 Jan 2022 22:02:24 +1100 Subject: [PATCH 09/12] InputText: Fixed undo/redo state corruption when editing buffer in user callback. (#4947, #4949) --- imgui_widgets.cpp | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 707d06fd0c1b..5d146da9ccdc 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3929,6 +3929,48 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f return true; } +static inline void InputTextUpdateUndoStateAfterUserCallback(ImGuiInputTextState *state, const char *new_buf) { + // Find the shortest single replacement we can make to get the new text + // from the old text. + ImWchar *old_buf = state->TextW.Data; + int old_length = state->CurLenW; + int new_length = ImTextCountCharsFromUtf8(new_buf, NULL); + int shorter_length = ImMin(old_length, new_length); + + int where = 0; + while (where < shorter_length) { + unsigned int b; + ImTextCharFromUtf8(&b, new_buf + where, NULL); + if (old_buf[where] != b) + break; + + where += 1; + } + + int old_last_diff = old_length - 1; + int new_last_diff = new_length - 1; + while (old_last_diff >= where && new_last_diff >= 0) { + unsigned int b; + ImTextCharFromUtf8(&b, new_buf + new_last_diff, NULL); + if (old_buf[old_last_diff] != b) + break; + + old_last_diff -= 1; + new_last_diff -= 1; + } + + int insert_len = new_last_diff - where + 1; + int delete_len = old_last_diff - where + 1; + + if (insert_len > 0 || delete_len > 0) { + STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->Stb.undostate, where, delete_len, insert_len); + if (p) { + for (int i=0; i < delete_len; ++i) + p[i] = ImStb::STB_TEXTEDIT_GETCHAR(state, where + i); + } + } +} + // Edit a string of text // - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!". // This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match @@ -4516,6 +4558,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (buf_dirty) { IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! + InputTextUpdateUndoStateAfterUserCallback(state, callback_data.Buf); if (callback_data.BufTextLen > backup_current_text_length && is_resizable) state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL); From a35e8769780e7ef0a89678cac27b98a532357af5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 7 Jun 2022 17:28:31 +0200 Subject: [PATCH 10/12] InputText: Amends and tidying up: Fixed undo/redo state corruption when editing buffer in user callback. (#4947, #4949) --- docs/CHANGELOG.txt | 4 ++- imgui.h | 1 + imgui_widgets.cpp | 65 +++++++++++++++++++++------------------------- 3 files changed, 33 insertions(+), 37 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 7a59c214d7c8..23a55026d36b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -88,7 +88,9 @@ Other Changes: trickled with the new input queue (happened on some backends only). (#2467, #1336) - InputText: Fixed a one-frame display glitch where pressing Escape to revert after a deletion would lead to small garbage being displayed for one frame. Curiously a rather old bug! (#3008) -- InputText: Fixed an undo-state corruption issue when editing buffer before reactivating item. (#4947) +- InputText: Fixed an undo-state corruption issue when editing main buffer before reactivating item. (#4947) +- InputText: Fixed an undo-state corruption issue when editing in-flight buffer in user callback. + (#4947, #4949] [@JoshuaWebb] - Tables: Fixed incorrect border height used for logic when resizing one of several synchronized instance of a same table ID, when instances have a different height. (#3955). - Tables: Fixed incorrect auto-fit of parent windows when using non-resizable weighted columns. (#5276) diff --git a/imgui.h b/imgui.h index c9bdb2d7cd04..20b6ea9bf5d3 100644 --- a/imgui.h +++ b/imgui.h @@ -1809,6 +1809,7 @@ struct ImVector inline void resize(int new_size, const T& v) { if (new_size > Capacity) reserve(_grow_capacity(new_size)); if (new_size > Size) for (int n = Size; n < new_size; n++) memcpy(&Data[n], &v, sizeof(v)); Size = new_size; } inline void shrink(int new_size) { IM_ASSERT(new_size <= Size); Size = new_size; } // Resize a vector to a smaller size, guaranteed not to cause a reallocation inline void reserve(int new_capacity) { if (new_capacity <= Capacity) return; T* new_data = (T*)IM_ALLOC((size_t)new_capacity * sizeof(T)); if (Data) { memcpy(new_data, Data, (size_t)Size * sizeof(T)); IM_FREE(Data); } Data = new_data; Capacity = new_capacity; } + inline void reserve_discard(int new_capacity) { if (new_capacity <= Capacity) return; if (Data) IM_FREE(Data); Data = (T*)IM_ALLOC((size_t)new_capacity * sizeof(T)); Capacity = new_capacity; } // NB: It is illegal to call push_back/push_front/insert with a reference pointing inside the ImVector data itself! e.g. v.push_back(v[10]) is forbidden. inline void push_back(const T& v) { if (Size == Capacity) reserve(_grow_capacity(Size + 1)); memcpy(&Data[Size], &v, sizeof(v)); Size++; } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 5d146da9ccdc..aaa42dcf8166 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3929,46 +3929,39 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f return true; } -static inline void InputTextUpdateUndoStateAfterUserCallback(ImGuiInputTextState *state, const char *new_buf) { - // Find the shortest single replacement we can make to get the new text - // from the old text. - ImWchar *old_buf = state->TextW.Data; - int old_length = state->CurLenW; - int new_length = ImTextCountCharsFromUtf8(new_buf, NULL); - int shorter_length = ImMin(old_length, new_length); - - int where = 0; - while (where < shorter_length) { - unsigned int b; - ImTextCharFromUtf8(&b, new_buf + where, NULL); - if (old_buf[where] != b) +// Find the shortest single replacement we can make to get the new text from the old text. +// Important: needs to be run before TextW is rewritten with the new characters because calling STB_TEXTEDIT_GETCHAR() at the end. +// FIXME: Ideally we should transition toward (1) making InsertChars()/DeleteChars() update undo-stack (2) discourage (and keep reconcile) or obsolete (and remove reconcile) accessing buffer directly. +static void InputTextReconcileUndoStateAfterUserCallback(ImGuiInputTextState* state, const char* new_buf_a, int new_length_a) +{ + ImGuiContext& g = *GImGui; + const ImWchar* old_buf = state->TextW.Data; + const int old_length = state->CurLenW; + const int new_length = ImTextCountCharsFromUtf8(new_buf_a, new_buf_a + new_length_a); + g.TempBuffer.reserve_discard((new_length + 1) * sizeof(ImWchar)); + ImWchar* new_buf = (ImWchar*)(void*)g.TempBuffer.Data; + ImTextStrFromUtf8(new_buf, new_length + 1, new_buf_a, new_buf_a + new_length_a); + + const int shorter_length = ImMin(old_length, new_length); + int first_diff; + for (first_diff = 0; first_diff < shorter_length; first_diff++) + if (old_buf[first_diff] != new_buf[first_diff]) break; - - where += 1; - } + if (first_diff == old_length && first_diff == new_length) + return; int old_last_diff = old_length - 1; int new_last_diff = new_length - 1; - while (old_last_diff >= where && new_last_diff >= 0) { - unsigned int b; - ImTextCharFromUtf8(&b, new_buf + new_last_diff, NULL); - if (old_buf[old_last_diff] != b) + for (; old_last_diff >= first_diff && new_last_diff >= first_diff; old_last_diff--, new_last_diff--) + if (old_buf[old_last_diff] != new_buf[new_last_diff]) break; - old_last_diff -= 1; - new_last_diff -= 1; - } - - int insert_len = new_last_diff - where + 1; - int delete_len = old_last_diff - where + 1; - - if (insert_len > 0 || delete_len > 0) { - STB_TEXTEDIT_CHARTYPE *p = stb_text_createundo(&state->Stb.undostate, where, delete_len, insert_len); - if (p) { - for (int i=0; i < delete_len; ++i) - p[i] = ImStb::STB_TEXTEDIT_GETCHAR(state, where + i); - } - } + const int insert_len = new_last_diff - first_diff + 1; + const int delete_len = old_last_diff - first_diff + 1; + if (insert_len > 0 || delete_len > 0) + if (STB_TEXTEDIT_CHARTYPE* p = stb_text_createundo(&state->Stb.undostate, first_diff, delete_len, insert_len)) + for (int i = 0; i < delete_len; i++) + p[i] = ImStb::STB_TEXTEDIT_GETCHAR(state, first_diff + i); } // Edit a string of text @@ -4558,9 +4551,9 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ if (buf_dirty) { IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! - InputTextUpdateUndoStateAfterUserCallback(state, callback_data.Buf); + 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) - state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); + state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); // Worse case scenario resize state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL); state->CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() state->CursorAnimReset(); From 0b1bcfcc205077d31493ec851f87ca4b54b4eab5 Mon Sep 17 00:00:00 2001 From: Rokas Kupstys Date: Wed, 8 Jun 2022 12:34:35 +0300 Subject: [PATCH 11/12] Menus: Separate menu sets by nav layer. (#3496, #4797) + Demo: Remove incorrect and useless suggestion to use PushID(). Fixes a common case where opening menu in one nav layer and hovering a menu in another nav layer would open that menu without a click. --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 1 + imgui_demo.cpp | 7 ------- imgui_internal.h | 3 ++- imgui_widgets.cpp | 11 ++++++++--- 5 files changed, 13 insertions(+), 11 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 23a55026d36b..e1e8737f358a 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -101,6 +101,8 @@ Other Changes: - Menus: Adjusted BeginMenu() closing logic so hovering void or non-MenuItem() in parent window always lead to menu closure. Fixes using items that are not MenuItem() or BeginItem() at the root level of a popup with a child menu opened. +- Menus: Menus emitted from the main/scrolling layer are not part of the same menuset as menus emitted + from the menu-bar, avoiding accidental hovering from one to the other. (#3496, #4797) [@rokups] - Stack Tool: Added option to copy item path to clipboard. (#4631) - Settings: Fixed out-of-bounds read when .ini file on disk is empty. (#5351) [@quantum5] - DrawList: Fixed PathArcTo() emitting terminating vertices too close to arc vertices. (#4993) [@thedmd] diff --git a/imgui.cpp b/imgui.cpp index 89204387231b..19fac158b113 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6071,6 +6071,7 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) { ImGuiPopupData& popup_ref = g.OpenPopupStack[g.BeginPopupStack.Size]; popup_ref.Window = window; + popup_ref.ParentNavLayer = parent_window_in_stack->DC.NavLayerCurrent; g.BeginPopupStack.push_back(popup_ref); window->PopupId = popup_ref.PopupId; } diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 46fe4f2943f6..6684936c239e 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3546,19 +3546,12 @@ static void ShowDemoWindowPopups() ImGui::TextWrapped("Below we are testing adding menu items to a regular window. It's rather unusual but should work!"); ImGui::Separator(); - // Note: As a quirk in this very specific example, we want to differentiate the parent of this menu from the - // parent of the various popup menus above. To do so we are encloding the items in a PushID()/PopID() block - // to make them two different menusets. If we don't, opening any popup above and hovering our menu here would - // open it. This is because once a menu is active, we allow to switch to a sibling menu by just hovering on it, - // which is the desired behavior for regular menus. - ImGui::PushID("foo"); ImGui::MenuItem("Menu item", "CTRL+M"); if (ImGui::BeginMenu("Menu inside a regular window")) { ShowExampleMenuFile(); ImGui::EndMenu(); } - ImGui::PopID(); ImGui::Separator(); ImGui::TreePop(); } diff --git a/imgui_internal.h b/imgui_internal.h index 3f7cc5f82f82..309b4bd71548 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1038,12 +1038,13 @@ struct ImGuiPopupData ImGuiID PopupId; // Set on OpenPopup() ImGuiWindow* Window; // Resolved on BeginPopup() - may stay unresolved if user never calls OpenPopup() ImGuiWindow* SourceWindow; // Set on OpenPopup() copy of NavWindow at the time of opening the popup + int ParentNavLayer; // Resolved on BeginPopup(). Actually a ImGuiNavLayer type (declared down below), initialized to -1 which is not part of an enum, but serves well-enough as "not any of layers" value int OpenFrameCount; // Set on OpenPopup() ImGuiID OpenParentId; // Set on OpenPopup(), we need this to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) ImVec2 OpenPopupPos; // Set on OpenPopup(), preferred popup position (typically == OpenMousePos when using mouse) ImVec2 OpenMousePos; // Set on OpenPopup(), copy of mouse position at the time of opening popup - ImGuiPopupData() { memset(this, 0, sizeof(*this)); OpenFrameCount = -1; } + ImGuiPopupData() { memset(this, 0, sizeof(*this)); ParentNavLayer = OpenFrameCount = -1; } }; enum ImGuiNextWindowDataFlags_ diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index aaa42dcf8166..980172e8e6cb 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -6939,14 +6939,19 @@ static bool IsRootOfOpenMenuSet() if ((g.OpenPopupStack.Size <= g.BeginPopupStack.Size) || (window->Flags & ImGuiWindowFlags_ChildMenu)) return false; - // Initially we used 'OpenParentId' to differentiate multiple menu sets from each others (e.g. inside menu bar vs loose menu items) based on parent ID. + // Initially we used 'upper_popup->OpenParentId == window->IDStack.back()' to differentiate multiple menu sets from each others + // (e.g. inside menu bar vs loose menu items) based on parent ID. // This would however prevent the use of e.g. PuhsID() user code submitting menus. // Previously this worked between popup and a first child menu because the first child menu always had the _ChildWindow flag, // making hovering on parent popup possible while first child menu was focused - but this was generally a bug with other side effects. // Instead we don't treat Popup specifically (in order to consistently support menu features in them), maybe the first child menu of a Popup - // doesn't have the _ChildWindow flag, and we rely on this IsRootOfOpenMenuSet() check to allow hovering between root window/popup and first chilld menu. + // doesn't have the _ChildWindow flag, and we rely on this IsRootOfOpenMenuSet() check to allow hovering between root window/popup and first child menu. + // In the end, lack of ID check made it so we could no longer differentiate between separate menu sets. To compensate for that, we at least check parent window nav layer. + // This fixes the most common case of menu opening on hover when moving between window content and menu bar. Multiple different menu sets in same nav layer would still + // open on hover, but that should be a lesser problem, because if such menus are close in proximity in window content then it won't feel weird and if they are far apart + // it likely won't be a problem anyone runs into. const ImGuiPopupData* upper_popup = &g.OpenPopupStack[g.BeginPopupStack.Size]; - return (/*upper_popup->OpenParentId == window->IDStack.back() &&*/ upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu)); + return (window->DC.NavLayerCurrent == upper_popup->ParentNavLayer && upper_popup->Window && (upper_popup->Window->Flags & ImGuiWindowFlags_ChildMenu)); } bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) From 076d8fc86889b482785902628f64216533cdf849 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 10 Jun 2022 19:25:53 +0200 Subject: [PATCH 12/12] Nav: Fixed issues with nav request being transferred to another window when calling SetKeyboardFocusHere() and simultaneous changing window focus. (#4449) --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 23 +++++++++++++++++------ imgui.h | 2 +- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index e1e8737f358a..22152e963f10 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -82,6 +82,8 @@ Other Changes: - InputScalar: Automatically allow hexadecimal input when format is %X (without extra flag). - InputScalar: Automatically allow scientific input when format is float/double (without extra flag). - Nav: Fixed nav movement in a scope with only one disabled item from focusing the disabled item. (#5189) +- Nav: Fixed issues with nav request being transferred to another window when calling SetKeyboardFocusHere() + and simultaneous changing window focus. (#4449) - IsItemHovered(): added ImGuiHoveredFlags_NoNavOverride to disable the behavior where the return value is overriden by focus when gamepad/keyboard navigation is active. - InputText: Fixed pressing Tab emitting two tabs characters because of dual Keys/Chars events being diff --git a/imgui.cpp b/imgui.cpp index 19fac158b113..bfd624f70963 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7383,7 +7383,10 @@ void ImGui::SetKeyboardFocusHere(int offset) ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; IM_ASSERT(offset >= -1); // -1 is allowed but not below + g.NavWindow = window; + g.NavInitRequest = g.NavMoveSubmitted = g.NavMoveScoringItems = false; + ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY; NavMoveRequestSubmit(ImGuiDir_None, offset < 0 ? ImGuiDir_Up : ImGuiDir_Down, ImGuiNavMoveFlags_Tabbing | ImGuiNavMoveFlags_FocusApi, scroll_flags); // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable. if (offset == -1) @@ -9574,8 +9577,11 @@ void ImGui::SetFocusID(ImGuiID id, ImGuiWindow* window) // Note that window may be != g.CurrentWindow (e.g. SetFocusID call in InputTextEx for multi-line text) const ImGuiNavLayer nav_layer = window->DC.NavLayerCurrent; if (g.NavWindow != window) - g.NavInitRequest = false; - g.NavWindow = window; + { + g.NavWindow = window; + g.NavInitRequest = g.NavMoveSubmitted = g.NavMoveScoringItems = false; + NavUpdateAnyRequestFlag(); + } g.NavId = id; g.NavLayer = nav_layer; g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; @@ -9823,7 +9829,12 @@ static void ImGui::NavProcessItem() // Update window-relative bounding box of navigated item if (g.NavId == id) { - g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window. + if (g.NavWindow != window) + { + g.NavWindow = window; // Always refresh g.NavWindow, because some operations such as FocusItem() don't have a window. + g.NavInitRequest = g.NavMoveSubmitted = g.NavMoveScoringItems = false; + NavUpdateAnyRequestFlag(); + } g.NavLayer = window->DC.NavLayerCurrent; g.NavFocusScopeId = window->DC.NavFocusScopeIdCurrent; g.NavIdIsAlive = true; @@ -9901,10 +9912,11 @@ void ImGui::NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavM g.NavMoveScrollFlags = scroll_flags; g.NavMoveForwardToNextFrame = false; g.NavMoveKeyMods = g.IO.KeyMods; - g.NavTabbingCounter = 0; g.NavMoveResultLocal.Clear(); g.NavMoveResultLocalVisible.Clear(); g.NavMoveResultOther.Clear(); + g.NavTabbingCounter = 0; + g.NavTabbingResultFirst.Clear(); NavUpdateAnyRequestFlag(); } @@ -9971,7 +9983,7 @@ void ImGui::NavRestoreLayer(ImGuiNavLayer layer) { ImGuiContext& g = *GImGui; if (layer == ImGuiNavLayer_Main) - g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow); + g.NavWindow = NavRestoreLastChildNavWindow(g.NavWindow); // FIXME-NAV: Should clear ongoing nav requests? ImGuiWindow* window = g.NavWindow; if (window->NavLastIds[layer] != 0) { @@ -10451,7 +10463,6 @@ void ImGui::NavUpdateCreateTabbingRequest() ImGuiScrollFlags scroll_flags = window->Appearing ? ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_AlwaysCenterY : ImGuiScrollFlags_KeepVisibleEdgeX | ImGuiScrollFlags_KeepVisibleEdgeY; ImGuiDir clip_dir = (g.NavTabbingDir < 0) ? ImGuiDir_Up : ImGuiDir_Down; NavMoveRequestSubmit(ImGuiDir_None, clip_dir, ImGuiNavMoveFlags_Tabbing, scroll_flags); // FIXME-NAV: Once we refactor tabbing, add LegacyApi flag to not activate non-inputable. - g.NavTabbingResultFirst.Clear(); g.NavTabbingCounter = -1; } diff --git a/imgui.h b/imgui.h index 20b6ea9bf5d3..4a8a4865e58a 100644 --- a/imgui.h +++ b/imgui.h @@ -65,7 +65,7 @@ Index of this file: // Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens) #define IMGUI_VERSION "1.88 WIP" -#define IMGUI_VERSION_NUM 18727 +#define IMGUI_VERSION_NUM 18728 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) #define IMGUI_HAS_TABLE