瀏覽代碼

Merge branch 'master' into docking

ocornut 1 天之前
父節點
當前提交
71f45c12e9
共有 8 個文件被更改,包括 234 次插入228 次删除
  1. 1 1
      .github/workflows/static-analysis.yml
  2. 7 1
      docs/CHANGELOG.txt
  3. 14 3
      imgui.cpp
  4. 1 1
      imgui.h
  5. 7 3
      imgui_demo.cpp
  6. 7 5
      imgui_internal.h
  7. 196 213
      imgui_widgets.cpp
  8. 1 1
      imstb_textedit.h

+ 1 - 1
.github/workflows/static-analysis.yml

@@ -42,5 +42,5 @@ jobs:
           fi
           fi
           cd examples/example_null
           cd examples/example_null
           pvs-studio-analyzer trace -- make WITH_EXTRA_WARNINGS=1
           pvs-studio-analyzer trace -- make WITH_EXTRA_WARNINGS=1
-          pvs-studio-analyzer analyze -e ../../imstb_rectpack.h -e ../../imstb_textedit.h -e ../../imstb_truetype.h -l ../../pvs-studio.lic -o pvs-studio.log
+          pvs-studio-analyzer analyze --disableLicenseExpirationCheck -e ../../imstb_rectpack.h -e ../../imstb_textedit.h -e ../../imstb_truetype.h -l ../../pvs-studio.lic -o pvs-studio.log
           plog-converter -a 'GA:1,2;OP:1' -d V1071 -t errorfile -w pvs-studio.log
           plog-converter -a 'GA:1,2;OP:1' -d V1071 -t errorfile -w pvs-studio.log

+ 7 - 1
docs/CHANGELOG.txt

@@ -66,14 +66,18 @@ Other Changes:
   In theory the buffer size should always account for a zero-terminator, but idioms
   In theory the buffer size should always account for a zero-terminator, but idioms
   such as using InputTextMultiline() with ImGuiInputTextFlags_ReadOnly to display
   such as using InputTextMultiline() with ImGuiInputTextFlags_ReadOnly to display
   a text blob are facilitated by allowing this.
   a text blob are facilitated by allowing this.
+- InputText: refactored internals to simplify and optimizing rendering of selection.
+  Very large selection (e.g. 1 MB) now take less overhead.
 - InputText: revert a change in 1.79 where pressing Down or PageDown on the last line
 - InputText: revert a change in 1.79 where pressing Down or PageDown on the last line
   of a multi-line buffer without a trailing carriage return would keep the cursor
   of a multi-line buffer without a trailing carriage return would keep the cursor
   unmoved. We revert back to move to the end of line in this situation.
   unmoved. We revert back to move to the end of line in this situation.
+- InputText: fixed pressing End (without Shift) in a multi-line selection from
+  mistakenly moving cursor based on selection start.
 - Focus, InputText: fixed an issue where SetKeyboardFocusHere() did not work
 - Focus, InputText: fixed an issue where SetKeyboardFocusHere() did not work
   on InputTextMultiline() fields with ImGuiInputTextFlags_AllowTabInput, since
   on InputTextMultiline() fields with ImGuiInputTextFlags_AllowTabInput, since
   they normally inhibit activation to allow tabbing through multiple items. (#8928)
   they normally inhibit activation to allow tabbing through multiple items. (#8928)
 - Selectable: added ImGuiSelectableFlags_SelectOnNav to auto-select an item when
 - Selectable: added ImGuiSelectableFlags_SelectOnNav to auto-select an item when
-  moved into (automatic when in a BeginMultiSelect() block).
+  moved into, unless Ctrl is held. (automatic when in a BeginMultiSelect() block).
 - TabBar: fixed an issue were forcefully selecting a tab using internal API would
 - TabBar: fixed an issue were forcefully selecting a tab using internal API would
   be ignored on first/appearing frame before tabs are submitted (#8929, #6681)
   be ignored on first/appearing frame before tabs are submitted (#8929, #6681)
 - DrawList: fixed CloneOutput() unnecessarily taking a copy of the ImDrawListSharedData
 - DrawList: fixed CloneOutput() unnecessarily taking a copy of the ImDrawListSharedData
@@ -85,6 +89,8 @@ Other Changes:
   is now skipped. (#8904, #4631)
   is now skipped. (#8904, #4631)
 - Debug Tools: ID Stack Tool: added option to hex-encode non-ASCII characters in
 - Debug Tools: ID Stack Tool: added option to hex-encode non-ASCII characters in
   output path. (#8904, #4631)
   output path. (#8904, #4631)
+- Debug Tools: Fixed assertion failure when opening a combo box while using
+  io.ConfigDebugBeginReturnValueOnce/ConfigDebugBeginReturnValueLoop. (#8931) [@harrymander]
 - Demo: tweaked ShowFontSelector() and ShowStyleSelector() to update selection
 - Demo: tweaked ShowFontSelector() and ShowStyleSelector() to update selection
   while navigating and to not close popup automatically.
   while navigating and to not close popup automatically.
 - Examples: Android: Android+OpenGL3: update Gradle project (#8888, #8878) [@scribam]
 - Examples: Android: Android+OpenGL3: update Gradle project (#8888, #8878) [@scribam]

+ 14 - 3
imgui.cpp

@@ -3137,19 +3137,21 @@ void ImGuiTextBuffer::appendfv(const char* fmt, va_list args)
     va_end(args_copy);
     va_end(args_copy);
 }
 }
 
 
+IM_MSVC_RUNTIME_CHECKS_OFF
 void ImGuiTextIndex::append(const char* base, int old_size, int new_size)
 void ImGuiTextIndex::append(const char* base, int old_size, int new_size)
 {
 {
     IM_ASSERT(old_size >= 0 && new_size >= old_size && new_size >= EndOffset);
     IM_ASSERT(old_size >= 0 && new_size >= old_size && new_size >= EndOffset);
     if (old_size == new_size)
     if (old_size == new_size)
         return;
         return;
     if (EndOffset == 0 || base[EndOffset - 1] == '\n')
     if (EndOffset == 0 || base[EndOffset - 1] == '\n')
-        LineOffsets.push_back(EndOffset);
+        Offsets.push_back(EndOffset);
     const char* base_end = base + new_size;
     const char* base_end = base + new_size;
     for (const char* p = base + old_size; (p = (const char*)ImMemchr(p, '\n', base_end - p)) != 0; )
     for (const char* p = base + old_size; (p = (const char*)ImMemchr(p, '\n', base_end - p)) != 0; )
         if (++p < base_end) // Don't push a trailing offset on last \n
         if (++p < base_end) // Don't push a trailing offset on last \n
-            LineOffsets.push_back((int)(intptr_t)(p - base));
+            Offsets.push_back((int)(intptr_t)(p - base));
     EndOffset = ImMax(EndOffset, new_size);
     EndOffset = ImMax(EndOffset, new_size);
 }
 }
+IM_MSVC_RUNTIME_CHECKS_RESTORE
 
 
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 // [SECTION] ImGuiListClipper
 // [SECTION] ImGuiListClipper
@@ -3477,6 +3479,13 @@ bool ImGuiListClipper::Step()
     return ret;
     return ret;
 }
 }
 
 
+// Generic helper, equivalent to old ImGui::CalcListClipping() but statelesss
+void ImGui::CalcClipRectVisibleItemsY(const ImRect& clip_rect, const ImVec2& pos, float items_height, int* out_visible_start, int* out_visible_end)
+{
+    *out_visible_start = ImMax((int)((clip_rect.Min.y - pos.y) / items_height), 0);
+    *out_visible_end = ImMax((int)ImCeil((clip_rect.Max.y - pos.y) / items_height), *out_visible_start);
+}
+
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 // [SECTION] STYLING
 // [SECTION] STYLING
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
@@ -4473,6 +4482,7 @@ void ImGui::Shutdown()
     g.ClipboardHandlerData.clear();
     g.ClipboardHandlerData.clear();
     g.MenusIdSubmittedThisFrame.clear();
     g.MenusIdSubmittedThisFrame.clear();
     g.InputTextState.ClearFreeMemory();
     g.InputTextState.ClearFreeMemory();
+    g.InputTextLineIndex.clear();
     g.InputTextDeactivatedState.ClearFreeMemory();
     g.InputTextDeactivatedState.ClearFreeMemory();
 
 
     g.SettingsWindows.clear();
     g.SettingsWindows.clear();
@@ -4593,6 +4603,7 @@ void ImGui::GcCompactTransientMiscBuffers()
     ImGuiContext& g = *GImGui;
     ImGuiContext& g = *GImGui;
     g.ItemFlagsStack.clear();
     g.ItemFlagsStack.clear();
     g.GroupStack.clear();
     g.GroupStack.clear();
+    g.InputTextLineIndex.clear();
     g.MultiSelectTempDataStacked = 0;
     g.MultiSelectTempDataStacked = 0;
     g.MultiSelectTempData.clear_destruct();
     g.MultiSelectTempData.clear_destruct();
     TableGcCompactSettings();
     TableGcCompactSettings();
@@ -23752,7 +23763,7 @@ void ImGui::ShowFontSelector(const char* label)
         for (ImFont* font : io.Fonts->Fonts)
         for (ImFont* font : io.Fonts->Fonts)
         {
         {
             PushID((void*)font);
             PushID((void*)font);
-            if (Selectable(font->GetDebugName(), font == font_current, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_NoAutoClosePopups))
+            if (Selectable(font->GetDebugName(), font == font_current, ImGuiSelectableFlags_SelectOnNav))
                 io.FontDefault = font;
                 io.FontDefault = font;
             if (font == font_current)
             if (font == font_current)
                 SetItemDefaultFocus();
                 SetItemDefaultFocus();

+ 1 - 1
imgui.h

@@ -1380,7 +1380,7 @@ enum ImGuiSelectableFlags_
     ImGuiSelectableFlags_Disabled           = 1 << 3,   // Cannot be selected, display grayed out text
     ImGuiSelectableFlags_Disabled           = 1 << 3,   // Cannot be selected, display grayed out text
     ImGuiSelectableFlags_AllowOverlap       = 1 << 4,   // (WIP) Hit testing to allow subsequent widgets to overlap this one
     ImGuiSelectableFlags_AllowOverlap       = 1 << 4,   // (WIP) Hit testing to allow subsequent widgets to overlap this one
     ImGuiSelectableFlags_Highlight          = 1 << 5,   // Make the item be displayed as if it is hovered
     ImGuiSelectableFlags_Highlight          = 1 << 5,   // Make the item be displayed as if it is hovered
-    ImGuiSelectableFlags_SelectOnNav        = 1 << 6,   // Auto-select when moved into. Automatic when in a BeginMultiSelect() block.
+    ImGuiSelectableFlags_SelectOnNav        = 1 << 6,   // Auto-select when moved into, unless Ctrl is held. Automatic when in a BeginMultiSelect() block.
 
 
 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
 #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
     ImGuiSelectableFlags_DontClosePopups    = ImGuiSelectableFlags_NoAutoClosePopups,   // Renamed in 1.91.0
     ImGuiSelectableFlags_DontClosePopups    = ImGuiSelectableFlags_NoAutoClosePopups,   // Renamed in 1.91.0

+ 7 - 3
imgui_demo.cpp

@@ -8328,17 +8328,18 @@ void ImGui::ShowAboutWindow(bool* p_open)
 //-----------------------------------------------------------------------------
 //-----------------------------------------------------------------------------
 
 
 // Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options.
 // Demo helper function to select among default colors. See ShowStyleEditor() for more advanced options.
-// Here we use the simplified Combo() api that packs items into a single literal string.
-// Useful for quick combo boxes where the choices are known locally.
 bool ImGui::ShowStyleSelector(const char* label)
 bool ImGui::ShowStyleSelector(const char* label)
 {
 {
+    // FIXME: This is a bit tricky to get right as style are functions, they don't register a name nor the fact that one is active.
+    // So we keep track of last active one among our limited selection.
     static int style_idx = -1;
     static int style_idx = -1;
     const char* style_names[] = { "Dark", "Light", "Classic" };
     const char* style_names[] = { "Dark", "Light", "Classic" };
     bool ret = false;
     bool ret = false;
     if (ImGui::BeginCombo(label, (style_idx >= 0 && style_idx < IM_ARRAYSIZE(style_names)) ? style_names[style_idx] : ""))
     if (ImGui::BeginCombo(label, (style_idx >= 0 && style_idx < IM_ARRAYSIZE(style_names)) ? style_names[style_idx] : ""))
     {
     {
         for (int n = 0; n < IM_ARRAYSIZE(style_names); n++)
         for (int n = 0; n < IM_ARRAYSIZE(style_names); n++)
-            if (ImGui::Selectable(style_names[n], style_idx == n, ImGuiSelectableFlags_SelectOnNav | ImGuiSelectableFlags_NoAutoClosePopups))
+        {
+            if (ImGui::Selectable(style_names[n], style_idx == n, ImGuiSelectableFlags_SelectOnNav))
             {
             {
                 style_idx = n;
                 style_idx = n;
                 ret = true;
                 ret = true;
@@ -8349,6 +8350,9 @@ bool ImGui::ShowStyleSelector(const char* label)
                 case 2: ImGui::StyleColorsClassic(); break;
                 case 2: ImGui::StyleColorsClassic(); break;
                 }
                 }
             }
             }
+            else if (style_idx == n)
+                ImGui::SetItemDefaultFocus();
+        }
         ImGui::EndCombo();
         ImGui::EndCombo();
     }
     }
     return ret;
     return ret;

+ 7 - 5
imgui_internal.h

@@ -815,13 +815,13 @@ struct ImChunkStream
 // Maintain a line index for a text buffer. This is a strong candidate to be moved into the public API.
 // Maintain a line index for a text buffer. This is a strong candidate to be moved into the public API.
 struct ImGuiTextIndex
 struct ImGuiTextIndex
 {
 {
-    ImVector<int>   LineOffsets;
+    ImVector<int>   Offsets;
     int             EndOffset = 0;                          // Because we don't own text buffer we need to maintain EndOffset (may bake in LineOffsets?)
     int             EndOffset = 0;                          // Because we don't own text buffer we need to maintain EndOffset (may bake in LineOffsets?)
 
 
-    void            clear()                                 { LineOffsets.clear(); EndOffset = 0; }
-    int             size()                                  { return LineOffsets.Size; }
-    const char*     get_line_begin(const char* base, int n) { return base + LineOffsets[n]; }
-    const char*     get_line_end(const char* base, int n)   { return base + (n + 1 < LineOffsets.Size ? (LineOffsets[n + 1] - 1) : EndOffset); }
+    void            clear()                                 { Offsets.clear(); EndOffset = 0; }
+    int             size()                                  { return Offsets.Size; }
+    const char*     get_line_begin(const char* base, int n) { return base + (Offsets.Size != 0 ? Offsets[n] : 0); }
+    const char*     get_line_end(const char* base, int n)   { return base + (n + 1 < Offsets.Size ? (Offsets[n + 1] - 1) : EndOffset); }
     void            append(const char* base, int old_size, int new_size);
     void            append(const char* base, int old_size, int new_size);
 };
 };
 
 
@@ -2635,6 +2635,7 @@ struct ImGuiContext
 
 
     // Widget state
     // Widget state
     ImGuiInputTextState     InputTextState;
     ImGuiInputTextState     InputTextState;
+    ImGuiTextIndex          InputTextLineIndex;                 // Temporary storage
     ImGuiInputTextDeactivatedState InputTextDeactivatedState;
     ImGuiInputTextDeactivatedState InputTextDeactivatedState;
     ImFontBaked             InputTextPasswordFontBackupBaked;
     ImFontBaked             InputTextPasswordFontBackupBaked;
     ImFontFlags             InputTextPasswordFontBackupFlags;
     ImFontFlags             InputTextPasswordFontBackupFlags;
@@ -3495,6 +3496,7 @@ namespace ImGui
     IMGUI_API float         CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x);
     IMGUI_API float         CalcWrapWidthForPos(const ImVec2& pos, float wrap_pos_x);
     IMGUI_API void          PushMultiItemsWidths(int components, float width_full);
     IMGUI_API void          PushMultiItemsWidths(int components, float width_full);
     IMGUI_API void          ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess, float width_min);
     IMGUI_API void          ShrinkWidths(ImGuiShrinkWidthItem* items, int count, float width_excess, float width_min);
+    IMGUI_API void          CalcClipRectVisibleItemsY(const ImRect& clip_rect, const ImVec2& pos, float items_height, int* out_visible_start, int* out_visible_end);
 
 
     // Parameter stacks (shared)
     // Parameter stacks (shared)
     IMGUI_API const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx);
     IMGUI_API const ImGuiStyleVarInfo* GetStyleVarInfo(ImGuiStyleVar idx);

+ 196 - 213
imgui_widgets.cpp

@@ -135,7 +135,6 @@ static const ImU64          IM_U64_MAX = (2ULL * 9223372036854775807LL + 1);
 
 
 // For InputTextEx()
 // For InputTextEx()
 static bool     InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false);
 static bool     InputTextFilterCharacter(ImGuiContext* ctx, unsigned int* p_char, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data, bool input_source_is_clipboard = false);
-static int      InputTextCalcTextLenAndLineCount(ImGuiContext* ctx, const char* text_begin, const char** out_text_end, float wrap_width);
 static ImVec2   InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining = NULL, ImVec2* out_offset = NULL, ImDrawTextFlags flags = 0);
 static ImVec2   InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining = NULL, ImVec2* out_offset = NULL, ImDrawTextFlags flags = 0);
 
 
 //-------------------------------------------------------------------------
 //-------------------------------------------------------------------------
@@ -2038,7 +2037,8 @@ bool ImGui::BeginComboPopup(ImGuiID popup_id, const ImRect& bb, ImGuiComboFlags
     if (!ret)
     if (!ret)
     {
     {
         EndPopup();
         EndPopup();
-        IM_ASSERT(0);   // This should never happen as we tested for IsPopupOpen() above
+        if (!g.IO.ConfigDebugBeginReturnValueOnce && !g.IO.ConfigDebugBeginReturnValueLoop) // Begin may only return false with those debug tools activated.
+            IM_ASSERT(0); // This should never happen as we tested for IsPopupOpen() above
         return false;
         return false;
     }
     }
     g.BeginComboDepth++;
     g.BeginComboDepth++;
@@ -3943,46 +3943,6 @@ bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, si
     return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
     return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
 }
 }
 
 
-// This is only used in the path where the multiline widget is inactive.
-static int InputTextCalcTextLenAndLineCount(ImGuiContext* ctx, const char* text_begin, const char** out_text_end, float wrap_width)
-{
-    int line_count = 0;
-    const char* s = text_begin;
-    if (wrap_width == 0.0f)
-    {
-        while (true)
-        {
-            const char* s_eol = strchr(s, '\n');
-            line_count++;
-            if (s_eol == NULL)
-            {
-                s = s + ImStrlen(s);
-                break;
-            }
-            s = s_eol + 1;
-        }
-    }
-    else
-    {
-        // FIXME-WORDWRAP, FIXME-OPT: This is very suboptimal.
-        // We basically want both text_end and text_size, they could more optimally be emitted from a RenderText call that uses word-wrapping.
-        ImGuiContext& g = *ctx;
-        ImFont* font = g.Font;
-        const char* text_end = text_begin + strlen(text_begin);
-        while (s < text_end)
-        {
-            s = ImFontCalcWordWrapPositionEx(font, g.FontSize, s, text_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks);
-            s = (*s == '\n') ? s + 1 : s;
-            line_count++;
-        }
-        if (text_end > text_begin && text_end[-1] == '\n')
-            line_count++;
-        IM_ASSERT(s == text_end);
-    }
-    *out_text_end = s;
-    return line_count;
-}
-
 static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining, ImVec2* out_offset, ImDrawTextFlags flags)
 static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end_display, const char* text_end, const char** out_remaining, ImVec2* out_offset, ImDrawTextFlags flags)
 {
 {
     ImGuiContext& g = *ctx;
     ImGuiContext& g = *ctx;
@@ -4560,6 +4520,97 @@ void ImGui::InputTextDeactivateHook(ImGuiID id)
     }
     }
 }
 }
 
 
+static int* ImLowerBound(int* in_begin, int* in_end, int v)
+{
+    int* in_p = in_begin;
+    for (size_t count = (size_t)(in_end - in_p); count > 0; )
+    {
+        size_t count2 = count >> 1;
+        int* mid = in_p + count2;
+        if (*mid < v)
+        {
+            in_p = ++mid;
+            count -= count2 + 1;
+        }
+        else
+        {
+            count = count2;
+        }
+    }
+    return in_p;
+}
+
+// FIXME-WORDWRAP: Bundle some of this into ImGuiTextIndex and/or extract as a different tool?
+// 'max_output_buffer_size' happens to be a meaningful optimization to avoid writing the full line_index when not necessarily needed (e.g. very large buffer, scrolled up, inactive)
+static int InputTextLineIndexBuild(ImGuiInputTextFlags flags, ImGuiTextIndex* line_index, const char* buf, const char* buf_end, float wrap_width, int max_output_buffer_size, const char** out_buf_end)
+{
+    ImGuiContext& g = *GImGui;
+    int size = 0;
+    const char* s;
+    if (flags & ImGuiInputTextFlags_WordWrap)
+    {
+        for (s = buf; s < buf_end; s = (*s == '\n') ? s + 1 : s)
+        {
+            if (size++ <= max_output_buffer_size)
+                line_index->Offsets.push_back((int)(s - buf));
+            s = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, s, buf_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks);
+        }
+    }
+    else if (buf_end != NULL)
+    {
+        for (s = buf; s < buf_end; s = s ? s + 1 : buf_end)
+        {
+            if (size++ <= max_output_buffer_size)
+                line_index->Offsets.push_back((int)(s - buf));
+            s = (const char*)ImMemchr(s, '\n', buf_end - s);
+        }
+    }
+    else
+    {
+        const char* s_eol;
+        for (s = buf; ; s = s_eol + 1)
+        {
+            if (size++ <= max_output_buffer_size)
+                line_index->Offsets.push_back((int)(s - buf));
+            if ((s_eol = strchr(s, '\n')) != NULL)
+                continue;
+            s += strlen(s);
+            break;
+        }
+    }
+    if (out_buf_end != NULL)
+        *out_buf_end = buf_end = s;
+    if (size == 0)
+    {
+        line_index->Offsets.push_back(0);
+        size++;
+    }
+    if (buf_end > buf && buf_end[-1] == '\n' && size <= max_output_buffer_size)
+    {
+        line_index->Offsets.push_back((int)(buf_end - buf));
+        size++;
+    }
+    return size;
+}
+
+static ImVec2 InputTextLineIndexGetPosOffset(ImGuiContext& g, ImGuiInputTextState* state, ImGuiTextIndex* line_index, const char* buf, const char* buf_end, int cursor_n)
+{
+    const char* cursor_ptr = buf + cursor_n;
+    int* it_begin = line_index->Offsets.begin();
+    int* it_end = line_index->Offsets.end();
+    const int* it = ImLowerBound(it_begin, it_end, cursor_n);
+    if (it > it_begin)
+        if (it == it_end || *it != cursor_n || (cursor_ptr[-1] != '\n' && cursor_ptr[-1] != 0 && state != NULL && state->LastMoveDirectionLR == ImGuiDir_Right))
+            it--;
+
+    const int line_no = (it == it_begin) ? 0 : line_index->Offsets.index_from_ptr(it);
+    const char* line_start = line_index->get_line_begin(buf, line_no);
+    ImVec2 offset;
+    offset.x = InputTextCalcTextSize(&g, line_start, cursor_ptr, buf_end, NULL, NULL, ImDrawTextFlags_WrapKeepBlanks).x;
+    offset.y = (line_no + 1) * g.FontSize;
+    return offset;
+}
+
 // Edit a string of text
 // Edit a string of text
 // - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
 // - buf_size account for the zero-terminator, so a buf_size of 6 can hold "Hello" but not "Hello!".
 //   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
 //   This is so we can easily call InputText() on static arrays using ARRAYSIZE() and to match
@@ -5332,15 +5383,42 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
         buf_display = hint;
         buf_display = hint;
         buf_display_end = hint + ImStrlen(hint);
         buf_display_end = hint + ImStrlen(hint);
     }
     }
+    else
+    {
+        if (render_cursor || render_selection || g.ActiveId == id)
+            buf_display_end = buf_display + state->TextLen; //-V595
+        else if (is_multiline && !is_wordwrap)
+            buf_display_end = NULL; // Inactive multi-line: end of buffer will be output by InputTextLineIndexBuild() special strchr() path.
+        else
+            buf_display_end = buf_display + ImStrlen(buf_display);
+    }
+
+    // Calculate visibility
+    int line_visible_n0 = 0, line_visible_n1 = 1;
+    if (is_multiline)
+        CalcClipRectVisibleItemsY(clip_rect, draw_pos, g.FontSize, &line_visible_n0, &line_visible_n1);
+
+    // Build line index for easy data access (makes code below simpler and faster)
+    ImGuiTextIndex* line_index = &g.InputTextLineIndex;
+    line_index->Offsets.resize(0);
+    int line_count = 1;
+    if (is_multiline)
+        line_count = InputTextLineIndexBuild(flags, line_index, buf_display, buf_display_end, wrap_width, (render_cursor && state && state->CursorFollow) ? INT_MAX : line_visible_n1 + 1, buf_display_end ? NULL : &buf_display_end);
+    line_index->EndOffset = (int)(buf_display_end - buf_display);
+    line_visible_n1 = ImMin(line_visible_n1, line_count);
+
+    // Store text height (we don't need width)
+    text_size = ImVec2(inner_size.x, line_count * g.FontSize);
+    //GetForegroundDrawList()->AddRect(draw_pos + ImVec2(0, line_visible_n0 * g.FontSize), draw_pos + ImVec2(frame_size.x, line_visible_n1 * g.FontSize), IM_COL32(255, 0, 0, 255));
+
+    // Calculate blinking cursor position
+    const ImVec2 cursor_offset = render_cursor && state ? InputTextLineIndexGetPosOffset(g, state, line_index, buf_display, buf_display_end, state->Stb->cursor) : ImVec2(0.0f, 0.0f);
+    ImVec2 draw_scroll;
 
 
     // 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: This is one of the messiest piece of the whole codebase.
+    const ImU32 text_col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
     if (render_cursor || render_selection)
     if (render_cursor || render_selection)
     {
     {
-        IM_ASSERT(state != NULL);
-        if (!is_displaying_hint)
-            buf_display_end = buf_display + state->TextLen;
-
         // Render text (with cursor and selection)
         // Render text (with cursor and selection)
         // This is going to be messy. We need to:
         // This is going to be messy. We need to:
         // - Display the text (this alone can be more easily clipped)
         // - Display the text (this alone can be more easily clipped)
@@ -5348,85 +5426,8 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
         // - Measure text height (for scrollbar)
         // - 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)
         // 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.
         // FIXME: This should occur on buf_display but we'd need to maintain cursor/select_start/select_end for UTF-8.
-        const char* text_begin = buf_display;
-        const char* text_end = text_begin + state->TextLen;
-        ImVec2 cursor_offset;
-        float select_start_offset_y = 0.0f; // Offset of beginning of non-wrapped line for selection.
-
-        {
-            // Find lines numbers straddling cursor and selection min position
-            int cursor_line_no = render_cursor ? -1 : -1000;
-            int selmin_line_no = render_selection ? -1 : -1000;
-            const char* cursor_ptr = render_cursor ? text_begin + state->Stb->cursor : NULL;
-            const char* selmin_ptr = render_selection ? text_begin + ImMin(state->Stb->select_start, state->Stb->select_end) : NULL;
-            const char* cursor_line_start = NULL;
-            const char* selmin_line_start = NULL;
-            bool cursor_straddle_word_wrap = false;
-
-            // Count lines and find line number for cursor and selection ends
-            // FIXME: Switch to zero-based index to reduce confusion.
-            int line_count = 1;
-            if (is_multiline)
-            {
-                if (!is_wordwrap)
-                {
-                    for (const char* s = text_begin; (s = (const char*)ImMemchr(s, '\n', (size_t)(text_end - s))) != NULL; s++)
-                    {
-                        if (cursor_line_no == -1 && s >= cursor_ptr) { cursor_line_no = line_count; }
-                        if (selmin_line_no == -1 && s >= selmin_ptr) { selmin_line_no = line_count; }
-                        line_count++;
-                    }
-                }
-                else
-                {
-                    bool is_start_of_non_wrapped_line = true;
-                    int line_count_for_non_wrapped_line = 1;
-                    for (const char* s = text_begin; s < text_end; s = (*s == '\n') ? s + 1 : s)
-                    {
-                        const char* s_eol = ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, s, text_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks);
-                        const char* s_prev = s;
-                        s = s_eol;
-                        if (cursor_line_no == -1 && s >= cursor_ptr) { cursor_line_start = s_prev; cursor_line_no = line_count; }
-                        if (selmin_line_no == -1 && s >= selmin_ptr) { selmin_line_start = s_prev; selmin_line_no = line_count_for_non_wrapped_line; }
-                        if (s == cursor_ptr && *cursor_ptr != '\n' && *cursor_ptr != 0)
-                            cursor_straddle_word_wrap = true;
-                        is_start_of_non_wrapped_line = (*s == '\n');
-                        line_count++;
-                        if (is_start_of_non_wrapped_line)
-                            line_count_for_non_wrapped_line = line_count;
-                    }
-                }
-                //IMGUI_DEBUG_LOG("%d\n", selmin_line_no);
-            }
-            if (cursor_line_no == -1)
-                cursor_line_no = line_count;
-            if (cursor_line_start == NULL)
-                cursor_line_start = ImStrbol(cursor_ptr, text_begin);
-            if (selmin_line_no == -1)
-                selmin_line_no = line_count;
-            if (selmin_line_start == NULL)
-                selmin_line_start = ImStrbol(cursor_ptr, text_begin);
-
-            // Calculate 2d position by finding the beginning of the line and measuring distance
-            if (render_cursor)
-            {
-                cursor_offset.x = InputTextCalcTextSize(&g, cursor_line_start, cursor_ptr, text_end, NULL, NULL, ImDrawTextFlags_WrapKeepBlanks).x;
-                cursor_offset.y = cursor_line_no * g.FontSize;
-                if (is_multiline && cursor_straddle_word_wrap && state->LastMoveDirectionLR == ImGuiDir_Left)
-                    cursor_offset = ImVec2(0.0f, cursor_offset.y + g.FontSize);
-            }
-            if (selmin_line_no >= 0)
-                select_start_offset_y = selmin_line_no * g.FontSize;
-
-            // Store text height (note that we haven't calculated text width at all, see GitHub issues #383, #1224)
-            if (is_multiline)
-            {
-                if (is_wordwrap && text_end > text_begin && text_end[-1] != '\n')
-                    line_count--;
-                text_size = ImVec2(inner_size.x, line_count * g.FontSize);
-            }
-            state->LineCount = line_count;
-        }
+        IM_ASSERT(state != NULL);
+        state->LineCount = line_count;
 
 
         // Scroll
         // Scroll
         float new_scroll_y = scroll_y;
         float new_scroll_y = scroll_y;
@@ -5444,7 +5445,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
             }
             }
             else
             else
             {
             {
-                state->Scroll.y = 0.0f;
+                state->Scroll.x = 0.0f;
             }
             }
 
 
             // Vertical scroll
             // Vertical scroll
@@ -5452,7 +5453,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
             {
             {
                 // Test if cursor is vertically visible
                 // Test if cursor is vertically visible
                 if (cursor_offset.y - g.FontSize < scroll_y)
                 if (cursor_offset.y - g.FontSize < scroll_y)
-                    new_scroll_y  = ImMax(0.0f, cursor_offset.y - g.FontSize);
+                    new_scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize);
                 else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y)
                 else if (cursor_offset.y - (inner_size.y - style.FramePadding.y * 2.0f) >= scroll_y)
                     new_scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
                     new_scroll_y = cursor_offset.y - inner_size.y + style.FramePadding.y * 2.0f;
             }
             }
@@ -5471,103 +5472,86 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
             scroll_y = ImClamp(new_scroll_y, 0.0f, scroll_max_y);
             scroll_y = ImClamp(new_scroll_y, 0.0f, scroll_max_y);
             draw_pos.y += (draw_window->Scroll.y - scroll_y);   // Manipulate cursor pos immediately avoid a frame of lag
             draw_pos.y += (draw_window->Scroll.y - scroll_y);   // Manipulate cursor pos immediately avoid a frame of lag
             draw_window->Scroll.y = scroll_y;
             draw_window->Scroll.y = scroll_y;
+            CalcClipRectVisibleItemsY(clip_rect, draw_pos, g.FontSize, &line_visible_n0, &line_visible_n1);
+            line_visible_n1 = ImMin(line_visible_n1, line_count);
         }
         }
 
 
         // Draw selection
         // Draw selection
-        const ImVec2 draw_scroll = ImVec2(state->Scroll.x, 0.0f);
+        draw_scroll.x = state->Scroll.x;
         if (render_selection)
         if (render_selection)
         {
         {
-            const char* text_selected_begin = text_begin + ImMin(state->Stb->select_start, state->Stb->select_end);
-            const char* text_selected_end = text_begin + ImMax(state->Stb->select_start, state->Stb->select_end);
+            const 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.
+            const 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.
+            const float bg_offy_dn = is_multiline ? 0.0f : 2.0f;
+            const float bg_eol_width = IM_TRUNC(g.FontBaked->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
 
 
-            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;
-            float bg_min_width = IM_TRUNC(g.FontBaked->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
-            ImVec2 rect_pos = draw_pos - draw_scroll;
-            rect_pos.y += select_start_offset_y;
-            for (const char* p = ImStrbol(text_selected_begin, text_begin); p < text_selected_end; rect_pos.y += g.FontSize)
+            const char* text_selected_begin = buf_display + ImMin(state->Stb->select_start, state->Stb->select_end);
+            const char* text_selected_end = buf_display + ImMax(state->Stb->select_start, state->Stb->select_end);
+            for (int line_n = line_visible_n0; line_n < line_visible_n1; line_n++)
             {
             {
-                if (rect_pos.y > clip_rect.Max.y + g.FontSize)
-                    break;
-                const char* p_eol = is_wordwrap ? ImFontCalcWordWrapPositionEx(g.Font, g.FontSize, p, text_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks) : (const char*)ImMemchr((void*)p, '\n', text_selected_end - p);
-                if (p_eol == NULL)
-                    p_eol = text_selected_end;
-                const char* p_next = is_wordwrap ? (*p_eol == '\n' ? p_eol + 1 : p_eol) : (p_eol + 1);
-                if (rect_pos.y >= clip_rect.Min.y)
-                {
-                    const char* line_selected_begin = (text_selected_begin > p) ? text_selected_begin : p;
-                    const char* line_selected_end = (text_selected_end < p_eol) ? text_selected_end : p_eol;
-                    if ((*p_eol == '\n' && text_selected_begin <= p_eol) || (text_selected_begin < p_eol))
-                    {
-                        ImVec2 rect_offset = CalcTextSize(p, line_selected_begin);
-                        ImVec2 rect_size = CalcTextSize(line_selected_begin, line_selected_end);
-                        rect_size.x = ImMax(rect_size.x, bg_min_width); // So we can see selected empty lines
-                        ImRect rect(rect_pos + ImVec2(rect_offset.x, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_offset.x + rect_size.x, bg_offy_dn));
-                        rect.ClipWith(clip_rect);
-                        if (rect.Overlaps(clip_rect))
-                            draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
-                    }
-                }
-                p = p_next;
+                const char* p = line_index->get_line_begin(buf_display, line_n);
+                const char* p_eol = line_index->get_line_end(buf_display, line_n);
+                const bool p_eol_is_wrap = (p_eol < buf_display_end && *p_eol != '\n');
+                if (p_eol_is_wrap)
+                    p_eol++;
+                const char* line_selected_begin = (text_selected_begin > p) ? text_selected_begin : p;
+                const char* line_selected_end = (text_selected_end < p_eol) ? text_selected_end : p_eol;
+
+                float rect_width = 0.0f;
+                if (line_selected_begin < line_selected_end)
+                    rect_width += CalcTextSize(line_selected_begin, line_selected_end).x;
+                if (text_selected_begin <= p_eol && text_selected_end > p_eol && !p_eol_is_wrap)
+                    rect_width += bg_eol_width; // So we can see selected empty lines
+                if (rect_width == 0.0f)
+                    continue;
+
+                ImRect rect;
+                rect.Min.x = draw_pos.x - draw_scroll.x + CalcTextSize(p, line_selected_begin).x;
+                rect.Min.y = draw_pos.y - draw_scroll.y + line_n * g.FontSize;
+                rect.Max.x = rect.Min.x + rect_width;
+                rect.Max.y = rect.Min.y + bg_offy_dn + g.FontSize;
+                rect.Min.y -= bg_offy_up;
+                rect.ClipWith(clip_rect);
+                draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
             }
             }
         }
         }
+    }
 
 
-        // 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.
-        // FIXME-OPT: Multiline could submit a smaller amount of contents to AddText() since we already iterated through it.
-        if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
-        {
-            ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
-            if (col & IM_COL32_A_MASK)
-                g.Font->RenderText(draw_window->DrawList, g.FontSize, draw_pos - draw_scroll, col, clip_rect.AsVec4(), buf_display, buf_display_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks);
-            //draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, wrap_width, is_multiline ? NULL : &clip_rect.AsVec4());
-        }
+    // Find render position for right alignment (single-line only)
+    if (g.ActiveId != id && flags & ImGuiInputTextFlags_ElideLeft)
+        draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x);
+    //draw_scroll.x = state->Scroll.x; // Preserve scroll when inactive?
 
 
-        // Draw blinking cursor
-        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 = ImTrunc(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_InputTextCursor), 1.0f); // FIXME-DPI: Cursor thickness (#7031)
+    // Render text
+    if ((is_multiline || (buf_display_end - buf_display) < buf_display_max_length) && (text_col & IM_COL32_A_MASK) && (line_visible_n0 < line_visible_n1))
+        g.Font->RenderText(draw_window->DrawList, g.FontSize,
+            draw_pos - draw_scroll + ImVec2(0.0f, line_visible_n0 * g.FontSize),
+            text_col, clip_rect.AsVec4(),
+            line_index->get_line_begin(buf_display, line_visible_n0),
+            line_index->get_line_end(buf_display, line_visible_n1 - 1),
+            wrap_width, ImDrawTextFlags_WrapKeepBlanks);
 
 
-            // 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.)
-            // This is required for some backends (SDL3) to start emitting character/text inputs.
-            // As per #6341, make sure we don't set that on the deactivating frame.
-            if (!is_readonly && g.ActiveId == id)
-            {
-                ImGuiPlatformImeData* ime_data = &g.PlatformImeData; // (this is a public struct, passed to io.Platform_SetImeDataFn() handler)
-                ime_data->WantVisible = true;
-                ime_data->WantTextInput = true;
-                ime_data->InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
-                ime_data->InputLineHeight = g.FontSize;
-                ime_data->ViewportId = window->Viewport->ID;
-            }
-        }
-    }
-    else
+    // Render blinking cursor
+    if (render_cursor)
     {
     {
-        // Render text only (no selection, no cursor)
-        if (is_multiline)
-            text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(&g, buf_display, &buf_display_end, wrap_width) * g.FontSize); // We don't need width
-        else if (!is_displaying_hint && g.ActiveId == id)
-            buf_display_end = buf_display + state->TextLen;
-        else if (!is_displaying_hint)
-            buf_display_end = buf_display + ImStrlen(buf_display);
+        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 = ImTrunc(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_InputTextCursor), 1.0f); // FIXME-DPI: Cursor thickness (#7031)
 
 
-        if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length)
+        // 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.)
+        // This is required for some backends (SDL3) to start emitting character/text inputs.
+        // As per #6341, make sure we don't set that on the deactivating frame.
+        if (!is_readonly && g.ActiveId == id)
         {
         {
-            // Find render position for right alignment
-            if (flags & ImGuiInputTextFlags_ElideLeft)
-                draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x);
-
-            const ImVec2 draw_scroll = /*state ? ImVec2(state->Scroll.x, 0.0f) :*/ ImVec2(0.0f, 0.0f); // Preserve scroll when inactive?
-            ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text);
-            if (col & IM_COL32_A_MASK)
-                g.Font->RenderText(draw_window->DrawList, g.FontSize, draw_pos - draw_scroll, col, clip_rect.AsVec4(), buf_display, buf_display_end, wrap_width, ImDrawTextFlags_WrapKeepBlanks);
-            //draw_window->DrawList->AddText(g.Font, g.FontSize, draw_pos - draw_scroll, col, buf_display, buf_display_end, wrap_width, is_multiline ? NULL : &clip_rect.AsVec4());
+            ImGuiPlatformImeData* ime_data = &g.PlatformImeData; // (this is a public struct, passed to io.Platform_SetImeDataFn() handler)
+            ime_data->WantVisible = true;
+            ime_data->WantTextInput = true;
+            ime_data->InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
+            ime_data->InputLineHeight = g.FontSize;
+            ime_data->ViewportId = window->Viewport->ID;
         }
         }
     }
     }
 
 
@@ -7371,7 +7355,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
         //   - (2) usage will fail with clipped items
         //   - (2) usage will fail with clipped items
         //   The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
         //   The multi-select API aim to fix those issues, e.g. may be replaced with a BeginSelection() API.
         if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)
         if ((flags & ImGuiSelectableFlags_SelectOnNav) && g.NavJustMovedToId != 0 && g.NavJustMovedToFocusScopeId == g.CurrentFocusScopeId)
-            if (g.NavJustMovedToId == id)
+            if (g.NavJustMovedToId == id && (g.NavJustMovedToKeyMods & ImGuiMod_Ctrl) == 0)
                 selected = pressed = auto_selected = true;
                 selected = pressed = auto_selected = true;
     }
     }
 
 
@@ -7424,8 +7408,7 @@ bool ImGui::Selectable(const char* label, bool selected, ImGuiSelectableFlags fl
 
 
     // Automatically close popups
     // Automatically close popups
     if (pressed && !auto_selected && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups))
     if (pressed && !auto_selected && (window->Flags & ImGuiWindowFlags_Popup) && !(flags & ImGuiSelectableFlags_NoAutoClosePopups) && (g.LastItemData.ItemFlags & ImGuiItemFlags_AutoClosePopups))
-        if (!(flags & ImGuiSelectableFlags_SelectOnNav) || g.NavJustMovedToId != id)
-            CloseCurrentPopup();
+        CloseCurrentPopup();
 
 
     if (disabled_item && !disabled_global)
     if (disabled_item && !disabled_global)
         EndDisabled();
         EndDisabled();

+ 1 - 1
imstb_textedit.h

@@ -1154,7 +1154,7 @@ retry:
 #endif
 #endif
       case STB_TEXTEDIT_K_LINEEND: {
       case STB_TEXTEDIT_K_LINEEND: {
          stb_textedit_clamp(str, state);
          stb_textedit_clamp(str, state);
-         stb_textedit_move_to_first(state);
+         stb_textedit_move_to_last(str, state);
          state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
          state->cursor = STB_TEXTEDIT_MOVELINEEND(str, state, state->cursor);
          state->has_preferred_x = 0;
          state->has_preferred_x = 0;
          break;
          break;