From 04316bd223f98e4c40bd82dc5f2a35c4891e4e5f Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 26 Sep 2022 16:32:09 +0200 Subject: [PATCH 01/21] ColorEdit3: fixed id collision leading to an assertion. (#5707) --- docs/CHANGELOG.txt | 1 + imgui_widgets.cpp | 23 +++++++++++++---------- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index c19ce671..fc8b1d47 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -110,6 +110,7 @@ Other Changes: - InputText: added support for shift+click style selection. (#5619) [@procedural] - InputText: clarified that callbacks cannot modify buffer when using the ReadOnly flag. - InputText: fixed minor one-frame selection glitch when reverting with Escape. +- ColorEdit3: fixed id collision leading to an assertion. (#5707) - IsItemHovered: Added ImGuiHoveredFlags_DelayNormal and ImGuiHoveredFlags_DelayShort flags, allowing to introduce a shared delay for tooltip idioms. The delays are respectively io.HoverDelayNormal (default to 0.30f) and io.HoverDelayFast (default to 0.10f). (#1485) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 3318ca90..053e37a7 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5114,16 +5114,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(); } } @@ -5186,7 +5189,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; @@ -5574,7 +5577,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(); From 56c3eaed20fc664c556790f3dba2b8f016c9509c Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 27 Sep 2022 14:24:21 +0200 Subject: [PATCH 02/21] ImDrawList: asserting on incorrect value for CurveTessellationTol (#5713) --- imgui_draw.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d4c6dcfd..e3092987 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1295,6 +1295,7 @@ void ImDrawList::PathBezierCubicCurveTo(const ImVec2& p2, const ImVec2& p3, cons ImVec2 p1 = _Path.back(); if (num_segments == 0) { + IM_ASSERT(_Data->CurveTessellationTol > 0.0f); PathBezierCubicCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y, _Data->CurveTessellationTol, 0); // Auto-tessellated } else @@ -1310,6 +1311,7 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, ImVec2 p1 = _Path.back(); if (num_segments == 0) { + IM_ASSERT(_Data->CurveTessellationTol > 0.0f); PathBezierQuadraticCurveToCasteljau(&_Path, p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, _Data->CurveTessellationTol, 0);// Auto-tessellated } else From 325299f94220ac1f556815a42aea3d101beed4a7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 14 Sep 2022 15:46:27 +0200 Subject: [PATCH 03/21] Backends: OpenGL: Add ability to #define IMGUI_IMPL_OPENGL_DEBUG. (#4468, #4825, #4832, #5127, #5655, #5709) --- backends/imgui_impl_opengl3.cpp | 73 +++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 30 deletions(-) diff --git a/backends/imgui_impl_opengl3.cpp b/backends/imgui_impl_opengl3.cpp index e52e0b53..5944eb80 100644 --- a/backends/imgui_impl_opengl3.cpp +++ b/backends/imgui_impl_opengl3.cpp @@ -14,6 +14,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2022-09-27: OpenGL: Added ability to '#define IMGUI_IMPL_OPENGL_DEBUG'. // 2022-05-23: OpenGL: Reworking 2021-12-15 "Using buffer orphaning" so it only happens on Intel GPU, seems to cause problems otherwise. (#4468, #4825, #4832, #5127). // 2022-05-13: OpenGL: Fix state corruption on OpenGL ES 2.0 due to not preserving GL_ELEMENT_ARRAY_BUFFER_BINDING and vertex attribute states. // 2021-12-15: OpenGL: Using buffer orphaning + glBufferSubData(), seems to fix leaks with multi-viewports with some Intel HD drivers. @@ -178,6 +179,15 @@ #define IMGUI_IMPL_OPENGL_MAY_HAVE_EXTENSIONS #endif +// [Debugging] +//#define IMGUI_IMPL_OPENGL_DEBUG +#ifdef IMGUI_IMPL_OPENGL_DEBUG +#include +#define GL_CALL(_CALL) do { _CALL; GLenum gl_err = glGetError(); if (gl_err != 0) fprintf(stderr, "GL error 0x%x returned from '%s'.\n", gl_err, #_CALL); } while (0) // Call with error check +#else +#define GL_CALL(_CALL) _CALL // Call without error check +#endif + // OpenGL Data struct ImGui_ImplOpenGL3_Data { @@ -270,11 +280,14 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version) if (strncmp(vendor, "Intel", 5) == 0) bd->UseBufferSubData = true; #endif - //printf("GL_MAJOR_VERSION = %d\nGL_MINOR_VERSION = %d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", major, minor, (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG] #else bd->GlVersion = 200; // GLES 2 #endif +#ifdef IMGUI_IMPL_OPENGL_DEBUG + printf("GL_MAJOR_VERSION = %d\nGL_MINOR_VERSION = %d\nGL_VENDOR = '%s'\nGL_RENDERER = '%s'\n", major, minor, (const char*)glGetString(GL_VENDOR), (const char*)glGetString(GL_RENDERER)); // [DEBUG] +#endif + #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET if (bd->GlVersion >= 320) io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. @@ -373,7 +386,7 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid // Setup viewport, orthographic projection matrix // Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. - glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + GL_CALL(glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height)); float L = draw_data->DisplayPos.x; float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x; float T = draw_data->DisplayPos.y; @@ -403,14 +416,14 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid #endif // Bind vertex/index buffers and setup attributes for ImDrawVert - glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle); - glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle); - glEnableVertexAttribArray(bd->AttribLocationVtxPos); - glEnableVertexAttribArray(bd->AttribLocationVtxUV); - glEnableVertexAttribArray(bd->AttribLocationVtxColor); - glVertexAttribPointer(bd->AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos)); - glVertexAttribPointer(bd->AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv)); - glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col)); + GL_CALL(glBindBuffer(GL_ARRAY_BUFFER, bd->VboHandle)); + GL_CALL(glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bd->ElementsHandle)); + GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxPos)); + GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxUV)); + GL_CALL(glEnableVertexAttribArray(bd->AttribLocationVtxColor)); + GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxPos, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, pos))); + GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxUV, 2, GL_FLOAT, GL_FALSE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, uv))); + GL_CALL(glVertexAttribPointer(bd->AttribLocationVtxColor, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(ImDrawVert), (GLvoid*)IM_OFFSETOF(ImDrawVert, col))); } // OpenGL3 Render function. @@ -470,7 +483,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) // The renderer would actually work without any VAO bound, but then our VertexAttrib calls would overwrite the default one currently bound. GLuint vertex_array_object = 0; #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY - glGenVertexArrays(1, &vertex_array_object); + GL_CALL(glGenVertexArrays(1, &vertex_array_object)); #endif ImGui_ImplOpenGL3_SetupRenderState(draw_data, fb_width, fb_height, vertex_array_object); @@ -494,20 +507,20 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) if (bd->VertexBufferSize < vtx_buffer_size) { bd->VertexBufferSize = vtx_buffer_size; - glBufferData(GL_ARRAY_BUFFER, bd->VertexBufferSize, NULL, GL_STREAM_DRAW); + GL_CALL(glBufferData(GL_ARRAY_BUFFER, bd->VertexBufferSize, NULL, GL_STREAM_DRAW)); } if (bd->IndexBufferSize < idx_buffer_size) { bd->IndexBufferSize = idx_buffer_size; - glBufferData(GL_ELEMENT_ARRAY_BUFFER, bd->IndexBufferSize, NULL, GL_STREAM_DRAW); + GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, bd->IndexBufferSize, NULL, GL_STREAM_DRAW)); } - glBufferSubData(GL_ARRAY_BUFFER, 0, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data); - glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data); + GL_CALL(glBufferSubData(GL_ARRAY_BUFFER, 0, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data)); + GL_CALL(glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data)); } else { - glBufferData(GL_ARRAY_BUFFER, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW); - glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW); + GL_CALL(glBufferData(GL_ARRAY_BUFFER, vtx_buffer_size, (const GLvoid*)cmd_list->VtxBuffer.Data, GL_STREAM_DRAW)); + GL_CALL(glBufferData(GL_ELEMENT_ARRAY_BUFFER, idx_buffer_size, (const GLvoid*)cmd_list->IdxBuffer.Data, GL_STREAM_DRAW)); } for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) @@ -531,23 +544,23 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) continue; // Apply scissor/clipping rectangle (Y is inverted in OpenGL) - glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y)); + GL_CALL(glScissor((int)clip_min.x, (int)((float)fb_height - clip_max.y), (int)(clip_max.x - clip_min.x), (int)(clip_max.y - clip_min.y))); // Bind texture, Draw - glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID()); + GL_CALL(glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->GetTexID())); #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_VTX_OFFSET if (bd->GlVersion >= 320) - glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset); + GL_CALL(glDrawElementsBaseVertex(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)), (GLint)pcmd->VtxOffset)); else #endif - glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx))); + GL_CALL(glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, (void*)(intptr_t)(pcmd->IdxOffset * sizeof(ImDrawIdx)))); } } } // Destroy the temporary VAO #ifdef IMGUI_IMPL_OPENGL_USE_VERTEX_ARRAY - glDeleteVertexArrays(1, &vertex_array_object); + GL_CALL(glDeleteVertexArrays(1, &vertex_array_object)); #endif // Restore modified GL state @@ -600,21 +613,21 @@ bool ImGui_ImplOpenGL3_CreateFontsTexture() // Upload texture to graphics system // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) GLint last_texture; - glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); - glGenTextures(1, &bd->FontTexture); - glBindTexture(GL_TEXTURE_2D, bd->FontTexture); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); + GL_CALL(glGenTextures(1, &bd->FontTexture)); + GL_CALL(glBindTexture(GL_TEXTURE_2D, bd->FontTexture)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); + GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); #ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES - glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); #endif - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); // Store our identifier io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); // Restore state - glBindTexture(GL_TEXTURE_2D, last_texture); + GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture)); return true; } From a725db17b7632f93f655327c49b55878be309315 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 27 Sep 2022 18:47:20 +0200 Subject: [PATCH 04/21] Comments for flags discoverability + add to debug log (#3795, #4559) --- imgui.cpp | 2 ++ imgui.h | 3 +++ 2 files changed, 5 insertions(+) diff --git a/imgui.cpp b/imgui.cpp index 0f554940..04b43a31 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4229,6 +4229,7 @@ static void StartLockWheelingWindow(ImGuiWindow* window) ImGuiContext& g = *GImGui; if (g.WheelingWindow == window) return; + IMGUI_DEBUG_LOG_IO("StartLockWheelingWindow() \"%s\"\n", window ? window->Name : "NULL"); g.WheelingWindow = window; g.WheelingWindowRefMousePos = g.IO.MousePos; g.WheelingWindowTimer = WINDOWS_MOUSE_WHEEL_SCROLL_LOCK_TIMER; @@ -4246,6 +4247,7 @@ void ImGui::UpdateMouseWheel() g.WheelingWindowTimer = 0.0f; if (g.WheelingWindowTimer <= 0.0f) { + IMGUI_DEBUG_LOG_IO("UpdateMouseWheel() release WheelingWindow lock \"%s\"\n", g.WheelingWindow->Name); g.WheelingWindow = NULL; g.WheelingWindowTimer = 0.0f; } diff --git a/imgui.h b/imgui.h index 8e1dc915..d7bb365d 100644 --- a/imgui.h +++ b/imgui.h @@ -947,6 +947,7 @@ namespace ImGui //----------------------------------------------------------------------------- // Flags for ImGui::Begin() +// (Those are per-window flags. There are shared flags in ImGuiIO: io.ConfigWindowsResizeFromEdges and io.ConfigWindowsMoveFromTitleBarOnly) enum ImGuiWindowFlags_ { ImGuiWindowFlags_None = 0, @@ -984,6 +985,7 @@ enum ImGuiWindowFlags_ }; // Flags for ImGui::InputText() +// (Those are per-item flags. There are shared flags in ImGuiIO: io.ConfigInputTextCursorBlink and io.ConfigInputTextEnterKeepActive) enum ImGuiInputTextFlags_ { ImGuiInputTextFlags_None = 0, @@ -1678,6 +1680,7 @@ enum ImGuiColorEditFlags_ // Flags for DragFloat(), DragInt(), SliderFloat(), SliderInt() etc. // We use the same sets of flags for DragXXX() and SliderXXX() functions as the features are the same and it makes it easier to swap them. +// (Those are per-item flags. There are shared flags in ImGuiIO: io.ConfigDragClickToInputText) enum ImGuiSliderFlags_ { ImGuiSliderFlags_None = 0, From 73efcec564adc18d5618471ecb1a6e352787c510 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 27 Sep 2022 22:21:47 +0200 Subject: [PATCH 05/21] Examples: disable GL related warnings on Mac + amend to ignore list. --- .gitignore | 3 +++ backends/imgui_impl_glut.cpp | 1 + examples/example_glfw_opengl3/main.cpp | 1 + examples/example_glut_opengl2/main.cpp | 1 + examples/example_sdl_sdlrenderer/main.cpp | 2 +- 5 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 86bed609..44225c9a 100644 --- a/.gitignore +++ b/.gitignore @@ -45,9 +45,12 @@ examples/example_emscripten_wgpu/web/* cmake-build-* ## Unix executables from our example Makefiles +examples/example_glfw_metal/example_glfw_metal examples/example_glfw_opengl2/example_glfw_opengl2 examples/example_glfw_opengl3/example_glfw_opengl3 examples/example_glut_opengl2/example_glut_opengl2 examples/example_null/example_null +examples/example_sdl_metal/example_sdl_metal examples/example_sdl_opengl2/example_sdl_opengl2 examples/example_sdl_opengl3/example_sdl_opengl3 +examples/example_sdl_sdlrenderer/example_sdl_sdlrenderer diff --git a/backends/imgui_impl_glut.cpp b/backends/imgui_impl_glut.cpp index 26b48318..7a9a32a6 100644 --- a/backends/imgui_impl_glut.cpp +++ b/backends/imgui_impl_glut.cpp @@ -31,6 +31,7 @@ #include "imgui.h" #include "imgui_impl_glut.h" +#define GL_SILENCE_DEPRECATION #ifdef __APPLE__ #include #else diff --git a/examples/example_glfw_opengl3/main.cpp b/examples/example_glfw_opengl3/main.cpp index 93313415..222e27de 100644 --- a/examples/example_glfw_opengl3/main.cpp +++ b/examples/example_glfw_opengl3/main.cpp @@ -7,6 +7,7 @@ #include "imgui_impl_glfw.h" #include "imgui_impl_opengl3.h" #include +#define GL_SILENCE_DEPRECATION #if defined(IMGUI_IMPL_OPENGL_ES2) #include #endif diff --git a/examples/example_glut_opengl2/main.cpp b/examples/example_glut_opengl2/main.cpp index d9bd9040..81239cd6 100644 --- a/examples/example_glut_opengl2/main.cpp +++ b/examples/example_glut_opengl2/main.cpp @@ -17,6 +17,7 @@ #include "imgui.h" #include "imgui_impl_glut.h" #include "imgui_impl_opengl2.h" +#define GL_SILENCE_DEPRECATION #ifdef __APPLE__ #include #else diff --git a/examples/example_sdl_sdlrenderer/main.cpp b/examples/example_sdl_sdlrenderer/main.cpp index c48eecd4..736e7c6f 100644 --- a/examples/example_sdl_sdlrenderer/main.cpp +++ b/examples/example_sdl_sdlrenderer/main.cpp @@ -38,7 +38,7 @@ int main(int, char**) if (renderer == NULL) { SDL_Log("Error creating SDL_Renderer!"); - return false; + return 0; } //SDL_RendererInfo info; //SDL_GetRendererInfo(renderer, &info); From 12c024689023bd5dcd5905bf96ad6e1694b8c043 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 28 Sep 2022 12:07:43 +0200 Subject: [PATCH 06/21] Removed support for 1.42-era IMGUI_DISABLE_INCLUDE_IMCONFIG_H / IMGUI_INCLUDE_IMCONFIG_H. (#255) --- docs/CHANGELOG.txt | 2 ++ imgui.h | 5 ++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index fc8b1d47..90349171 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -94,6 +94,8 @@ Breaking changes: Without '#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS' this will silently be fixed until we obsolete it. (This incorrect pattern has been mentioned or suggested in: #4510, #3355, #1760, #1490, #4152, #150, threads have been amended to refer to this issue). + - Removed support for 1.42-era IMGUI_DISABLE_INCLUDE_IMCONFIG_H / IMGUI_INCLUDE_IMCONFIG_H. (#255) + They only made sense before we could use IMGUI_USER_CONFIG. Other Changes: diff --git a/imgui.h b/imgui.h index d7bb365d..6932971a 100644 --- a/imgui.h +++ b/imgui.h @@ -48,13 +48,12 @@ Index of this file: #pragma once -// Configuration file with compile-time options (edit imconfig.h or '#define IMGUI_USER_CONFIG "myfilename.h" from your build system') +// Configuration file with compile-time options +// (edit imconfig.h or '#define IMGUI_USER_CONFIG "myfilename.h" from your build system') #ifdef IMGUI_USER_CONFIG #include IMGUI_USER_CONFIG #endif -#if !defined(IMGUI_DISABLE_INCLUDE_IMCONFIG_H) || defined(IMGUI_INCLUDE_IMCONFIG_H) #include "imconfig.h" -#endif #ifndef IMGUI_DISABLE From 5c4426c5b8103782d8df74464a4cf43804b8d793 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 28 Sep 2022 12:22:34 +0200 Subject: [PATCH 07/21] Demo: Fixed Log & Console from losing scrolling position with Auto-Scroll when child is clipped. (#5721) --- docs/CHANGELOG.txt | 1 + imgui_demo.cpp | 211 +++++++++++++++++++++++---------------------- 2 files changed, 108 insertions(+), 104 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 90349171..596448a0 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -149,6 +149,7 @@ Other Changes: - Docs: Fixed various typos in comments and documentations. (#5649, #5675, #5679) [@tocic, @lessigsx] - Demo: Improved "Constrained-resizing window" example, more clearly showcase aspect-ratio. (#5627) - Demo: Added more explicit "Center window" mode to "Overlay example". (#5618) +- Demo: Fixed Log & Console from losing scrolling position with Auto-Scroll when child is clipped. (#5721) - Examples: Added all SDL examples to default VS solution. - Backends: GLFW: Honor GLFW_CURSOR_DISABLED by not setting mouse position. (#5625) [@scorpion-26] - Backends: SDL: Disable SDL 2.0.22 new "auto capture" which prevents drag and drop across windows diff --git a/imgui_demo.cpp b/imgui_demo.cpp index c1a3bc51..349e7020 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -6664,66 +6664,68 @@ struct ExampleAppConsole // Reserve enough left-over height for 1 separator + 1 input text const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); - ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); - if (ImGui::BeginPopupContextWindow()) + if (ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar)) { - if (ImGui::Selectable("Clear")) ClearLog(); - ImGui::EndPopup(); - } + if (ImGui::BeginPopupContextWindow()) + { + if (ImGui::Selectable("Clear")) ClearLog(); + ImGui::EndPopup(); + } - // Display every line as a separate entry so we can change their color or add custom widgets. - // If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); - // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping - // to only process visible items. The clipper will automatically measure the height of your first item and then - // "seek" to display only items in the visible area. - // To use the clipper we can replace your standard loop: - // for (int i = 0; i < Items.Size; i++) - // With: - // ImGuiListClipper clipper; - // clipper.Begin(Items.Size); - // while (clipper.Step()) - // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) - // - That your items are evenly spaced (same height) - // - That you have cheap random access to your elements (you can access them given their index, - // without processing all the ones before) - // You cannot this code as-is if a filter is active because it breaks the 'cheap random-access' property. - // We would need random-access on the post-filtered list. - // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices - // or offsets of items that passed the filtering test, recomputing this array when user changes the filter, - // and appending newly elements as they are inserted. This is left as a task to the user until we can manage - // to improve this example code! - // If your items are of variable height: - // - Split them into same height items would be simpler and facilitate random-seeking into your list. - // - Consider using manual call to IsRectVisible() and skipping extraneous decoration from your items. - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing - if (copy_to_clipboard) - ImGui::LogToClipboard(); - for (int i = 0; i < Items.Size; i++) - { - const char* item = Items[i]; - if (!Filter.PassFilter(item)) - continue; + // Display every line as a separate entry so we can change their color or add custom widgets. + // If you only want raw text you can use ImGui::TextUnformatted(log.begin(), log.end()); + // NB- if you have thousands of entries this approach may be too inefficient and may require user-side clipping + // to only process visible items. The clipper will automatically measure the height of your first item and then + // "seek" to display only items in the visible area. + // To use the clipper we can replace your standard loop: + // for (int i = 0; i < Items.Size; i++) + // With: + // ImGuiListClipper clipper; + // clipper.Begin(Items.Size); + // while (clipper.Step()) + // for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) + // - That your items are evenly spaced (same height) + // - That you have cheap random access to your elements (you can access them given their index, + // without processing all the ones before) + // You cannot this code as-is if a filter is active because it breaks the 'cheap random-access' property. + // We would need random-access on the post-filtered list. + // A typical application wanting coarse clipping and filtering may want to pre-compute an array of indices + // or offsets of items that passed the filtering test, recomputing this array when user changes the filter, + // and appending newly elements as they are inserted. This is left as a task to the user until we can manage + // to improve this example code! + // If your items are of variable height: + // - Split them into same height items would be simpler and facilitate random-seeking into your list. + // - Consider using manual call to IsRectVisible() and skipping extraneous decoration from your items. + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 1)); // Tighten spacing + if (copy_to_clipboard) + ImGui::LogToClipboard(); + for (int i = 0; i < Items.Size; i++) + { + const char* item = Items[i]; + if (!Filter.PassFilter(item)) + continue; - // Normally you would store more information in your item than just a string. - // (e.g. make Items[] an array of structure, store color/type etc.) - ImVec4 color; - bool has_color = false; - if (strstr(item, "[error]")) { color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); has_color = true; } - else if (strncmp(item, "# ", 2) == 0) { color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); has_color = true; } - if (has_color) - ImGui::PushStyleColor(ImGuiCol_Text, color); - ImGui::TextUnformatted(item); - if (has_color) - ImGui::PopStyleColor(); - } - if (copy_to_clipboard) - ImGui::LogFinish(); + // Normally you would store more information in your item than just a string. + // (e.g. make Items[] an array of structure, store color/type etc.) + ImVec4 color; + bool has_color = false; + if (strstr(item, "[error]")) { color = ImVec4(1.0f, 0.4f, 0.4f, 1.0f); has_color = true; } + else if (strncmp(item, "# ", 2) == 0) { color = ImVec4(1.0f, 0.8f, 0.6f, 1.0f); has_color = true; } + if (has_color) + ImGui::PushStyleColor(ImGuiCol_Text, color); + ImGui::TextUnformatted(item); + if (has_color) + ImGui::PopStyleColor(); + } + if (copy_to_clipboard) + ImGui::LogFinish(); - if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) - ImGui::SetScrollHereY(1.0f); - ScrollToBottom = false; + if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) + ImGui::SetScrollHereY(1.0f); + ScrollToBottom = false; - ImGui::PopStyleVar(); + ImGui::PopStyleVar(); + } ImGui::EndChild(); ImGui::Separator(); @@ -6971,63 +6973,64 @@ struct ExampleAppLog Filter.Draw("Filter", -100.0f); ImGui::Separator(); - ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar); - - if (clear) - Clear(); - if (copy) - ImGui::LogToClipboard(); - ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); - const char* buf = Buf.begin(); - const char* buf_end = Buf.end(); - if (Filter.IsActive()) - { - // In this example we don't use the clipper when Filter is enabled. - // This is because we don't have random access to the result of our filter. - // A real application processing logs with ten of thousands of entries may want to store the result of - // search/filter.. especially if the filtering function is not trivial (e.g. reg-exp). - for (int line_no = 0; line_no < LineOffsets.Size; line_no++) - { - const char* line_start = buf + LineOffsets[line_no]; - const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; - if (Filter.PassFilter(line_start, line_end)) - ImGui::TextUnformatted(line_start, line_end); - } - } - else + if (ImGui::BeginChild("scrolling", ImVec2(0, 0), false, ImGuiWindowFlags_HorizontalScrollbar)) { - // The simplest and easy way to display the entire buffer: - // ImGui::TextUnformatted(buf_begin, buf_end); - // And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward - // to skip non-visible lines. Here we instead demonstrate using the clipper to only process lines that are - // within the visible area. - // If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them - // on your side is recommended. Using ImGuiListClipper requires - // - A) random access into your data - // - B) items all being the same height, - // both of which we can handle since we have an array pointing to the beginning of each line of text. - // When using the filter (in the block of code above) we don't have random access into the data to display - // anymore, which is why we don't use the clipper. Storing or skimming through the search result would make - // it possible (and would be recommended if you want to search through tens of thousands of entries). - ImGuiListClipper clipper; - clipper.Begin(LineOffsets.Size); - while (clipper.Step()) - { - for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) + if (clear) + Clear(); + if (copy) + ImGui::LogToClipboard(); + + ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); + const char* buf = Buf.begin(); + const char* buf_end = Buf.end(); + if (Filter.IsActive()) + { + // In this example we don't use the clipper when Filter is enabled. + // This is because we don't have random access to the result of our filter. + // A real application processing logs with ten of thousands of entries may want to store the result of + // search/filter.. especially if the filtering function is not trivial (e.g. reg-exp). + for (int line_no = 0; line_no < LineOffsets.Size; line_no++) { const char* line_start = buf + LineOffsets[line_no]; const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; - ImGui::TextUnformatted(line_start, line_end); + if (Filter.PassFilter(line_start, line_end)) + ImGui::TextUnformatted(line_start, line_end); } } - clipper.End(); - } - ImGui::PopStyleVar(); - - if (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) - ImGui::SetScrollHereY(1.0f); + else + { + // The simplest and easy way to display the entire buffer: + // ImGui::TextUnformatted(buf_begin, buf_end); + // And it'll just work. TextUnformatted() has specialization for large blob of text and will fast-forward + // to skip non-visible lines. Here we instead demonstrate using the clipper to only process lines that are + // within the visible area. + // If you have tens of thousands of items and their processing cost is non-negligible, coarse clipping them + // on your side is recommended. Using ImGuiListClipper requires + // - A) random access into your data + // - B) items all being the same height, + // both of which we can handle since we have an array pointing to the beginning of each line of text. + // When using the filter (in the block of code above) we don't have random access into the data to display + // anymore, which is why we don't use the clipper. Storing or skimming through the search result would make + // it possible (and would be recommended if you want to search through tens of thousands of entries). + ImGuiListClipper clipper; + clipper.Begin(LineOffsets.Size); + while (clipper.Step()) + { + for (int line_no = clipper.DisplayStart; line_no < clipper.DisplayEnd; line_no++) + { + const char* line_start = buf + LineOffsets[line_no]; + const char* line_end = (line_no + 1 < LineOffsets.Size) ? (buf + LineOffsets[line_no + 1] - 1) : buf_end; + ImGui::TextUnformatted(line_start, line_end); + } + } + clipper.End(); + } + ImGui::PopStyleVar(); + if (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY()) + ImGui::SetScrollHereY(1.0f); + } ImGui::EndChild(); ImGui::End(); } From 4d4889bf1b201fbe81e3ff05bd3a63a2c913832f Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 28 Sep 2022 12:42:06 +0200 Subject: [PATCH 08/21] Refactor CalcWordWrapPositionA() to take on the responsability of minimum character display. Add CalcWordWrapNextLineStartA(), simplify caller code. Should be no-op but incrementing IMGUI_VERSION_NUM just in case. Preparing for #5720 --- imgui.h | 2 +- imgui_draw.cpp | 47 ++++++++++++++++++++--------------------------- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/imgui.h b/imgui.h index 6932971a..0d32389a 100644 --- a/imgui.h +++ b/imgui.h @@ -23,7 +23,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM > 12345') #define IMGUI_VERSION "1.89 WIP" -#define IMGUI_VERSION_NUM 18823 +#define IMGUI_VERSION_NUM 18824 #define IMGUI_HAS_TABLE /* diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e3092987..e7eda792 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3333,11 +3333,21 @@ const ImFontGlyph* ImFont::FindGlyphNoFallback(ImWchar c) const return &Glyphs.Data[i]; } -const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const +// Wrapping skips upcoming blanks +static inline const char* CalcWordWrapNextLineStartA(const char* text, const char* text_end) { - // Simple word-wrapping for English, not full-featured. Please submit failing cases! - // FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) + while (text < text_end && ImCharIsBlankA(*text)) + text++; + if (*text == '\n') + text++; + return text; +} +// Simple word-wrapping for English, not full-featured. Please submit failing cases! +// This will return the next location to wrap from. If no wrapping if necessary, this will fast-forward to e.g. text_end. +// FIXME: Much possible improvements (don't cut things like "word !", "word!!!" but cut within "word,,,,", more sensible support for punctuations, support for Unicode punctuations, etc.) +const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const +{ // For references, possible wrap point marked with ^ // "aaa bbb, ccc,ddd. eee fff. ggg!" // ^ ^ ^ ^ ^__ ^ ^ @@ -3349,7 +3359,6 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c // Cut words that cannot possibly fit within one line. // e.g.: "The tropical fish" with ~5 characters worth of width --> "The tr" "opical" "fish" - float line_width = 0.0f; float word_width = 0.0f; float blank_width = 0.0f; @@ -3429,6 +3438,10 @@ const char* ImFont::CalcWordWrapPositionA(float scale, const char* text, const c s = next_s; } + // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. + // +1 may not be a character start point in UTF-8 but it's ok because caller loops use (text >= word_wrap_eol). + if (s == text && text < text_end) + return s + 1; return s; } @@ -3453,11 +3466,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons { // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. if (!word_wrap_eol) - { word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - line_width); - if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. - word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below - } if (s >= word_wrap_eol) { @@ -3466,13 +3475,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons text_size.y += line_height; line_width = 0.0f; word_wrap_eol = NULL; - - // Wrapping skips upcoming blanks - while (s < text_end) - { - const char c = *s; - if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } - } + s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks continue; } } @@ -3557,7 +3560,6 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im const float scale = size / FontSize; const float line_height = FontSize * scale; const bool word_wrap_enabled = (wrap_width > 0.0f); - const char* word_wrap_eol = NULL; // Fast-forward to first visible line const char* s = text_begin; @@ -3597,6 +3599,7 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im unsigned int vtx_current_idx = draw_list->_VtxCurrentIdx; const ImU32 col_untinted = col | ~IM_COL32_A_MASK; + const char* word_wrap_eol = NULL; while (s < text_end) { @@ -3604,24 +3607,14 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im { // Calculate how far we can render. Requires two passes on the string data but keeps the code simple and not intrusive for what's essentially an uncommon feature. if (!word_wrap_eol) - { word_wrap_eol = CalcWordWrapPositionA(scale, s, text_end, wrap_width - (x - start_x)); - if (word_wrap_eol == s) // Wrap_width is too small to fit anything. Force displaying 1 character to minimize the height discontinuity. - word_wrap_eol++; // +1 may not be a character start point in UTF-8 but it's ok because we use s >= word_wrap_eol below - } if (s >= word_wrap_eol) { x = start_x; y += line_height; word_wrap_eol = NULL; - - // Wrapping skips upcoming blanks - while (s < text_end) - { - const char c = *s; - if (ImCharIsBlankA(c)) { s++; } else if (c == '\n') { s++; break; } else { break; } - } + s = CalcWordWrapNextLineStartA(s, text_end); // Wrapping skips upcoming blanks continue; } } From e0330c1696b8fcb4b5ffb10db27410d02ea5c6d4 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 28 Sep 2022 14:54:38 +0200 Subject: [PATCH 09/21] Fonts, Text: Fixed wrapped-text not doing a fast-forward on lines above the clipping region. (#5720) which would result in an abnormal number of vertices created. --- docs/CHANGELOG.txt | 3 +++ imgui_draw.cpp | 17 ++++++++++++++--- imgui_internal.h | 2 ++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 596448a0..d08a9ff3 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -138,6 +138,9 @@ Other Changes: - Nav: Fixed moving/resizing window with gamepad or keyboard when running at very high framerate. - Nav: Pressing Space/GamepadFaceDown on a repeating button uses the same repeating rate as a mouse hold. - Nav: Fixed an issue opening a menu with Right key from a non-menu window. +- Fonts, Text: Fixed wrapped-text not doing a fast-forward on lines above the clipping region, + which would result in an abnormal number of vertices created (was slower and more likely to + asserts with 16-bits ImDrawVtx). (#5720) - Platform IME: [Windows] Removed call to ImmAssociateContextEx() leading to freeze on some setups. (#2589, #5535, #5264, #4972) - Misc: ImGuiKey is now a typed enum, allowing ImGuiKey_XXX symbols to be named in debuggers. (#4921) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e7eda792..27fcd6db 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3563,11 +3563,22 @@ void ImFont::RenderText(ImDrawList* draw_list, float size, const ImVec2& pos, Im // Fast-forward to first visible line const char* s = text_begin; - if (y + line_height < clip_rect.y && !word_wrap_enabled) + if (y + line_height < clip_rect.y) while (y + line_height < clip_rect.y && s < text_end) { - s = (const char*)memchr(s, '\n', text_end - s); - s = s ? s + 1 : text_end; + const char* line_end = (const char*)memchr(s, '\n', text_end - s); + if (word_wrap_enabled) + { + // FIXME-OPT: This is not optimal as do first do a search for \n before calling CalcWordWrapPositionA(). + // If the specs for CalcWordWrapPositionA() were reworked to optionally return on \n we could combine both. + // However it is still better than nothing performing the fast-forward! + s = CalcWordWrapPositionA(scale, s, line_end, wrap_width); + s = CalcWordWrapNextLineStartA(s, text_end); + } + else + { + s = line_end ? line_end + 1 : text_end; + } y += line_height; } diff --git a/imgui_internal.h b/imgui_internal.h index 427ebdec..382cd8a0 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -334,8 +334,10 @@ IMGUI_API const ImWchar*ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* bu IMGUI_API const char* ImStristr(const char* haystack, const char* haystack_end, const char* needle, const char* needle_end); IMGUI_API void ImStrTrimBlanks(char* str); IMGUI_API const char* ImStrSkipBlank(const char* str); +IM_MSVC_RUNTIME_CHECKS_OFF static inline bool ImCharIsBlankA(char c) { return c == ' ' || c == '\t'; } static inline bool ImCharIsBlankW(unsigned int c) { return c == ' ' || c == '\t' || c == 0x3000; } +IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Formatting IMGUI_API int ImFormatString(char* buf, size_t buf_size, const char* fmt, ...) IM_FMTARGS(3); From a229a7f39fece2ec40308e47684792a9ceb519cd Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 28 Sep 2022 16:57:09 +0200 Subject: [PATCH 10/21] Examples: Win32: Always use RegisterClassW() to ensure windows are Unicode. (#5725) --- docs/CHANGELOG.txt | 1 + examples/example_win32_directx10/main.cpp | 10 +++++----- examples/example_win32_directx11/main.cpp | 10 +++++----- examples/example_win32_directx12/main.cpp | 10 +++++----- examples/example_win32_directx9/main.cpp | 10 +++++----- 5 files changed, 21 insertions(+), 20 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index d08a9ff3..dc4a8db1 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -154,6 +154,7 @@ Other Changes: - Demo: Added more explicit "Center window" mode to "Overlay example". (#5618) - Demo: Fixed Log & Console from losing scrolling position with Auto-Scroll when child is clipped. (#5721) - Examples: Added all SDL examples to default VS solution. +- Examples: Win32: Always use RegisterClassW() to ensure windows are Unicode. (#5725) - Backends: GLFW: Honor GLFW_CURSOR_DISABLED by not setting mouse position. (#5625) [@scorpion-26] - Backends: SDL: Disable SDL 2.0.22 new "auto capture" which prevents drag and drop across windows (e.g. for multi-viewport support) and don't capture mouse when drag and dropping. (#5710) diff --git a/examples/example_win32_directx10/main.cpp b/examples/example_win32_directx10/main.cpp index e0bd4b5c..e2ec2f04 100644 --- a/examples/example_win32_directx10/main.cpp +++ b/examples/example_win32_directx10/main.cpp @@ -26,15 +26,15 @@ int main(int, char**) { // Create application window //ImGui_ImplWin32_EnableDpiAwareness(); - WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, _T("ImGui Example"), NULL }; - ::RegisterClassEx(&wc); - HWND hwnd = ::CreateWindow(wc.lpszClassName, _T("Dear ImGui DirectX10 Example"), WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); + WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, L"ImGui Example", NULL }; + ::RegisterClassExW(&wc); + HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX10 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); // Initialize Direct3D if (!CreateDeviceD3D(hwnd)) { CleanupDeviceD3D(); - ::UnregisterClass(wc.lpszClassName, wc.hInstance); + ::UnregisterClassW(wc.lpszClassName, wc.hInstance); return 1; } @@ -154,7 +154,7 @@ int main(int, char**) CleanupDeviceD3D(); ::DestroyWindow(hwnd); - ::UnregisterClass(wc.lpszClassName, wc.hInstance); + ::UnregisterClassW(wc.lpszClassName, wc.hInstance); return 0; } diff --git a/examples/example_win32_directx11/main.cpp b/examples/example_win32_directx11/main.cpp index 4f4a2bd7..d2e31c4f 100644 --- a/examples/example_win32_directx11/main.cpp +++ b/examples/example_win32_directx11/main.cpp @@ -26,15 +26,15 @@ int main(int, char**) { // Create application window //ImGui_ImplWin32_EnableDpiAwareness(); - WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, _T("ImGui Example"), NULL }; - ::RegisterClassEx(&wc); - HWND hwnd = ::CreateWindow(wc.lpszClassName, _T("Dear ImGui DirectX11 Example"), WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); + WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, L"ImGui Example", NULL }; + ::RegisterClassExW(&wc); + HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX11 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); // Initialize Direct3D if (!CreateDeviceD3D(hwnd)) { CleanupDeviceD3D(); - ::UnregisterClass(wc.lpszClassName, wc.hInstance); + ::UnregisterClassW(wc.lpszClassName, wc.hInstance); return 1; } @@ -155,7 +155,7 @@ int main(int, char**) CleanupDeviceD3D(); ::DestroyWindow(hwnd); - ::UnregisterClass(wc.lpszClassName, wc.hInstance); + ::UnregisterClassW(wc.lpszClassName, wc.hInstance); return 0; } diff --git a/examples/example_win32_directx12/main.cpp b/examples/example_win32_directx12/main.cpp index 5ad7d41d..4de6246e 100644 --- a/examples/example_win32_directx12/main.cpp +++ b/examples/example_win32_directx12/main.cpp @@ -61,15 +61,15 @@ int main(int, char**) { // Create application window //ImGui_ImplWin32_EnableDpiAwareness(); - WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, _T("ImGui Example"), NULL }; - ::RegisterClassEx(&wc); - HWND hwnd = ::CreateWindow(wc.lpszClassName, _T("Dear ImGui DirectX12 Example"), WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); + WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, L"ImGui Example", NULL }; + ::RegisterClassExW(&wc); + HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX12 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); // Initialize Direct3D if (!CreateDeviceD3D(hwnd)) { CleanupDeviceD3D(); - ::UnregisterClass(wc.lpszClassName, wc.hInstance); + ::UnregisterClassW(wc.lpszClassName, wc.hInstance); return 1; } @@ -223,7 +223,7 @@ int main(int, char**) CleanupDeviceD3D(); ::DestroyWindow(hwnd); - ::UnregisterClass(wc.lpszClassName, wc.hInstance); + ::UnregisterClassW(wc.lpszClassName, wc.hInstance); return 0; } diff --git a/examples/example_win32_directx9/main.cpp b/examples/example_win32_directx9/main.cpp index a876cbcb..4c38768f 100644 --- a/examples/example_win32_directx9/main.cpp +++ b/examples/example_win32_directx9/main.cpp @@ -24,15 +24,15 @@ int main(int, char**) { // Create application window //ImGui_ImplWin32_EnableDpiAwareness(); - WNDCLASSEX wc = { sizeof(WNDCLASSEX), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, _T("ImGui Example"), NULL }; - ::RegisterClassEx(&wc); - HWND hwnd = ::CreateWindow(wc.lpszClassName, _T("Dear ImGui DirectX9 Example"), WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); + WNDCLASSEXW wc = { sizeof(wc), CS_CLASSDC, WndProc, 0L, 0L, GetModuleHandle(NULL), NULL, NULL, NULL, NULL, L"ImGui Example", NULL }; + ::RegisterClassExW(&wc); + HWND hwnd = ::CreateWindowW(wc.lpszClassName, L"Dear ImGui DirectX9 Example", WS_OVERLAPPEDWINDOW, 100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL); // Initialize Direct3D if (!CreateDeviceD3D(hwnd)) { CleanupDeviceD3D(); - ::UnregisterClass(wc.lpszClassName, wc.hInstance); + ::UnregisterClassW(wc.lpszClassName, wc.hInstance); return 1; } @@ -161,7 +161,7 @@ int main(int, char**) CleanupDeviceD3D(); ::DestroyWindow(hwnd); - ::UnregisterClass(wc.lpszClassName, wc.hInstance); + ::UnregisterClassW(wc.lpszClassName, wc.hInstance); return 0; } From 0a7054c7e448457cad37a6904dff1d044aeb3b2b Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 28 Sep 2022 17:00:45 +0200 Subject: [PATCH 11/21] Backends: Win32: Convert WM_CHAR values with MultiByteToWideChar() when window class was registered as MBCS (not Unicode). (#5725, #1807, #471, #2815, #1060) --- backends/imgui_impl_win32.cpp | 16 +++++++++++++--- docs/CHANGELOG.txt | 2 ++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/backends/imgui_impl_win32.cpp b/backends/imgui_impl_win32.cpp index 1c198d99..43a9962e 100644 --- a/backends/imgui_impl_win32.cpp +++ b/backends/imgui_impl_win32.cpp @@ -34,6 +34,7 @@ typedef DWORD (WINAPI *PFN_XInputGetState)(DWORD, XINPUT_STATE*); // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2022-09-28: Inputs: Convert WM_CHAR values with MultiByteToWideChar() when window class was registered as MBCS (not Unicode). // 2022-09-26: Inputs: Renamed ImGuiKey_ModXXX introduced in 1.87 to ImGuiMod_XXX (old names still supported). // 2022-01-26: Inputs: replaced short-lived io.AddKeyModsEvent() (added two weeks ago) with io.AddKeyEvent() using ImGuiKey_ModXXX flags. Sorry for the confusion. // 2021-01-20: Inputs: calling new io.AddKeyAnalogEvent() for gamepad support, instead of writing directly to io.NavInputs[]. @@ -613,9 +614,18 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARA io.AddFocusEvent(msg == WM_SETFOCUS); return 0; case WM_CHAR: - // You can also use ToAscii()+GetKeyboardState() to retrieve characters. - if (wParam > 0 && wParam < 0x10000) - io.AddInputCharacterUTF16((unsigned short)wParam); + if (::IsWindowUnicode(hwnd)) + { + // You can also use ToAscii()+GetKeyboardState() to retrieve characters. + if (wParam > 0 && wParam < 0x10000) + io.AddInputCharacterUTF16((unsigned short)wParam); + } + else + { + wchar_t wch = 0; + ::MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (char*)&wParam, 1, &wch, 1); + io.AddInputCharacter(wch); + } return 0; case WM_SETCURSOR: // This is required to restore cursor when transitioning from e.g resize borders to client area. diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index dc4a8db1..e47523f1 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -158,6 +158,8 @@ Other Changes: - Backends: GLFW: Honor GLFW_CURSOR_DISABLED by not setting mouse position. (#5625) [@scorpion-26] - Backends: SDL: Disable SDL 2.0.22 new "auto capture" which prevents drag and drop across windows (e.g. for multi-viewport support) and don't capture mouse when drag and dropping. (#5710) +- Backends: Win32: Convert WM_CHAR values with MultiByteToWideChar() when window class was + registered as MBCS (not Unicode). (#5725, #1807, #471, #2815, #1060) [@or75, @ocornut] - Backends: Metal: Use __bridge for ARC based systems. (#5403) [@stack] - Backends: Metal: Add dispatch synchronization. (#5447) [@luigifcruz] - Backends: Metal: Update deprecated property 'sampleCount'->'rasterSampleCount'. (#5603) [@dcvz] From d17627b9c6f3162cebe8285e3048418bd730d8a8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 28 Sep 2022 17:38:41 +0200 Subject: [PATCH 12/21] InputText: leave state->Flags uncleared for the purpose of backends emitting an on-screen keyboard for passwords. (#5724) --- imgui.h | 2 +- imgui_internal.h | 2 +- imgui_widgets.cpp | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/imgui.h b/imgui.h index 0d32389a..de1340f9 100644 --- a/imgui.h +++ b/imgui.h @@ -23,7 +23,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM > 12345') #define IMGUI_VERSION "1.89 WIP" -#define IMGUI_VERSION_NUM 18824 +#define IMGUI_VERSION_NUM 18825 #define IMGUI_HAS_TABLE /* diff --git a/imgui_internal.h b/imgui_internal.h index 382cd8a0..6f591478 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1025,7 +1025,7 @@ struct IMGUI_API ImGuiInputTextState bool CursorFollow; // set when we want scrolling to follow the current cursor position (not always!) bool SelectedAllMouseLock; // after a double-click to select all, we ignore further mouse drags to update selection bool Edited; // edited this frame - ImGuiInputTextFlags Flags; // copy of InputText() flags + ImGuiInputTextFlags Flags; // copy of InputText() flags. may be used to check if e.g. ImGuiInputTextFlags_Password is set. ImGuiInputTextState() { memset(this, 0, sizeof(*this)); } void ClearText() { CurLenW = CurLenA = 0; TextW[0] = 0; TextA[0] = 0; CursorClamp(); } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 053e37a7..44518881 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -4590,9 +4590,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) From e74a50f525e880f3a121ecd5b9ff0b33025d5778 Mon Sep 17 00:00:00 2001 From: "Andrew D. Zonenberg" Date: Wed, 28 Sep 2022 08:19:34 -0700 Subject: [PATCH 13/21] Added GetGlyphRangesGreek() helper for Greek & Coptic glyph range. (#5676, #5727) --- docs/CHANGELOG.txt | 3 ++- imgui.h | 1 + imgui_draw.cpp | 11 +++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index e47523f1..9bb79347 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -138,9 +138,10 @@ Other Changes: - Nav: Fixed moving/resizing window with gamepad or keyboard when running at very high framerate. - Nav: Pressing Space/GamepadFaceDown on a repeating button uses the same repeating rate as a mouse hold. - Nav: Fixed an issue opening a menu with Right key from a non-menu window. -- Fonts, Text: Fixed wrapped-text not doing a fast-forward on lines above the clipping region, +- Text: Fixed wrapped-text not doing a fast-forward on lines above the clipping region, which would result in an abnormal number of vertices created (was slower and more likely to asserts with 16-bits ImDrawVtx). (#5720) +- Fonts: Added GetGlyphRangesGreek() helper for Greek & Coptic glyph range. (#5676, #5727) [@azonenberg] - Platform IME: [Windows] Removed call to ImmAssociateContextEx() leading to freeze on some setups. (#2589, #5535, #5264, #4972) - Misc: ImGuiKey is now a typed enum, allowing ImGuiKey_XXX symbols to be named in debuggers. (#4921) diff --git a/imgui.h b/imgui.h index de1340f9..7a3fab7e 100644 --- a/imgui.h +++ b/imgui.h @@ -2781,6 +2781,7 @@ struct ImFontAtlas // NB: Make sure that your string are UTF-8 and NOT in your local code page. In C++11, you can create UTF-8 string literal using the u8"Hello world" syntax. See FAQ for details. // NB: Consider using ImFontGlyphRangesBuilder to build glyph ranges from textual data. IMGUI_API const ImWchar* GetGlyphRangesDefault(); // Basic Latin, Extended Latin + IMGUI_API const ImWchar* GetGlyphRangesGreek(); // Default + Greek and Coptic IMGUI_API const ImWchar* GetGlyphRangesKorean(); // Default + Korean characters IMGUI_API const ImWchar* GetGlyphRangesJapanese(); // Default + Hiragana, Katakana, Half-Width, Selection of 2999 Ideographs IMGUI_API const ImWchar* GetGlyphRangesChineseFull(); // Default + Half-Width + Japanese Hiragana/Katakana + full set of about 21000 CJK Unified Ideographs diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 27fcd6db..00dbe06c 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2821,6 +2821,17 @@ const ImWchar* ImFontAtlas::GetGlyphRangesDefault() return &ranges[0]; } +const ImWchar* ImFontAtlas::GetGlyphRangesGreek() +{ + static const ImWchar ranges[] = + { + 0x0020, 0x00FF, // Basic Latin + Latin Supplement + 0x0370, 0x03FF, // Greek and Coptic + 0, + }; + return &ranges[0]; +} + const ImWchar* ImFontAtlas::GetGlyphRangesKorean() { static const ImWchar ranges[] = From d5d705069dafc8f26b9d8b0e4a16a54321f56376 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Sep 2022 17:26:52 +0200 Subject: [PATCH 14/21] Various comments As it turns out, functions like IsItemHovered() won't work on an open BeginMenu() because LastItemData is overriden by BeginPopup(). Probably an easy fix. --- imgui.cpp | 3 +++ imgui_internal.h | 16 ++++++++++------ imgui_widgets.cpp | 9 ++++++--- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 04b43a31..8867192a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3584,6 +3584,7 @@ bool ImGui::IsItemHovered(ImGuiHoveredFlags flags) return false; IM_ASSERT((flags & (ImGuiHoveredFlags_AnyWindow | ImGuiHoveredFlags_RootWindow | ImGuiHoveredFlags_ChildWindows | ImGuiHoveredFlags_NoPopupHierarchy)) == 0); // Flags not supported by this function + // Done with rectangle culling so we can perform heavier checks now // Test if we are hovering the right window (our window could be behind another window) // [2021/03/02] Reworked / reverted the revert, finally. Note we want e.g. BeginGroup/ItemAdd/EndGroup to work as well. (#3851) // [2017/10/16] Reverted commit 344d48be3 and testing RootWindow instead. I believe it is correct to NOT test for RootWindow but this leaves us unable @@ -3648,6 +3649,8 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) return false; if (!IsMouseHoveringRect(bb.Min, bb.Max)) return false; + + // Done with rectangle culling so we can perform heavier checks now. if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) { g.HoveredIdDisabled = true; diff --git a/imgui_internal.h b/imgui_internal.h index 6f591478..a866fb8f 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -149,8 +149,8 @@ typedef int ImGuiLayoutType; // -> enum ImGuiLayoutType_ // E typedef int ImGuiActivateFlags; // -> enum ImGuiActivateFlags_ // Flags: for navigation/focus function (will be for ActivateItem() later) typedef int ImGuiDebugLogFlags; // -> enum ImGuiDebugLogFlags_ // Flags: for ShowDebugLogWindow(), g.DebugLogFlags typedef int ImGuiInputFlags; // -> enum ImGuiInputFlags_ // Flags: for IsKeyPressedEx() -typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag() -typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for DC.LastItemStatusFlags +typedef int ImGuiItemFlags; // -> enum ImGuiItemFlags_ // Flags: for PushItemFlag(), g.LastItemData.InFlags +typedef int ImGuiItemStatusFlags; // -> enum ImGuiItemStatusFlags_ // Flags: for g.LastItemData.StatusFlags typedef int ImGuiOldColumnFlags; // -> enum ImGuiOldColumnFlags_ // Flags: for BeginColumns() typedef int ImGuiNavHighlightFlags; // -> enum ImGuiNavHighlightFlags_ // Flags: for RenderNavHighlight() typedef int ImGuiNavMoveFlags; // -> enum ImGuiNavMoveFlags_ // Flags: for navigation requests @@ -758,7 +758,10 @@ struct ImDrawDataBuilder // [SECTION] Widgets support: flags, enums, data structures //----------------------------------------------------------------------------- -// Transient per-window flags, reset at the beginning of the frame. For child window, inherited from parent on first Begin(). +// Flags used by upcoming items +// - input: PushItemFlag() manipulates g.CurrentItemFlags, ItemAdd() calls may add extra flags. +// - output: stored in g.LastItemData.InFlags +// Current window shared by all windows. // This is going to be exposed in imgui.h when stabilized enough. enum ImGuiItemFlags_ { @@ -774,10 +777,11 @@ enum ImGuiItemFlags_ ImGuiItemFlags_ReadOnly = 1 << 7, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. // Controlled by widget code - ImGuiItemFlags_Inputable = 1 << 8, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature. + ImGuiItemFlags_Inputable = 1 << 10, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature. }; -// Storage for LastItem data +// Status flags for an already submitted item +// - output: stored in g.LastItemData.StatusFlags enum ImGuiItemStatusFlags_ { ImGuiItemStatusFlags_None = 0, @@ -2650,7 +2654,7 @@ namespace ImGui IMGUI_API ImVec2 GetContentRegionMaxAbs(); IMGUI_API void ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess); - // Parameter stacks + // Parameter stacks (shared) IMGUI_API void PushItemFlag(ImGuiItemFlags option, bool enabled); IMGUI_API void PopItemFlag(); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 44518881..049df88f 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7018,6 +7018,7 @@ 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) @@ -7156,9 +7157,10 @@ 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. + // FIXME: This technically breaks functions relying on LastItemData, somehow nobody complained yet. Should backup/restore 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(); } else @@ -7177,7 +7179,7 @@ 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. + // A menu doesn't close itself because EndMenuBar() wants to 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. // FIXME: This doesn't work if the parent BeginMenu() is not on a menu. ImGuiContext& g = *GImGui; @@ -7203,6 +7205,7 @@ 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) From 3532ed1621171647ce4a969be40fd69c58e47b75 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Sep 2022 18:07:35 +0200 Subject: [PATCH 15/21] Menus, Nav: Fixed keyboard/gamepad navigation occasionally erroneously landing on menu-item in parent when the parent is not a popup. (#5730) Replace BeginMenu/MenuItem swapping g.NavWindow with a more adequate ImGuiItemFlags_NoWindowHoverableCheck. Expecting more subtle issues to stem from this. Note that NoWindowHoverableCheck is not supported by IsItemHovered() but then IsItemHovered() on BeginMenu() never worked: fix should be easy in BeginMenu() + add test is IsItemHovered(), will do later --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 4 ++-- imgui.h | 2 +- imgui_internal.h | 1 + imgui_widgets.cpp | 10 ++++------ 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9bb79347..b1a28251 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -135,6 +135,8 @@ Other Changes: towards a sub-menu. (#2517, #5614). [@rokups] - Menus: Fixed gaps in closing logic which would make child-menu erroneously close when crossing the gap between a menu item inside a window and a child-menu in a secondary viewport. (#5614) +- Menus, Nav: Fixed keyboard/gamepad navigation occasionally erroneously landing on menu-item in + parent when the parent is not a popup. (#5730) - Nav: Fixed moving/resizing window with gamepad or keyboard when running at very high framerate. - Nav: Pressing Space/GamepadFaceDown on a repeating button uses the same repeating rate as a mouse hold. - Nav: Fixed an issue opening a menu with Right key from a non-menu window. diff --git a/imgui.cpp b/imgui.cpp index 8867192a..69569a91 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3651,7 +3651,8 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) return false; // Done with rectangle culling so we can perform heavier checks now. - if (!IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) + ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags); + if (!(item_flags & ImGuiItemFlags_NoWindowHoverableCheck) && !IsWindowContentHoverable(window, ImGuiHoveredFlags_None)) { g.HoveredIdDisabled = true; return false; @@ -3663,7 +3664,6 @@ bool ImGui::ItemHoverable(const ImRect& bb, ImGuiID id) SetHoveredID(id); // When disabled we'll return false but still set HoveredId - ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.InFlags : g.CurrentItemFlags); if (item_flags & ImGuiItemFlags_Disabled) { // Release active id if turning disabled diff --git a/imgui.h b/imgui.h index 7a3fab7e..27e97e64 100644 --- a/imgui.h +++ b/imgui.h @@ -23,7 +23,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM > 12345') #define IMGUI_VERSION "1.89 WIP" -#define IMGUI_VERSION_NUM 18825 +#define IMGUI_VERSION_NUM 18826 #define IMGUI_HAS_TABLE /* diff --git a/imgui_internal.h b/imgui_internal.h index a866fb8f..bae32248 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -775,6 +775,7 @@ enum ImGuiItemFlags_ ImGuiItemFlags_SelectableDontClosePopup = 1 << 5, // false // Disable MenuItem/Selectable() automatically closing their popup window ImGuiItemFlags_MixedValue = 1 << 6, // false // [BETA] Represent a mixed/indeterminate value, generally multi-selection where values differ. Currently only supported by Checkbox() (later should support all sorts of widgets) ImGuiItemFlags_ReadOnly = 1 << 7, // false // [ALPHA] Allow hovering interactions but underlying value is not changed. + ImGuiItemFlags_NoWindowHoverableCheck = 1 << 8, // false // Disable hoverable check in ItemHoverable() // Controlled by widget code ImGuiItemFlags_Inputable = 1 << 10, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 049df88f..1c19eca1 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7020,9 +7020,8 @@ bool ImGui::BeginMenuEx(const char* label, const char* icon, bool enabled) // 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(). @@ -7071,7 +7070,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; @@ -7207,9 +7206,8 @@ bool ImGui::MenuItemEx(const char* label, const char* icon, const char* shortcut // 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. @@ -7261,7 +7259,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; } From bd2355a047570c2cc1ff0a31c40a526484f3709e Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Sep 2022 19:25:26 +0200 Subject: [PATCH 16/21] Menus, Nav: Fixed using left/right navigation when appending to an existing menu (multiple BeginMenu() call with same names). (#1207) --- docs/CHANGELOG.txt | 2 ++ imgui.cpp | 3 ++- imgui.h | 2 +- imgui_internal.h | 1 + imgui_widgets.cpp | 14 ++++++++------ 5 files changed, 14 insertions(+), 8 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index b1a28251..a1288591 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -137,6 +137,8 @@ Other Changes: the gap between a menu item inside a window and a child-menu in a secondary viewport. (#5614) - Menus, Nav: Fixed keyboard/gamepad navigation occasionally erroneously landing on menu-item in parent when the parent is not a popup. (#5730) +- Menus, Nav: Fixed using left/right navigation when appending to an existing menu (multiple + BeginMenu() call with same names). (#1207) - Nav: Fixed moving/resizing window with gamepad or keyboard when running at very high framerate. - Nav: Pressing Space/GamepadFaceDown on a repeating button uses the same repeating rate as a mouse hold. - Nav: Fixed an issue opening a menu with Right key from a non-menu window. diff --git a/imgui.cpp b/imgui.cpp index 69569a91..5af4b2d9 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4608,9 +4608,10 @@ void ImGui::NewFrame() { ImGuiWindow* window = g.Windows[i]; window->WasActive = window->Active; - window->BeginCount = 0; window->Active = false; window->WriteAccessed = false; + window->BeginCountPreviousFrame = window->BeginCount; + window->BeginCount = 0; // Garbage collect transient buffers of recently unused windows if (!window->WasActive && !window->MemoryCompacted && window->LastTimeActive < memory_compact_start_time) diff --git a/imgui.h b/imgui.h index 27e97e64..33270458 100644 --- a/imgui.h +++ b/imgui.h @@ -23,7 +23,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM > 12345') #define IMGUI_VERSION "1.89 WIP" -#define IMGUI_VERSION_NUM 18826 +#define IMGUI_VERSION_NUM 18827 #define IMGUI_HAS_TABLE /* diff --git a/imgui_internal.h b/imgui_internal.h index bae32248..d564598d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2112,6 +2112,7 @@ struct IMGUI_API ImGuiWindow bool HasCloseButton; // Set when the window has a close button (p_open != NULL) signed char ResizeBorderHeld; // Current border being held for resize (-1: none, otherwise 0-3) short BeginCount; // Number of Begin() during the current frame (generally 0 or 1, 1+ if appending via multiple Begin/End pairs) + short BeginCountPreviousFrame; // Number of Begin() during the previous frame short BeginOrderWithinParent; // Begin() order within immediate parent window, if we are a child window. Otherwise 0. short BeginOrderWithinContext; // Begin() order within entire imgui context. This is mostly used for debugging submission order related issues. short FocusOrder; // Order within WindowsFocusOrder[], altered when windows are focused. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 1c19eca1..cadad5bc 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7183,13 +7183,15 @@ void ImGui::EndMenu() // FIXME: This doesn't work if the parent BeginMenu() is not on a menu. 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 + if (window->BeginCount == window->BeginCountPreviousFrame) + 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(); + } EndPopup(); } From 9f6aae3bf267c4fb7ac2941f000393dc8341eaa8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Sep 2022 19:48:27 +0200 Subject: [PATCH 17/21] Nav: Fixed race condition pressing Esc during popup opening frame causing crash. --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 5af4b2d9..69efe64b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10859,7 +10859,7 @@ static void ImGui::NavUpdateCancelRequest() SetNavID(child_window->ChildId, ImGuiNavLayer_Main, 0, WindowRectAbsToRel(parent_window, child_rect)); NavRestoreHighlightAfterMove(); } - else if (g.OpenPopupStack.Size > 0 && !(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) + else if (g.OpenPopupStack.Size > 0 && g.OpenPopupStack.back().Window != NULL && !(g.OpenPopupStack.back().Window->Flags & ImGuiWindowFlags_Modal)) { // Close open popup/menu ClosePopupToLevel(g.OpenPopupStack.Size - 1, true); From 0749453355a8fd47e79b2cc2e6bbbfa06eda5860 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Sep 2022 19:51:54 +0200 Subject: [PATCH 18/21] Menus, Nav: Fixed not being able to close a menu with Left arrow when parent is not a popup. (#5730) --- docs/CHANGELOG.txt | 5 +++-- imgui_widgets.cpp | 14 ++++++-------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a1288591..622bb289 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -135,8 +135,9 @@ Other Changes: towards a sub-menu. (#2517, #5614). [@rokups] - Menus: Fixed gaps in closing logic which would make child-menu erroneously close when crossing the gap between a menu item inside a window and a child-menu in a secondary viewport. (#5614) -- Menus, Nav: Fixed keyboard/gamepad navigation occasionally erroneously landing on menu-item in - parent when the parent is not a popup. (#5730) +- Menus, Nav: Fixed keyboard/gamepad navigation occasionally erroneously landing on menu-item + in parent window when the parent is not a popup. (#5730) +- Menus, Nav: Fixed not being able to close a menu with Left arrow when parent is not a popup. (#5730) - Menus, Nav: Fixed using left/right navigation when appending to an existing menu (multiple BeginMenu() call with same names). (#1207) - Nav: Fixed moving/resizing window with gamepad or keyboard when running at very high framerate. diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index cadad5bc..f74fd1b5 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -7177,21 +7177,19 @@ 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 to 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. - // FIXME: This doesn't work if the parent BeginMenu() is not on a menu. + // Nav: When a left move request our menu failed, close ourselves. ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; 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() && window->DC.LayoutType == ImGuiLayoutType_Vertical) - if (g.NavWindow && (g.NavWindow->RootWindowForNav->Flags & ImGuiWindowFlags_Popup) && g.NavWindow->RootWindowForNav->ParentWindow == window) + if (g.NavMoveDir == ImGuiDir_Left && NavMoveRequestButNoResultYet()) + if (g.NavWindow && (g.NavWindow->RootWindowForNav == window) && parent_window->DC.LayoutType == ImGuiLayoutType_Vertical) { - ClosePopupToLevel(g.BeginPopupStack.Size, true); + ClosePopupToLevel(g.BeginPopupStack.Size - 1, true); NavMoveRequestCancel(); } + EndPopup(); } From 9e7f460c0934370a32065eed97af453afeada590 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Sep 2022 20:42:45 +0200 Subject: [PATCH 19/21] Fixed GetKeyName() for ImGuiMod_XXX values, made invalid MousePos display in log nicer. (#4921, #456) Amend fd408c9 --- imgui.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 69efe64b..80710fab 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7817,6 +7817,8 @@ const char* ImGui::GetKeyName(ImGuiKey key) #endif if (key == ImGuiKey_None) return "None"; + if (key & ImGuiMod_Mask_) + key = ConvertSingleModFlagToKey(key); if (!IsNamedKey(key)) return "Unknown"; @@ -8086,7 +8088,7 @@ static const char* GetInputSourceName(ImGuiInputSource source) static void DebugPrintInputEvent(const char* prefix, const ImGuiInputEvent* e) { ImGuiContext& g = *GImGui; - if (e->Type == ImGuiInputEventType_MousePos) { IMGUI_DEBUG_LOG_IO("%s: MousePos (%.1f, %.1f)\n", prefix, e->MousePos.PosX, e->MousePos.PosY); return; } + if (e->Type == ImGuiInputEventType_MousePos) { if (e->MousePos.PosX == -FLT_MAX && e->MousePos.PosY == -FLT_MAX) IMGUI_DEBUG_LOG_IO("%s: MousePos (-FLT_MAX, -FLT_MAX)\n", prefix); else IMGUI_DEBUG_LOG_IO("%s: MousePos (%.1f, %.1f)\n", prefix, e->MousePos.PosX, e->MousePos.PosY); return; } if (e->Type == ImGuiInputEventType_MouseButton) { IMGUI_DEBUG_LOG_IO("%s: MouseButton %d %s\n", prefix, e->MouseButton.Button, e->MouseButton.Down ? "Down" : "Up"); return; } if (e->Type == ImGuiInputEventType_MouseWheel) { IMGUI_DEBUG_LOG_IO("%s: MouseWheel (%.1f, %.1f)\n", prefix, e->MouseWheel.WheelX, e->MouseWheel.WheelY); return; } if (e->Type == ImGuiInputEventType_Key) { IMGUI_DEBUG_LOG_IO("%s: Key \"%s\" %s\n", prefix, ImGui::GetKeyName(e->Key.Key), e->Key.Down ? "Down" : "Up"); return; } @@ -8205,7 +8207,7 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) } else if (e->Type == ImGuiInputEventType_Focus) { - // We intentionally overwrite this and process lower, in order to give a chance + // We intentionally overwrite this and process in NewFrame(), in order to give a chance // to multi-viewports backends to queue AddFocusEvent(false) + AddFocusEvent(true) in same frame. const bool focus_lost = !e->AppFocused.Focused; e->IgnoredAsSame = (io.AppFocusLost == focus_lost); From fac8295d6e7f5356d77ac1c92a1c9205cef11733 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Sep 2022 21:31:36 +0200 Subject: [PATCH 20/21] IO: remove ImGuiInputEvent::IgnoredAsSame (revert part of 839c3100), will filter earlier in next commit. (#5599) Making it a separate commit as this leads to much indentation change. --- imgui.cpp | 92 +++++++++++++++++++----------------------------- imgui_internal.h | 1 - 2 files changed, 37 insertions(+), 56 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 80710fab..4390d158 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -8121,79 +8121,63 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) ImGuiInputEvent* e = &g.InputEventsQueue[event_n]; if (e->Type == ImGuiInputEventType_MousePos) { + // Trickling Rule: Stop processing queued events if we already handled a mouse button change ImVec2 event_pos(e->MousePos.PosX, e->MousePos.PosY); if (IsMousePosValid(&event_pos)) event_pos = ImVec2(ImFloorSigned(event_pos.x), ImFloorSigned(event_pos.y)); // Apply same flooring as UpdateMouseInputs() - e->IgnoredAsSame = (io.MousePos.x == event_pos.x && io.MousePos.y == event_pos.y); - if (!e->IgnoredAsSame) - { - // Trickling Rule: Stop processing queued events if we already handled a mouse button change - if (trickle_fast_inputs && (mouse_button_changed != 0 || mouse_wheeled || key_changed || text_inputted)) - break; - io.MousePos = event_pos; - mouse_moved = true; - } + if (trickle_fast_inputs && (mouse_button_changed != 0 || mouse_wheeled || key_changed || text_inputted)) + break; + io.MousePos = event_pos; + mouse_moved = true; } else if (e->Type == ImGuiInputEventType_MouseButton) { + // Trickling Rule: Stop processing queued events if we got multiple action on the same button const ImGuiMouseButton button = e->MouseButton.Button; IM_ASSERT(button >= 0 && button < ImGuiMouseButton_COUNT); - e->IgnoredAsSame = (io.MouseDown[button] == e->MouseButton.Down); - if (!e->IgnoredAsSame) - { - // Trickling Rule: Stop processing queued events if we got multiple action on the same button - if (trickle_fast_inputs && ((mouse_button_changed & (1 << button)) || mouse_wheeled)) - break; - io.MouseDown[button] = e->MouseButton.Down; - mouse_button_changed |= (1 << button); - } + if (trickle_fast_inputs && ((mouse_button_changed & (1 << button)) || mouse_wheeled)) + break; + io.MouseDown[button] = e->MouseButton.Down; + mouse_button_changed |= (1 << button); } else if (e->Type == ImGuiInputEventType_MouseWheel) { - e->IgnoredAsSame = (e->MouseWheel.WheelX == 0.0f && e->MouseWheel.WheelY == 0.0f); - if (!e->IgnoredAsSame) - { - // Trickling Rule: Stop processing queued events if we got multiple action on the event - if (trickle_fast_inputs && (mouse_moved || mouse_button_changed != 0)) - break; - io.MouseWheelH += e->MouseWheel.WheelX; - io.MouseWheel += e->MouseWheel.WheelY; - mouse_wheeled = true; - } + // Trickling Rule: Stop processing queued events if we got multiple action on the event + if (trickle_fast_inputs && (mouse_moved || mouse_button_changed != 0)) + break; + io.MouseWheelH += e->MouseWheel.WheelX; + io.MouseWheel += e->MouseWheel.WheelY; + mouse_wheeled = true; } else if (e->Type == ImGuiInputEventType_Key) { + // Trickling Rule: Stop processing queued events if we got multiple action on the same button ImGuiKey key = e->Key.Key; IM_ASSERT(key != ImGuiKey_None); ImGuiKeyData* key_data = GetKeyData(key); const int key_data_index = (int)(key_data - g.IO.KeysData); - e->IgnoredAsSame = (key_data->Down == e->Key.Down && key_data->AnalogValue == e->Key.AnalogValue); - if (!e->IgnoredAsSame) - { - // Trickling Rule: Stop processing queued events if we got multiple action on the same button - if (trickle_fast_inputs && key_data->Down != e->Key.Down && (key_changed_mask.TestBit(key_data_index) || text_inputted || mouse_button_changed != 0)) - break; - key_data->Down = e->Key.Down; - key_data->AnalogValue = e->Key.AnalogValue; - key_changed = true; - key_changed_mask.SetBit(key_data_index); + if (trickle_fast_inputs && key_data->Down != e->Key.Down && (key_changed_mask.TestBit(key_data_index) || text_inputted || mouse_button_changed != 0)) + break; + key_data->Down = e->Key.Down; + key_data->AnalogValue = e->Key.AnalogValue; + key_changed = true; + key_changed_mask.SetBit(key_data_index); - if (key == ImGuiMod_Ctrl || key == ImGuiMod_Shift || key == ImGuiMod_Alt || key == ImGuiMod_Super) - { - if (key == ImGuiMod_Ctrl) { io.KeyCtrl = key_data->Down; } - if (key == ImGuiMod_Shift) { io.KeyShift = key_data->Down; } - if (key == ImGuiMod_Alt) { io.KeyAlt = key_data->Down; } - if (key == ImGuiMod_Super) { io.KeySuper = key_data->Down; } - io.KeyMods = GetMergedModsFromBools(); - } + if (key == ImGuiMod_Ctrl || key == ImGuiMod_Shift || key == ImGuiMod_Alt || key == ImGuiMod_Super) + { + if (key == ImGuiMod_Ctrl) { io.KeyCtrl = key_data->Down; } + if (key == ImGuiMod_Shift) { io.KeyShift = key_data->Down; } + if (key == ImGuiMod_Alt) { io.KeyAlt = key_data->Down; } + if (key == ImGuiMod_Super) { io.KeySuper = key_data->Down; } + io.KeyMods = GetMergedModsFromBools(); + } - // Allow legacy code using io.KeysDown[GetKeyIndex()] with new backends + // Allow legacy code using io.KeysDown[GetKeyIndex()] with new backends #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO - io.KeysDown[key_data_index] = key_data->Down; - if (io.KeyMap[key_data_index] != -1) - io.KeysDown[io.KeyMap[key_data_index]] = key_data->Down; + io.KeysDown[key_data_index] = key_data->Down; + if (io.KeyMap[key_data_index] != -1) + io.KeysDown[io.KeyMap[key_data_index]] = key_data->Down; #endif - } } else if (e->Type == ImGuiInputEventType_Text) { @@ -8210,9 +8194,7 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) // We intentionally overwrite this and process in NewFrame(), in order to give a chance // to multi-viewports backends to queue AddFocusEvent(false) + AddFocusEvent(true) in same frame. const bool focus_lost = !e->AppFocused.Focused; - e->IgnoredAsSame = (io.AppFocusLost == focus_lost); - if (!e->IgnoredAsSame) - io.AppFocusLost = focus_lost; + io.AppFocusLost = focus_lost; } else { @@ -8229,7 +8211,7 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) #ifndef IMGUI_DISABLE_DEBUG_TOOLS if (event_n != 0 && (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO)) for (int n = 0; n < g.InputEventsQueue.Size; n++) - DebugPrintInputEvent(n < event_n ? (g.InputEventsQueue[n].IgnoredAsSame ? "Processed (Same)" : "Processed") : "Remaining", &g.InputEventsQueue[n]); + DebugPrintInputEvent(n < event_n ? "Processed" : "Remaining", &g.InputEventsQueue[n]); #endif // Remaining events will be processed on the next frame diff --git a/imgui_internal.h b/imgui_internal.h index d564598d..204040fb 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1245,7 +1245,6 @@ struct ImGuiInputEvent ImGuiInputEventText Text; // if Type == ImGuiInputEventType_Text ImGuiInputEventAppFocused AppFocused; // if Type == ImGuiInputEventType_Focus }; - bool IgnoredAsSame; bool AddedByTestEngine; ImGuiInputEvent() { memset(this, 0, sizeof(*this)); } From cc5058e5d7dcbf94f28277d6bee5face7f8b0a63 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 29 Sep 2022 21:59:32 +0200 Subject: [PATCH 21/21] IO: Filter duplicate input events during the AddXXX() calls. (#5599, #4921) --- docs/CHANGELOG.txt | 1 + imgui.cpp | 68 +++++++++++++++++++++++++++++++++++----------- imgui.h | 2 +- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 622bb289..f0a2fb5a 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -130,6 +130,7 @@ Other Changes: - IO: Added ImGuiMod_Shortcut which is ImGuiMod_Super on Mac and ImGuiMod_Ctrl otherwise. (#456) - IO: Added ImGuiKey_MouseXXX aliases for mouse buttons/wheel so all operations done on ImGuiKey can apply to mouse data as well. (#4921) +- IO: Filter duplicate input events during the AddXXX() calls. (#5599, #4921) - Menus: Fixed incorrect sub-menu parent association when opening a menu by closing another. Among other things, it would accidentally break part of the closing heuristic logic when moving towards a sub-menu. (#2517, #5614). [@rokups] diff --git a/imgui.cpp b/imgui.cpp index 4390d158..1304170d 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1282,11 +1282,13 @@ void ImGuiIO::AddInputCharactersUTF8(const char* utf8_chars) } } +// FIXME: Perhaps we could clear queued events as well? void ImGuiIO::ClearInputCharacters() { InputQueueCharacters.resize(0); } +// FIXME: Perhaps we could clear queued events as well? void ImGuiIO::ClearInputKeys() { #ifndef IMGUI_DISABLE_OBSOLETE_KEYIO @@ -1302,6 +1304,23 @@ void ImGuiIO::ClearInputKeys() KeyMods = ImGuiMod_None; } +static ImGuiInputEvent* FindLatestInputEvent(ImGuiInputEventType type, int arg = -1) +{ + ImGuiContext& g = *GImGui; + for (int n = g.InputEventsQueue.Size - 1; n >= 0; n--) + { + ImGuiInputEvent* e = &g.InputEventsQueue[n]; + if (e->Type != type) + continue; + if (type == ImGuiInputEventType_Key && e->Key.Key != arg) + continue; + if (type == ImGuiInputEventType_MouseButton && e->MouseButton.Button != arg) + continue; + return e; + } + return NULL; +} + // Queue a new key down/up event. // - ImGuiKey key: Translated key (as in, generally ImGuiKey_A matches the key end-user would use to emit an 'A' character) // - bool down: Is the key down? use false to signify a key release. @@ -1327,17 +1346,13 @@ void ImGuiIO::AddKeyAnalogEvent(ImGuiKey key, bool down, float analog_value) if (ImGui::IsGamepadKey(key)) BackendUsingLegacyNavInputArray = false; - // Partial filter of duplicates (not strictly needed, but makes data neater in particular for key mods and gamepad values which are most commonly spmamed) - ImGuiKeyData* key_data = ImGui::GetKeyData(key); - if (key_data->Down == down && key_data->AnalogValue == analog_value) - { - bool found = false; - for (int n = g.InputEventsQueue.Size - 1; n >= 0 && !found; n--) - if (g.InputEventsQueue[n].Type == ImGuiInputEventType_Key && g.InputEventsQueue[n].Key.Key == key) - found = true; - if (!found) - return; - } + // Filter duplicate (in particular: key mods and gamepad analog values are commonly spammed) + const ImGuiInputEvent* latest_event = FindLatestInputEvent(ImGuiInputEventType_Key, (int)key); + const ImGuiKeyData* key_data = ImGui::GetKeyData(key); + const bool latest_key_down = latest_event ? latest_event->Key.Down : key_data->Down; + const float latest_key_analog = latest_event ? latest_event->Key.AnalogValue : key_data->AnalogValue; + if (latest_key_down == down && latest_key_analog == analog_value) + return; // Add event ImGuiInputEvent e; @@ -1395,11 +1410,20 @@ void ImGuiIO::AddMousePosEvent(float x, float y) if (!AppAcceptingEvents) return; + // Apply same flooring as UpdateMouseInputs() + ImVec2 pos((x > -FLT_MAX) ? ImFloorSigned(x) : x, (y > -FLT_MAX) ? ImFloorSigned(y) : y); + + // Filter duplicate + const ImGuiInputEvent* latest_event = FindLatestInputEvent(ImGuiInputEventType_MousePos); + const ImVec2 latest_pos = latest_event ? ImVec2(latest_event->MousePos.PosX, latest_event->MousePos.PosY) : g.IO.MousePos; + if (latest_pos.x == pos.x && latest_pos.y == pos.y) + return; + ImGuiInputEvent e; e.Type = ImGuiInputEventType_MousePos; e.Source = ImGuiInputSource_Mouse; - e.MousePos.PosX = x; - e.MousePos.PosY = y; + e.MousePos.PosX = pos.x; + e.MousePos.PosY = pos.y; g.InputEventsQueue.push_back(e); } @@ -1411,6 +1435,12 @@ void ImGuiIO::AddMouseButtonEvent(int mouse_button, bool down) if (!AppAcceptingEvents) return; + // Filter duplicate + const ImGuiInputEvent* latest_event = FindLatestInputEvent(ImGuiInputEventType_MouseButton, (int)mouse_button); + const bool latest_button_down = latest_event ? latest_event->MouseButton.Down : g.IO.MouseDown[mouse_button]; + if (latest_button_down == down) + return; + ImGuiInputEvent e; e.Type = ImGuiInputEventType_MouseButton; e.Source = ImGuiInputSource_Mouse; @@ -1424,7 +1454,9 @@ 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) || !AppAcceptingEvents) + + // Filter duplicate (unlike most events, wheel values are relative and easy to filter) + if (!AppAcceptingEvents || (wheel_x == 0.0f && wheel_y == 0.0f)) return; ImGuiInputEvent e; @@ -1440,6 +1472,12 @@ void ImGuiIO::AddFocusEvent(bool focused) ImGuiContext& g = *GImGui; IM_ASSERT(&g.IO == this && "Can only add events to current context."); + // Filter duplicate + const ImGuiInputEvent* latest_event = FindLatestInputEvent(ImGuiInputEventType_Focus); + const bool latest_focused = latest_event ? latest_event->AppFocused.Focused : !g.IO.AppFocusLost; + if (latest_focused == focused) + return; + ImGuiInputEvent e; e.Type = ImGuiInputEventType_Focus; e.AppFocused.Focused = focused; @@ -8123,8 +8161,6 @@ void ImGui::UpdateInputEvents(bool trickle_fast_inputs) { // Trickling Rule: Stop processing queued events if we already handled a mouse button change ImVec2 event_pos(e->MousePos.PosX, e->MousePos.PosY); - if (IsMousePosValid(&event_pos)) - event_pos = ImVec2(ImFloorSigned(event_pos.x), ImFloorSigned(event_pos.y)); // Apply same flooring as UpdateMouseInputs() if (trickle_fast_inputs && (mouse_button_changed != 0 || mouse_wheeled || key_changed || text_inputted)) break; io.MousePos = event_pos; diff --git a/imgui.h b/imgui.h index 33270458..acdb73ed 100644 --- a/imgui.h +++ b/imgui.h @@ -23,7 +23,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM > 12345') #define IMGUI_VERSION "1.89 WIP" -#define IMGUI_VERSION_NUM 18827 +#define IMGUI_VERSION_NUM 18828 #define IMGUI_HAS_TABLE /*