From 77833003ffb69f4cb769eebccd3c1a89f0c1c337 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 19 Feb 2019 17:32:14 +0100 Subject: [PATCH 01/18] Fixed unused argument warning when compiling with IM_ASERT() evaluating to an empty macro. --- imgui_widgets.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 2ac389c1..f48a55bc 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2616,6 +2616,7 @@ int ImParseFormatPrecision(const char* fmt, int default_precision) // FIXME: Facilitate using this in variety of other situations. bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const char* label, ImGuiDataType data_type, void* data_ptr, const char* format) { + IM_UNUSED(id); ImGuiContext& g = *GImGui; // On the first frame, g.ScalarAsInputTextId == 0, then on subsequent frames it becomes == id. From d0c98bf8805bb04782dff84ead37a38c220cbc7d Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 19 Feb 2019 20:13:06 +0100 Subject: [PATCH 02/18] Examples: VS: Made project paths independant of SolutionDir so they can be built aside from the solution. --- .../example_glfw_opengl2.vcxproj | 16 ++++++++-------- .../example_glfw_opengl3.vcxproj | 16 ++++++++-------- .../example_glfw_vulkan.vcxproj | 18 +++++++++--------- .../example_sdl_opengl2.vcxproj | 8 ++++---- .../example_sdl_opengl3.vcxproj | 8 ++++---- .../example_win32_directx9.vcxproj | 8 ++++---- 6 files changed, 37 insertions(+), 37 deletions(-) diff --git a/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj b/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj index 73c7ba9d..7f6c2cbb 100644 --- a/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj +++ b/examples/example_glfw_opengl2/example_glfw_opengl2.vcxproj @@ -85,11 +85,11 @@ Level4 Disabled - ..\..;..;$(SolutionDir)\libs\glfw\include;%(AdditionalIncludeDirectories) + ..\..;..;..\libs\glfw\include;%(AdditionalIncludeDirectories) true - $(SolutionDir)\libs\glfw\lib-vc2010-32;%(AdditionalLibraryDirectories) + ..\libs\glfw\lib-vc2010-32;%(AdditionalLibraryDirectories) opengl32.lib;glfw3.lib;%(AdditionalDependencies) Console msvcrt.lib @@ -99,11 +99,11 @@ Level4 Disabled - ..\..;..;$(SolutionDir)\libs\glfw\include;%(AdditionalIncludeDirectories) + ..\..;..;..\libs\glfw\include;%(AdditionalIncludeDirectories) true - $(SolutionDir)\libs\glfw\lib-vc2010-64;%(AdditionalLibraryDirectories) + ..\libs\glfw\lib-vc2010-64;%(AdditionalLibraryDirectories) opengl32.lib;glfw3.lib;%(AdditionalDependencies) Console msvcrt.lib @@ -115,14 +115,14 @@ MaxSpeed true true - ..\..;..;$(SolutionDir)\libs\glfw\include;%(AdditionalIncludeDirectories) + ..\..;..;..\libs\glfw\include;%(AdditionalIncludeDirectories) false true true true - $(SolutionDir)\libs\glfw\lib-vc2010-32;%(AdditionalLibraryDirectories) + ..\libs\glfw\lib-vc2010-32;%(AdditionalLibraryDirectories) opengl32.lib;glfw3.lib;%(AdditionalDependencies) Console @@ -135,14 +135,14 @@ MaxSpeed true true - ..\..;..;$(SolutionDir)\libs\glfw\include;%(AdditionalIncludeDirectories) + ..\..;..;..\libs\glfw\include;%(AdditionalIncludeDirectories) false true true true - $(SolutionDir)\libs\glfw\lib-vc2010-64;%(AdditionalLibraryDirectories) + ..\libs\glfw\lib-vc2010-64;%(AdditionalLibraryDirectories) opengl32.lib;glfw3.lib;%(AdditionalDependencies) Console diff --git a/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj b/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj index 172a34d8..b80ca54e 100644 --- a/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj +++ b/examples/example_glfw_opengl3/example_glfw_opengl3.vcxproj @@ -85,11 +85,11 @@ Level4 Disabled - ..\..;..;$(SolutionDir)\libs\glfw\include;$(SolutionDir)\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..;..\libs\glfw\include;..\libs\gl3w;%(AdditionalIncludeDirectories) true - $(SolutionDir)\libs\glfw\lib-vc2010-32;%(AdditionalLibraryDirectories) + ..\libs\glfw\lib-vc2010-32;%(AdditionalLibraryDirectories) opengl32.lib;glfw3.lib;%(AdditionalDependencies) Console msvcrt.lib @@ -99,11 +99,11 @@ Level4 Disabled - ..\..;..;$(SolutionDir)\libs\glfw\include;$(SolutionDir)\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..;..\libs\glfw\include;..\libs\gl3w;%(AdditionalIncludeDirectories) true - $(SolutionDir)\libs\glfw\lib-vc2010-64;%(AdditionalLibraryDirectories) + ..\libs\glfw\lib-vc2010-64;%(AdditionalLibraryDirectories) opengl32.lib;glfw3.lib;%(AdditionalDependencies) Console msvcrt.lib @@ -115,14 +115,14 @@ MaxSpeed true true - ..\..;..;$(SolutionDir)\libs\glfw\include;$(SolutionDir)\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..;..\libs\glfw\include;..\libs\gl3w;%(AdditionalIncludeDirectories) false true true true - $(SolutionDir)\libs\glfw\lib-vc2010-32;%(AdditionalLibraryDirectories) + ..\libs\glfw\lib-vc2010-32;%(AdditionalLibraryDirectories) opengl32.lib;glfw3.lib;%(AdditionalDependencies) Console @@ -135,14 +135,14 @@ MaxSpeed true true - ..\..;..;$(SolutionDir)\libs\glfw\include;$(SolutionDir)\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..;..\libs\glfw\include;..\libs\gl3w;%(AdditionalIncludeDirectories) false true true true - $(SolutionDir)\libs\glfw\lib-vc2010-64;%(AdditionalLibraryDirectories) + ..\libs\glfw\lib-vc2010-64;%(AdditionalLibraryDirectories) opengl32.lib;glfw3.lib;%(AdditionalDependencies) Console diff --git a/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj b/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj index b0305e8f..53e09a8c 100644 --- a/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj +++ b/examples/example_glfw_vulkan/example_glfw_vulkan.vcxproj @@ -1,4 +1,4 @@ - + @@ -85,11 +85,11 @@ Level4 Disabled - ..\..;..;%VULKAN_SDK%\include;$(SolutionDir)\libs\glfw\include;%(AdditionalIncludeDirectories) + ..\..;..;%VULKAN_SDK%\include;..\libs\glfw\include;%(AdditionalIncludeDirectories) true - %VULKAN_SDK%\lib32;$(SolutionDir)\libs\glfw\lib-vc2010-32;%(AdditionalLibraryDirectories) + %VULKAN_SDK%\lib32;..\libs\glfw\lib-vc2010-32;%(AdditionalLibraryDirectories) vulkan-1.lib;glfw3.lib;%(AdditionalDependencies) Console msvcrt.lib @@ -99,11 +99,11 @@ Level4 Disabled - ..\..;..;%VULKAN_SDK%\include;$(SolutionDir)\libs\glfw\include;%(AdditionalIncludeDirectories) + ..\..;..;%VULKAN_SDK%\include;..\libs\glfw\include;%(AdditionalIncludeDirectories) true - %VULKAN_SDK%\lib;$(SolutionDir)\libs\glfw\lib-vc2010-64;%(AdditionalLibraryDirectories) + %VULKAN_SDK%\lib;..\libs\glfw\lib-vc2010-64;%(AdditionalLibraryDirectories) vulkan-1.lib;glfw3.lib;%(AdditionalDependencies) Console msvcrt.lib @@ -115,14 +115,14 @@ MaxSpeed true true - ..\..;..;%VULKAN_SDK%\include;$(SolutionDir)\libs\glfw\include;%(AdditionalIncludeDirectories) + ..\..;..;%VULKAN_SDK%\include;..\libs\glfw\include;%(AdditionalIncludeDirectories) false true true true - %VULKAN_SDK%\lib32;$(SolutionDir)\libs\glfw\lib-vc2010-32;%(AdditionalLibraryDirectories) + %VULKAN_SDK%\lib32;..\libs\glfw\lib-vc2010-32;%(AdditionalLibraryDirectories) vulkan-1.lib;glfw3.lib;%(AdditionalDependencies) Console @@ -135,14 +135,14 @@ MaxSpeed true true - ..\..;..;%VULKAN_SDK%\include;$(SolutionDir)\libs\glfw\include;%(AdditionalIncludeDirectories) + ..\..;..;%VULKAN_SDK%\include;..\libs\glfw\include;%(AdditionalIncludeDirectories) false true true true - %VULKAN_SDK%\lib;$(SolutionDir)\libs\glfw\lib-vc2010-64;%(AdditionalLibraryDirectories) + %VULKAN_SDK%\lib;..\libs\glfw\lib-vc2010-64;%(AdditionalLibraryDirectories) vulkan-1.lib;glfw3.lib;%(AdditionalDependencies) Console diff --git a/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj b/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj index fa6b8d3a..bcc9de95 100644 --- a/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj +++ b/examples/example_sdl_opengl2/example_sdl_opengl2.vcxproj @@ -85,7 +85,7 @@ Level4 Disabled - ..\..;..;%SDL2_DIR%\include;$(SolutionDir)\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) true @@ -99,7 +99,7 @@ Level4 Disabled - ..\..;..;%SDL2_DIR%\include;$(SolutionDir)\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) true @@ -115,7 +115,7 @@ MaxSpeed true true - ..\..;..;%SDL2_DIR%\include;$(SolutionDir)\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) false @@ -135,7 +135,7 @@ MaxSpeed true true - ..\..;..;%SDL2_DIR%\include;$(SolutionDir)\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..;%SDL2_DIR%\include;%(AdditionalIncludeDirectories) false diff --git a/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj b/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj index 9fda1897..2bc265b0 100644 --- a/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj +++ b/examples/example_sdl_opengl3/example_sdl_opengl3.vcxproj @@ -85,7 +85,7 @@ Level4 Disabled - ..\..;..;%SDL2_DIR%\include;$(SolutionDir)\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..;%SDL2_DIR%\include;..\libs\gl3w;%(AdditionalIncludeDirectories) true @@ -99,7 +99,7 @@ Level4 Disabled - ..\..;..;%SDL2_DIR%\include;$(SolutionDir)\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..;%SDL2_DIR%\include;..\libs\gl3w;%(AdditionalIncludeDirectories) true @@ -115,7 +115,7 @@ MaxSpeed true true - ..\..;..;%SDL2_DIR%\include;$(SolutionDir)\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..;%SDL2_DIR%\include;..\libs\gl3w;%(AdditionalIncludeDirectories) false @@ -135,7 +135,7 @@ MaxSpeed true true - ..\..;..;%SDL2_DIR%\include;$(SolutionDir)\libs\gl3w;%(AdditionalIncludeDirectories) + ..\..;..;%SDL2_DIR%\include;..\libs\gl3w;%(AdditionalIncludeDirectories) false diff --git a/examples/example_win32_directx9/example_win32_directx9.vcxproj b/examples/example_win32_directx9/example_win32_directx9.vcxproj index 08f21c87..b1c40c27 100644 --- a/examples/example_win32_directx9/example_win32_directx9.vcxproj +++ b/examples/example_win32_directx9/example_win32_directx9.vcxproj @@ -85,7 +85,7 @@ true - $(DXSDK_DIR)Lib\x86;%(AdditionalLibraryDirectories) + $(DXSDK_DIR)/Lib/x86;%(AdditionalLibraryDirectories) d3d9.lib;%(AdditionalDependencies) Console @@ -98,7 +98,7 @@ true - $(DXSDK_DIR)Lib\x64;%(AdditionalLibraryDirectories) + $(DXSDK_DIR)/Lib/x64;%(AdditionalLibraryDirectories) d3d9.lib;%(AdditionalDependencies) Console @@ -116,7 +116,7 @@ true true true - $(DXSDK_DIR)Lib\x86;%(AdditionalLibraryDirectories) + $(DXSDK_DIR)/Lib/x86;%(AdditionalLibraryDirectories) d3d9.lib;%(AdditionalDependencies) Console @@ -134,7 +134,7 @@ true true true - $(DXSDK_DIR)Lib\x64;%(AdditionalLibraryDirectories) + $(DXSDK_DIR)/Lib/x64;%(AdditionalLibraryDirectories) d3d9.lib;%(AdditionalDependencies) Console From 91cc32379dc28907e068d6c97cb562706c510d8b Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 19 Feb 2019 20:27:47 +0100 Subject: [PATCH 03/18] Updated binaries (now auto-generated by a script! next step would be to slowly transition all this stuff into a public repo) --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index d1c1c272..ebf7f926 100644 --- a/docs/README.md +++ b/docs/README.md @@ -102,7 +102,7 @@ Demo Binaries ------------- You should be able to build the examples from sources (tested on Windows/Mac/Linux). If you don't, let me know! If you want to have a quick look at some Dear ImGui features, you can download Windows binaries of the demo app here: -- [imgui-demo-binaries-20181008.zip](http://www.miracleworld.net/imgui/binaries/imgui-demo-binaries-20181008.zip) (Windows binaries, Dear ImGui 1.66 WIP built 2018/10/08, master branch, 5 executables) +- [imgui-demo-binaries-20190219.zip](http://www.miracleworld.net/imgui/binaries/imgui-demo-binaries-20190219.zip) (Windows binaries, Dear ImGui 1.68 built 2019/02/19, master branch, 5 executables) The demo applications are unfortunately not yet DPI aware so expect some blurriness on a 4K screen. For DPI awareness in your application, you can load/reload your font at different scale, and scale your Style with `style.ScaleAllSizes()`. From 93b06e6e7c7295c442c464ebec8d4b0c344b0d28 Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 19 Feb 2019 23:39:44 +0100 Subject: [PATCH 04/18] Internal: Changed Scrollbar() signature. Using GetScrollbarID() in InputTextMultiline(). Removed multiple semi-colons (#2368) --- imgui.cpp | 8 ++++---- imgui_internal.h | 4 ++-- imgui_widgets.cpp | 21 ++++++++++----------- 3 files changed, 16 insertions(+), 17 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index da0b068d..d04fe1ac 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5265,9 +5265,9 @@ bool ImGui::Begin(const char* name, bool* p_open, ImGuiWindowFlags flags) // Scrollbars if (window->ScrollbarX) - Scrollbar(ImGuiLayoutType_Horizontal); + Scrollbar(ImGuiAxis_X); if (window->ScrollbarY) - Scrollbar(ImGuiLayoutType_Vertical); + Scrollbar(ImGuiAxis_Y); // Render resize grips (after their input handling so we don't have a frame of latency) if (!(flags & ImGuiWindowFlags_NoResize)) @@ -6528,13 +6528,13 @@ ImGuiID ImGui::GetID(const void* ptr_id) bool ImGui::IsRectVisible(const ImVec2& size) { - ImGuiWindow* window = GImGui->CurrentWindow;; + ImGuiWindow* window = GImGui->CurrentWindow; return window->ClipRect.Overlaps(ImRect(window->DC.CursorPos, window->DC.CursorPos + size)); } bool ImGui::IsRectVisible(const ImVec2& rect_min, const ImVec2& rect_max) { - ImGuiWindow* window = GImGui->CurrentWindow;; + ImGuiWindow* window = GImGui->CurrentWindow; return window->ClipRect.Overlaps(ImRect(rect_min, rect_max)); } diff --git a/imgui_internal.h b/imgui_internal.h index e5b65040..4ebbaa81 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1463,8 +1463,8 @@ namespace ImGui IMGUI_API bool CloseButton(ImGuiID id, const ImVec2& pos, float radius); IMGUI_API bool CollapseButton(ImGuiID id, const ImVec2& pos); IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags); - IMGUI_API void Scrollbar(ImGuiLayoutType direction); - IMGUI_API ImGuiID GetScrollbarID(ImGuiLayoutType direction); + IMGUI_API void Scrollbar(ImGuiAxis axis); + IMGUI_API ImGuiID GetScrollbarID(ImGuiWindow* window, ImGuiAxis axis); IMGUI_API void VerticalSeparator(); // Vertical separator, for menu bars (use current line height). Not exposed because it is misleading and it doesn't have an effect on regular layout. // Widgets low-level behaviors diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index f48a55bc..943a2792 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -710,11 +710,9 @@ bool ImGui::CollapseButton(ImGuiID id, const ImVec2& pos) return pressed; } -ImGuiID ImGui::GetScrollbarID(ImGuiLayoutType direction) +ImGuiID ImGui::GetScrollbarID(ImGuiWindow* window, ImGuiAxis axis) { - ImGuiContext& g = *GImGui; - ImGuiWindow* window = g.CurrentWindow; - return window->GetID((direction == ImGuiLayoutType_Horizontal) ? "#SCROLLX" : "#SCROLLY"); + return window->GetIDNoKeepAlive(axis == ImGuiAxis_X ? "#SCROLLX" : "#SCROLLY"); } // Vertical/Horizontal scrollbar @@ -722,15 +720,16 @@ ImGuiID ImGui::GetScrollbarID(ImGuiLayoutType direction) // - We handle absolute seeking (when first clicking outside the grab) and relative manipulation (afterward or when clicking inside the grab) // - We store values as normalized ratio and in a form that allows the window content to change while we are holding on a scrollbar // - We handle both horizontal and vertical scrollbars, which makes the terminology not ideal. -void ImGui::Scrollbar(ImGuiLayoutType direction) +void ImGui::Scrollbar(ImGuiAxis axis) { ImGuiContext& g = *GImGui; ImGuiWindow* window = g.CurrentWindow; - const bool horizontal = (direction == ImGuiLayoutType_Horizontal); + const bool horizontal = (axis == ImGuiAxis_X); const ImGuiStyle& style = g.Style; - const ImGuiID id = GetScrollbarID(direction); - + const ImGuiID id = GetScrollbarID(window, axis); + KeepAliveID(id); + // Render background bool other_scrollbar = (horizontal ? window->ScrollbarY : window->ScrollbarX); float other_scrollbar_size_w = other_scrollbar ? style.ScrollbarSize : 0.0f; @@ -748,7 +747,7 @@ void ImGui::Scrollbar(ImGuiLayoutType direction) // When we are too small, start hiding and disabling the grab (this reduce visual noise on very small window and facilitate using the resize grab) float alpha = 1.0f; - if ((direction == ImGuiLayoutType_Vertical) && bb_height < g.FontSize + g.Style.FramePadding.y * 2.0f) + if ((axis == ImGuiAxis_Y) && bb_height < g.FontSize + g.Style.FramePadding.y * 2.0f) { alpha = ImSaturate((bb_height - g.FontSize) / (g.Style.FramePadding.y * 2.0f)); if (alpha <= 0.0f) @@ -3210,7 +3209,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code; const bool user_clicked = hovered && io.MouseClicked[0]; - const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == draw_window->GetIDNoKeepAlive("#SCROLLY"); + const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == GetScrollbarID(draw_window, ImGuiAxis_Y); const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard)); bool clear_active_id = false; @@ -3622,7 +3621,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; ImVec2 text_size(0.f, 0.f); - const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == draw_window->GetIDNoKeepAlive("#SCROLLY")); + const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == GetScrollbarID(draw_window, ImGuiAxis_Y)); if (g.ActiveId == id || is_currently_scrolling) { edit_state.CursorAnim += io.DeltaTime; From 257f5d204e411ea49e2eac04603451f87fe8eb8f Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 20 Feb 2019 00:11:36 +0100 Subject: [PATCH 05/18] Version 1.69 WIP --- docs/CHANGELOG.txt | 5 +++++ imgui.cpp | 2 +- imgui.h | 6 +++--- imgui_demo.cpp | 2 +- imgui_draw.cpp | 2 +- imgui_internal.h | 2 +- imgui_widgets.cpp | 2 +- 7 files changed, 13 insertions(+), 8 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 350752fb..e2119884 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -29,6 +29,11 @@ HOW TO UPDATE? - Please report any issue! +----------------------------------------------------------------------- + VERSION 1.69 (In Progress) +----------------------------------------------------------------------- + + ----------------------------------------------------------------------- VERSION 1.68 (Released 2019-02-19) ----------------------------------------------------------------------- diff --git a/imgui.cpp b/imgui.cpp index d04fe1ac..19dfb983 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.68 +// dear imgui, v1.69 WIP // (main code and documentation) // Call and read ImGui::ShowDemoWindow() in imgui_demo.cpp for demo code. diff --git a/imgui.h b/imgui.h index 3cd00a35..e109f3a4 100644 --- a/imgui.h +++ b/imgui.h @@ -1,4 +1,4 @@ -// dear imgui, v1.68 +// dear imgui, v1.69 WIP // (headers) // See imgui.cpp file for documentation. @@ -45,8 +45,8 @@ Index of this file: // Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY00 then bounced up to XYY01 when release tagging happens) -#define IMGUI_VERSION "1.68" -#define IMGUI_VERSION_NUM 16801 +#define IMGUI_VERSION "1.69 WIP" +#define IMGUI_VERSION_NUM 16899 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert)) // Define attributes of all API symbols declarations (e.g. for DLL under Windows) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index d2fa3a3b..560224f1 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.68 +// dear imgui, v1.69 WIP // (demo code) // Message to the person tempted to delete this file when integrating Dear ImGui into their code base: diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 075ea0d2..05480266 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.68 +// dear imgui, v1.69 WIP // (drawing and font code) /* diff --git a/imgui_internal.h b/imgui_internal.h index 4ebbaa81..b5d72ae8 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -1,4 +1,4 @@ -// dear imgui, v1.68 +// dear imgui, v1.69 WIP // (internal structures/api) // You may use this file to debug, understand or extend ImGui features but we don't provide any guarantee of forward compatibility! diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 943a2792..327e3d83 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1,4 +1,4 @@ -// dear imgui, v1.68 +// dear imgui, v1.69 WIP // (widgets code) /* From 7c51cba74f4a871c502cf291bc0d134cf41e3bd4 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 20 Feb 2019 00:20:11 +0100 Subject: [PATCH 06/18] InputInt, InputFloat, InputScalar: Fix to keep the label of the +/- buttons centered when style.FramePadding.x is abnormally larger than style.FramePadding.y. Since the buttons are meant to be square (to align with e.g. color button) we always use FramePadding.y. (#2367) --- docs/CHANGELOG.txt | 6 ++++++ imgui_widgets.cpp | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index e2119884..16664518 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -33,6 +33,12 @@ HOW TO UPDATE? VERSION 1.69 (In Progress) ----------------------------------------------------------------------- +Other Changes: + +- InputInt, InputFloat, InputScalar: Fix to keep the label of the +/- buttons centered when + style.FramePadding.x is abnormally larger than style.FramePadding.y. Since the buttons are + meant to be square (to align with e.g. color button) we always use FramePadding.y. (#2367) + ----------------------------------------------------------------------- VERSION 1.68 (Released 2019-02-19) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 327e3d83..1fb7dd6d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2648,7 +2648,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_p return false; ImGuiContext& g = *GImGui; - const ImGuiStyle& style = g.Style; + ImGuiStyle& style = g.Style; IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT); if (format == NULL) @@ -2674,6 +2674,8 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_p PopItemWidth(); // Step buttons + const ImVec2 backup_frame_padding = style.FramePadding; + style.FramePadding.x = style.FramePadding.y; ImGuiButtonFlags button_flags = ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups; if (flags & ImGuiInputTextFlags_ReadOnly) button_flags |= ImGuiButtonFlags_Disabled; @@ -2691,6 +2693,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_p } SameLine(0, style.ItemInnerSpacing.x); TextUnformatted(label, FindRenderedTextEnd(label)); + style.FramePadding = backup_frame_padding; PopID(); EndGroup(); From 782b747a17299ec9018257d0671d472d4cbdf355 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 20 Feb 2019 00:40:09 +0100 Subject: [PATCH 07/18] InputText: Renamed some local variables to clarify code. Should be a no-op functionality wise. TODO items. --- docs/TODO.txt | 7 +++++-- imgui_widgets.cpp | 12 +++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/docs/TODO.txt b/docs/TODO.txt index 114bd995..01b54716 100644 --- a/docs/TODO.txt +++ b/docs/TODO.txt @@ -72,8 +72,11 @@ It's mostly a bunch of personal notes, probably incomplete. Feel free to query i - input text: add discard flag (e.g. ImGuiInputTextFlags_DiscardActiveBuffer) or make it easier to clear active focus for text replacement during edition (#725) - input text: display bug when clicking a drag/slider after an input text in a different window has all-selected text (order dependent). actually a very old bug but no one appears to have noticed it. - input text: allow centering/positioning text so that ctrl+clicking Drag or Slider keeps the textual value at the same pixel position. - - input text: what's the easiest way to implement a nice IP/Mac address input editor? - - input text: Global callback system so user can plug in an expression evaluator easily. + - input text: decorrelate layout from inputs - e.g. what's the easiest way to implement a nice IP/Mac address input editor? + - input text: global callback system so user can plug in an expression evaluator easily. + - input text: force scroll to end or scroll to a given line/contents (so user can implement a log or a search feature) + - input text: a side bar that could e.g. preview where errors are. probably left to the user to draw but we'd need to give them the info there. + - input text: a way for the user to provide syntax coloring. - input text multi-line: don't directly call AddText() which does an unnecessary vertex reserve for character count prior to clipping. and/or more line-based clipping to AddText(). and/or reorganize TextUnformatted/RenderText for more efficiency for large text (e.g TextUnformatted could clip and log separately, etc). - input text multi-line: support for cut/paste without selection (cut/paste the current line) - input text multi-line: line numbers? status bar? (follow up on #200) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 1fb7dd6d..57cd25a3 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3212,13 +3212,14 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code; const bool user_clicked = hovered && io.MouseClicked[0]; - const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.ID == id && g.ActiveIdPreviousFrame == GetScrollbarID(draw_window, ImGuiAxis_Y); const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard)); + const bool user_scroll_finish = is_multiline && edit_state.ID == id && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetScrollbarID(draw_window, ImGuiAxis_Y); + const bool user_scroll_active = is_multiline && edit_state.ID == id && g.ActiveId == GetScrollbarID(draw_window, ImGuiAxis_Y); bool clear_active_id = false; bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline); - if (focus_requested || user_clicked || user_scrolled || user_nav_input_start) + if (focus_requested || user_clicked || user_scroll_finish || user_nav_input_start) { if (g.ActiveId != id) { @@ -3624,9 +3625,9 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; ImVec2 text_size(0.f, 0.f); - const bool is_currently_scrolling = (edit_state.ID == id && is_multiline && g.ActiveId == GetScrollbarID(draw_window, ImGuiAxis_Y)); - if (g.ActiveId == id || is_currently_scrolling) + if (g.ActiveId == id || user_scroll_active) { + // Animate cursor edit_state.CursorAnim += io.DeltaTime; // This is going to be messy. We need to: @@ -3707,7 +3708,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); else if (cursor_offset.y - size.y >= scroll_y) scroll_y = cursor_offset.y - size.y; - draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // To avoid a frame of lag + draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag draw_window->Scroll.y = scroll_y; render_pos.y = draw_window->DC.CursorPos.y; } @@ -3751,6 +3752,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 } } + // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash. const int buf_display_len = edit_state.CurLenA; if (is_multiline || buf_display_len < buf_display_max_length) draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect); From 2068dd509c6599d0b4339d610e6e68eaa9b0d61e Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 20 Feb 2019 14:31:19 +0100 Subject: [PATCH 08/18] Examples: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if the OpenGL headers/loader happens to define the value. (#2366, #2186) --- docs/CHANGELOG.txt | 2 ++ examples/imgui_impl_opengl3.cpp | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 16664518..199882b9 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -38,6 +38,8 @@ Other Changes: - InputInt, InputFloat, InputScalar: Fix to keep the label of the +/- buttons centered when style.FramePadding.x is abnormally larger than style.FramePadding.y. Since the buttons are meant to be square (to align with e.g. color button) we always use FramePadding.y. (#2367) +- Examples: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN + even if the OpenGL headers/loader happens to define the value. (#2366, #2186) ----------------------------------------------------------------------- diff --git a/examples/imgui_impl_opengl3.cpp b/examples/imgui_impl_opengl3.cpp index bd8c94e6..9a53d3c6 100644 --- a/examples/imgui_impl_opengl3.cpp +++ b/examples/imgui_impl_opengl3.cpp @@ -11,10 +11,11 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2019-02-20: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if defined by the headers/loader. // 2019-02-11: OpenGL: Projecting clipping rectangles correctly using draw_data->FramebufferScale to allow multi-viewports for retina display. // 2019-02-01: OpenGL: Using GLSL 410 shaders for any version over 410 (e.g. 430, 450). // 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. -// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT). +// 2018-11-13: OpenGL: Support for GL 4.5's glClipControl(GL_UPPER_LEFT) / GL_CLIP_ORIGIN. // 2018-08-29: OpenGL: Added support for more OpenGL loaders: glew and glad, with comments indicative that any loader can be used. // 2018-08-09: OpenGL: Default to OpenGL ES 3 on iOS and Android. GLSL version default to "#version 300 ES". // 2018-07-30: OpenGL: Support for GLSL 300 ES and 410 core. Fixes for Emscripten compilation. @@ -169,7 +170,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data) GLboolean last_enable_depth_test = glIsEnabled(GL_DEPTH_TEST); GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); bool clip_origin_lower_left = true; -#ifdef GL_CLIP_ORIGIN +#if defined(GL_CLIP_ORIGIN) && !defined(__APPLE__) GLenum last_clip_origin = 0; glGetIntegerv(GL_CLIP_ORIGIN, (GLint*)&last_clip_origin); // Support for GL 4.5's glClipControl(GL_UPPER_LEFT) if (last_clip_origin == GL_UPPER_LEFT) clip_origin_lower_left = false; From 79f7778e48fb262b3c8cd52a97bbdd91533ec459 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 20 Feb 2019 15:10:23 +0100 Subject: [PATCH 09/18] Moved binaries to dearimgui.org/binaries --- docs/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/README.md b/docs/README.md index ebf7f926..7e041181 100644 --- a/docs/README.md +++ b/docs/README.md @@ -102,7 +102,7 @@ Demo Binaries ------------- You should be able to build the examples from sources (tested on Windows/Mac/Linux). If you don't, let me know! If you want to have a quick look at some Dear ImGui features, you can download Windows binaries of the demo app here: -- [imgui-demo-binaries-20190219.zip](http://www.miracleworld.net/imgui/binaries/imgui-demo-binaries-20190219.zip) (Windows binaries, Dear ImGui 1.68 built 2019/02/19, master branch, 5 executables) +- [imgui-demo-binaries-20190219.zip](http://www.dearimgui.org/binaries/imgui-demo-binaries-20190219.zip) (Windows binaries, Dear ImGui 1.68 built 2019/02/19, master branch, 5 executables) The demo applications are unfortunately not yet DPI aware so expect some blurriness on a 4K screen. For DPI awareness in your application, you can load/reload your font at different scale, and scale your Style with `style.ScaleAllSizes()`. From 677e64e71eba79a74496ac1aae40f180aa936213 Mon Sep 17 00:00:00 2001 From: omar Date: Wed, 20 Feb 2019 21:25:17 +0100 Subject: [PATCH 10/18] Internal: InputText: Comments. Renamed internal member. Renamed ImGuiStb->ImStb. --- imgui_draw.cpp | 4 ++-- imgui_internal.h | 20 +++++++++--------- imgui_widgets.cpp | 53 ++++++++++++++++++++++++----------------------- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 05480266..37e8ff51 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -83,7 +83,7 @@ Index of this file: //------------------------------------------------------------------------- // Compile time options: -//#define IMGUI_STB_NAMESPACE ImGuiStb +//#define IMGUI_STB_NAMESPACE ImStb //#define IMGUI_STB_TRUETYPE_FILENAME "my_folder/stb_truetype.h" //#define IMGUI_STB_RECT_PACK_FILENAME "my_folder/stb_rect_pack.h" //#define IMGUI_DISABLE_STB_TRUETYPE_IMPLEMENTATION @@ -163,7 +163,7 @@ namespace IMGUI_STB_NAMESPACE #endif #ifdef IMGUI_STB_NAMESPACE -} // namespace ImGuiStb +} // namespace ImStb using namespace IMGUI_STB_NAMESPACE; #endif diff --git a/imgui_internal.h b/imgui_internal.h index b5d72ae8..ac36ba61 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -96,7 +96,7 @@ typedef int ImGuiDragFlags; // -> enum ImGuiDragFlags_ // Flags: // STB libraries includes //------------------------------------------------------------------------- -namespace ImGuiStb +namespace ImStb { #undef STB_TEXTEDIT_STRING @@ -106,7 +106,7 @@ namespace ImGuiStb #define STB_TEXTEDIT_GETWIDTH_NEWLINE -1.0f #include "imstb_textedit.h" -} // namespace ImGuiStb +} // namespace ImStb //----------------------------------------------------------------------------- // Context pointer @@ -567,10 +567,10 @@ struct IMGUI_API ImGuiInputTextState int CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. int BufCapacityA; // end-user buffer capacity float ScrollX; - ImGuiStb::STB_TexteditState StbState; - float CursorAnim; - bool CursorFollow; - bool SelectedAllMouseLock; + ImStb::STB_TexteditState Stb; // state for stb_textedit.h + float CursorAnim; // timer for cursor blink, reset on every user action so the cursor reappears immediately + 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 // Temporarily set when active ImGuiInputTextFlags UserFlags; @@ -579,10 +579,10 @@ struct IMGUI_API ImGuiInputTextState ImGuiInputTextState() { memset(this, 0, sizeof(*this)); } void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking - void CursorClamp() { StbState.cursor = ImMin(StbState.cursor, CurLenW); StbState.select_start = ImMin(StbState.select_start, CurLenW); StbState.select_end = ImMin(StbState.select_end, CurLenW); } - bool HasSelection() const { return StbState.select_start != StbState.select_end; } - void ClearSelection() { StbState.select_start = StbState.select_end = StbState.cursor; } - void SelectAll() { StbState.select_start = 0; StbState.cursor = StbState.select_end = CurLenW; StbState.has_preferred_x = 0; } + void CursorClamp() { Stb.cursor = ImMin(Stb.cursor, CurLenW); Stb.select_start = ImMin(Stb.select_start, CurLenW); Stb.select_end = ImMin(Stb.select_end, CurLenW); } + bool HasSelection() const { return Stb.select_start != Stb.select_end; } + void ClearSelection() { Stb.select_start = Stb.select_end = Stb.cursor; } + void SelectAll() { Stb.select_start = 0; Stb.cursor = Stb.select_end = CurLenW; Stb.has_preferred_x = 0; } void OnKeyPressed(int key); // Cannot be inline because we call in code in stb_textedit.h implementation }; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 57cd25a3..86fa6d1b 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2898,7 +2898,7 @@ static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* t } // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) -namespace ImGuiStb +namespace ImStb { static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return obj->CurLenW; } @@ -3001,7 +3001,7 @@ static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const Im void ImGuiInputTextState::OnKeyPressed(int key) { - stb_textedit_key(this, &StbState, key); + stb_textedit_key(this, &Stb, key); CursorFollow = true; CursorAnimReset(); } @@ -3249,12 +3249,12 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 { edit_state.ID = id; edit_state.ScrollX = 0.0f; - stb_textedit_initialize_state(&edit_state.StbState, !is_multiline); + stb_textedit_initialize_state(&edit_state.Stb, !is_multiline); if (!is_multiline && focus_requested_by_code) select_all = true; } if (flags & ImGuiInputTextFlags_AlwaysInsertMode) - edit_state.StbState.insert_mode = 1; + edit_state.Stb.insert_mode = 1; if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl))) select_all = true; } @@ -3318,13 +3318,13 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 { if (hovered) { - stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y); + stb_textedit_click(&edit_state, &edit_state.Stb, mouse_x, mouse_y); edit_state.CursorAnimReset(); } } else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) { - stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y); + stb_textedit_drag(&edit_state, &edit_state.Stb, mouse_x, mouse_y); edit_state.CursorAnimReset(); edit_state.CursorFollow = true; } @@ -3424,8 +3424,8 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // Cut, Copy if (io.SetClipboardTextFn) { - const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0; - const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : edit_state.CurLenW; + const int ib = edit_state.HasSelection() ? ImMin(edit_state.Stb.select_start, edit_state.Stb.select_end) : 0; + const int ie = edit_state.HasSelection() ? ImMax(edit_state.Stb.select_start, edit_state.Stb.select_end) : edit_state.CurLenW; edit_state.TempBuffer.resize((ie-ib) * 4 + 1); ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data+ib, edit_state.TextW.Data+ie); SetClipboardText(edit_state.TempBuffer.Data); @@ -3435,7 +3435,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 if (!edit_state.HasSelection()) edit_state.SelectAll(); edit_state.CursorFollow = true; - stb_textedit_cut(&edit_state, &edit_state.StbState); + stb_textedit_cut(&edit_state, &edit_state.Stb); } } else if (is_paste) @@ -3459,7 +3459,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 clipboard_filtered[clipboard_filtered_len] = 0; if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation { - stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len); + stb_textedit_paste(&edit_state, &edit_state.Stb, clipboard_filtered, clipboard_filtered_len); edit_state.CursorFollow = true; } MemFree(clipboard_filtered); @@ -3538,9 +3538,9 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188) ImWchar* text = edit_state.TextW.Data; - const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor); - const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start); - const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end); + const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.Stb.cursor); + const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.Stb.select_start); + const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.Stb.select_end); // Call user code callback(&callback_data); @@ -3549,9 +3549,9 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data); // Invalid to modify those fields IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA); IM_ASSERT(callback_data.Flags == flags); - if (callback_data.CursorPos != utf8_cursor_pos) { edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; } - if (callback_data.SelectionStart != utf8_selection_start) { edit_state.StbState.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } - if (callback_data.SelectionEnd != utf8_selection_end) { edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } + if (callback_data.CursorPos != utf8_cursor_pos) { edit_state.Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; } + if (callback_data.SelectionStart != utf8_selection_start) { edit_state.Stb.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } + if (callback_data.SelectionEnd != utf8_selection_end) { edit_state.Stb.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } if (callback_data.BufDirty) { IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! @@ -3607,15 +3607,16 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 if (clear_active_id && g.ActiveId == id) ClearActiveID(); - // Render - // Select which buffer we are going to display. When ImGuiInputTextFlags_NoLiveEdit is set 'buf' might still be the old value. We set buf to NULL to prevent accidental usage from now on. - const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; buf = NULL; - // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether. // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash. const int buf_display_max_length = 2 * 1024 * 1024; + // Select which buffer we are going to display. We set buf to NULL to prevent accidental usage from now on. + const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; + buf = NULL; + + // Render if (!is_multiline) { RenderNavHighlight(frame_bb, id); @@ -3642,13 +3643,13 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 { // Count lines + find lines numbers straddling 'cursor' and 'select_start' position. const ImWchar* searches_input_ptr[2]; - searches_input_ptr[0] = text_begin + edit_state.StbState.cursor; + searches_input_ptr[0] = text_begin + edit_state.Stb.cursor; searches_input_ptr[1] = NULL; int searches_remaining = 1; int searches_result_line_number[2] = { -1, -999 }; - if (edit_state.StbState.select_start != edit_state.StbState.select_end) + if (edit_state.Stb.select_start != edit_state.Stb.select_end) { - searches_input_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); + searches_input_ptr[1] = text_begin + ImMin(edit_state.Stb.select_start, edit_state.Stb.select_end); searches_result_line_number[1] = -1; searches_remaining++; } @@ -3717,10 +3718,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f); // Draw selection - if (edit_state.StbState.select_start != edit_state.StbState.select_end) + if (edit_state.Stb.select_start != edit_state.Stb.select_end) { - const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); - const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end); + const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.Stb.select_start, edit_state.Stb.select_end); + const ImWchar* text_selected_end = text_begin + ImMax(edit_state.Stb.select_start, edit_state.Stb.select_end); float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. float bg_offy_dn = is_multiline ? 0.0f : 2.0f; From 2e9a175057fa9e983332aa5b0683203b5bb42317 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 21 Feb 2019 12:24:50 +0100 Subject: [PATCH 11/18] Internal: InputText: Refactor to clarify access pattern to the InputTextState (we are now accessing via a pointer which can be NULL, shortened its name while we are at it) + added an assert to track an issue that existed already before. --- imgui_widgets.cpp | 243 ++++++++++++++++++++++++---------------------- 1 file changed, 128 insertions(+), 115 deletions(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 86fa6d1b..4598194f 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3205,7 +3205,9 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 } // NB: we are only allowed to access 'edit_state' if we are the active widget. - ImGuiInputTextState& edit_state = g.InputTextState; + ImGuiInputTextState* state = NULL; + if (g.InputTextState.ID == id) + state = &g.InputTextState; const bool focus_requested = FocusableItemRegister(window, id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent); @@ -3213,8 +3215,8 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const bool user_clicked = hovered && io.MouseClicked[0]; const bool user_nav_input_start = (g.ActiveId != id) && ((g.NavInputId == id) || (g.NavActivateId == id && g.NavInputSource == ImGuiInputSource_NavKeyboard)); - const bool user_scroll_finish = is_multiline && edit_state.ID == id && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetScrollbarID(draw_window, ImGuiAxis_Y); - const bool user_scroll_active = is_multiline && edit_state.ID == id && g.ActiveId == GetScrollbarID(draw_window, ImGuiAxis_Y); + const bool user_scroll_finish = is_multiline && state != NULL && g.ActiveId == 0 && g.ActiveIdPreviousFrame == GetScrollbarID(draw_window, ImGuiAxis_Y); + const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetScrollbarID(draw_window, ImGuiAxis_Y); bool clear_active_id = false; @@ -3223,41 +3225,46 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 { if (g.ActiveId != id) { + // Access state even if we don't own it yet. + state = &g.InputTextState; + // Start edition // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) - const int prev_len_w = edit_state.CurLenW; + const int prev_len_w = state->CurLenW; const int init_buf_len = (int)strlen(buf); - edit_state.TextW.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash. - edit_state.InitialText.resize(init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash. - memcpy(edit_state.InitialText.Data, buf, init_buf_len + 1); + state->TextW.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash. + state->InitialText.resize(init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash. + memcpy(state->InitialText.Data, buf, init_buf_len + 1); const char* buf_end = NULL; - edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, buf_size, buf, NULL, &buf_end); - edit_state.CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. - edit_state.CursorAnimReset(); + state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end); + state->CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. + state->CursorAnimReset(); // Preserve cursor position and undo/redo stack if we come back to same widget // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar). - const bool recycle_state = (edit_state.ID == id) && (prev_len_w == edit_state.CurLenW); + const bool recycle_state = (state->ID == id) && (prev_len_w == state->CurLenW); if (recycle_state) { // Recycle existing cursor/selection/undo stack but clamp position // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. - edit_state.CursorClamp(); + state->CursorClamp(); } else { - edit_state.ID = id; - edit_state.ScrollX = 0.0f; - stb_textedit_initialize_state(&edit_state.Stb, !is_multiline); + state->ID = id; + state->ScrollX = 0.0f; + stb_textedit_initialize_state(&state->Stb, !is_multiline); if (!is_multiline && focus_requested_by_code) select_all = true; } if (flags & ImGuiInputTextFlags_AlwaysInsertMode) - edit_state.Stb.insert_mode = 1; + state->Stb.insert_mode = 1; if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl))) select_all = true; } + + IM_ASSERT(state && state->ID == id); SetActiveID(id, window); SetFocusID(id, window); FocusWindow(window); @@ -3277,21 +3284,22 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 if (g.ActiveId == id) { + IM_ASSERT(state != NULL); if (!is_editable && !g.ActiveIdIsJustActivated) { // When read-only we always use the live data passed to the function - edit_state.TextW.resize(buf_size+1); const char* buf_end = NULL; - edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, buf, NULL, &buf_end); - edit_state.CurLenA = (int)(buf_end - buf); - edit_state.CursorClamp(); + state->TextW.resize(buf_size+1); + state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end); + state->CurLenA = (int)(buf_end - buf); + state->CursorClamp(); } - backup_current_text_length = edit_state.CurLenA; - edit_state.BufCapacityA = buf_size; - edit_state.UserFlags = flags; - edit_state.UserCallback = callback; - edit_state.UserCallbackData = callback_user_data; + backup_current_text_length = state->CurLenA; + state->BufCapacityA = buf_size; + state->UserFlags = flags; + state->UserCallback = callback; + state->UserCallbackData = callback_user_data; // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. // Down the line we should have a cleaner library-wide concept of Selected vs Active. @@ -3299,37 +3307,37 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 g.WantTextInputNextFrame = 1; // Edit in progress - const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX; + const float mouse_x = (io.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + state->ScrollX; const float mouse_y = (is_multiline ? (io.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f)); const bool is_osx = io.ConfigMacOSXBehaviors; if (select_all || (hovered && !is_osx && io.MouseDoubleClicked[0])) { - edit_state.SelectAll(); - edit_state.SelectedAllMouseLock = true; + state->SelectAll(); + state->SelectedAllMouseLock = true; } else if (hovered && is_osx && io.MouseDoubleClicked[0]) { // Double-click select a word only, OS X style (by simulating keystrokes) - edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); - edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); + state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT); + state->OnKeyPressed(STB_TEXTEDIT_K_WORDRIGHT | STB_TEXTEDIT_K_SHIFT); } - else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock) + else if (io.MouseClicked[0] && !state->SelectedAllMouseLock) { if (hovered) { - stb_textedit_click(&edit_state, &edit_state.Stb, mouse_x, mouse_y); - edit_state.CursorAnimReset(); + stb_textedit_click(state, &state->Stb, mouse_x, mouse_y); + state->CursorAnimReset(); } } - else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) + else if (io.MouseDown[0] && !state->SelectedAllMouseLock && (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f)) { - stb_textedit_drag(&edit_state, &edit_state.Stb, mouse_x, mouse_y); - edit_state.CursorAnimReset(); - edit_state.CursorFollow = true; + stb_textedit_drag(state, &state->Stb, mouse_x, mouse_y); + state->CursorAnimReset(); + state->CursorFollow = true; } - if (edit_state.SelectedAllMouseLock && !io.MouseDown[0]) - edit_state.SelectedAllMouseLock = false; + if (state->SelectedAllMouseLock && !io.MouseDown[0]) + state->SelectedAllMouseLock = false; if (io.InputQueueCharacters.Size > 0) { @@ -3342,7 +3350,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // Insert character if they pass filtering unsigned int c = (unsigned int)io.InputQueueCharacters[n]; if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) - edit_state.OnKeyPressed((int)c); + state->OnKeyPressed((int)c); } // Consume characters @@ -3354,6 +3362,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) { // Handle key-presses + IM_ASSERT(state != NULL); const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); const bool is_osx = io.ConfigMacOSXBehaviors; const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl @@ -3363,27 +3372,29 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper; const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper; - const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || edit_state.HasSelection()); - const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || edit_state.HasSelection()); + const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || state->HasSelection()); + const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection()); const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable; const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable); const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable; - if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else edit_state.OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } + if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_UpArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMax(draw_window->Scroll.y - g.FontSize, 0.0f)); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTSTART : STB_TEXTEDIT_K_UP) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable) { - if (!edit_state.HasSelection()) + if (!state->HasSelection()) { - if (is_wordmove_key_down) edit_state.OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT); - else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) edit_state.OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT); + if (is_wordmove_key_down) + state->OnKeyPressed(STB_TEXTEDIT_K_WORDLEFT|STB_TEXTEDIT_K_SHIFT); + else if (is_osx && io.KeySuper && !io.KeyAlt && !io.KeyCtrl) + state->OnKeyPressed(STB_TEXTEDIT_K_LINESTART|STB_TEXTEDIT_K_SHIFT); } - edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); + state->OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Enter)) { @@ -3396,14 +3407,14 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 { unsigned int c = '\n'; // Insert new line if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) - edit_state.OnKeyPressed((int)c); + state->OnKeyPressed((int)c); } } else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable) { unsigned int c = '\t'; // Insert TAB if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) - edit_state.OnKeyPressed((int)c); + state->OnKeyPressed((int)c); } else if (IsKeyPressedMap(ImGuiKey_Escape)) { @@ -3411,31 +3422,31 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 } else if (is_undo || is_redo) { - edit_state.OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO); - edit_state.ClearSelection(); + state->OnKeyPressed(is_undo ? STB_TEXTEDIT_K_UNDO : STB_TEXTEDIT_K_REDO); + state->ClearSelection(); } else if (is_shortcut_key && IsKeyPressedMap(ImGuiKey_A)) { - edit_state.SelectAll(); - edit_state.CursorFollow = true; + state->SelectAll(); + state->CursorFollow = true; } else if (is_cut || is_copy) { // Cut, Copy if (io.SetClipboardTextFn) { - const int ib = edit_state.HasSelection() ? ImMin(edit_state.Stb.select_start, edit_state.Stb.select_end) : 0; - const int ie = edit_state.HasSelection() ? ImMax(edit_state.Stb.select_start, edit_state.Stb.select_end) : edit_state.CurLenW; - edit_state.TempBuffer.resize((ie-ib) * 4 + 1); - ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data+ib, edit_state.TextW.Data+ie); - SetClipboardText(edit_state.TempBuffer.Data); + const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0; + const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW; + state->TempBuffer.resize((ie-ib) * 4 + 1); + ImTextStrToUtf8(state->TempBuffer.Data, state->TempBuffer.Size, state->TextW.Data+ib, state->TextW.Data+ie); + SetClipboardText(state->TempBuffer.Data); } if (is_cut) { - if (!edit_state.HasSelection()) - edit_state.SelectAll(); - edit_state.CursorFollow = true; - stb_textedit_cut(&edit_state, &edit_state.Stb); + if (!state->HasSelection()) + state->SelectAll(); + state->CursorFollow = true; + stb_textedit_cut(state, &state->Stb); } } else if (is_paste) @@ -3459,8 +3470,8 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 clipboard_filtered[clipboard_filtered_len] = 0; if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation { - stb_textedit_paste(&edit_state, &edit_state.Stb, clipboard_filtered, clipboard_filtered_len); - edit_state.CursorFollow = true; + stb_textedit_paste(state, &state->Stb, clipboard_filtered, clipboard_filtered_len); + state->CursorFollow = true; } MemFree(clipboard_filtered); } @@ -3469,15 +3480,16 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 if (g.ActiveId == id) { + IM_ASSERT(state != NULL); const char* apply_new_text = NULL; int apply_new_text_length = 0; if (cancel_edit) { // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. - if (is_editable && strcmp(buf, edit_state.InitialText.Data) != 0) + if (is_editable && strcmp(buf, state->InitialText.Data) != 0) { - apply_new_text = edit_state.InitialText.Data; - apply_new_text_length = edit_state.InitialText.Size - 1; + apply_new_text = state->InitialText.Data; + apply_new_text_length = state->InitialText.Size - 1; } } @@ -3492,8 +3504,8 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. if (is_editable) { - edit_state.TempBuffer.resize(edit_state.TextW.Size * 4 + 1); - ImTextStrToUtf8(edit_state.TempBuffer.Data, edit_state.TempBuffer.Size, edit_state.TextW.Data, NULL); + state->TempBuffer.resize(state->TextW.Size * 4 + 1); + ImTextStrToUtf8(state->TempBuffer.Data, state->TempBuffer.Size, state->TextW.Data, NULL); } // User callback @@ -3531,44 +3543,44 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 callback_data.UserData = callback_user_data; callback_data.EventKey = event_key; - callback_data.Buf = edit_state.TempBuffer.Data; - callback_data.BufTextLen = edit_state.CurLenA; - callback_data.BufSize = edit_state.BufCapacityA; + callback_data.Buf = state->TempBuffer.Data; + callback_data.BufTextLen = state->CurLenA; + callback_data.BufSize = state->BufCapacityA; callback_data.BufDirty = false; // We have to convert from wchar-positions to UTF-8-positions, which can be pretty slow (an incentive to ditch the ImWchar buffer, see https://github.com/nothings/stb/issues/188) - ImWchar* text = edit_state.TextW.Data; - const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.Stb.cursor); - const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.Stb.select_start); - const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.Stb.select_end); + ImWchar* text = state->TextW.Data; + const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + state->Stb.cursor); + const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_start); + const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + state->Stb.select_end); // Call user code callback(&callback_data); // Read back what user may have modified - IM_ASSERT(callback_data.Buf == edit_state.TempBuffer.Data); // Invalid to modify those fields - IM_ASSERT(callback_data.BufSize == edit_state.BufCapacityA); + IM_ASSERT(callback_data.Buf == state->TempBuffer.Data); // Invalid to modify those fields + IM_ASSERT(callback_data.BufSize == state->BufCapacityA); IM_ASSERT(callback_data.Flags == flags); - if (callback_data.CursorPos != utf8_cursor_pos) { edit_state.Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); edit_state.CursorFollow = true; } - if (callback_data.SelectionStart != utf8_selection_start) { edit_state.Stb.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } - if (callback_data.SelectionEnd != utf8_selection_end) { edit_state.Stb.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } + if (callback_data.CursorPos != utf8_cursor_pos) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; } + if (callback_data.SelectionStart != utf8_selection_start) { state->Stb.select_start = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionStart); } + if (callback_data.SelectionEnd != utf8_selection_end) { state->Stb.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); } if (callback_data.BufDirty) { IM_ASSERT(callback_data.BufTextLen == (int)strlen(callback_data.Buf)); // You need to maintain BufTextLen if you change the text! if (callback_data.BufTextLen > backup_current_text_length && is_resizable) - edit_state.TextW.resize(edit_state.TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); - edit_state.CurLenW = ImTextStrFromUtf8(edit_state.TextW.Data, edit_state.TextW.Size, callback_data.Buf, NULL); - edit_state.CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() - edit_state.CursorAnimReset(); + state->TextW.resize(state->TextW.Size + (callback_data.BufTextLen - backup_current_text_length)); + state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, callback_data.Buf, NULL); + state->CurLenA = callback_data.BufTextLen; // Assume correct length and valid UTF-8 from user, saves us an extra strlen() + state->CursorAnimReset(); } } } // Will copy result string if modified - if (is_editable && strcmp(edit_state.TempBuffer.Data, buf) != 0) + if (is_editable && strcmp(state->TempBuffer.Data, buf) != 0) { - apply_new_text = edit_state.TempBuffer.Data; - apply_new_text_length = edit_state.CurLenA; + apply_new_text = state->TempBuffer.Data; + apply_new_text_length = state->CurLenA; } } @@ -3598,9 +3610,9 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 } // Clear temporary user storage - edit_state.UserFlags = 0; - edit_state.UserCallback = NULL; - edit_state.UserCallbackData = NULL; + state->UserFlags = 0; + state->UserCallback = NULL; + state->UserCallbackData = NULL; } // Release active ID at the end of the function (so e.g. pressing Return still does a final application of the value) @@ -3613,7 +3625,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const int buf_display_max_length = 2 * 1024 * 1024; // Select which buffer we are going to display. We set buf to NULL to prevent accidental usage from now on. - const char* buf_display = (g.ActiveId == id && is_editable) ? edit_state.TempBuffer.Data : buf; + const char* buf_display = (state != NULL && is_editable) ? state->TempBuffer.Data : buf; buf = NULL; // Render @@ -3629,7 +3641,8 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 if (g.ActiveId == id || user_scroll_active) { // Animate cursor - edit_state.CursorAnim += io.DeltaTime; + IM_ASSERT(state != NULL); + state->CursorAnim += io.DeltaTime; // This is going to be messy. We need to: // - Display the text (this alone can be more easily clipped) @@ -3637,19 +3650,19 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // - Measure text height (for scrollbar) // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. - const ImWchar* text_begin = edit_state.TextW.Data; + const ImWchar* text_begin = state->TextW.Data; ImVec2 cursor_offset, select_start_offset; { // Count lines + find lines numbers straddling 'cursor' and 'select_start' position. const ImWchar* searches_input_ptr[2]; - searches_input_ptr[0] = text_begin + edit_state.Stb.cursor; + searches_input_ptr[0] = text_begin + state->Stb.cursor; searches_input_ptr[1] = NULL; int searches_remaining = 1; int searches_result_line_number[2] = { -1, -999 }; - if (edit_state.Stb.select_start != edit_state.Stb.select_end) + if (state->Stb.select_start != state->Stb.select_end) { - searches_input_ptr[1] = text_begin + ImMin(edit_state.Stb.select_start, edit_state.Stb.select_end); + searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); searches_result_line_number[1] = -1; searches_remaining++; } @@ -3685,20 +3698,20 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 } // Scroll - if (edit_state.CursorFollow) + if (state->CursorFollow) { // Horizontal scroll in chunks of quarter width if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) { const float scroll_increment_x = size.x * 0.25f; - if (cursor_offset.x < edit_state.ScrollX) - edit_state.ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x); - else if (cursor_offset.x - size.x >= edit_state.ScrollX) - edit_state.ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x); + if (cursor_offset.x < state->ScrollX) + state->ScrollX = (float)(int)ImMax(0.0f, cursor_offset.x - scroll_increment_x); + else if (cursor_offset.x - size.x >= state->ScrollX) + state->ScrollX = (float)(int)(cursor_offset.x - size.x + scroll_increment_x); } else { - edit_state.ScrollX = 0.0f; + state->ScrollX = 0.0f; } // Vertical scroll @@ -3714,14 +3727,14 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 render_pos.y = draw_window->DC.CursorPos.y; } } - edit_state.CursorFollow = false; - const ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f); + state->CursorFollow = false; + const ImVec2 render_scroll = ImVec2(state->ScrollX, 0.0f); // Draw selection - if (edit_state.Stb.select_start != edit_state.Stb.select_end) + if (state->Stb.select_start != state->Stb.select_end) { - const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.Stb.select_start, edit_state.Stb.select_end); - const ImWchar* text_selected_end = text_begin + ImMax(edit_state.Stb.select_start, edit_state.Stb.select_end); + const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); + const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end); float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. float bg_offy_dn = is_multiline ? 0.0f : 2.0f; @@ -3754,7 +3767,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 } // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash. - const int buf_display_len = edit_state.CurLenA; + const int buf_display_len = state->CurLenA; if (is_multiline || buf_display_len < buf_display_max_length) draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect); From cc3be5d4289587c7e1b0b29bfe37ab1c94e3aea8 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 21 Feb 2019 12:25:21 +0100 Subject: [PATCH 12/18] InputText: Fixed an edge case crash that would happen if another widget sharing the same ID is being swapped with an InputText that has yet to be activated. --- docs/CHANGELOG.txt | 2 ++ imgui_widgets.cpp | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 199882b9..3634bf2c 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -38,6 +38,8 @@ Other Changes: - InputInt, InputFloat, InputScalar: Fix to keep the label of the +/- buttons centered when style.FramePadding.x is abnormally larger than style.FramePadding.y. Since the buttons are meant to be square (to align with e.g. color button) we always use FramePadding.y. (#2367) +- InputText: Fixed an edge case crash that would happen if another widget sharing the same ID + is being swapped with an InputText that has yet to be activated. - Examples: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if the OpenGL headers/loader happens to define the value. (#2366, #2186) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 4598194f..6bbd417a 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3278,6 +3278,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 clear_active_id = true; } + // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped) + if (g.ActiveId == id && state == NULL) + ClearActiveID(); + bool value_changed = false; bool enter_pressed = false; int backup_current_text_length = 0; From 81a8730022266791b0ba5cee56cea7b4e6bde78a Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 21 Feb 2019 12:35:21 +0100 Subject: [PATCH 13/18] Internal: InputText: Renamed is_editable to !is_readonly, Hopefully more explicit. Renamed internal member. Shuffled some code. Added comments, assert (_will_ trigger on !readonly > readonly edge, old bug). --- imgui.cpp | 14 +++--- imgui_internal.h | 7 +-- imgui_widgets.cpp | 122 ++++++++++++++++++++++++---------------------- 3 files changed, 73 insertions(+), 70 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 19dfb983..ddcd10c2 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -58,9 +58,9 @@ CODE // [SECTION] FORWARD DECLARATIONS // [SECTION] CONTEXT AND MEMORY ALLOCATORS // [SECTION] MAIN USER FACING STRUCTURES (ImGuiStyle, ImGuiIO) -// [SECTION] MISC HELPER/UTILITIES (Maths, String, Format, Hash, File functions) -// [SECTION] MISC HELPER/UTILITIES (ImText* functions) -// [SECTION] MISC HELPER/UTILITIES (Color functions) +// [SECTION] MISC HELPERS/UTILITIES (Maths, String, Format, Hash, File functions) +// [SECTION] MISC HELPERS/UTILITIES (ImText* functions) +// [SECTION] MISC HELPERS/UTILITIES (Color functions) // [SECTION] ImGuiStorage // [SECTION] ImGuiTextFilter // [SECTION] ImGuiTextBuffer @@ -1226,7 +1226,7 @@ void ImGuiIO::ClearInputCharacters() } //----------------------------------------------------------------------------- -// [SECTION] MISC HELPER/UTILITIES (Maths, String, Format, Hash, File functions) +// [SECTION] MISC HELPERS/UTILITIES (Maths, String, Format, Hash, File functions) //----------------------------------------------------------------------------- ImVec2 ImLineClosestPoint(const ImVec2& a, const ImVec2& b, const ImVec2& p) @@ -1749,7 +1749,7 @@ int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_e } //----------------------------------------------------------------------------- -// [SECTION] MISC HELPER/UTILTIES (Color functions) +// [SECTION] MISC HELPERS/UTILTIES (Color functions) // Note: The Convert functions are early design which are not consistent with other API. //----------------------------------------------------------------------------- @@ -3603,9 +3603,7 @@ void ImGui::Shutdown(ImGuiContext* context) g.DrawDataBuilder.ClearFreeMemory(); g.OverlayDrawList.ClearFreeMemory(); g.PrivateClipboard.clear(); - g.InputTextState.TextW.clear(); - g.InputTextState.InitialText.clear(); - g.InputTextState.TempBuffer.clear(); + g.InputTextState.ClearFreeMemory(); for (int i = 0; i < g.SettingsWindows.Size; i++) IM_DELETE(g.SettingsWindows[i].Name); diff --git a/imgui_internal.h b/imgui_internal.h index ac36ba61..2f39967a 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -562,11 +562,11 @@ struct IMGUI_API ImGuiInputTextState { ImGuiID ID; // widget id owning the text state ImVector TextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. - ImVector InitialText; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) - ImVector TempBuffer; // temporary buffer for callback and other other operations. size=capacity. + ImVector InitialTextA; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) + ImVector TempBufferA; // temporary buffer for callbacks and other operations. size=capacity. int CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. int BufCapacityA; // end-user buffer capacity - float ScrollX; + float ScrollX; // horizontal scrolling/offset ImStb::STB_TexteditState Stb; // state for stb_textedit.h float CursorAnim; // timer for cursor blink, reset on every user action so the cursor reappears immediately bool CursorFollow; // set when we want scrolling to follow the current cursor position (not always!) @@ -578,6 +578,7 @@ struct IMGUI_API ImGuiInputTextState void* UserCallbackData; ImGuiInputTextState() { memset(this, 0, sizeof(*this)); } + void ClearFreeMemory() { TextW.clear(); InitialTextA.clear(); TempBufferA.clear(); } void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking void CursorClamp() { Stb.cursor = ImMin(Stb.cursor, CurLenW); Stb.select_start = ImMin(Stb.select_start, CurLenW); Stb.select_end = ImMin(Stb.select_end, CurLenW); } bool HasSelection() const { return Stb.select_start != Stb.select_end; } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 6bbd417a..7673491b 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -2637,7 +2637,7 @@ bool ImGui::InputScalarAsWidgetReplacement(const ImRect& bb, ImGuiID id, const c g.ScalarAsInputTextId = g.ActiveId; } if (value_changed) - return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialText.Data, data_type, data_ptr, NULL); + return DataTypeApplyOpFromText(data_buf, g.InputTextState.InitialTextA.Data, data_type, data_ptr, NULL); return false; } @@ -2670,7 +2670,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_p PushID(label); PushItemWidth(ImMax(1.0f, CalcItemWidth() - (button_size + style.ItemInnerSpacing.x) * 2)); if (InputText("", buf, IM_ARRAYSIZE(buf), flags)) // PushId(label) + "" gives us the expected ID from outside point of view - value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format); + value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, data_ptr, format); PopItemWidth(); // Step buttons @@ -2701,7 +2701,7 @@ bool ImGui::InputScalar(const char* label, ImGuiDataType data_type, void* data_p else { if (InputText(label, buf, IM_ARRAYSIZE(buf), flags)) - value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialText.Data, data_type, data_ptr, format); + value_changed = DataTypeApplyOpFromText(buf, g.InputTextState.InitialTextA.Data, data_type, data_ptr, format); } return value_changed; @@ -3045,10 +3045,10 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons ImGuiContext& g = *GImGui; ImGuiInputTextState* edit_state = &g.InputTextState; IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID); - IM_ASSERT(Buf == edit_state->TempBuffer.Data); + IM_ASSERT(Buf == edit_state->TempBufferA.Data); int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; - edit_state->TempBuffer.reserve(new_buf_size + 1); - Buf = edit_state->TempBuffer.Data; + edit_state->TempBufferA.reserve(new_buf_size + 1); + Buf = edit_state->TempBufferA.Data; BufSize = edit_state->BufCapacityA = new_buf_size; } @@ -3128,7 +3128,8 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f // Note that in std::string world, capacity() would omit 1 byte used by the zero-terminator. // - When active, hold on a privately held copy of the text (and apply back to 'buf'). So changing 'buf' while the InputText is active has no effect. // - If you want to use ImGui::InputText() with std::string, see misc/cpp/imgui_stdlib.h -// (FIXME: Rather messy function partly because we are doing UTF8 > u16 > UTF8 conversions on the go to more easily handle stb_textedit calls. Ideally we should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188) +// (FIXME: Rather confusing and messy function, among the worse part of our codebase, expecting to rewrite a V2 at some point.. Partly because we are +// doing UTF8 > U16 > UTF8 conversions on the go to easily interface with stb_textedit. Ideally should stay in UTF-8 all the time. See https://github.com/nothings/stb/issues/188) bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* callback_user_data) { ImGuiWindow* window = GetCurrentWindow(); @@ -3143,7 +3144,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const ImGuiStyle& style = g.Style; const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; - const bool is_editable = (flags & ImGuiInputTextFlags_ReadOnly) == 0; + const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0; const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; const bool is_undoable = (flags & ImGuiInputTextFlags_NoUndoRedo) == 0; const bool is_resizable = (flags & ImGuiInputTextFlags_CallbackResize) != 0; @@ -3228,17 +3229,18 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // Access state even if we don't own it yet. state = &g.InputTextState; - // Start edition // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) + const int buf_len = (int)strlen(buf); + state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. + memcpy(state->InitialTextA.Data, buf, buf_len + 1); + + // Start edition const int prev_len_w = state->CurLenW; - const int init_buf_len = (int)strlen(buf); - state->TextW.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data isn't NULL so it doesn't crash. - state->InitialText.resize(init_buf_len + 1); // UTF-8. we use +1 to make sure that .Data isn't NULL so it doesn't crash. - memcpy(state->InitialText.Data, buf, init_buf_len + 1); const char* buf_end = NULL; + state->TextW.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string. state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end); - state->CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. + state->CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. state->CursorAnimReset(); // Preserve cursor position and undo/redo stack if we come back to same widget @@ -3286,10 +3288,11 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 bool enter_pressed = false; int backup_current_text_length = 0; + // Process mouse inputs and character inputs if (g.ActiveId == id) { IM_ASSERT(state != NULL); - if (!is_editable && !g.ActiveIdIsJustActivated) + if (is_readonly && !g.ActiveIdIsJustActivated) { // When read-only we always use the live data passed to the function const char* buf_end = NULL; @@ -3348,7 +3351,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // Process text input (before we check for Return because using some IME will effectively send a Return?) // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters. bool ignore_inputs = (io.KeyCtrl && !io.KeyAlt) || (is_osx && io.KeySuper); - if (!ignore_inputs && is_editable && !user_nav_input_start) + if (!ignore_inputs && !is_readonly && !user_nav_input_start) for (int n = 0; n < io.InputQueueCharacters.Size; n++) { // Insert character if they pass filtering @@ -3362,10 +3365,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 } } + // Process other shortcuts/key-presses bool cancel_edit = false; if (g.ActiveId == id && !g.ActiveIdIsJustActivated && !clear_active_id) { - // Handle key-presses IM_ASSERT(state != NULL); const int k_mask = (io.KeyShift ? STB_TEXTEDIT_K_SHIFT : 0); const bool is_osx = io.ConfigMacOSXBehaviors; @@ -3376,11 +3379,11 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const bool is_ctrl_key_only = io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !io.KeySuper; const bool is_shift_key_only = io.KeyShift && !io.KeyCtrl && !io.KeyAlt && !io.KeySuper; - const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && is_editable && !is_password && (!is_multiline || state->HasSelection()); + const bool is_cut = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_X)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Delete))) && !is_readonly && !is_password && (!is_multiline || state->HasSelection()); const bool is_copy = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_C)) || (is_ctrl_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_password && (!is_multiline || state->HasSelection()); - const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && is_editable; - const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && is_editable && is_undoable); - const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && is_editable && is_undoable; + const bool is_paste = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_V)) || (is_shift_key_only && IsKeyPressedMap(ImGuiKey_Insert))) && !is_readonly; + const bool is_undo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Z)) && !is_readonly && is_undoable); + const bool is_redo = ((is_shortcut_key && IsKeyPressedMap(ImGuiKey_Y)) || (is_osx_shift_shortcut && IsKeyPressedMap(ImGuiKey_Z))) && !is_readonly && is_undoable; if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINESTART : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDLEFT : STB_TEXTEDIT_K_LEFT) | k_mask); } else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_LINEEND : is_wordmove_key_down ? STB_TEXTEDIT_K_WORDRIGHT : STB_TEXTEDIT_K_RIGHT) | k_mask); } @@ -3388,8 +3391,8 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 else if (IsKeyPressedMap(ImGuiKey_DownArrow) && is_multiline) { if (io.KeyCtrl) SetWindowScrollY(draw_window, ImMin(draw_window->Scroll.y + g.FontSize, GetScrollMaxY())); else state->OnKeyPressed((is_startend_key_down ? STB_TEXTEDIT_K_TEXTEND : STB_TEXTEDIT_K_DOWN) | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Home)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } else if (IsKeyPressedMap(ImGuiKey_End)) { state->OnKeyPressed(io.KeyCtrl ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Delete) && is_editable) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Backspace) && is_editable) + else if (IsKeyPressedMap(ImGuiKey_Delete) && !is_readonly) { state->OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Backspace) && !is_readonly) { if (!state->HasSelection()) { @@ -3407,14 +3410,14 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 { enter_pressed = clear_active_id = true; } - else if (is_editable) + else if (!is_readonly) { unsigned int c = '\n'; // Insert new line if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) state->OnKeyPressed((int)c); } } - else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && is_editable) + else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !io.KeyCtrl && !io.KeyShift && !io.KeyAlt && !is_readonly) { unsigned int c = '\t'; // Insert TAB if (InputTextFilterCharacter(&c, flags, callback, callback_user_data)) @@ -3441,9 +3444,9 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 { const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0; const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW; - state->TempBuffer.resize((ie-ib) * 4 + 1); - ImTextStrToUtf8(state->TempBuffer.Data, state->TempBuffer.Size, state->TextW.Data+ib, state->TextW.Data+ie); - SetClipboardText(state->TempBuffer.Data); + state->TempBufferA.resize((ie-ib) * 4 + 1); + ImTextStrToUtf8(state->TempBufferA.Data, state->TempBufferA.Size, state->TextW.Data+ib, state->TextW.Data+ie); + SetClipboardText(state->TempBufferA.Data); } if (is_cut) { @@ -3482,6 +3485,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 } } + // Process callbacks and apply result back to user's buffer. if (g.ActiveId == id) { IM_ASSERT(state != NULL); @@ -3490,10 +3494,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 if (cancel_edit) { // Restore initial value. Only return true if restoring to the initial value changes the current buffer contents. - if (is_editable && strcmp(buf, state->InitialText.Data) != 0) + if (!is_readonly && strcmp(buf, state->InitialTextA.Data) != 0) { - apply_new_text = state->InitialText.Data; - apply_new_text_length = state->InitialText.Size - 1; + apply_new_text = state->InitialTextA.Data; + apply_new_text_length = state->InitialTextA.Size - 1; } } @@ -3506,10 +3510,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // Note that as soon as the input box is active, the in-widget value gets priority over any underlying modification of the input buffer // FIXME: We actually always render 'buf' when calling DrawList->AddText, making the comment above incorrect. // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. - if (is_editable) + if (!is_readonly) { - state->TempBuffer.resize(state->TextW.Size * 4 + 1); - ImTextStrToUtf8(state->TempBuffer.Data, state->TempBuffer.Size, state->TextW.Data, NULL); + state->TempBufferA.resize(state->TextW.Size * 4 + 1); + ImTextStrToUtf8(state->TempBufferA.Data, state->TempBufferA.Size, state->TextW.Data, NULL); } // User callback @@ -3547,7 +3551,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 callback_data.UserData = callback_user_data; callback_data.EventKey = event_key; - callback_data.Buf = state->TempBuffer.Data; + callback_data.Buf = state->TempBufferA.Data; callback_data.BufTextLen = state->CurLenA; callback_data.BufSize = state->BufCapacityA; callback_data.BufDirty = false; @@ -3562,7 +3566,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 callback(&callback_data); // Read back what user may have modified - IM_ASSERT(callback_data.Buf == state->TempBuffer.Data); // Invalid to modify those fields + IM_ASSERT(callback_data.Buf == state->TempBufferA.Data); // Invalid to modify those fields IM_ASSERT(callback_data.BufSize == state->BufCapacityA); IM_ASSERT(callback_data.Flags == flags); if (callback_data.CursorPos != utf8_cursor_pos) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; } @@ -3581,9 +3585,9 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 } // Will copy result string if modified - if (is_editable && strcmp(state->TempBuffer.Data, buf) != 0) + if (!is_readonly && strcmp(state->TempBufferA.Data, buf) != 0) { - apply_new_text = state->TempBuffer.Data; + apply_new_text = state->TempBufferA.Data; apply_new_text_length = state->CurLenA; } } @@ -3629,7 +3633,8 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const int buf_display_max_length = 2 * 1024 * 1024; // Select which buffer we are going to display. We set buf to NULL to prevent accidental usage from now on. - const char* buf_display = (state != NULL && is_editable) ? state->TempBuffer.Data : buf; + const char* buf_display = (state != NULL && !is_readonly) ? state->TempBufferA.Data : buf; + IM_ASSERT(buf_display); buf = NULL; // Render @@ -3640,20 +3645,18 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 } const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size - ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; - ImVec2 text_size(0.f, 0.f); + ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; + ImVec2 text_size(0.0f, 0.0f); if (g.ActiveId == id || user_scroll_active) { - // Animate cursor - IM_ASSERT(state != NULL); - state->CursorAnim += io.DeltaTime; - + // Render text (with cursor and selection) // This is going to be messy. We need to: // - Display the text (this alone can be more easily clipped) // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation) // - Measure text height (for scrollbar) // We are attempting to do most of that in **one main pass** to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8. + IM_ASSERT(state != NULL); const ImWchar* text_begin = state->TextW.Data; ImVec2 cursor_offset, select_start_offset; @@ -3728,14 +3731,14 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 scroll_y = cursor_offset.y - size.y; draw_window->DC.CursorPos.y += (draw_window->Scroll.y - scroll_y); // Manipulate cursor pos immediately avoid a frame of lag draw_window->Scroll.y = scroll_y; - render_pos.y = draw_window->DC.CursorPos.y; + draw_pos.y = draw_window->DC.CursorPos.y; } } + const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f); state->CursorFollow = false; - const ImVec2 render_scroll = ImVec2(state->ScrollX, 0.0f); // Draw selection - if (state->Stb.select_start != state->Stb.select_end) + if (state->HasSelection()) { const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end); @@ -3743,7 +3746,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. float bg_offy_dn = is_multiline ? 0.0f : 2.0f; ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg); - ImVec2 rect_pos = render_pos + select_start_offset - render_scroll; + ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll; for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) { if (rect_pos.y > clip_rect.w + g.FontSize) @@ -3765,7 +3768,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 if (rect.Overlaps(clip_rect)) draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); } - rect_pos.x = render_pos.x - render_scroll.x; + rect_pos.x = draw_pos.x - draw_scroll.x; rect_pos.y += g.FontSize; } } @@ -3773,29 +3776,30 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // We test for 'buf_display_max_length' as a way to avoid some pathological cases (e.g. single-line 1 MB string) which would make ImDrawList crash. const int buf_display_len = state->CurLenA; if (is_multiline || buf_display_len < buf_display_max_length) - draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect); + draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect); // Draw blinking cursor - bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (g.InputTextState.CursorAnim <= 0.0f) || ImFmod(g.InputTextState.CursorAnim, 1.20f) <= 0.80f; - ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll; - ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y-g.FontSize+0.5f, cursor_screen_pos.x+1.0f, cursor_screen_pos.y-1.5f); + state->CursorAnim += io.DeltaTime; + bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; + ImVec2 cursor_screen_pos = draw_pos + cursor_offset - draw_scroll; + ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) - if (is_editable) - g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1, cursor_screen_pos.y - g.FontSize); + if (!is_readonly) + g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize); } else { - // Render text only + // Render text only (no selection, no cursor) const char* buf_end = NULL; if (is_multiline) text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width else buf_end = buf_display + strlen(buf_display); if (is_multiline || (buf_end - buf_display) < buf_display_max_length) - draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect); + draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect); } if (is_multiline) @@ -3810,7 +3814,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // Log as text if (g.LogEnabled && !is_password) - LogRenderedText(&render_pos, buf_display, NULL); + LogRenderedText(&draw_pos, buf_display, NULL); if (label_size.x > 0) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); From 332f8f2462e8a3d0ab1390ecdfcaea9dd7c02838 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 21 Feb 2019 17:33:57 +0100 Subject: [PATCH 14/18] Internal: InputText: Made clipboard copy/cut use its own temporary buffer (like paste) so we can guarantee that TempBuffer if not altered and can be preserved. Renamed TempBufferA to TextA to celebrate this. --- imgui_internal.h | 6 +++--- imgui_widgets.cpp | 28 +++++++++++++++------------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index 2f39967a..5a80fc6e 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -561,10 +561,10 @@ struct IMGUI_API ImGuiMenuColumns struct IMGUI_API ImGuiInputTextState { ImGuiID ID; // widget id owning the text state + int CurLenW, CurLenA; // we need to maintain our buffer length in both UTF-8 and wchar format. ImVector TextW; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. + ImVector TextA; // temporary UTF8 buffer for callbacks and other operations. this is not updated in every code-path! size=capacity. ImVector InitialTextA; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) - ImVector TempBufferA; // temporary buffer for callbacks and other operations. size=capacity. - int CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. int BufCapacityA; // end-user buffer capacity float ScrollX; // horizontal scrolling/offset ImStb::STB_TexteditState Stb; // state for stb_textedit.h @@ -578,7 +578,7 @@ struct IMGUI_API ImGuiInputTextState void* UserCallbackData; ImGuiInputTextState() { memset(this, 0, sizeof(*this)); } - void ClearFreeMemory() { TextW.clear(); InitialTextA.clear(); TempBufferA.clear(); } + void ClearFreeMemory() { TextW.clear(); TextA.clear(); InitialTextA.clear(); } void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking void CursorClamp() { Stb.cursor = ImMin(Stb.cursor, CurLenW); Stb.select_start = ImMin(Stb.select_start, CurLenW); Stb.select_end = ImMin(Stb.select_end, CurLenW); } bool HasSelection() const { return Stb.select_start != Stb.select_end; } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 7673491b..87ce703d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3045,10 +3045,10 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons ImGuiContext& g = *GImGui; ImGuiInputTextState* edit_state = &g.InputTextState; IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID); - IM_ASSERT(Buf == edit_state->TempBufferA.Data); + IM_ASSERT(Buf == edit_state->TextA.Data); int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; - edit_state->TempBufferA.reserve(new_buf_size + 1); - Buf = edit_state->TempBufferA.Data; + edit_state->TextA.reserve(new_buf_size + 1); + Buf = edit_state->TextA.Data; BufSize = edit_state->BufCapacityA = new_buf_size; } @@ -3444,9 +3444,11 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 { const int ib = state->HasSelection() ? ImMin(state->Stb.select_start, state->Stb.select_end) : 0; const int ie = state->HasSelection() ? ImMax(state->Stb.select_start, state->Stb.select_end) : state->CurLenW; - state->TempBufferA.resize((ie-ib) * 4 + 1); - ImTextStrToUtf8(state->TempBufferA.Data, state->TempBufferA.Size, state->TextW.Data+ib, state->TextW.Data+ie); - SetClipboardText(state->TempBufferA.Data); + const int clipboard_data_len = ImTextCountUtf8BytesFromStr(state->TextW.Data + ib, state->TextW.Data + ie) + 1; + char* clipboard_data = (char*)MemAlloc(clipboard_data_len * sizeof(char)); + ImTextStrToUtf8(clipboard_data, clipboard_data_len, state->TextW.Data + ib, state->TextW.Data + ie); + SetClipboardText(clipboard_data); + MemFree(clipboard_data); } if (is_cut) { @@ -3512,8 +3514,8 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks. if (!is_readonly) { - state->TempBufferA.resize(state->TextW.Size * 4 + 1); - ImTextStrToUtf8(state->TempBufferA.Data, state->TempBufferA.Size, state->TextW.Data, NULL); + state->TextA.resize(state->TextW.Size * 4 + 1); + ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL); } // User callback @@ -3551,7 +3553,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 callback_data.UserData = callback_user_data; callback_data.EventKey = event_key; - callback_data.Buf = state->TempBufferA.Data; + callback_data.Buf = state->TextA.Data; callback_data.BufTextLen = state->CurLenA; callback_data.BufSize = state->BufCapacityA; callback_data.BufDirty = false; @@ -3566,7 +3568,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 callback(&callback_data); // Read back what user may have modified - IM_ASSERT(callback_data.Buf == state->TempBufferA.Data); // Invalid to modify those fields + IM_ASSERT(callback_data.Buf == state->TextA.Data); // Invalid to modify those fields IM_ASSERT(callback_data.BufSize == state->BufCapacityA); IM_ASSERT(callback_data.Flags == flags); if (callback_data.CursorPos != utf8_cursor_pos) { state->Stb.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); state->CursorFollow = true; } @@ -3585,9 +3587,9 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 } // Will copy result string if modified - if (!is_readonly && strcmp(state->TempBufferA.Data, buf) != 0) + if (!is_readonly && strcmp(state->TextA.Data, buf) != 0) { - apply_new_text = state->TempBufferA.Data; + apply_new_text = state->TextA.Data; apply_new_text_length = state->CurLenA; } } @@ -3633,7 +3635,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const int buf_display_max_length = 2 * 1024 * 1024; // Select which buffer we are going to display. We set buf to NULL to prevent accidental usage from now on. - const char* buf_display = (state != NULL && !is_readonly) ? state->TempBufferA.Data : buf; + const char* buf_display = (state != NULL && !is_readonly) ? state->TextA.Data : buf; IM_ASSERT(buf_display); buf = NULL; From be593f2c163de500aad6a2b3683402c27e7f4259 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 21 Feb 2019 19:45:28 +0100 Subject: [PATCH 15/18] Internal: InputText: refactor the flow to easily decorrelate rendering of selection vs cursor, which would allow us to render selection on inactive items, and generally makes the code clearer. + Some renaming. --- imgui_widgets.cpp | 78 ++++++++++++++++++++++++++++------------------- 1 file changed, 47 insertions(+), 31 deletions(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 87ce703d..a6c7e34e 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3143,6 +3143,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 ImGuiIO& io = g.IO; const ImGuiStyle& style = g.Style; + const bool RENDER_SELECTION_WHEN_INACTIVE = true; const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0; const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0; @@ -3220,8 +3221,8 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const bool user_scroll_active = is_multiline && state != NULL && g.ActiveId == GetScrollbarID(draw_window, ImGuiAxis_Y); bool clear_active_id = false; - bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline); + if (focus_requested || user_clicked || user_scroll_finish || user_nav_input_start) { if (g.ActiveId != id) @@ -3649,7 +3650,12 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x, frame_bb.Min.y + size.y); // Not using frame_bb.Max because we have adjusted size ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; ImVec2 text_size(0.0f, 0.0f); - if (g.ActiveId == id || user_scroll_active) + + // We currently only render selection when the widget is active or while scrolling. + // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive. + const bool render_cursor = (g.ActiveId == id) || user_scroll_active; + const bool render_selection = state && state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor); + if (render_cursor || render_selection) { // Render text (with cursor and selection) // This is going to be messy. We need to: @@ -3663,16 +3669,20 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 ImVec2 cursor_offset, select_start_offset; { - // Count lines + find lines numbers straddling 'cursor' and 'select_start' position. - const ImWchar* searches_input_ptr[2]; - searches_input_ptr[0] = text_begin + state->Stb.cursor; - searches_input_ptr[1] = NULL; - int searches_remaining = 1; - int searches_result_line_number[2] = { -1, -999 }; - if (state->Stb.select_start != state->Stb.select_end) + // Find lines numbers straddling 'cursor' (slot 0) and 'select_start' (slot 1) positions. + const ImWchar* searches_input_ptr[2] = { NULL, NULL }; + int searches_result_line_no[2] = { -1000, -1000 }; + int searches_remaining = 0; + if (render_cursor) + { + searches_input_ptr[0] = text_begin + state->Stb.cursor; + searches_result_line_no[0] = -1; + searches_remaining++; + } + if (render_selection) { searches_input_ptr[1] = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); - searches_result_line_number[1] = -1; + searches_result_line_no[1] = -1; searches_remaining++; } @@ -3685,20 +3695,22 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 if (*s == '\n') { line_count++; - if (searches_result_line_number[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_number[0] = line_count; if (--searches_remaining <= 0) break; } - if (searches_result_line_number[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_number[1] = line_count; if (--searches_remaining <= 0) break; } + if (searches_result_line_no[0] == -1 && s >= searches_input_ptr[0]) { searches_result_line_no[0] = line_count; if (--searches_remaining <= 0) break; } + if (searches_result_line_no[1] == -1 && s >= searches_input_ptr[1]) { searches_result_line_no[1] = line_count; if (--searches_remaining <= 0) break; } } line_count++; - if (searches_result_line_number[0] == -1) searches_result_line_number[0] = line_count; - if (searches_result_line_number[1] == -1) searches_result_line_number[1] = line_count; + if (searches_result_line_no[0] == -1) + searches_result_line_no[0] = line_count; + if (searches_result_line_no[1] == -1) + searches_result_line_no[1] = line_count; // Calculate 2d position by finding the beginning of the line and measuring distance cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[0], text_begin), searches_input_ptr[0]).x; - cursor_offset.y = searches_result_line_number[0] * g.FontSize; - if (searches_result_line_number[1] >= 0) + cursor_offset.y = searches_result_line_no[0] * g.FontSize; + if (searches_result_line_no[1] >= 0) { select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(searches_input_ptr[1], text_begin), searches_input_ptr[1]).x; - select_start_offset.y = searches_result_line_number[1] * g.FontSize; + select_start_offset.y = searches_result_line_no[1] * g.FontSize; } // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224) @@ -3707,7 +3719,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 } // Scroll - if (state->CursorFollow) + if (render_cursor && state->CursorFollow) { // Horizontal scroll in chunks of quarter width if (!(flags & ImGuiInputTextFlags_NoHorizontalScroll)) @@ -3735,19 +3747,20 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 draw_window->Scroll.y = scroll_y; draw_pos.y = draw_window->DC.CursorPos.y; } + + state->CursorFollow = false; } - const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f); - state->CursorFollow = false; // Draw selection - if (state->HasSelection()) + const ImVec2 draw_scroll = ImVec2(state->ScrollX, 0.0f); + if (render_selection) { const ImWchar* text_selected_begin = text_begin + ImMin(state->Stb.select_start, state->Stb.select_end); const ImWchar* text_selected_end = text_begin + ImMax(state->Stb.select_start, state->Stb.select_end); + ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg, render_cursor ? 1.0f : 0.6f); // FIXME: current code flow mandate that render_cursor is always true here, we are leaving the transparent one for tests. float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. float bg_offy_dn = is_multiline ? 0.0f : 2.0f; - ImU32 bg_color = GetColorU32(ImGuiCol_TextSelectedBg); ImVec2 rect_pos = draw_pos + select_start_offset - draw_scroll; for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) { @@ -3781,16 +3794,19 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect); // Draw blinking cursor - state->CursorAnim += io.DeltaTime; - bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; - ImVec2 cursor_screen_pos = draw_pos + cursor_offset - draw_scroll; - ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); - if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) - draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); + if (render_cursor) + { + state->CursorAnim += io.DeltaTime; + bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; + ImVec2 cursor_screen_pos = draw_pos + cursor_offset - draw_scroll; + ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); + if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) + draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); - // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) - if (!is_readonly) - g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize); + // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) + if (!is_readonly) + g.PlatformImePos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize); + } } else { From f988618ebe9cfb216268d659e6b250fc377647c6 Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 21 Feb 2019 23:00:47 +0100 Subject: [PATCH 16/18] Internal: InputText: Tweaks (including a large indentation change, compare ignoring space) to make next commit more digestible. --- imgui_widgets.cpp | 110 +++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 55 deletions(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index a6c7e34e..cd059cc3 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -3223,50 +3223,51 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 bool clear_active_id = false; bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline); - if (focus_requested || user_clicked || user_scroll_finish || user_nav_input_start) + const bool init_make_active = (focus_requested || user_clicked || user_scroll_finish || user_nav_input_start); + if (init_make_active && g.ActiveId != id) { - if (g.ActiveId != id) + // Access state even if we don't own it yet. + state = &g.InputTextState; + state->CursorAnimReset(); + + // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) + // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) + const int buf_len = (int)strlen(buf); + state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. + memcpy(state->InitialTextA.Data, buf, buf_len + 1); + + // Start edition + const int prev_len_w = state->CurLenW; + const char* buf_end = NULL; + state->TextW.resize(buf_size + 1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string. + state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end); + state->CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. + + // Preserve cursor position and undo/redo stack if we come back to same widget + // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar). + const bool recycle_state = (state->ID == id) && (prev_len_w == state->CurLenW); + if (recycle_state) { - // Access state even if we don't own it yet. - state = &g.InputTextState; - - // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) - // From the moment we focused we are ignoring the content of 'buf' (unless we are in read-only mode) - const int buf_len = (int)strlen(buf); - state->InitialTextA.resize(buf_len + 1); // UTF-8. we use +1 to make sure that .Data is always pointing to at least an empty string. - memcpy(state->InitialTextA.Data, buf, buf_len + 1); - - // Start edition - const int prev_len_w = state->CurLenW; - const char* buf_end = NULL; - state->TextW.resize(buf_size+1); // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string. - state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end); - state->CurLenA = (int)(buf_end - buf); // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. - state->CursorAnimReset(); - - // Preserve cursor position and undo/redo stack if we come back to same widget - // FIXME: We should probably compare the whole buffer to be on the safety side. Comparing buf (utf8) and edit_state.Text (wchar). - const bool recycle_state = (state->ID == id) && (prev_len_w == state->CurLenW); - if (recycle_state) - { - // Recycle existing cursor/selection/undo stack but clamp position - // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. - state->CursorClamp(); - } - else - { - state->ID = id; - state->ScrollX = 0.0f; - stb_textedit_initialize_state(&state->Stb, !is_multiline); - if (!is_multiline && focus_requested_by_code) - select_all = true; - } - if (flags & ImGuiInputTextFlags_AlwaysInsertMode) - state->Stb.insert_mode = 1; - if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl))) + // Recycle existing cursor/selection/undo stack but clamp position + // Note a single mouse click will override the cursor/position immediately by calling stb_textedit_click handler. + state->CursorClamp(); + } + else + { + state->ID = id; + state->ScrollX = 0.0f; + stb_textedit_initialize_state(&state->Stb, !is_multiline); + if (!is_multiline && focus_requested_by_code) select_all = true; } + if (flags & ImGuiInputTextFlags_AlwaysInsertMode) + state->Stb.insert_mode = 1; + if (!is_multiline && (focus_requested_by_tab || (user_clicked && io.KeyCtrl))) + select_all = true; + } + if (init_make_active) + { IM_ASSERT(state && state->ID == id); SetActiveID(id, window); SetFocusID(id, window); @@ -3275,11 +3276,10 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 if (!is_multiline && !(flags & ImGuiInputTextFlags_CallbackHistory)) g.ActiveIdAllowNavDirFlags = ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down)); } - else if (io.MouseClicked[0]) - { - // Release focus when we click outside + + // Release focus when we click outside + if (!init_make_active && io.MouseClicked[0]) clear_active_id = true; - } // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped) if (g.ActiveId == id && state == NULL) @@ -3630,17 +3630,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 if (clear_active_id && g.ActiveId == id) ClearActiveID(); - // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line - // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether. - // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash. - const int buf_display_max_length = 2 * 1024 * 1024; - - // Select which buffer we are going to display. We set buf to NULL to prevent accidental usage from now on. - const char* buf_display = (state != NULL && !is_readonly) ? state->TextA.Data : buf; - IM_ASSERT(buf_display); - buf = NULL; - - // Render + // Render frame if (!is_multiline) { RenderNavHighlight(frame_bb, id); @@ -3651,7 +3641,17 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2 ImVec2 draw_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; ImVec2 text_size(0.0f, 0.0f); - // We currently only render selection when the widget is active or while scrolling. + // Set upper limit of single-line InputTextEx() at 2 million characters strings. The current pathological worst case is a long line + // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether. + // Note that we only use this limit on single-line InputText(), so a pathologically large line on a InputTextMultiline() would still crash. + const int buf_display_max_length = 2 * 1024 * 1024; + + // Select which buffer we are going to display. We set buf to NULL to prevent accidental usage from now on. + const char* buf_display = (state != NULL && !is_readonly) ? state->TextA.Data : buf; + IM_ASSERT(buf_display); + buf = NULL; + + // Render text. We currently only render selection when the widget is active or while scrolling. // FIXME: We could remove the '&& render_cursor' to keep rendering selection when inactive. const bool render_cursor = (g.ActiveId == id) || user_scroll_active; const bool render_selection = state && state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor); From 0f83145aa870a13a416a04a715a1e98bf4f8bbfb Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 22 Feb 2019 12:24:27 +0100 Subject: [PATCH 17/18] TabBar: Fixed a crash when using BeginTabBar() recursively (didn't affect docking). (#2371) Added ImPool::Contains() helper. --- docs/CHANGELOG.txt | 1 + imgui_internal.h | 23 +++++++++++++++++------ imgui_widgets.cpp | 41 ++++++++++++++++++++++++++++++----------- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 3634bf2c..31305f18 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -40,6 +40,7 @@ Other Changes: meant to be square (to align with e.g. color button) we always use FramePadding.y. (#2367) - InputText: Fixed an edge case crash that would happen if another widget sharing the same ID is being swapped with an InputText that has yet to be activated. +- TabBar: Fixed a crash when using BeginTabBar() recursively (didn't affect docking). (#2371) - Examples: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if the OpenGL headers/loader happens to define the value. (#2366, #2186) diff --git a/imgui_internal.h b/imgui_internal.h index 5a80fc6e..cc355e95 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -275,7 +275,8 @@ struct IMGUI_API ImPool T* GetByIndex(ImPoolIdx n) { return &Data[n]; } ImPoolIdx GetIndex(const T* p) const { IM_ASSERT(p >= Data.Data && p < Data.Data + Data.Size); return (ImPoolIdx)(p - Data.Data); } T* GetOrAddByKey(ImGuiID key) { int* p_idx = Map.GetIntRef(key, -1); if (*p_idx != -1) return &Data[*p_idx]; *p_idx = FreeIdx; return Add(); } - void Clear() { for (int n = 0; n < Map.Data.Size; n++) { int idx = Map.Data[n].val_i; if (idx != -1) Data[idx].~T(); } Map.Clear(); Data.clear(); FreeIdx = 0; } + bool Contains(const T* p) const { return (p >= Data.Data && p < Data.Data + Data.Size); } + void Clear() { for (int n = 0; n < Map.Data.Size; n++) { int idx = Map.Data[n].val_i; if (idx != -1) Data[idx].~T(); } Map.Clear(); Data.clear(); FreeIdx = 0; } T* Add() { int idx = FreeIdx; if (idx == Data.Size) { Data.resize(Data.Size + 1); FreeIdx++; } else { FreeIdx = *(int*)&Data[idx]; } IM_PLACEMENT_NEW(&Data[idx]) T(); return &Data[idx]; } void Remove(ImGuiID key, const T* p) { Remove(key, GetIndex(p)); } void Remove(ImGuiID key, ImPoolIdx idx) { Data[idx].~T(); *(int*)&Data[idx] = FreeIdx; FreeIdx = idx; Map.SetInt(key, -1); } @@ -749,8 +750,17 @@ struct ImGuiNextWindowData struct ImGuiTabBarSortItem { - int Index; - float Width; + int Index; + float Width; +}; + +struct ImGuiTabBarRef +{ + ImGuiTabBar* Ptr; // Either field can be set, not both. Dock node tab bars are loose while BeginTabBar() ones are in a pool. + int IndexInMainPool; + + ImGuiTabBarRef(ImGuiTabBar* ptr) { Ptr = ptr; IndexInMainPool = -1; } + ImGuiTabBarRef(int index_in_main_pool) { Ptr = NULL; IndexInMainPool = index_in_main_pool; } }; //----------------------------------------------------------------------------- @@ -883,8 +893,9 @@ struct ImGuiContext unsigned char DragDropPayloadBufLocal[8]; // Local buffer for small payloads // Tab bars - ImPool TabBars; - ImVector CurrentTabBar; + ImPool TabBars; + ImGuiTabBar* CurrentTabBar; + ImVector CurrentTabBarStack; ImVector TabSortByWidthBuffer; // Widget state @@ -1246,7 +1257,7 @@ struct ImGuiItemHoveredDataBackup enum ImGuiTabBarFlagsPrivate_ { - ImGuiTabBarFlags_DockNode = 1 << 20, // Part of a dock node + ImGuiTabBarFlags_DockNode = 1 << 20, // Part of a dock node [we don't use this in the master branch but it facilitate branch syncing to keep this around] ImGuiTabBarFlags_IsFocused = 1 << 21, ImGuiTabBarFlags_SaveSettings = 1 << 22 // FIXME: Settings are handled by the docking system, this only request the tab bar to mark settings dirty when reordering tabs }; diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index cd059cc3..aaec4cb3 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5905,6 +5905,20 @@ static int IMGUI_CDECL TabBarSortItemComparer(const void* lhs, const void* rhs) return (b->Index - a->Index); } +static ImGuiTabBar* GetTabBarFromTabBarRef(const ImGuiTabBarRef& ref) +{ + ImGuiContext& g = *GImGui; + return ref.Ptr ? ref.Ptr : g.TabBars.GetByIndex(ref.IndexInMainPool); +} + +static ImGuiTabBarRef GetTabBarRefFromTabBar(ImGuiTabBar* tab_bar) +{ + ImGuiContext& g = *GImGui; + if (g.TabBars.Contains(tab_bar)) + return ImGuiTabBarRef(g.TabBars.GetIndex(tab_bar)); + return ImGuiTabBarRef(tab_bar); +} + bool ImGui::BeginTabBar(const char* str_id, ImGuiTabBarFlags flags) { ImGuiContext& g = *GImGui; @@ -5929,7 +5943,10 @@ bool ImGui::BeginTabBarEx(ImGuiTabBar* tab_bar, const ImRect& tab_bar_bb, ImG if ((flags & ImGuiTabBarFlags_DockNode) == 0) window->IDStack.push_back(tab_bar->ID); - g.CurrentTabBar.push_back(tab_bar); + // Add to stack + g.CurrentTabBarStack.push_back(GetTabBarRefFromTabBar(tab_bar)); + g.CurrentTabBar = tab_bar; + if (tab_bar->CurrFrameVisible == g.FrameCount) { //IMGUI_DEBUG_LOG("BeginTabBarEx already called this frame\n", g.FrameCount); @@ -5975,8 +5992,8 @@ void ImGui::EndTabBar() if (window->SkipItems) return; - IM_ASSERT(!g.CurrentTabBar.empty()); // Mismatched BeginTabBar/EndTabBar - ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); + ImGuiTabBar* tab_bar = g.CurrentTabBar; + IM_ASSERT(tab_bar != NULL && "Mismatched BeginTabBar()/EndTabBar()!"); if (tab_bar->WantLayout) TabBarLayout(tab_bar); @@ -5989,7 +6006,9 @@ void ImGui::EndTabBar() if ((tab_bar->Flags & ImGuiTabBarFlags_DockNode) == 0) PopID(); - g.CurrentTabBar.pop_back(); + + g.CurrentTabBarStack.pop_back(); + g.CurrentTabBar = g.CurrentTabBarStack.empty() ? NULL : GetTabBarFromTabBarRef(g.CurrentTabBarStack.back()); } // This is called only once a frame before by the first call to ItemTab() @@ -6356,8 +6375,8 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f if (g.CurrentWindow->SkipItems) return false; - IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!"); - ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); + ImGuiTabBar* tab_bar = g.CurrentTabBar; + IM_ASSERT(tab_bar && "Needs to be called between BeginTabBar() and EndTabBar()!"); bool ret = TabItemEx(tab_bar, label, p_open, flags); if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) { @@ -6373,9 +6392,9 @@ void ImGui::EndTabItem() if (g.CurrentWindow->SkipItems) return; - IM_ASSERT(g.CurrentTabBar.Size > 0 && "Needs to be called between BeginTabBar() and EndTabBar()!"); - ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); - IM_ASSERT(tab_bar->LastTabItemIdx >= 0 && "Needs to be called between BeginTabItem() and EndTabItem()"); + ImGuiTabBar* tab_bar = g.CurrentTabBar; + IM_ASSERT(tab_bar != NULL && "Needs to be called between BeginTabBar() and EndTabBar()!"); + IM_ASSERT(tab_bar->LastTabItemIdx >= 0); ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; if (!(tab->Flags & ImGuiTabItemFlags_NoPushId)) g.CurrentWindow->IDStack.pop_back(); @@ -6575,10 +6594,10 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, void ImGui::SetTabItemClosed(const char* label) { ImGuiContext& g = *GImGui; - bool is_within_manual_tab_bar = (g.CurrentTabBar.Size > 0) && !(g.CurrentTabBar.back()->Flags & ImGuiTabBarFlags_DockNode); + bool is_within_manual_tab_bar = g.CurrentTabBar && !(g.CurrentTabBar->Flags & ImGuiTabBarFlags_DockNode); if (is_within_manual_tab_bar) { - ImGuiTabBar* tab_bar = g.CurrentTabBar.back(); + ImGuiTabBar* tab_bar = g.CurrentTabBar; IM_ASSERT(tab_bar->WantLayout); // Needs to be called AFTER BeginTabBar() and BEFORE the first call to BeginTabItem() ImGuiID tab_id = TabBarCalcTabID(tab_bar, label); TabBarRemoveTab(tab_bar, tab_id); From 9da48c16c5eeb8ea98e89951c83fab0f9d2b037b Mon Sep 17 00:00:00 2001 From: omar Date: Fri, 22 Feb 2019 12:27:41 +0100 Subject: [PATCH 18/18] TabBar: Added extra mis-usage error recovery. Past the assert, common mis-usage don't lead to hard crashes any more, facilitating integration with scripting languages. (#1651) --- docs/CHANGELOG.txt | 2 ++ imgui_widgets.cpp | 18 +++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 31305f18..73a59691 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -41,6 +41,8 @@ Other Changes: - InputText: Fixed an edge case crash that would happen if another widget sharing the same ID is being swapped with an InputText that has yet to be activated. - TabBar: Fixed a crash when using BeginTabBar() recursively (didn't affect docking). (#2371) +- TabBar: Added extra mis-usage error recovery. Past the assert, common mis-usage don't lead to + hard crashes any more, facilitating integration with scripting languages. (#1651) - Examples: OpenGL: Fix for OSX not supporting OpenGL 4.5, we don't try to read GL_CLIP_ORIGIN even if the OpenGL headers/loader happens to define the value. (#2366, #2186) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index aaec4cb3..f49fa46d 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -5993,7 +5993,11 @@ void ImGui::EndTabBar() return; ImGuiTabBar* tab_bar = g.CurrentTabBar; - IM_ASSERT(tab_bar != NULL && "Mismatched BeginTabBar()/EndTabBar()!"); + if (tab_bar == NULL) + { + IM_ASSERT(tab_bar != NULL && "Mismatched BeginTabBar()/EndTabBar()!"); + return; // FIXME-ERRORHANDLING + } if (tab_bar->WantLayout) TabBarLayout(tab_bar); @@ -6376,7 +6380,11 @@ bool ImGui::BeginTabItem(const char* label, bool* p_open, ImGuiTabItemFlags f return false; ImGuiTabBar* tab_bar = g.CurrentTabBar; - IM_ASSERT(tab_bar && "Needs to be called between BeginTabBar() and EndTabBar()!"); + if (tab_bar == NULL) + { + IM_ASSERT(tab_bar && "Needs to be called between BeginTabBar() and EndTabBar()!"); + return false; // FIXME-ERRORHANDLING + } bool ret = TabItemEx(tab_bar, label, p_open, flags); if (ret && !(flags & ImGuiTabItemFlags_NoPushId)) { @@ -6393,7 +6401,11 @@ void ImGui::EndTabItem() return; ImGuiTabBar* tab_bar = g.CurrentTabBar; - IM_ASSERT(tab_bar != NULL && "Needs to be called between BeginTabBar() and EndTabBar()!"); + if (tab_bar == NULL) + { + IM_ASSERT(tab_bar != NULL && "Needs to be called between BeginTabBar() and EndTabBar()!"); + return; + } IM_ASSERT(tab_bar->LastTabItemIdx >= 0); ImGuiTabItem* tab = &tab_bar->Tabs[tab_bar->LastTabItemIdx]; if (!(tab->Flags & ImGuiTabItemFlags_NoPushId))