Forráskód Böngészése

Merge branch 'master' into docking

omar 6 éve
szülő
commit
6b43a314bf
3 módosított fájl, 50 hozzáadás és 33 törlés
  1. 3 0
      docs/CHANGELOG.txt
  2. 6 1
      imgui_internal.h
  3. 41 32
      imgui_widgets.cpp

+ 3 - 0
docs/CHANGELOG.txt

@@ -105,6 +105,9 @@ Other Changes:
   meant to be square (to align with e.g. color button) we always use FramePadding.y. (#2367)
   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
 - 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.
   is being swapped with an InputText that has yet to be activated.
+- InputText: Fixed various display corruption related to swapping the underlying buffer while
+  a input widget is active (both for writable and read-only paths). Often they would manifest
+  when manipulating the scrollbar of a multi-line input text.
 - TabBar: Fixed a crash when using BeginTabBar() recursively (didn't affect docking). (#2371)
 - 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
 - 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)
   hard crashes any more, facilitating integration with scripting languages. (#1651)

+ 6 - 1
imgui_internal.h

@@ -108,6 +108,8 @@ namespace ImStb
 #define STB_TEXTEDIT_STRING             ImGuiInputTextState
 #define STB_TEXTEDIT_STRING             ImGuiInputTextState
 #define STB_TEXTEDIT_CHARTYPE           ImWchar
 #define STB_TEXTEDIT_CHARTYPE           ImWchar
 #define STB_TEXTEDIT_GETWIDTH_NEWLINE   -1.0f
 #define STB_TEXTEDIT_GETWIDTH_NEWLINE   -1.0f
+#define STB_TEXTEDIT_UNDOSTATECOUNT     99
+#define STB_TEXTEDIT_UNDOCHARCOUNT      999
 #include "imstb_textedit.h"
 #include "imstb_textedit.h"
 
 
 } // namespace ImStb
 } // namespace ImStb
@@ -585,10 +587,11 @@ struct IMGUI_API ImGuiMenuColumns
 struct IMGUI_API ImGuiInputTextState
 struct IMGUI_API ImGuiInputTextState
 {
 {
     ImGuiID                 ID;                     // widget id owning the text state
     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.
+    int                     CurLenW, CurLenA;       // we need to maintain our buffer length in both UTF-8 and wchar format. UTF-8 len is valid even if TextA is not.
     ImVector<ImWchar>       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<ImWchar>       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<char>          TextA;                  // temporary UTF8 buffer for callbacks and other operations. this is not updated in every code-path! size=capacity.
     ImVector<char>          TextA;                  // temporary UTF8 buffer for callbacks and other operations. this is not updated in every code-path! size=capacity.
     ImVector<char>          InitialTextA;           // backup of end-user buffer at the time of focus (in UTF-8, unaltered)
     ImVector<char>          InitialTextA;           // backup of end-user buffer at the time of focus (in UTF-8, unaltered)
+    bool                    TextAIsValid;           // temporary UTF8 buffer is not initially valid before we make the widget active (until then we pull the data from user argument)
     int                     BufCapacityA;           // end-user buffer capacity
     int                     BufCapacityA;           // end-user buffer capacity
     float                   ScrollX;                // horizontal scrolling/offset
     float                   ScrollX;                // horizontal scrolling/offset
     ImStb::STB_TexteditState Stb;                   // state for stb_textedit.h
     ImStb::STB_TexteditState Stb;                   // state for stb_textedit.h
@@ -608,6 +611,8 @@ struct IMGUI_API ImGuiInputTextState
     bool                HasSelection() const        { return Stb.select_start != Stb.select_end; }
     bool                HasSelection() const        { return Stb.select_start != Stb.select_end; }
     void                ClearSelection()            { Stb.select_start = Stb.select_end = Stb.cursor; }
     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                SelectAll()                 { Stb.select_start = 0; Stb.cursor = Stb.select_end = CurLenW; Stb.has_preferred_x = 0; }
+    int                 GetUndoAvailCount() const   { return Stb.undostate.undo_point; }
+    int                 GetRedoAvailCount() const   { return STB_TEXTEDIT_UNDOSTATECOUNT - Stb.undostate.redo_point; }
     void                OnKeyPressed(int key);      // Cannot be inline because we call in code in stb_textedit.h implementation
     void                OnKeyPressed(int key);      // Cannot be inline because we call in code in stb_textedit.h implementation
 };
 };
 
 

+ 41 - 32
imgui_widgets.cpp

@@ -3174,7 +3174,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
     ImGuiIO& io = g.IO;
     ImGuiIO& io = g.IO;
     const ImGuiStyle& style = g.Style;
     const ImGuiStyle& style = g.Style;
 
 
-    const bool RENDER_SELECTION_WHEN_INACTIVE = true;
+    const bool RENDER_SELECTION_WHEN_INACTIVE = false;
     const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
     const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0;
     const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
     const bool is_readonly = (flags & ImGuiInputTextFlags_ReadOnly) != 0;
     const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
     const bool is_password = (flags & ImGuiInputTextFlags_Password) != 0;
@@ -3255,7 +3255,8 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
     bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
     bool select_all = (g.ActiveId != id) && ((flags & ImGuiInputTextFlags_AutoSelectAll) != 0 || user_nav_input_start) && (!is_multiline);
 
 
     const bool init_make_active = (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)
+    const bool init_state = (init_make_active || user_scroll_active);
+    if (init_state && g.ActiveId != id)
     {
     {
         // Access state even if we don't own it yet.
         // Access state even if we don't own it yet.
         state = &g.InputTextState;
         state = &g.InputTextState;
@@ -3268,15 +3269,16 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
         memcpy(state->InitialTextA.Data, buf, buf_len + 1);
         memcpy(state->InitialTextA.Data, buf, buf_len + 1);
 
 
         // Start edition
         // Start edition
-        const int prev_len_w = state->CurLenW;
         const char* buf_end = NULL;
         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->TextW.resize(buf_size + 1);          // wchar count <= UTF-8 count. we use +1 to make sure that .Data is always pointing to at least an empty string.
+        state->TextA.resize(0);
+        state->TextAIsValid = false;                // TextA is not valid yet (we will display buf until then)
         state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, buf_size, buf, NULL, &buf_end);
         state->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.
 
 
         // Preserve cursor position and undo/redo stack if we come back to same widget
         // 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);
+        // FIXME: For non-readonly widgets we might be able to require that TextAIsValid && TextA == buf ? (untested) and discard undo stack if user buffer has changed.
+        const bool recycle_state = (state->ID == id);
         if (recycle_state)
         if (recycle_state)
         {
         {
             // Recycle existing cursor/selection/undo stack but clamp position
             // Recycle existing cursor/selection/undo stack but clamp position
@@ -3297,7 +3299,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
             select_all = true;
             select_all = true;
     }
     }
 
 
-    if (init_make_active)
+    if (g.ActiveId != id && init_make_active)
     {
     {
         IM_ASSERT(state && state->ID == id);
         IM_ASSERT(state && state->ID == id);
         SetActiveID(id, window);
         SetActiveID(id, window);
@@ -3308,32 +3310,38 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
             g.ActiveIdAllowNavDirFlags = ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
             g.ActiveIdAllowNavDirFlags = ((1 << ImGuiDir_Up) | (1 << ImGuiDir_Down));
     }
     }
 
 
-    // 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)
+    // We have an edge case if ActiveId was set through another widget (e.g. widget being swapped), clear id immediately (don't wait until the end of the function)
     if (g.ActiveId == id && state == NULL)
     if (g.ActiveId == id && state == NULL)
         ClearActiveID();
         ClearActiveID();
 
 
+    // Release focus when we click outside
+    if (g.ActiveId == id && io.MouseClicked[0] && !init_state && !init_make_active)
+        clear_active_id = true;
+
     bool value_changed = false;
     bool value_changed = false;
     bool enter_pressed = false;
     bool enter_pressed = false;
     int backup_current_text_length = 0;
     int backup_current_text_length = 0;
 
 
-    // Process mouse inputs and character inputs
-    if (g.ActiveId == id)
+    // When read-only we always use the live data passed to the function
+    // FIXME-OPT: Because our selection/cursor code currently needs the wide text we need to convert it when active, which is not ideal :(
+    if (is_readonly && state != NULL)
     {
     {
-        IM_ASSERT(state != NULL);
-        if (is_readonly && !g.ActiveIdIsJustActivated)
+        const bool will_render_cursor = (g.ActiveId == id) || (state && user_scroll_active);
+        const bool will_render_selection = state && state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || will_render_cursor);
+        if (will_render_cursor || will_render_selection)
         {
         {
-            // When read-only we always use the live data passed to the function
             const char* buf_end = NULL;
             const char* buf_end = NULL;
-            state->TextW.resize(buf_size+1);
+            state->TextW.resize(buf_size + 1);
             state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end);
             state->CurLenW = ImTextStrFromUtf8(state->TextW.Data, state->TextW.Size, buf, NULL, &buf_end);
             state->CurLenA = (int)(buf_end - buf);
             state->CurLenA = (int)(buf_end - buf);
             state->CursorClamp();
             state->CursorClamp();
         }
         }
+    }
 
 
+    // Process mouse inputs and character inputs
+    if (g.ActiveId == id)
+    {
+        IM_ASSERT(state != NULL);
         backup_current_text_length = state->CurLenA;
         backup_current_text_length = state->CurLenA;
         state->BufCapacityA = buf_size;
         state->BufCapacityA = buf_size;
         state->UserFlags = flags;
         state->UserFlags = flags;
@@ -3546,6 +3554,7 @@ 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.
             // 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)
             if (!is_readonly)
             {
             {
+                state->TextAIsValid = true;
                 state->TextA.resize(state->TextW.Size * 4 + 1);
                 state->TextA.resize(state->TextW.Size * 4 + 1);
                 ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL);
                 ImTextStrToUtf8(state->TextA.Data, state->TextA.Size, state->TextW.Data, NULL);
             }
             }
@@ -3676,15 +3685,12 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
     // without any carriage return, which would makes ImFont::RenderText() reserve too many vertices and probably crash. Avoid it altogether.
     // 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.
     // 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;
     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 && state && !is_readonly) ? state->TextA.Data : buf;
-    IM_ASSERT(buf_display);
-    buf = NULL;
+    const char* buf_display = NULL;
+    const char* buf_display_end = NULL;
 
 
     // Render text. We currently only render selection when the widget is active or while scrolling.
     // 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.
     // 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_cursor = (g.ActiveId == id) || (state && user_scroll_active);
     const bool render_selection = state && state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
     const bool render_selection = state && state->HasSelection() && (RENDER_SELECTION_WHEN_INACTIVE || render_cursor);
     if (render_cursor || render_selection)
     if (render_cursor || render_selection)
     {
     {
@@ -3820,9 +3826,10 @@ 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.
         // 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, draw_pos - draw_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display + buf_display_len, 0.0f, is_multiline ? NULL : &clip_rect);
+        buf_display = (!is_readonly && state->TextAIsValid) ? state->TextA.Data : buf;
+        buf_display_end = buf_display + state->CurLenA;
+        if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
+            draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, GetColorU32(ImGuiCol_Text), buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
 
 
         // Draw blinking cursor
         // Draw blinking cursor
         if (render_cursor)
         if (render_cursor)
@@ -3845,13 +3852,15 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
     else
     else
     {
     {
         // Render text only (no selection, no cursor)
         // Render text only (no selection, no cursor)
-        const char* buf_end = NULL;
+        buf_display = (g.ActiveId == id && !is_readonly && state->TextAIsValid) ? state->TextA.Data : buf;
         if (is_multiline)
         if (is_multiline)
-            text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_end) * g.FontSize); // We don't need width
+            text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf_display, &buf_display_end) * g.FontSize); // We don't need width
+        else if (g.ActiveId == id)
+            buf_display_end = buf_display + state->CurLenA;
         else
         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, draw_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_end, 0.0f, is_multiline ? NULL : &clip_rect);
+            buf_display_end = buf_display + strlen(buf_display);
+        if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
+            draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos, GetColorU32(ImGuiCol_Text), buf_display, buf_display_end, 0.0f, is_multiline ? NULL : &clip_rect);
     }
     }
 
 
     if (is_multiline)
     if (is_multiline)
@@ -3866,7 +3875,7 @@ bool ImGui::InputTextEx(const char* label, char* buf, int buf_size, const ImVec2
 
 
     // Log as text
     // Log as text
     if (g.LogEnabled && !is_password)
     if (g.LogEnabled && !is_password)
-        LogRenderedText(&draw_pos, buf_display, NULL);
+        LogRenderedText(&draw_pos, buf_display, buf_display_end);
 
 
     if (label_size.x > 0)
     if (label_size.x > 0)
         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);
         RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label);