1
0
Fork 0
mirror of https://github.com/ocornut/imgui.git synced 2026-01-11 00:04:24 +00:00

Merge branch 'master' into docking

This commit is contained in:
ocornut 2025-09-11 21:28:02 +02:00
commit 71f45c12e9
8 changed files with 234 additions and 228 deletions

View file

@ -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

View file

@ -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]

View file

@ -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();

View file

@ -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

View file

@ -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;

View file

@ -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; } void clear() { Offsets.clear(); EndOffset = 0; }
int size() { return LineOffsets.Size; } int size() { return Offsets.Size; }
const char* get_line_begin(const char* base, int n) { return base + LineOffsets[n]; } 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 < LineOffsets.Size ? (LineOffsets[n + 1] - 1) : EndOffset); } 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);

View file

@ -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; IM_ASSERT(state != NULL);
const char* text_end = text_begin + state->TextLen; state->LineCount = line_count;
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;
}
// 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 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 char* text_selected_end = text_begin + ImMax(state->Stb->select_start, state->Stb->select_end); 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. const char* text_selected_begin = buf_display + ImMin(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. const char* text_selected_end = buf_display + ImMax(state->Stb->select_start, state->Stb->select_end);
float bg_offy_dn = is_multiline ? 0.0f : 2.0f; for (int line_n = line_visible_n0; line_n < line_visible_n1; line_n++)
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)
{ {
if (rect_pos.y > clip_rect.Max.y + g.FontSize) const char* p = line_index->get_line_begin(buf_display, line_n);
break; const char* p_eol = line_index->get_line_end(buf_display, line_n);
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); const bool p_eol_is_wrap = (p_eol < buf_display_end && *p_eol != '\n');
if (p_eol == NULL) if (p_eol_is_wrap)
p_eol = text_selected_end; p_eol++;
const char* p_next = is_wordwrap ? (*p_eol == '\n' ? p_eol + 1 : p_eol) : (p_eol + 1); const char* line_selected_begin = (text_selected_begin > p) ? text_selected_begin : p;
if (rect_pos.y >= clip_rect.Min.y) const char* line_selected_end = (text_selected_end < p_eol) ? text_selected_end : 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;
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;
}
}
// 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. float rect_width = 0.0f;
// FIXME-OPT: Multiline could submit a smaller amount of contents to AddText() since we already iterated through it. if (line_selected_begin < line_selected_end)
if (is_multiline || (buf_display_end - buf_display) < buf_display_max_length) 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)
ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); rect_width += bg_eol_width; // So we can see selected empty lines
if (col & IM_COL32_A_MASK) if (rect_width == 0.0f)
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); continue;
//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());
}
// Draw blinking cursor ImRect rect;
if (render_cursor) 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;
state->CursorAnim += io.DeltaTime; rect.Max.x = rect.Min.x + rect_width;
bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; rect.Max.y = rect.Min.y + bg_offy_dn + g.FontSize;
ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); rect.Min.y -= bg_offy_up;
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); rect.ClipWith(clip_rect);
if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color);
draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_InputTextCursor), 1.0f); // FIXME-DPI: Cursor thickness (#7031)
// 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
// 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?
// 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);
// Render blinking cursor
if (render_cursor)
{ {
// Render text only (no selection, no cursor) state->CursorAnim += io.DeltaTime;
if (is_multiline) bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f;
text_size = ImVec2(inner_size.x, InputTextCalcTextLenAndLineCount(&g, buf_display, &buf_display_end, wrap_width) * g.FontSize); // We don't need width ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll);
else if (!is_displaying_hint && g.ActiveId == id) 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);
buf_display_end = buf_display + state->TextLen; if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
else if (!is_displaying_hint) draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_InputTextCursor), 1.0f); // FIXME-DPI: Cursor thickness (#7031)
buf_display_end = buf_display + ImStrlen(buf_display);
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 ImGuiPlatformImeData* ime_data = &g.PlatformImeData; // (this is a public struct, passed to io.Platform_SetImeDataFn() handler)
if (flags & ImGuiInputTextFlags_ElideLeft) ime_data->WantVisible = true;
draw_pos.x = ImMin(draw_pos.x, frame_bb.Max.x - CalcTextSize(buf_display, NULL).x - style.FramePadding.x); ime_data->WantTextInput = true;
ime_data->InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
const ImVec2 draw_scroll = /*state ? ImVec2(state->Scroll.x, 0.0f) :*/ ImVec2(0.0f, 0.0f); // Preserve scroll when inactive? ime_data->InputLineHeight = g.FontSize;
ImU32 col = GetColorU32(is_displaying_hint ? ImGuiCol_TextDisabled : ImGuiCol_Text); ime_data->ViewportId = window->Viewport->ID;
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());
} }
} }
@ -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();

View file

@ -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;