From 0df7b472c25487336abdf57e2f4ff41bc54674eb Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2015 15:46:45 -0600 Subject: [PATCH 01/44] InputTextMultiline() WIP - still gazillion problems with selection visualisation & scrolling --- imgui.cpp | 242 +++++++++++++++++++++++++++++++++--------------------- imgui.h | 10 ++- 2 files changed, 155 insertions(+), 97 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index f71d54f84..c15a34637 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -495,6 +495,7 @@ namespace IMGUI_STB_NAMESPACE #undef STB_TEXTEDIT_CHARTYPE #define STB_TEXTEDIT_STRING ImGuiTextEditState #define STB_TEXTEDIT_CHARTYPE ImWchar +#define STB_TEXTEDIT_GETWIDTH_NEWLINE -1.0f #include "stb_textedit.h" #ifdef __clang__ @@ -1187,7 +1188,7 @@ struct ImGuiTextEditState char InitialText[1024*3+1]; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) size_t CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. size_t BufSizeA; // end-user buffer size, <= 1024 (or increase above) - float Width; // widget width + ImVec2 Size; // widget width/height float ScrollX; STB_TexteditState StbState; float CursorAnim; @@ -1201,7 +1202,7 @@ struct ImGuiTextEditState void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking bool CursorIsVisible() const { return CursorAnim <= 0.0f || fmodf(CursorAnim, 1.20f) <= 0.80f; } // Blinking bool HasSelection() const { return StbState.select_start != StbState.select_end; } - void SelectAll() { StbState.select_start = 0; StbState.select_end = (int)ImStrlenW(Text); StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; } + void SelectAll() { StbState.select_start = 0; StbState.select_end = (int)CurLenW; StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; } void OnKeyPressed(int key); void UpdateScrollOffset(); @@ -1209,8 +1210,8 @@ struct ImGuiTextEditState // Static functions because they are used to render non-focused instances of a text input box static const char* GetTextPointerClippedA(ImFont* font, float font_size, const char* text, float width, ImVec2* out_text_size = NULL); - static const ImWchar* GetTextPointerClippedW(ImFont* font, float font_size, const ImWchar* text, float width, ImVec2* out_text_size = NULL); - static void RenderTextScrolledClipped(ImFont* font, float font_size, const char* text, ImVec2 pos_base, float width, float scroll_x); + static const ImWchar* GetTextPointerClippedW(ImFont* font, float font_size, const ImWchar* text, const ImWchar* text_end, float width, ImVec2* out_text_size = NULL); + static void RenderTextScrolledClipped(ImFont* font, float font_size, const char* text, const ImVec2& pos, const ImVec2& size, float scroll_x); }; // Data saved in imgui.ini file @@ -6511,16 +6512,71 @@ bool ImGui::RadioButton(const char* label, int* v, int v_button) return pressed; } +static ImVec2 CalcTextSizeW(ImFont* font, float font_size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false) +{ + IM_ASSERT(text_end); + + const float scale = font_size / font->FontSize; + const float line_height = font->FontSize * scale; + + ImVec2 text_size = ImVec2(0,0); + float line_width = 0.0f; + + const ImWchar* s = text_begin; + while (s < text_end) + { + const unsigned int c = (unsigned int)(*s++); + + if (c < 32) + { + if (c == '\n') + { + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + if (stop_on_new_line) + break; + continue; + } + if (c == '\r') + continue; + } + + const float char_width = font->GetCharAdvance((unsigned short)c); + if (line_width + char_width >= max_width) + { + s--; + break; + } + + line_width += char_width; + } + + if (text_size.x < line_width) + text_size.x = line_width; + + if (out_offset) + *out_offset = ImVec2(line_width, text_size.y + line_height); // offset allow for the possibility of sitting after a trailing \n + + if (line_width > 0 || text_size.y == 0.0f) // whereas size.y will ignore the trailing \n + text_size.y += line_height; + + if (remaining) + *remaining = s; + + return text_size; +} + // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) -static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return (int)ImStrlenW(obj->Text); } +static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return (int)obj->CurLenW; } static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->Text[idx]; } -static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { (void)line_start_idx; return obj->Font->CalcTextSizeW(obj->FontSize, FLT_MAX, &obj->Text[char_idx], &obj->Text[char_idx]+1, NULL).x; } +static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar* s = &obj->Text[line_start_idx+char_idx]; if (*s == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return CalcTextSizeW(obj->Font, obj->FontSize, FLT_MAX, s, s+1, NULL).x; } static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; } static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) { const ImWchar* text_remaining = NULL; - const ImVec2 size = obj->Font->CalcTextSizeW(obj->FontSize, FLT_MAX, obj->Text + line_start_idx, NULL, &text_remaining); + const ImVec2 size = CalcTextSizeW(obj->Font, obj->FontSize, FLT_MAX, obj->Text + line_start_idx, obj->Text + obj->CurLenW, &text_remaining, NULL, true); r->x0 = 0.0f; r->x1 = size.x; r->baseline_y_delta = size.y; @@ -6603,14 +6659,14 @@ void ImGuiTextEditState::OnKeyPressed(int key) void ImGuiTextEditState::UpdateScrollOffset() { // Scroll in chunks of quarter width - const float scroll_x_increment = Width * 0.25f; - const float cursor_offset_x = Font->CalcTextSizeW(FontSize, FLT_MAX, Text, Text+StbState.cursor, NULL).x; + const float scroll_x_increment = Size.x * 0.25f; + const float cursor_offset_x = CalcTextSizeW(Font, FontSize, FLT_MAX, Text, Text+StbState.cursor, NULL).x; // If widget became bigger than text (because of a resize), reset horizontal scrolling if (ScrollX > 0.0f) { - const float text_width = cursor_offset_x + Font->CalcTextSizeW(FontSize, FLT_MAX, Text+StbState.cursor, NULL, NULL).x; - if (text_width < Width) + const float text_width = cursor_offset_x + CalcTextSizeW(Font, FontSize, FLT_MAX, Text+StbState.cursor, NULL, NULL).x; + if (text_width < Size.x) { ScrollX = 0.0f; return; @@ -6619,17 +6675,19 @@ void ImGuiTextEditState::UpdateScrollOffset() if (cursor_offset_x < ScrollX) ScrollX = ImMax(0.0f, cursor_offset_x - scroll_x_increment); - else if (cursor_offset_x - Width >= ScrollX) - ScrollX = cursor_offset_x - Width + scroll_x_increment; + else if (cursor_offset_x - Size.x >= ScrollX) + ScrollX = cursor_offset_x - Size.x + scroll_x_increment; } ImVec2 ImGuiTextEditState::CalcDisplayOffsetFromCharIdx(int i) const { - const ImWchar* text_start = GetTextPointerClippedW(Font, FontSize, Text, ScrollX, NULL); + const ImWchar* text_start = GetTextPointerClippedW(Font, FontSize, Text, Text+CurLenW, ScrollX, NULL); const ImWchar* text_end = (Text+i >= text_start) ? Text+i : text_start; // Clip if requested character is outside of display IM_ASSERT(text_end >= text_start); - const ImVec2 offset = Font->CalcTextSizeW(FontSize, Width+1, text_start, text_end, NULL); + // FIXME-WIP-MULTILINE + ImVec2 offset; + CalcTextSizeW(Font, FontSize, Size.x+1, text_start, text_end, NULL, &offset); return offset; } @@ -6647,20 +6705,20 @@ const char* ImGuiTextEditState::GetTextPointerClippedA(ImFont* font, float font_ } // [Static] -const ImWchar* ImGuiTextEditState::GetTextPointerClippedW(ImFont* font, float font_size, const ImWchar* text, float width, ImVec2* out_text_size) +const ImWchar* ImGuiTextEditState::GetTextPointerClippedW(ImFont* font, float font_size, const ImWchar* text, const ImWchar* text_end, float width, ImVec2* out_text_size) { if (width <= 0.0f) return text; const ImWchar* text_clipped_end = NULL; - const ImVec2 text_size = font->CalcTextSizeW(font_size, width, text, NULL, &text_clipped_end); + const ImVec2 text_size = CalcTextSizeW(font, font_size, width, text, text_end, &text_clipped_end); if (out_text_size) *out_text_size = text_size; return text_clipped_end; } // [Static] -void ImGuiTextEditState::RenderTextScrolledClipped(ImFont* font, float font_size, const char* buf, ImVec2 pos, float width, float scroll_x) +void ImGuiTextEditState::RenderTextScrolledClipped(ImFont* font, float font_size, const char* buf, const ImVec2& pos, const ImVec2& size, float scroll_x) { ImGuiWindow* window = GetCurrentWindow(); const ImU32 font_color = window->Color(ImGuiCol_Text); @@ -6669,7 +6727,7 @@ void ImGuiTextEditState::RenderTextScrolledClipped(ImFont* font, float font_size // Determine start and end of visible string // FIXME-OPT: This is pretty slow for what it does. const char* text_start = scroll_x <= 0.0f ? buf : GetTextPointerClippedA(font, font_size, buf, scroll_x, NULL); - const char* text_end = GetTextPointerClippedA(font, font_size, text_start, width + 1, NULL); // +1 to allow character spacing to fit outside the allowed width + const char* text_end = GetTextPointerClippedA(font, font_size, text_start, size.x + 1, NULL); // +1 to allow character spacing to fit outside the allowed width window->DrawList->AddText(font, font_size, pos, font_color, text_start, text_end); // Log as text @@ -6793,7 +6851,13 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f unsigned int c = *p_char; if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF))) - return false; + { + bool pass = false; + pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); + pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput)); + if (!pass) + return false; + } if (c >= 0xE000 && c <= 0xF8FF) // Filter private Unicode range. I don't imagine anybody would want to input them. GLFW on OSX seems to send private characters for special keys like arrow keys. return false; @@ -6836,21 +6900,30 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f } // Edit a string of text -bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImVec2& size_arg, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) { ImGuiWindow* window = GetCurrentWindow(); if (window->SkipItems) return false; + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackHistory) && (flags & ImGuiInputTextFlags_Multiline))); // Can't use both together (they both use up/down keys) + IM_ASSERT(!((flags & ImGuiInputTextFlags_CallbackCompletion) && (flags & ImGuiInputTextFlags_AllowTabInput))); // Can't use both together (they both use tab key) + ImGuiState& g = *GImGui; const ImGuiIO& io = g.IO; const ImGuiStyle& style = g.Style; const ImGuiID id = window->GetID(label); - const float w = ImGui::CalcItemWidth(); + const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; - const ImVec2 label_size = CalcTextSize(label, NULL, true); - const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y) + style.FramePadding*2.0f); + ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); + ImVec2 size = size_arg; + if (size.x == 0.0f) + size.x = ImGui::CalcItemWidth(); + if (size.y == 0.0f) + size.y = is_multiline ? label_size.y * 8.0f : label_size.y; // Arbitrary default + + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size + style.FramePadding*2.0f); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f)); ItemSize(total_bb, style.FramePadding.y); if (!ItemAdd(total_bb, &id)) @@ -6862,7 +6935,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT const bool is_ctrl_down = io.KeyCtrl; const bool is_shift_down = io.KeyShift; const bool is_alt_down = io.KeyAlt; - const bool focus_requested = window->FocusItemRegister(g.ActiveId == id, (flags & ImGuiInputTextFlags_CallbackCompletion) == 0); // Using completion callback disable keyboard tabbing + const bool focus_requested = window->FocusItemRegister(g.ActiveId == id, (flags & (ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_AllowTabInput)) == 0); // Using completion callback disable keyboard tabbing const bool focus_requested_by_code = focus_requested && (window->FocusIdxAllCounter == window->FocusIdxAllRequestCurrent); const bool focus_requested_by_tab = focus_requested && !focus_requested_by_code; @@ -6886,7 +6959,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT const char* buf_end = NULL; edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text, IM_ARRAYSIZE(edit_state.Text), buf, NULL, &buf_end); edit_state.CurLenA = buf_end - buf; // We can't get the result from ImFormatString() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. - edit_state.Width = w + style.FramePadding.x; + edit_state.Size = size + style.FramePadding; edit_state.InputCursorScreenPos = ImVec2(-1.f,-1.f); edit_state.CursorAnimReset(); @@ -6894,7 +6967,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT { edit_state.Id = id; edit_state.ScrollX = 0.0f; - stb_textedit_initialize_state(&edit_state.StbState, true); + stb_textedit_initialize_state(&edit_state.StbState, !is_multiline); if (focus_requested_by_code) select_all = true; } @@ -6931,7 +7004,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT //if (edit_state.Id == id) // Works, but double-click to select-all sets cursors to end which in turn tends to scroll toward the right when shrinking widget. { // Update some data if we are active or last active - edit_state.Width = w + style.FramePadding.x; + edit_state.Size = size + style.FramePadding; edit_state.BufSizeA = buf_size; edit_state.Font = g.Font; edit_state.FontSize = g.FontSize; @@ -6941,7 +7014,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT { // Edit in progress const float mx = g.IO.MousePos.x - frame_bb.Min.x - style.FramePadding.x; - const float my = g.FontSize*0.5f; // Flatten mouse because we are doing a single-line edit + const float my = is_multiline ? (g.IO.MousePos.y - frame_bb.Min.y - style.FramePadding.y) : g.FontSize*0.5f; if (select_all || (hovered && io.MouseDoubleClicked[0])) { @@ -6966,8 +7039,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT // Process text input (before we check for Return because using some IME will effectively send a Return?) for (int n = 0; n < IM_ARRAYSIZE(g.IO.InputCharacters) && g.IO.InputCharacters[n]; n++) { - unsigned int c = (unsigned int)g.IO.InputCharacters[n]; - if (c) + if (unsigned int c = (unsigned int)g.IO.InputCharacters[n]) { // Insert character if they pass filtering if (!InputTextFilterCharacter(&c, flags, callback, user_data)) @@ -6982,13 +7054,35 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT const int k_mask = (is_shift_down ? STB_TEXTEDIT_K_SHIFT : 0); const bool is_ctrl_only = is_ctrl_down && !is_alt_down && !is_shift_down; - if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDLEFT | k_mask : STB_TEXTEDIT_K_LEFT | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDRIGHT | k_mask : STB_TEXTEDIT_K_RIGHT | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Delete)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Backspace)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); } - else if (IsKeyPressedMap(ImGuiKey_Enter)) { SetActiveId(0); enter_pressed = true; } + if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDLEFT | k_mask : STB_TEXTEDIT_K_LEFT | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDRIGHT | k_mask : STB_TEXTEDIT_K_RIGHT | k_mask); } + else if (is_multiline && IsKeyPressedMap(ImGuiKey_UpArrow)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UP | k_mask); } + else if (is_multiline && IsKeyPressedMap(ImGuiKey_DownArrow)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DOWN| k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Delete)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Backspace)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_BACKSPACE | k_mask); } + else if (IsKeyPressedMap(ImGuiKey_Enter)) + { + bool ctrl_enter_for_new_line = (flags & ImGuiInputTextFlags_CtrlEnterForNewLine) != 0; + if (!is_multiline || (ctrl_enter_for_new_line && !is_ctrl_down) || (!ctrl_enter_for_new_line && is_ctrl_down)) + { + SetActiveId(0); + enter_pressed = true; + } + else // New line + { + unsigned int c = '\n'; + if (InputTextFilterCharacter(&c, flags, callback, user_data)) + edit_state.OnKeyPressed((int)c); + } + } + else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab)) + { + unsigned int c = '\t'; + if (InputTextFilterCharacter(&c, flags, callback, user_data)) + edit_state.OnKeyPressed((int)c); + } else if (IsKeyPressedMap(ImGuiKey_Escape)) { SetActiveId(0); cancel_edit = true; } else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_Z)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); } else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_Y)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); } @@ -7146,7 +7240,7 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT //const float render_scroll_x = (g.ActiveId == id) ? edit_state.ScrollX : 0.0f; const float render_scroll_x = (edit_state.Id == id) ? edit_state.ScrollX : 0.0f; - ImGuiTextEditState::RenderTextScrolledClipped(g.Font, g.FontSize, buf, frame_bb.Min + style.FramePadding, w + style.FramePadding.x, render_scroll_x); + ImGuiTextEditState::RenderTextScrolledClipped(g.Font, g.FontSize, buf, frame_bb.Min + style.FramePadding, size + style.FramePadding, render_scroll_x); if (g.ActiveId == id) { @@ -7172,6 +7266,19 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT return value_changed; } +bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + IM_ASSERT(!(flags & ImGuiInputTextFlags_Multiline)); // call InputTextMultiline() + bool ret = InputTextEx(label, buf, buf_size, ImVec2(0,0), flags, callback, user_data); + return ret; +} + +bool ImGui::InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiTextEditCallback callback, void* user_data) +{ + bool ret = InputTextEx(label, buf, buf_size, size, flags | ImGuiInputTextFlags_Multiline, callback, user_data); + return ret; +} + static bool InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags) { ImGuiState& g = *GImGui; @@ -9874,64 +9981,11 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons line_width += char_width; } - if (line_width > 0 || text_size.y == 0.0f) - { - if (text_size.x < line_width) - text_size.x = line_width; - text_size.y += line_height; - } - - if (remaining) - *remaining = s; - - return text_size; -} - -ImVec2 ImFont::CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining) const -{ - if (!text_end) - text_end = text_begin + ImStrlenW(text_begin); - - const float scale = size / FontSize; - const float line_height = FontSize * scale; - - ImVec2 text_size = ImVec2(0,0); - float line_width = 0.0f; - - const ImWchar* s = text_begin; - while (s < text_end) - { - const unsigned int c = (unsigned int)(*s++); - - if (c < 32) - { - if (c == '\n') - { - text_size.x = ImMax(text_size.x, line_width); - text_size.y += line_height; - line_width = 0.0f; - continue; - } - if (c == '\r') - continue; - } - - const float char_width = ((size_t)c < IndexXAdvance.size()) ? IndexXAdvance[(size_t)c] * scale : FallbackXAdvance; - if (line_width + char_width >= max_width) - { - s--; - break; - } - - line_width += char_width; - } + if (text_size.x < line_width) + text_size.x = line_width; if (line_width > 0 || text_size.y == 0.0f) - { - if (text_size.x < line_width) - text_size.x = line_width; text_size.y += line_height; - } if (remaining) *remaining = s; diff --git a/imgui.h b/imgui.h index a5a94e066..785d56b7a 100644 --- a/imgui.h +++ b/imgui.h @@ -267,6 +267,7 @@ namespace ImGui // Widgets: Input IMGUI_API bool InputText(const char* label, char* buf, size_t buf_size, ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); + IMGUI_API bool InputTextMultiline(const char* label, char* buf, size_t buf_size, const ImVec2& size = ImVec2(0,0), ImGuiInputTextFlags flags = 0, ImGuiTextEditCallback callback = NULL, void* user_data = NULL); IMGUI_API bool InputFloat(const char* label, float* v, float step = 0.0f, float step_fast = 0.0f, int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); IMGUI_API bool InputFloat2(const char* label, float v[2], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); IMGUI_API bool InputFloat3(const char* label, float v[3], int decimal_precision = -1, ImGuiInputTextFlags extra_flags = 0); @@ -441,7 +442,11 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_CallbackCompletion = 1 << 6, // Call user function on pressing TAB (for completion handling) ImGuiInputTextFlags_CallbackHistory = 1 << 7, // Call user function on pressing Up/Down arrows (for history handling) ImGuiInputTextFlags_CallbackAlways = 1 << 8, // Call user function every time - ImGuiInputTextFlags_CallbackCharFilter = 1 << 9 // Call user function to filter character. Modify data->EventChar to replace/filter input, or return 1 to discard character. + ImGuiInputTextFlags_CallbackCharFilter = 1 << 9, // Call user function to filter character. Modify data->EventChar to replace/filter input, or return 1 to discard character. + ImGuiInputTextFlags_AllowTabInput = 1 << 10, // Pressing TAB input a '\t' character into the text field + ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 11, // In multi-line mode, allow exiting edition by pressing Enter. Ctrl+Enter to add new line. + // [Internal] + ImGuiInputTextFlags_Multiline = 1 << 20 // For internal use by InputTextMultiline() }; // Flags for ImGui::Selectable() @@ -1135,8 +1140,7 @@ struct ImFont // 'max_width' stops rendering after a certain width (could be turned into a 2d size). FLT_MAX to disable. // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. - IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 - IMGUI_API ImVec2 CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL) const; // wchar + IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 IMGUI_API void RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawList* draw_list, float wrap_width = 0.0f, const ImVec2* cpu_clip_max = NULL) const; IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; }; From 34986771b4c2ba556d7b7326f662aa79261f0a7d Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2015 15:57:04 -0600 Subject: [PATCH 02/44] Moved InputFloat() InputInt() below InputText() and not in the middle of internal bits --- imgui.cpp | 140 +++++++++++++++++++++++++++--------------------------- 1 file changed, 70 insertions(+), 70 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index f71d54f84..91c607638 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6677,76 +6677,6 @@ void ImGuiTextEditState::RenderTextScrolledClipped(ImFont* font, float font_size LogText(pos, buf, NULL); } -bool ImGui::InputFloat(const char* label, float *v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags) -{ - ImGuiState& g = *GImGui; - ImGuiWindow* window = GetCurrentWindow(); - if (window->SkipItems) - return false; - - const ImGuiStyle& style = g.Style; - const float w = ImGui::CalcItemWidth(); - const ImVec2 label_size = CalcTextSize(label, NULL, true); - const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y) + style.FramePadding*2.0f); - - ImGui::BeginGroup(); - ImGui::PushID(label); - const ImVec2 button_sz = ImVec2(g.FontSize, g.FontSize) + style.FramePadding * 2; - if (step > 0.0f) - ImGui::PushItemWidth(ImMax(1.0f, w - (button_sz.x + style.ItemInnerSpacing.x)*2)); - - char buf[64]; - if (decimal_precision < 0) - ImFormatString(buf, IM_ARRAYSIZE(buf), "%f", *v); // Ideally we'd have a minimum decimal precision of 1 to visually denote that it is a float, while hiding non-significant digits? - else - ImFormatString(buf, IM_ARRAYSIZE(buf), "%.*f", decimal_precision, *v); - bool value_changed = false; - const ImGuiInputTextFlags flags = extra_flags | (ImGuiInputTextFlags_CharsDecimal|ImGuiInputTextFlags_AutoSelectAll); - if (ImGui::InputText("", buf, IM_ARRAYSIZE(buf), flags)) - { - ApplyNumericalTextInput(buf, v); - value_changed = true; - } - - // Step buttons - if (step > 0.0f) - { - ImGui::PopItemWidth(); - ImGui::SameLine(0, (int)style.ItemInnerSpacing.x); - if (ButtonEx("-", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) - { - *v -= g.IO.KeyCtrl && step_fast > 0.0f ? step_fast : step; - value_changed = true; - } - ImGui::SameLine(0, (int)style.ItemInnerSpacing.x); - if (ButtonEx("+", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) - { - *v += g.IO.KeyCtrl && step_fast > 0.0f ? step_fast : step; - value_changed = true; - } - } - ImGui::PopID(); - - if (label_size.x > 0) - { - ImGui::SameLine(0, (int)style.ItemInnerSpacing.x); - RenderText(ImVec2(window->DC.CursorPos.x, window->DC.CursorPos.y + style.FramePadding.y), label); - ItemSize(label_size, style.FramePadding.y); - } - ImGui::EndGroup(); - - return value_changed; -} - -bool ImGui::InputInt(const char* label, int *v, int step, int step_fast, ImGuiInputTextFlags extra_flags) -{ - float f = (float)*v; - const bool value_changed = ImGui::InputFloat(label, &f, (float)step, (float)step_fast, 0, extra_flags); - if (value_changed) - *v = (int)f; - return value_changed; -} - // Public API to manipulate UTF-8 text // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) void ImGuiTextEditCallbackData::DeleteChars(int pos, int bytes_count) @@ -7172,6 +7102,76 @@ bool ImGui::InputText(const char* label, char* buf, size_t buf_size, ImGuiInputT return value_changed; } +bool ImGui::InputFloat(const char* label, float *v, float step, float step_fast, int decimal_precision, ImGuiInputTextFlags extra_flags) +{ + ImGuiState& g = *GImGui; + ImGuiWindow* window = GetCurrentWindow(); + if (window->SkipItems) + return false; + + const ImGuiStyle& style = g.Style; + const float w = ImGui::CalcItemWidth(); + const ImVec2 label_size = CalcTextSize(label, NULL, true); + const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + ImVec2(w, label_size.y) + style.FramePadding*2.0f); + + ImGui::BeginGroup(); + ImGui::PushID(label); + const ImVec2 button_sz = ImVec2(g.FontSize, g.FontSize) + style.FramePadding * 2; + if (step > 0.0f) + ImGui::PushItemWidth(ImMax(1.0f, w - (button_sz.x + style.ItemInnerSpacing.x)*2)); + + char buf[64]; + if (decimal_precision < 0) + ImFormatString(buf, IM_ARRAYSIZE(buf), "%f", *v); // Ideally we'd have a minimum decimal precision of 1 to visually denote that it is a float, while hiding non-significant digits? + else + ImFormatString(buf, IM_ARRAYSIZE(buf), "%.*f", decimal_precision, *v); + bool value_changed = false; + const ImGuiInputTextFlags flags = extra_flags | (ImGuiInputTextFlags_CharsDecimal|ImGuiInputTextFlags_AutoSelectAll); + if (ImGui::InputText("", buf, IM_ARRAYSIZE(buf), flags)) + { + ApplyNumericalTextInput(buf, v); + value_changed = true; + } + + // Step buttons + if (step > 0.0f) + { + ImGui::PopItemWidth(); + ImGui::SameLine(0, (int)style.ItemInnerSpacing.x); + if (ButtonEx("-", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) + { + *v -= g.IO.KeyCtrl && step_fast > 0.0f ? step_fast : step; + value_changed = true; + } + ImGui::SameLine(0, (int)style.ItemInnerSpacing.x); + if (ButtonEx("+", button_sz, ImGuiButtonFlags_Repeat | ImGuiButtonFlags_DontClosePopups)) + { + *v += g.IO.KeyCtrl && step_fast > 0.0f ? step_fast : step; + value_changed = true; + } + } + ImGui::PopID(); + + if (label_size.x > 0) + { + ImGui::SameLine(0, (int)style.ItemInnerSpacing.x); + RenderText(ImVec2(window->DC.CursorPos.x, window->DC.CursorPos.y + style.FramePadding.y), label); + ItemSize(label_size, style.FramePadding.y); + } + ImGui::EndGroup(); + + return value_changed; +} + +bool ImGui::InputInt(const char* label, int *v, int step, int step_fast, ImGuiInputTextFlags extra_flags) +{ + float f = (float)*v; + const bool value_changed = ImGui::InputFloat(label, &f, (float)step, (float)step_fast, 0, extra_flags); + if (value_changed) + *v = (int)f; + return value_changed; +} + static bool InputFloatN(const char* label, float* v, int components, int decimal_precision, ImGuiInputTextFlags extra_flags) { ImGuiState& g = *GImGui; From a248575dea675651730b4f3ad25ec363deb82373 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2015 17:21:07 -0600 Subject: [PATCH 03/44] Text rendering can be finely clipped cpu-side on top and left axises (for #200) --- imgui.cpp | 103 +++++++++++++++++++++++++++++++++--------------------- imgui.h | 4 +-- 2 files changed, 65 insertions(+), 42 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 91c607638..3c16c9a74 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -526,7 +526,7 @@ static void LogText(const ImVec2& ref_pos, const char* text, const char* static void RenderText(ImVec2 pos, const char* text, const char* text_end = NULL, bool hide_text_after_hash = true); static void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end, float wrap_width); -static void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2* clip_max = NULL, ImGuiAlign align = ImGuiAlign_Default); +static void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, ImGuiAlign align = ImGuiAlign_Default, const ImVec2* clip_min = NULL, const ImVec2* clip_max = NULL); static void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); static void RenderCollapseTriangle(ImVec2 p_min, bool opened, float scale = 1.0f, bool shadow = false); static void RenderCheckMark(ImVec2 pos, ImU32 col); @@ -2620,7 +2620,7 @@ static void RenderTextWrapped(ImVec2 pos, const char* text, const char* text_end } } -static void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2* clip_max, ImGuiAlign align) +static void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, ImGuiAlign align, const ImVec2* clip_min, const ImVec2* clip_max) { // Hide anything after a '##' string const char* text_display_end = FindTextDisplayEnd(text, text_end); @@ -2632,10 +2632,12 @@ static void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, cons ImGuiWindow* window = GetCurrentWindow(); // Perform CPU side clipping for single clipped element to avoid using scissor state - if (!clip_max) clip_max = &pos_max; ImVec2 pos = pos_min; const ImVec2 text_size = text_size_if_known ? *text_size_if_known : ImGui::CalcTextSize(text, text_display_end, false, 0.0f); - const bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y); + + if (!clip_max) clip_max = &pos_max; + bool need_clipping = (pos.x + text_size.x >= clip_max->x) || (pos.y + text_size.y >= clip_max->y); + if (!clip_min) clip_min = &pos_min; else need_clipping |= (pos.x < clip_min->x) || (pos.y < clip_min->y); // Align if (align & ImGuiAlign_Center) pos.x = ImMax(pos.x, (pos.x + pos_max.x - text_size.x) * 0.5f); @@ -2643,7 +2645,15 @@ static void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, cons if (align & ImGuiAlign_VCenter) pos.y = ImMax(pos.y, (pos.y + pos_max.y - text_size.y) * 0.5f); // Render - window->DrawList->AddText(g.Font, g.FontSize, pos, window->Color(ImGuiCol_Text), text, text_display_end, 0.0f, need_clipping ? clip_max : NULL); + if (need_clipping) + { + ImVec4 fine_clip_rect(clip_min->x, clip_min->y, clip_max->x, clip_max->y); + window->DrawList->AddText(g.Font, g.FontSize, pos, window->Color(ImGuiCol_Text), text, text_display_end, 0.0f, &fine_clip_rect); + } + else + { + window->DrawList->AddText(g.Font, g.FontSize, pos, window->Color(ImGuiCol_Text), text, text_display_end, 0.0f, NULL); + } if (g.LogEnabled) LogText(pos, text, text_display_end); } @@ -3888,7 +3898,7 @@ bool ImGui::Begin(const char* name, bool* p_opened, const ImVec2& size_on_first_ if (style.WindowTitleAlign & ImGuiAlign_Center) pad_right = pad_left; if (pad_left) text_min.x += g.FontSize + style.ItemInnerSpacing.x; if (pad_right) text_max.x -= g.FontSize + style.ItemInnerSpacing.x; - RenderTextClipped(text_min, text_max, name, NULL, &text_size, &clip_max, style.WindowTitleAlign); + RenderTextClipped(text_min, text_max, name, NULL, &text_size, style.WindowTitleAlign, NULL, &clip_max); } // Save clipped aabb so we can access it in constant-time in FindHoveredWindow() @@ -4874,7 +4884,7 @@ void ImGui::LabelTextV(const char* label, const char* fmt, va_list args) // Render const char* value_text_begin = &g.TempBuffer[0]; const char* value_text_end = value_text_begin + ImFormatStringV(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), fmt, args); - RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, NULL, ImGuiAlign_VCenter); + RenderTextClipped(value_bb.Min, value_bb.Max, value_text_begin, value_text_end, NULL, ImGuiAlign_VCenter); RenderText(ImVec2(value_bb.Max.x + style.ItemInnerSpacing.x, value_bb.Min.y + style.FramePadding.y), label); } @@ -4997,7 +5007,7 @@ static bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0,0), Im // Render const ImU32 col = window->Color((hovered && held) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button); RenderFrame(bb.Min, bb.Max, col, true, style.FrameRounding); - RenderTextClipped(bb.Min, bb.Max, label, NULL, &label_size, NULL, ImGuiAlign_Center | ImGuiAlign_VCenter); + RenderTextClipped(bb.Min, bb.Max, label, NULL, &label_size, ImGuiAlign_Center | ImGuiAlign_VCenter); // Automatically close popups //if (pressed && !(flags & ImGuiButtonFlags_DontClosePopups) && (window->Flags & ImGuiWindowFlags_Popup)) @@ -5842,7 +5852,7 @@ bool ImGui::SliderFloat(const char* label, float* v, float v_min, float v_max, c // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. char value_buf[64]; const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); - RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, NULL, ImGuiAlign_Center|ImGuiAlign_VCenter); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImGuiAlign_Center|ImGuiAlign_VCenter); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); @@ -5890,7 +5900,7 @@ bool ImGui::VSliderFloat(const char* label, const ImVec2& size, float* v, float // For the vertical slider we allow centered text to overlap the frame padding char value_buf[64]; char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); - RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, NULL, ImGuiAlign_Center); + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, value_buf, value_buf_end, NULL, ImGuiAlign_Center); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); @@ -6145,7 +6155,7 @@ bool ImGui::DragFloat(const char* label, float *v, float v_speed, float v_min, f // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. char value_buf[64]; const char* value_buf_end = value_buf + ImFormatString(value_buf, IM_ARRAYSIZE(value_buf), display_format, *v); - RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, NULL, ImGuiAlign_Center|ImGuiAlign_VCenter); + RenderTextClipped(frame_bb.Min, frame_bb.Max, value_buf, value_buf_end, NULL, ImGuiAlign_Center|ImGuiAlign_VCenter); if (label_size.x > 0.0f) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); @@ -6345,7 +6355,7 @@ static void Plot(ImGuiPlotType plot_type, const char* label, float (*values_gett // Text overlay if (overlay_text) - RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, NULL, ImGuiAlign_Center); + RenderTextClipped(ImVec2(frame_bb.Min.x, frame_bb.Min.y + style.FramePadding.y), frame_bb.Max, overlay_text, NULL, NULL, ImGuiAlign_Center); RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, inner_bb.Min.y), label); } @@ -8854,7 +8864,7 @@ void ImDrawList::AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, } } -void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec2* cpu_clip_max) +void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) { if ((col >> 24) == 0) return; @@ -8872,7 +8882,15 @@ void ImDrawList::AddText(const ImFont* font, float font_size, const ImVec2& pos, const size_t vtx_begin = vtx_buffer.size(); PrimReserve(vtx_count_max); - font->RenderText(font_size, pos, col, clip_rect_stack.back(), text_begin, text_end, this, wrap_width, cpu_clip_max); + ImVec4 clip_rect = clip_rect_stack.back(); + if (cpu_fine_clip_rect) + { + clip_rect.x = ImMax(clip_rect.x, cpu_fine_clip_rect->x); + clip_rect.y = ImMax(clip_rect.y, cpu_fine_clip_rect->y); + clip_rect.z = ImMin(clip_rect.z, cpu_fine_clip_rect->z); + clip_rect.w = ImMin(clip_rect.w, cpu_fine_clip_rect->w); + } + font->RenderText(font_size, pos, col, clip_rect, text_begin, text_end, this, wrap_width, cpu_fine_clip_rect != NULL); // give back unused vertices vtx_buffer.resize((size_t)(vtx_write - &vtx_buffer.front())); @@ -9939,7 +9957,7 @@ ImVec2 ImFont::CalcTextSizeW(float size, float max_width, const ImWchar* text_be return text_size; } -void ImFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect_ref, const char* text_begin, const char* text_end, ImDrawList* draw_list, float wrap_width, const ImVec2* cpu_clip_max) const +void ImFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawList* draw_list, float wrap_width, bool cpu_fine_clip) const { if (!text_end) text_end = text_begin + strlen(text_begin); @@ -9950,19 +9968,12 @@ void ImFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_re // Align to be pixel perfect pos.x = (float)(int)pos.x + DisplayOffset.x; pos.y = (float)(int)pos.y + DisplayOffset.y; + float x = pos.x; + float y = pos.y; const bool word_wrap_enabled = (wrap_width > 0.0f); const char* word_wrap_eol = NULL; - ImVec4 clip_rect = clip_rect_ref; - if (cpu_clip_max) - { - clip_rect.z = ImMin(clip_rect.z, cpu_clip_max->x); - clip_rect.w = ImMin(clip_rect.w, cpu_clip_max->y); - } - float x = pos.x; - float y = pos.y; - ImDrawVert* out_vertices = draw_list->vtx_write; const char* s = text_begin; @@ -10028,7 +10039,7 @@ void ImFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_re // Clipping on Y is more likely float y1 = (float)(y + glyph->YOffset * scale); float y2 = (float)(y1 + glyph->Height * scale); - if (y1 <= clip_rect.w && y2 >= clip_rect.y) + if (y1 <= clip_rect.w && y2 >= clip_rect.y) // FIMXE-OPT: could fast-forward until next line { float x1 = (float)(x + glyph->XOffset * scale); float x2 = (float)(x1 + glyph->Width * scale); @@ -10040,20 +10051,28 @@ void ImFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_re float u2 = glyph->U1; float v2 = glyph->V1; - // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quad and in the "max" direction (bottom-right) - if (cpu_clip_max) + // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads + if (cpu_fine_clip) { - if (x2 > cpu_clip_max->x) + if (x1 < clip_rect.x) { - const float clip_tx = (cpu_clip_max->x - x1) / (x2 - x1); - x2 = cpu_clip_max->x; - u2 = u1 + clip_tx * (u2 - u1); + u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1); + x1 = clip_rect.x; } - if (y2 > cpu_clip_max->y) + if (y1 < clip_rect.y) { - const float clip_ty = (cpu_clip_max->y - y1) / (y2 - y1); - y2 = cpu_clip_max->y; - v2 = v1 + clip_ty * (v2 - v1); + v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1); + y1 = clip_rect.y; + } + if (x2 > clip_rect.z) + { + u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1); + x2 = clip_rect.z; + } + if (y2 > clip_rect.w) + { + v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1); + y2 = clip_rect.w; } } @@ -10580,11 +10599,15 @@ void ImGui::ShowTestWindow(bool* opened) if (ImGui::TreeNode("Clipping")) { - static ImVec2 size(80, 20); - ImGui::TextWrapped("On a per-widget basis we are occasionally clipping text if it won't fit in its frame."); - ImGui::SliderFloat2("size", (float*)&size, 5.0f, 200.0f); - ImGui::Button("Line 1 hello\nLine 2 clip me!", size); - ImGui::TextWrapped("Otherwise we are doing coarser clipping + passing a scissor rectangle to the renderer. The system is designed to try minimizing both execution and CPU/GPU rendering cost."); + static ImVec2 size(100, 100), offset(50, 20); + ImGui::TextWrapped("On a per-widget basis we are occasionally clipping text if it won't fit in its frame. Otherwise we are doing coarser clipping + passing a scissor rectangle to the renderer. The system is designed to try minimizing both execution and CPU/GPU rendering cost."); + ImGui::DragFloat2("size", (float*)&size, 0.5f, 0.0f, 200.0f, "%.0f"); + ImGui::DragFloat2("offset", (float*)&offset, 0.5f, -200, 200.0f, "%.0f"); + ImVec2 pos = ImGui::GetCursorScreenPos(); + ImVec4 clip_rect(pos.x, pos.y, pos.x+size.x, pos.y+size.y); + ImGui::GetWindowDrawList()->AddRectFilled(pos, pos+size, ImColor(90,90,120,255)); + ImGui::GetWindowDrawList()->AddText(ImGui::GetWindowFont(), ImGui::GetWindowFontSize()*2.0f, pos+offset, ImColor(255,255,255,255), "Line 1 hello\nLine 2 clip me!", NULL, 0.0f, &clip_rect); + ImGui::Dummy(size); ImGui::TreePop(); } diff --git a/imgui.h b/imgui.h index a5a94e066..aeec4b017 100644 --- a/imgui.h +++ b/imgui.h @@ -1022,7 +1022,7 @@ struct ImDrawList IMGUI_API void AddCircle(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12); IMGUI_API void AddCircleFilled(const ImVec2& centre, float radius, ImU32 col, int num_segments = 12); IMGUI_API void AddArcFast(const ImVec2& center, float radius, ImU32 col, int a_min_12, int a_max_12, bool filled = false, const ImVec2& third_point_offset = ImVec2(0,0)); // Angles in 0..12 range - IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec2* cpu_clip_max = NULL); + IMGUI_API void AddText(const ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); IMGUI_API void AddImage(ImTextureID user_texture_id, const ImVec2& a, const ImVec2& b, const ImVec2& uv0, const ImVec2& uv1, ImU32 col = 0xFFFFFFFF); // Advanced @@ -1137,8 +1137,8 @@ struct ImFont // 'wrap_width' enable automatic word-wrapping across multiple lines to fit into given width. 0.0f to disable. IMGUI_API ImVec2 CalcTextSizeA(float size, float max_width, float wrap_width, const char* text_begin, const char* text_end = NULL, const char** remaining = NULL) const; // utf8 IMGUI_API ImVec2 CalcTextSizeW(float size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL) const; // wchar - IMGUI_API void RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawList* draw_list, float wrap_width = 0.0f, const ImVec2* cpu_clip_max = NULL) const; IMGUI_API const char* CalcWordWrapPositionA(float scale, const char* text, const char* text_end, float wrap_width) const; + IMGUI_API void RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_rect, const char* text_begin, const char* text_end, ImDrawList* draw_list, float wrap_width = 0.0f, bool cpu_fine_clip = false) const; }; //---- Include imgui_user.h at the end of imgui.h From d06ad43dca38913168f8dccc98d85813dc094023 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2015 17:50:55 -0600 Subject: [PATCH 04/44] ImFont::RenderText() additional early out (typically performed at TextUnformatted() level for large chunks but this is also useful) (#200) --- imgui.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index e4c102ed5..b0eaa6b0f 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10092,8 +10092,10 @@ void ImFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_re { // Clipping on Y is more likely float y1 = (float)(y + glyph->YOffset * scale); + if (y1 > clip_rect.w) + break; float y2 = (float)(y1 + glyph->Height * scale); - if (y1 <= clip_rect.w && y2 >= clip_rect.y) // FIMXE-OPT: could fast-forward until next line + if (y2 >= clip_rect.y) // FIMXE-OPT: could fast-forward until next line (without breaking word-wrapping) { float x1 = (float)(x + glyph->XOffset * scale); float x2 = (float)(x1 + glyph->Width * scale); From 6d31c498c08628c03a59d353ecc4001fd25a2ebb Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2015 20:14:54 -0600 Subject: [PATCH 05/44] InputText: multi-line selection, better scrolling, cleaning up (#200) Not horizontally scrolling on char boundaries anymore --- imgui.cpp | 106 +++++++++++++++++++----------------------------------- 1 file changed, 36 insertions(+), 70 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index b0eaa6b0f..2b7fcd986 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1207,11 +1207,6 @@ struct ImGuiTextEditState void OnKeyPressed(int key); void UpdateScrollOffset(); ImVec2 CalcDisplayOffsetFromCharIdx(int i) const; - - // Static functions because they are used to render non-focused instances of a text input box - static const char* GetTextPointerClippedA(ImFont* font, float font_size, const char* text, float width, ImVec2* out_text_size = NULL); - static const ImWchar* GetTextPointerClippedW(ImFont* font, float font_size, const ImWchar* text, const ImWchar* text_end, float width, ImVec2* out_text_size = NULL); - static void RenderTextScrolledClipped(ImFont* font, float font_size, const char* text, const ImVec2& pos, const ImVec2& size, float scroll_x); }; // Data saved in imgui.ini file @@ -6524,7 +6519,8 @@ bool ImGui::RadioButton(const char* label, int* v, int v_button) static ImVec2 CalcTextSizeW(ImFont* font, float font_size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false) { - IM_ASSERT(text_end); + if (!text_end) + text_end = text_begin + ImStrlenW(text_begin); const float scale = font_size / font->FontSize; const float line_height = font->FontSize * scale; @@ -6670,12 +6666,13 @@ void ImGuiTextEditState::UpdateScrollOffset() { // Scroll in chunks of quarter width const float scroll_x_increment = Size.x * 0.25f; - const float cursor_offset_x = CalcTextSizeW(Font, FontSize, FLT_MAX, Text, Text+StbState.cursor, NULL).x; + ImVec2 cursor_offset; + CalcTextSizeW(Font, FontSize, FLT_MAX, Text, Text+StbState.cursor, NULL, &cursor_offset); // If widget became bigger than text (because of a resize), reset horizontal scrolling if (ScrollX > 0.0f) { - const float text_width = cursor_offset_x + CalcTextSizeW(Font, FontSize, FLT_MAX, Text+StbState.cursor, NULL, NULL).x; + const float text_width = CalcTextSizeW(Font, FontSize, FLT_MAX, Text, NULL, NULL).x; if (text_width < Size.x) { ScrollX = 0.0f; @@ -6683,68 +6680,20 @@ void ImGuiTextEditState::UpdateScrollOffset() } } - if (cursor_offset_x < ScrollX) - ScrollX = ImMax(0.0f, cursor_offset_x - scroll_x_increment); - else if (cursor_offset_x - Size.x >= ScrollX) - ScrollX = cursor_offset_x - Size.x + scroll_x_increment; + if (cursor_offset.x < ScrollX) + ScrollX = ImMax(0.0f, cursor_offset.x - scroll_x_increment); + else if (cursor_offset.x - Size.x >= ScrollX) + ScrollX = cursor_offset.x - Size.x + scroll_x_increment; } ImVec2 ImGuiTextEditState::CalcDisplayOffsetFromCharIdx(int i) const { - const ImWchar* text_start = GetTextPointerClippedW(Font, FontSize, Text, Text+CurLenW, ScrollX, NULL); - const ImWchar* text_end = (Text+i >= text_start) ? Text+i : text_start; // Clip if requested character is outside of display - IM_ASSERT(text_end >= text_start); - - // FIXME-WIP-MULTILINE ImVec2 offset; - CalcTextSizeW(Font, FontSize, Size.x+1, text_start, text_end, NULL, &offset); + CalcTextSizeW(Font, FontSize, FLT_MAX, Text, Text+i, NULL, &offset); + offset.x -= ScrollX; return offset; } -// [Static] -const char* ImGuiTextEditState::GetTextPointerClippedA(ImFont* font, float font_size, const char* text, float width, ImVec2* out_text_size) -{ - if (width <= 0.0f) - return text; - - const char* text_clipped_end = NULL; - const ImVec2 text_size = font->CalcTextSizeA(font_size, width, 0.0f, text, NULL, &text_clipped_end); - if (out_text_size) - *out_text_size = text_size; - return text_clipped_end; -} - -// [Static] -const ImWchar* ImGuiTextEditState::GetTextPointerClippedW(ImFont* font, float font_size, const ImWchar* text, const ImWchar* text_end, float width, ImVec2* out_text_size) -{ - if (width <= 0.0f) - return text; - - const ImWchar* text_clipped_end = NULL; - const ImVec2 text_size = CalcTextSizeW(font, font_size, width, text, text_end, &text_clipped_end); - if (out_text_size) - *out_text_size = text_size; - return text_clipped_end; -} - -// [Static] -void ImGuiTextEditState::RenderTextScrolledClipped(ImFont* font, float font_size, const char* buf, const ImVec2& pos, const ImVec2& size, float scroll_x) -{ - ImGuiWindow* window = GetCurrentWindow(); - const ImU32 font_color = window->Color(ImGuiCol_Text); - //window->DrawList->AddLine(pos, pos+ImVec2(width,0), 0xFF00FFFF); - - // Determine start and end of visible string - // FIXME-OPT: This is pretty slow for what it does. - const char* text_start = scroll_x <= 0.0f ? buf : GetTextPointerClippedA(font, font_size, buf, scroll_x, NULL); - const char* text_end = GetTextPointerClippedA(font, font_size, text_start, size.x + 1, NULL); // +1 to allow character spacing to fit outside the allowed width - window->DrawList->AddText(font, font_size, pos, font_color, text_start, text_end); - - // Log as text - if (GImGui->LogEnabled) - LogText(pos, buf, NULL); -} - // Public API to manipulate UTF-8 text // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) void ImGuiTextEditCallbackData::DeleteChars(int pos, int bytes_count) @@ -7162,8 +7111,9 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV RenderFrame(frame_bb.Min, frame_bb.Max, window->Color(ImGuiCol_FrameBg), true, style.FrameRounding); - const ImVec2 font_off_up = ImVec2(0.0f, g.FontSize+1.0f); // FIXME: those offsets are part of the style or font API - const ImVec2 font_off_dn = ImVec2(0.0f, 2.0f); + const float font_offy_up = g.FontSize+1.0f; // FIXME: those offsets are part of the style or font API + const float font_offy_dn = 2.0f; + const ImVec2 render_pos = frame_bb.Min + style.FramePadding; if (g.ActiveId == id) { @@ -7172,23 +7122,39 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const int select_end_idx = edit_state.StbState.select_end; if (select_begin_idx != select_end_idx) { - const ImVec2 select_begin_pos = frame_bb.Min + style.FramePadding + edit_state.CalcDisplayOffsetFromCharIdx(ImMin(select_begin_idx,select_end_idx)); - const ImVec2 select_end_pos = frame_bb.Min + style.FramePadding + edit_state.CalcDisplayOffsetFromCharIdx(ImMax(select_begin_idx,select_end_idx)); - window->DrawList->AddRectFilled(select_begin_pos - font_off_up, select_end_pos + font_off_dn, window->Color(ImGuiCol_TextSelectedBg)); + ImVec2 rect_pos; + ImWchar* text_selected_begin = edit_state.Text + ImMin(select_begin_idx,select_end_idx); + ImWchar* text_selected_end = edit_state.Text + ImMax(select_begin_idx,select_end_idx); + CalcTextSizeW(edit_state.Font, edit_state.FontSize, FLT_MAX, edit_state.Text, text_selected_begin, NULL, &rect_pos); + + ImU32 font_color = window->Color(ImGuiCol_TextSelectedBg); + for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) + { + ImVec2 rect_size = CalcTextSizeW(edit_state.Font, edit_state.FontSize, FLT_MAX, p, text_selected_end, &p, NULL, true); + window->DrawList->AddRectFilled(render_pos + rect_pos + ImVec2(-edit_state.ScrollX, -font_offy_up), render_pos + rect_pos + ImVec2(rect_size.x - edit_state.ScrollX, +font_offy_dn), font_color); + rect_pos.x = 0.0f; + rect_pos.y += g.FontSize; + } } } + // FIMXE-WIP-MULTILINE //const float render_scroll_x = (g.ActiveId == id) ? edit_state.ScrollX : 0.0f; const float render_scroll_x = (edit_state.Id == id) ? edit_state.ScrollX : 0.0f; - ImGuiTextEditState::RenderTextScrolledClipped(g.Font, g.FontSize, buf, frame_bb.Min + style.FramePadding, size + style.FramePadding, render_scroll_x); + const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x + style.FramePadding.x*2.0f, frame_bb.Min.y + size.y + style.FramePadding.y*2.0f); + window->DrawList->AddText(g.Font, g.FontSize, render_pos - ImVec2(render_scroll_x, 0.0f), window->Color(ImGuiCol_Text), buf, NULL, 0.0f, &clip_rect); + + // Log as text + if (GImGui->LogEnabled) + LogText(render_pos, buf, NULL); if (g.ActiveId == id) { - const ImVec2 cursor_pos = frame_bb.Min + style.FramePadding + edit_state.CalcDisplayOffsetFromCharIdx(edit_state.StbState.cursor); + const ImVec2 cursor_pos = render_pos + edit_state.CalcDisplayOffsetFromCharIdx(edit_state.StbState.cursor); // Draw blinking cursor if (g.InputTextState.CursorIsVisible()) - window->DrawList->AddLine(cursor_pos - font_off_up + ImVec2(0,2), cursor_pos + font_off_dn - ImVec2(0,3), window->Color(ImGuiCol_Text)); + window->DrawList->AddLine(cursor_pos + ImVec2(0,2-font_offy_up), cursor_pos + ImVec2(0,-3+font_offy_dn), window->Color(ImGuiCol_Text)); // Notify OS of text input position for advanced IME if (io.ImeSetInputScreenPosFn && ImLengthSqr(edit_state.InputCursorScreenPos - cursor_pos) > 0.0001f) From ec7c1834b3d1d515b1f0d4839e08bbeb0fbb4380 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2015 20:25:08 -0600 Subject: [PATCH 06/44] InputTextMultiline() tabbing to a multi-line edit doesn't select all (unless ImGuiInputTextFlags_AutoSelectAll is set), somehow arbitrary (# Seems reasonable --- imgui.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 2b7fcd986..d66f06813 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6857,7 +6857,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV edit_state.Id = id; edit_state.ScrollX = 0.0f; stb_textedit_initialize_state(&edit_state.StbState, !is_multiline); - if (focus_requested_by_code) + if (!is_multiline && focus_requested_by_code) select_all = true; } else @@ -6868,7 +6868,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV edit_state.StbState.select_start = ImMin(edit_state.StbState.select_start, (int)edit_state.CurLenW); edit_state.StbState.select_end = ImMin(edit_state.StbState.select_end, (int)edit_state.CurLenW); } - if (focus_requested_by_tab || (user_clicked && is_ctrl_down)) + if (!is_multiline && (focus_requested_by_tab || (user_clicked && is_ctrl_down))) select_all = true; } SetActiveId(id, window); @@ -6966,7 +6966,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV edit_state.OnKeyPressed((int)c); } } - else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab)) + else if ((flags & ImGuiInputTextFlags_AllowTabInput) && IsKeyPressedMap(ImGuiKey_Tab) && !is_ctrl_down && !is_shift_down && !is_alt_down) { unsigned int c = '\t'; if (InputTextFilterCharacter(&c, flags, callback, user_data)) From 0795a60c6bcc512eb1dee1576e030f102295f7ea Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2015 20:35:46 -0600 Subject: [PATCH 07/44] InputText() fixed multi-line selection clipping. (#200) --- imgui.cpp | 14 ++++++++------ imgui.h | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index d66f06813..e9c5594f9 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7115,6 +7115,10 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const float font_offy_dn = 2.0f; const ImVec2 render_pos = frame_bb.Min + style.FramePadding; + //const float render_scroll_x = (g.ActiveId == id) ? edit_state.ScrollX : 0.0f; + const float render_scroll_x = (edit_state.Id == id) ? edit_state.ScrollX : 0.0f; + const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x + style.FramePadding.x*2.0f, frame_bb.Min.y + size.y + style.FramePadding.y*2.0f); + if (g.ActiveId == id) { // Draw selection @@ -7122,26 +7126,24 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const int select_end_idx = edit_state.StbState.select_end; if (select_begin_idx != select_end_idx) { - ImVec2 rect_pos; ImWchar* text_selected_begin = edit_state.Text + ImMin(select_begin_idx,select_end_idx); ImWchar* text_selected_end = edit_state.Text + ImMax(select_begin_idx,select_end_idx); + ImVec2 rect_pos; CalcTextSizeW(edit_state.Font, edit_state.FontSize, FLT_MAX, edit_state.Text, text_selected_begin, NULL, &rect_pos); ImU32 font_color = window->Color(ImGuiCol_TextSelectedBg); for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) { ImVec2 rect_size = CalcTextSizeW(edit_state.Font, edit_state.FontSize, FLT_MAX, p, text_selected_end, &p, NULL, true); - window->DrawList->AddRectFilled(render_pos + rect_pos + ImVec2(-edit_state.ScrollX, -font_offy_up), render_pos + rect_pos + ImVec2(rect_size.x - edit_state.ScrollX, +font_offy_dn), font_color); + ImRect rect(render_pos + rect_pos + ImVec2(-edit_state.ScrollX, -font_offy_up), render_pos + rect_pos + ImVec2(rect_size.x - edit_state.ScrollX, +font_offy_dn)); + rect.Clip(clip_rect); + window->DrawList->AddRectFilled(rect.Min, rect.Max, font_color); rect_pos.x = 0.0f; rect_pos.y += g.FontSize; } } } - // FIMXE-WIP-MULTILINE - //const float render_scroll_x = (g.ActiveId == id) ? edit_state.ScrollX : 0.0f; - const float render_scroll_x = (edit_state.Id == id) ? edit_state.ScrollX : 0.0f; - const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x + style.FramePadding.x*2.0f, frame_bb.Min.y + size.y + style.FramePadding.y*2.0f); window->DrawList->AddText(g.Font, g.FontSize, render_pos - ImVec2(render_scroll_x, 0.0f), window->Color(ImGuiCol_Text), buf, NULL, 0.0f, &clip_rect); // Log as text diff --git a/imgui.h b/imgui.h index 9bcd06824..aa34324ab 100644 --- a/imgui.h +++ b/imgui.h @@ -444,7 +444,7 @@ enum ImGuiInputTextFlags_ ImGuiInputTextFlags_CallbackAlways = 1 << 8, // Call user function every time ImGuiInputTextFlags_CallbackCharFilter = 1 << 9, // Call user function to filter character. Modify data->EventChar to replace/filter input, or return 1 to discard character. ImGuiInputTextFlags_AllowTabInput = 1 << 10, // Pressing TAB input a '\t' character into the text field - ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 11, // In multi-line mode, allow exiting edition by pressing Enter. Ctrl+Enter to add new line. + ImGuiInputTextFlags_CtrlEnterForNewLine = 1 << 11, // In multi-line mode, allow exiting edition by pressing Enter. Ctrl+Enter to add new line (by default adds new lines with Enter). // [Internal] ImGuiInputTextFlags_Multiline = 1 << 20 // For internal use by InputTextMultiline() }; From 73491e5adc09c61f94a18add480ccd8a479452f4 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2015 21:04:27 -0600 Subject: [PATCH 08/44] InputText() lifted 1024 characters limit (#200) Bit messy & not happy with using ImVector --- imgui.cpp | 73 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e9c5594f9..6346a8f32 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1184,10 +1184,11 @@ struct ImGuiDrawContext struct ImGuiTextEditState { ImGuiID Id; // widget id owning the text state - ImWchar Text[1024]; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. - char InitialText[1024*3+1]; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) + ImVector Text; // edit buffer, we need to persist but can't guarantee the persistence of the user-provided buffer. so we copy into own buffer. + ImVector InitialText; // backup of end-user buffer at the time of focus (in UTF-8, unaltered) + ImVector TempTextBuffer; size_t CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. - size_t BufSizeA; // end-user buffer size, <= 1024 (or increase above) + size_t BufSizeA; // end-user buffer size ImVec2 Size; // widget width/height float ScrollX; STB_TexteditState StbState; @@ -5566,7 +5567,7 @@ static void ApplyNumericalTextInput(const char* buf, float *v) float ref_v = *v; if (op) - if (sscanf(GImGui->InputTextState.InitialText, "%f", &ref_v) < 1) + if (sscanf(GImGui->InputTextState.InitialText.begin(), "%f", &ref_v) < 1) return; float op_v = 0.0f; @@ -6581,28 +6582,29 @@ static int STB_TEXTEDIT_KEYTOTEXT(int key) static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) { + const ImWchar* text = obj->Text.begin(); const ImWchar* text_remaining = NULL; - const ImVec2 size = CalcTextSizeW(obj->Font, obj->FontSize, FLT_MAX, obj->Text + line_start_idx, obj->Text + obj->CurLenW, &text_remaining, NULL, true); + const ImVec2 size = CalcTextSizeW(obj->Font, obj->FontSize, FLT_MAX, text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); r->x0 = 0.0f; r->x1 = size.x; r->baseline_y_delta = size.y; r->ymin = 0.0f; r->ymax = size.y; - r->num_chars = (int)(text_remaining - (obj->Text + line_start_idx)); + r->num_chars = (int)(text_remaining - (text + line_start_idx)); } static bool is_separator(unsigned int c) { return c==',' || c==';' || c=='(' || c==')' || c=='{' || c=='}' || c=='[' || c==']' || c=='|'; } #define STB_TEXTEDIT_IS_SPACE(CH) ( ImCharIsSpace((unsigned int)CH) || is_separator((unsigned int)CH) ) static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n) { - ImWchar* dst = obj->Text + pos; + ImWchar* dst = obj->Text.begin() + pos; // We maintain our buffer length in both UTF-8 and wchar formats obj->CurLenA -= ImTextCountUtf8BytesFromStr(dst, dst + n); obj->CurLenW -= n; // Offset remaining text - const ImWchar* src = obj->Text + pos + n; + const ImWchar* src = obj->Text.begin() + pos + n; while (ImWchar c = *src++) *dst++ = c; *dst = '\0'; @@ -6611,16 +6613,17 @@ static void STB_TEXTEDIT_DELETECHARS(STB_TEXTEDIT_STRING* obj, int pos, int n) static bool STB_TEXTEDIT_INSERTCHARS(STB_TEXTEDIT_STRING* obj, int pos, const ImWchar* new_text, int new_text_len) { const size_t text_len = obj->CurLenW; - if ((size_t)new_text_len + text_len + 1 > IM_ARRAYSIZE(obj->Text)) + if ((size_t)new_text_len + text_len + 1 > obj->Text.size()) return false; const int new_text_len_utf8 = ImTextCountUtf8BytesFromStr(new_text, new_text + new_text_len); if ((size_t)new_text_len_utf8 + obj->CurLenA + 1 > obj->BufSizeA) return false; + ImWchar* text = obj->Text.begin(); if (pos != (int)text_len) - memmove(obj->Text + (size_t)pos + new_text_len, obj->Text + (size_t)pos, (text_len - (size_t)pos) * sizeof(ImWchar)); - memcpy(obj->Text + (size_t)pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); + memmove(text + (size_t)pos + new_text_len, text + (size_t)pos, (text_len - (size_t)pos) * sizeof(ImWchar)); + memcpy(text + (size_t)pos, new_text, (size_t)new_text_len * sizeof(ImWchar)); obj->CurLenW += new_text_len; obj->CurLenA += new_text_len_utf8; @@ -6667,12 +6670,13 @@ void ImGuiTextEditState::UpdateScrollOffset() // Scroll in chunks of quarter width const float scroll_x_increment = Size.x * 0.25f; ImVec2 cursor_offset; - CalcTextSizeW(Font, FontSize, FLT_MAX, Text, Text+StbState.cursor, NULL, &cursor_offset); + const ImWchar* text = Text.begin(); + CalcTextSizeW(Font, FontSize, FLT_MAX, text, text+StbState.cursor, NULL, &cursor_offset); // If widget became bigger than text (because of a resize), reset horizontal scrolling if (ScrollX > 0.0f) { - const float text_width = CalcTextSizeW(Font, FontSize, FLT_MAX, Text, NULL, NULL).x; + const float text_width = CalcTextSizeW(Font, FontSize, FLT_MAX, text, NULL, NULL).x; if (text_width < Size.x) { ScrollX = 0.0f; @@ -6689,7 +6693,7 @@ void ImGuiTextEditState::UpdateScrollOffset() ImVec2 ImGuiTextEditState::CalcDisplayOffsetFromCharIdx(int i) const { ImVec2 offset; - CalcTextSizeW(Font, FontSize, FLT_MAX, Text, Text+i, NULL, &offset); + CalcTextSizeW(Font, FontSize, FLT_MAX, Text.begin(), Text.begin()+i, NULL, &offset); offset.x -= ScrollX; return offset; } @@ -6742,7 +6746,7 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF))) { bool pass = false; - pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); + pass |= ((c == '\n' || c == '\r') && (flags & ImGuiInputTextFlags_Multiline)); pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput)); if (!pass) return false; @@ -6844,9 +6848,11 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV // Start edition // Take a copy of the initial buffer value (both in original UTF-8 format and converted to wchar) // From the moment we focused we are ignoring the content of 'buf' - ImFormatString(edit_state.InitialText, IM_ARRAYSIZE(edit_state.InitialText), "%s", buf); + edit_state.Text.resize(buf_size); // wchar count <= utf-8 count + edit_state.InitialText.resize(buf_size); // utf-8 + ImFormatString(edit_state.InitialText.begin(), edit_state.InitialText.size(), "%s", buf); const char* buf_end = NULL; - edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text, IM_ARRAYSIZE(edit_state.Text), buf, NULL, &buf_end); + edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.begin(), edit_state.Text.size(), buf, NULL, &buf_end); edit_state.CurLenA = buf_end - buf; // We can't get the result from ImFormatString() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. edit_state.Size = size + style.FramePadding; edit_state.InputCursorScreenPos = ImVec2(-1.f,-1.f); @@ -6987,8 +6993,9 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV { const int ib = edit_state.HasSelection() ? ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end) : 0; const int ie = edit_state.HasSelection() ? ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end) : (int)edit_state.CurLenW; - ImTextStrToUtf8(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), edit_state.Text+ib, edit_state.Text+ie); - g.IO.SetClipboardTextFn(g.TempBuffer); + edit_state.TempTextBuffer.resize((ie-ib) * 4 + 1); + ImTextStrToUtf8(edit_state.TempTextBuffer.begin(), edit_state.TempTextBuffer.size(), edit_state.Text.begin()+ib, edit_state.Text.begin()+ie); + g.IO.SetClipboardTextFn(edit_state.TempTextBuffer.begin()); } if (cut) @@ -7040,7 +7047,8 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV // Note that as soon as we can focus into the input box, the in-widget value gets priority over any underlying modification of the input buffer // FIXME: We actually always render 'buf' in RenderTextScrolledClipped // FIXME-OPT: CPU waste to do this every time the widget is active, should mark dirty state from the stb_textedit callbacks - ImTextStrToUtf8(g.TempBuffer, IM_ARRAYSIZE(g.TempBuffer), edit_state.Text, NULL); + edit_state.TempTextBuffer.resize(edit_state.Text.size() * 4); + ImTextStrToUtf8(edit_state.TempTextBuffer.begin(), edit_state.TempTextBuffer.size(), edit_state.Text.begin(), NULL); // User callback if ((flags & (ImGuiInputTextFlags_CallbackCompletion | ImGuiInputTextFlags_CallbackHistory | ImGuiInputTextFlags_CallbackAlways)) != 0) @@ -7071,22 +7079,23 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV ImGuiTextEditCallbackData callback_data; callback_data.EventFlag = event_flag; callback_data.EventKey = event_key; - callback_data.Buf = g.TempBuffer; + callback_data.Buf = edit_state.TempTextBuffer.begin(); callback_data.BufSize = edit_state.BufSizeA; callback_data.BufDirty = false; callback_data.Flags = flags; callback_data.UserData = user_data; // We have to convert from position from wchar to UTF-8 positions - const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(edit_state.Text, edit_state.Text + edit_state.StbState.cursor); - const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(edit_state.Text, edit_state.Text + edit_state.StbState.select_start); - const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(edit_state.Text, edit_state.Text + edit_state.StbState.select_end); + ImWchar* text = edit_state.Text.begin(); + const int utf8_cursor_pos = callback_data.CursorPos = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.cursor); + const int utf8_selection_start = callback_data.SelectionStart = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_start); + const int utf8_selection_end = callback_data.SelectionEnd = ImTextCountUtf8BytesFromStr(text, text + edit_state.StbState.select_end); // Call user code callback(&callback_data); // Read back what user may have modified - IM_ASSERT(callback_data.Buf == g.TempBuffer); // Invalid to modify those fields + IM_ASSERT(callback_data.Buf == edit_state.TempTextBuffer.begin()); // Invalid to modify those fields IM_ASSERT(callback_data.BufSize == edit_state.BufSizeA); IM_ASSERT(callback_data.Flags == flags); if (callback_data.CursorPos != utf8_cursor_pos) edit_state.StbState.cursor = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.CursorPos); @@ -7094,16 +7103,16 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV if (callback_data.SelectionEnd != utf8_selection_end) edit_state.StbState.select_end = ImTextCountCharsFromUtf8(callback_data.Buf, callback_data.Buf + callback_data.SelectionEnd); if (callback_data.BufDirty) { - edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text, IM_ARRAYSIZE(edit_state.Text), g.TempBuffer, NULL); - edit_state.CurLenA = strlen(g.TempBuffer); + edit_state.CurLenW = ImTextStrFromUtf8(text, edit_state.Text.size(), edit_state.TempTextBuffer.begin(), NULL); + edit_state.CurLenA = strlen(edit_state.TempTextBuffer.begin()); edit_state.CursorAnimReset(); } } } - if (strcmp(g.TempBuffer, buf) != 0) + if (strcmp(edit_state.TempTextBuffer.begin(), buf) != 0) { - ImFormatString(buf, buf_size, "%s", g.TempBuffer); + ImFormatString(buf, buf_size, "%s", edit_state.TempTextBuffer.begin()); value_changed = true; } } @@ -7126,10 +7135,10 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const int select_end_idx = edit_state.StbState.select_end; if (select_begin_idx != select_end_idx) { - ImWchar* text_selected_begin = edit_state.Text + ImMin(select_begin_idx,select_end_idx); - ImWchar* text_selected_end = edit_state.Text + ImMax(select_begin_idx,select_end_idx); + ImWchar* text_selected_begin = edit_state.Text.begin() + ImMin(select_begin_idx,select_end_idx); + ImWchar* text_selected_end = edit_state.Text.begin() + ImMax(select_begin_idx,select_end_idx); ImVec2 rect_pos; - CalcTextSizeW(edit_state.Font, edit_state.FontSize, FLT_MAX, edit_state.Text, text_selected_begin, NULL, &rect_pos); + CalcTextSizeW(edit_state.Font, edit_state.FontSize, FLT_MAX, edit_state.Text.begin(), text_selected_begin, NULL, &rect_pos); ImU32 font_color = window->Color(ImGuiCol_TextSelectedBg); for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) From 917a1fdbf758d55b3ddff5ce5e8a2d50963e4746 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2015 21:08:54 -0600 Subject: [PATCH 09/44] InputText() fixing cancel (#200) --- imgui.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 6346a8f32..c35c9d032 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7038,7 +7038,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV if (cancel_edit) { // Restore initial value - ImFormatString(buf, buf_size, "%s", edit_state.InitialText); + ImFormatString(buf, buf_size, "%s", edit_state.InitialText.begin()); value_changed = true; } else @@ -7465,7 +7465,6 @@ bool ImGui::Combo(const char* label, int* current_item, bool (*items_getter)(voi if (height_in_items < 0) height_in_items = 7; - const ImVec2 backup_pos = ImGui::GetCursorPos(); const float popup_height = (label_size.y + style.ItemSpacing.y) * ImMin(items_count, height_in_items) + (style.FramePadding.y * 3); const ImRect popup_rect(ImVec2(frame_bb.Min.x, frame_bb.Max.y), ImVec2(frame_bb.Max.x, frame_bb.Max.y + popup_height)); ImGui::SetNextWindowPos(popup_rect.Min); From f1dfc4d7c4887b09d9cae37f63f52cce1c33418b Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2015 21:17:02 -0600 Subject: [PATCH 10/44] InputTextMultiline(): multi-line selection draw fix (#200) --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index c35c9d032..a6eed5b74 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7144,7 +7144,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) { ImVec2 rect_size = CalcTextSizeW(edit_state.Font, edit_state.FontSize, FLT_MAX, p, text_selected_end, &p, NULL, true); - ImRect rect(render_pos + rect_pos + ImVec2(-edit_state.ScrollX, -font_offy_up), render_pos + rect_pos + ImVec2(rect_size.x - edit_state.ScrollX, +font_offy_dn)); + ImRect rect(render_pos + rect_pos + ImVec2(-edit_state.ScrollX, (p == text_selected_begin) ? -font_offy_up : -g.FontSize), render_pos + rect_pos + ImVec2(rect_size.x - edit_state.ScrollX, (p == text_selected_end) ? +font_offy_dn : 0.0f)); rect.Clip(clip_rect); window->DrawList->AddRectFilled(rect.Min, rect.Max, font_color); rect_pos.x = 0.0f; From 417a7bc29bc5c9704b67f727cb28e7e73669ad17 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2015 21:19:47 -0600 Subject: [PATCH 11/44] InputTextMultiline(): multi-line selection draw fix (#200) --- imgui.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index a6eed5b74..5a3f849af 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7146,7 +7146,8 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV ImVec2 rect_size = CalcTextSizeW(edit_state.Font, edit_state.FontSize, FLT_MAX, p, text_selected_end, &p, NULL, true); ImRect rect(render_pos + rect_pos + ImVec2(-edit_state.ScrollX, (p == text_selected_begin) ? -font_offy_up : -g.FontSize), render_pos + rect_pos + ImVec2(rect_size.x - edit_state.ScrollX, (p == text_selected_end) ? +font_offy_dn : 0.0f)); rect.Clip(clip_rect); - window->DrawList->AddRectFilled(rect.Min, rect.Max, font_color); + if (rect.Overlaps(clip_rect)) + window->DrawList->AddRectFilled(rect.Min, rect.Max, font_color); rect_pos.x = 0.0f; rect_pos.y += g.FontSize; } From f75b8c72cf6f9e17d710962acaf2fb2a19612e35 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 16 Jun 2015 21:53:04 -0600 Subject: [PATCH 12/44] InputTextMultiline(): vertical scrolling wip, selection rendering fix (#200) --- imgui.cpp | 65 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 5a3f849af..17fe474aa 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1190,7 +1190,7 @@ struct ImGuiTextEditState size_t CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. size_t BufSizeA; // end-user buffer size ImVec2 Size; // widget width/height - float ScrollX; + ImVec2 Scroll; STB_TexteditState StbState; float CursorAnim; ImVec2 InputCursorScreenPos; // Cursor position in screen space to be used by IME callback. @@ -1207,7 +1207,6 @@ struct ImGuiTextEditState void OnKeyPressed(int key); void UpdateScrollOffset(); - ImVec2 CalcDisplayOffsetFromCharIdx(int i) const; }; // Data saved in imgui.ini file @@ -6668,34 +6667,39 @@ void ImGuiTextEditState::OnKeyPressed(int key) void ImGuiTextEditState::UpdateScrollOffset() { // Scroll in chunks of quarter width - const float scroll_x_increment = Size.x * 0.25f; ImVec2 cursor_offset; const ImWchar* text = Text.begin(); CalcTextSizeW(Font, FontSize, FLT_MAX, text, text+StbState.cursor, NULL, &cursor_offset); // If widget became bigger than text (because of a resize), reset horizontal scrolling - if (ScrollX > 0.0f) + // FIXME-OPT + if (Scroll.x > 0.0f || Scroll.y > 0.0f) { - const float text_width = CalcTextSizeW(Font, FontSize, FLT_MAX, text, NULL, NULL).x; - if (text_width < Size.x) - { - ScrollX = 0.0f; + ImVec2 text_size = CalcTextSizeW(Font, FontSize, FLT_MAX, text, NULL, NULL); + bool ret = false; + if (text_size.x < Size.x) { Scroll.x = 0.0f; ret = true; } + if (text_size.y < Size.y) { Scroll.y = 0.0f; ret = true; } + if (ret) return; - } } - if (cursor_offset.x < ScrollX) - ScrollX = ImMax(0.0f, cursor_offset.x - scroll_x_increment); - else if (cursor_offset.x - Size.x >= ScrollX) - ScrollX = cursor_offset.x - Size.x + scroll_x_increment; -} + const ImVec2 scroll_increment(Size.x * 0.25f, FontSize); + if (cursor_offset.x < Scroll.x) + Scroll.x = ImMax(0.0f, cursor_offset.x - scroll_increment.x); + else if (cursor_offset.x - Size.x >= Scroll.x) + Scroll.x = cursor_offset.x - Size.x + scroll_increment.x; -ImVec2 ImGuiTextEditState::CalcDisplayOffsetFromCharIdx(int i) const -{ - ImVec2 offset; - CalcTextSizeW(Font, FontSize, FLT_MAX, Text.begin(), Text.begin()+i, NULL, &offset); - offset.x -= ScrollX; - return offset; + //float height_aligned = FontSize * (float)(int)(0.5f + Size.y / FontSize); + if (cursor_offset.y - Scroll.y <= 0.0f) + Scroll.y = ImMax(0.0f, cursor_offset.y - FontSize * 2.0f); + else if (cursor_offset.y - Scroll.y > Size.y + 1) + Scroll.y = cursor_offset.y - Size.y + FontSize * 1.0f; + /* + if (cursor_offset.y <= Scroll.y) + Scroll.y = ImMax(0.0f, cursor_offset.y - FontSize * 2.0f); + else if (cursor_offset.y - height_aligned >= Scroll.y) + Scroll.y = cursor_offset.y - height_aligned + FontSize * 2.0f; + */ } // Public API to manipulate UTF-8 text @@ -6855,13 +6859,13 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.begin(), edit_state.Text.size(), buf, NULL, &buf_end); edit_state.CurLenA = buf_end - buf; // We can't get the result from ImFormatString() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. edit_state.Size = size + style.FramePadding; - edit_state.InputCursorScreenPos = ImVec2(-1.f,-1.f); + edit_state.InputCursorScreenPos = ImVec2(-1.f, -1.f); edit_state.CursorAnimReset(); if (edit_state.Id != id) { edit_state.Id = id; - edit_state.ScrollX = 0.0f; + edit_state.Scroll = ImVec2(0.f, 0.f); stb_textedit_initialize_state(&edit_state.StbState, !is_multiline); if (!is_multiline && focus_requested_by_code) select_all = true; @@ -6918,12 +6922,12 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV } else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock) { - stb_textedit_click(&edit_state, &edit_state.StbState, mx + edit_state.ScrollX, my); + stb_textedit_click(&edit_state, &edit_state.StbState, mx + edit_state.Scroll.x, my + edit_state.Scroll.y); edit_state.CursorAnimReset(); } else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock) { - stb_textedit_drag(&edit_state, &edit_state.StbState, mx + edit_state.ScrollX, my); + stb_textedit_drag(&edit_state, &edit_state.StbState, mx + edit_state.Scroll.x, my + edit_state.Scroll.y); edit_state.CursorAnimReset(); } if (edit_state.SelectedAllMouseLock && !io.MouseDown[0]) @@ -7124,8 +7128,8 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const float font_offy_dn = 2.0f; const ImVec2 render_pos = frame_bb.Min + style.FramePadding; - //const float render_scroll_x = (g.ActiveId == id) ? edit_state.ScrollX : 0.0f; - const float render_scroll_x = (edit_state.Id == id) ? edit_state.ScrollX : 0.0f; + //const ImVec2 render_scroll = (g.ActiveId == id) ? edit_state.Scroll : ImVec2(0.f, 0.f); + const ImVec2 render_scroll = (edit_state.Id == id) ? edit_state.Scroll : ImVec2(0.f, 0.f); const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x + style.FramePadding.x*2.0f, frame_bb.Min.y + size.y + style.FramePadding.y*2.0f); if (g.ActiveId == id) @@ -7144,7 +7148,8 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) { ImVec2 rect_size = CalcTextSizeW(edit_state.Font, edit_state.FontSize, FLT_MAX, p, text_selected_end, &p, NULL, true); - ImRect rect(render_pos + rect_pos + ImVec2(-edit_state.ScrollX, (p == text_selected_begin) ? -font_offy_up : -g.FontSize), render_pos + rect_pos + ImVec2(rect_size.x - edit_state.ScrollX, (p == text_selected_end) ? +font_offy_dn : 0.0f)); + if (rect_size.x <= 0.0f) rect_size.x = 2.0f; // So we can see selected empty lines + ImRect rect(render_pos - render_scroll + rect_pos + ImVec2(0.0f, (p == text_selected_begin) ? -font_offy_up : -g.FontSize), render_pos - render_scroll + rect_pos + ImVec2(rect_size.x, (p == text_selected_end) ? +font_offy_dn : 0.0f)); rect.Clip(clip_rect); if (rect.Overlaps(clip_rect)) window->DrawList->AddRectFilled(rect.Min, rect.Max, font_color); @@ -7154,7 +7159,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV } } - window->DrawList->AddText(g.Font, g.FontSize, render_pos - ImVec2(render_scroll_x, 0.0f), window->Color(ImGuiCol_Text), buf, NULL, 0.0f, &clip_rect); + window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, window->Color(ImGuiCol_Text), buf, NULL, 0.0f, &clip_rect); // Log as text if (GImGui->LogEnabled) @@ -7162,7 +7167,9 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV if (g.ActiveId == id) { - const ImVec2 cursor_pos = render_pos + edit_state.CalcDisplayOffsetFromCharIdx(edit_state.StbState.cursor); + ImVec2 cursor_pos; + CalcTextSizeW(edit_state.Font, edit_state.FontSize, FLT_MAX, edit_state.Text.begin(), edit_state.Text.begin() + edit_state.StbState.cursor, NULL, &cursor_pos); + cursor_pos += render_pos - edit_state.Scroll; // Draw blinking cursor if (g.InputTextState.CursorIsVisible()) From 7dc5228235af740d3216526ae14354a48e1fb5df Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jun 2015 15:49:18 -0600 Subject: [PATCH 13/44] InputText: doesn't reset scrolling when active text input becomes bigger than its content. (#200) Mostly because it's expensive. --- imgui.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 17fe474aa..a518506bf 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6671,18 +6671,6 @@ void ImGuiTextEditState::UpdateScrollOffset() const ImWchar* text = Text.begin(); CalcTextSizeW(Font, FontSize, FLT_MAX, text, text+StbState.cursor, NULL, &cursor_offset); - // If widget became bigger than text (because of a resize), reset horizontal scrolling - // FIXME-OPT - if (Scroll.x > 0.0f || Scroll.y > 0.0f) - { - ImVec2 text_size = CalcTextSizeW(Font, FontSize, FLT_MAX, text, NULL, NULL); - bool ret = false; - if (text_size.x < Size.x) { Scroll.x = 0.0f; ret = true; } - if (text_size.y < Size.y) { Scroll.y = 0.0f; ret = true; } - if (ret) - return; - } - const ImVec2 scroll_increment(Size.x * 0.25f, FontSize); if (cursor_offset.x < Scroll.x) Scroll.x = ImMax(0.0f, cursor_offset.x - scroll_increment.x); From 3df91b52eac4668ffb39e3d5cb04ef37f8917749 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jun 2015 16:02:50 -0600 Subject: [PATCH 14/44] Increased key repeat rate for non-character input key repeat. Dodgy - we should have a match here for character input and non-character input (e.g. holding 'a' vs holding 'backspace' should be same rate), but for the earlier we don't have the info? --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index a518506bf..9fafc935c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -670,7 +670,7 @@ ImGuiIO::ImGuiIO() MouseDoubleClickMaxDist = 6.0f; MouseDragThreshold = 6.0f; KeyRepeatDelay = 0.250f; - KeyRepeatRate = 0.020f; + KeyRepeatRate = 0.050f; UserData = NULL; // User functions From 5e323561be9e0908f8d1caa90440bf4630c4e4f5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jun 2015 17:07:49 -0600 Subject: [PATCH 15/44] InputTextMultiline() fixes, vertical scrolling, optimizations (#200) Using a child window for multi-line text. --- imgui.cpp | 165 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 93 insertions(+), 72 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 9fafc935c..8946fa4d5 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1189,24 +1189,19 @@ struct ImGuiTextEditState ImVector TempTextBuffer; size_t CurLenA, CurLenW; // we need to maintain our buffer length in both UTF-8 and wchar format. size_t BufSizeA; // end-user buffer size - ImVec2 Size; // widget width/height - ImVec2 Scroll; + float ScrollX; STB_TexteditState StbState; float CursorAnim; + bool CursorFollow; ImVec2 InputCursorScreenPos; // Cursor position in screen space to be used by IME callback. bool SelectedAllMouseLock; - ImFont* Font; - float FontSize; ImGuiTextEditState() { memset(this, 0, sizeof(*this)); } - void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking bool CursorIsVisible() const { return CursorAnim <= 0.0f || fmodf(CursorAnim, 1.20f) <= 0.80f; } // Blinking bool HasSelection() const { return StbState.select_start != StbState.select_end; } void SelectAll() { StbState.select_start = 0; StbState.select_end = (int)CurLenW; StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; } - void OnKeyPressed(int key); - void UpdateScrollOffset(); }; // Data saved in imgui.ini file @@ -6576,14 +6571,15 @@ static ImVec2 CalcTextSizeW(ImFont* font, float font_size, float max_width, cons // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return (int)obj->CurLenW; } static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->Text[idx]; } -static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar* s = &obj->Text[line_start_idx+char_idx]; if (*s == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return CalcTextSizeW(obj->Font, obj->FontSize, FLT_MAX, s, s+1, NULL).x; } +static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar* s = &obj->Text[line_start_idx+char_idx]; if (*s == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return CalcTextSizeW(GImGui->Font, GImGui->FontSize, FLT_MAX, s, s+1, NULL).x; } static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; } static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) { + ImGuiState& g = *GImGui; const ImWchar* text = obj->Text.begin(); const ImWchar* text_remaining = NULL; - const ImVec2 size = CalcTextSizeW(obj->Font, obj->FontSize, FLT_MAX, text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); + const ImVec2 size = CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); r->x0 = 0.0f; r->x1 = size.x; r->baseline_y_delta = size.y; @@ -6661,35 +6657,10 @@ namespace IMGUI_STB_NAMESPACE void ImGuiTextEditState::OnKeyPressed(int key) { stb_textedit_key(this, &StbState, key); + CursorFollow = true; CursorAnimReset(); } -void ImGuiTextEditState::UpdateScrollOffset() -{ - // Scroll in chunks of quarter width - ImVec2 cursor_offset; - const ImWchar* text = Text.begin(); - CalcTextSizeW(Font, FontSize, FLT_MAX, text, text+StbState.cursor, NULL, &cursor_offset); - - const ImVec2 scroll_increment(Size.x * 0.25f, FontSize); - if (cursor_offset.x < Scroll.x) - Scroll.x = ImMax(0.0f, cursor_offset.x - scroll_increment.x); - else if (cursor_offset.x - Size.x >= Scroll.x) - Scroll.x = cursor_offset.x - Size.x + scroll_increment.x; - - //float height_aligned = FontSize * (float)(int)(0.5f + Size.y / FontSize); - if (cursor_offset.y - Scroll.y <= 0.0f) - Scroll.y = ImMax(0.0f, cursor_offset.y - FontSize * 2.0f); - else if (cursor_offset.y - Scroll.y > Size.y + 1) - Scroll.y = cursor_offset.y - Size.y + FontSize * 1.0f; - /* - if (cursor_offset.y <= Scroll.y) - Scroll.y = ImMax(0.0f, cursor_offset.y - FontSize * 2.0f); - else if (cursor_offset.y - height_aligned >= Scroll.y) - Scroll.y = cursor_offset.y - height_aligned + FontSize * 2.0f; - */ -} - // Public API to manipulate UTF-8 text // We expose UTF-8 to the user (unlike the STB_TEXTEDIT_* functions which are manipulating wchar) void ImGuiTextEditCallbackData::DeleteChars(int pos, int bytes_count) @@ -6810,9 +6781,23 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size + style.FramePadding*2.0f); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f)); - ItemSize(total_bb, style.FramePadding.y); - if (!ItemAdd(total_bb, &id)) - return false; + + ImGuiWindow* child_window = NULL; + if (is_multiline) + { + ImGui::BeginGroup(); + ImGui::BeginChildFrame(id, frame_bb.GetSize()); + child_window = GetCurrentWindow(); + child_window->DC.CursorPos += style.FramePadding; + size.x -= child_window->ScrollbarWidth(); + } + else + { + ItemSize(total_bb, style.FramePadding.y); + if (!ItemAdd(total_bb, &id)) + return false; + } + ImGuiWindow* draw_window = child_window ? child_window : window; // NB: we are only allowed to access 'edit_state' if we are the active widget. ImGuiTextEditState& edit_state = g.InputTextState; @@ -6846,14 +6831,13 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const char* buf_end = NULL; edit_state.CurLenW = ImTextStrFromUtf8(edit_state.Text.begin(), edit_state.Text.size(), buf, NULL, &buf_end); edit_state.CurLenA = buf_end - buf; // We can't get the result from ImFormatString() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. - edit_state.Size = size + style.FramePadding; edit_state.InputCursorScreenPos = ImVec2(-1.f, -1.f); edit_state.CursorAnimReset(); if (edit_state.Id != id) { edit_state.Id = id; - edit_state.Scroll = ImVec2(0.f, 0.f); + edit_state.ScrollX = 0.f; stb_textedit_initialize_state(&edit_state.StbState, !is_multiline); if (!is_multiline && focus_requested_by_code) select_all = true; @@ -6891,17 +6875,13 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV //if (edit_state.Id == id) // Works, but double-click to select-all sets cursors to end which in turn tends to scroll toward the right when shrinking widget. { // Update some data if we are active or last active - edit_state.Size = size + style.FramePadding; edit_state.BufSizeA = buf_size; - edit_state.Font = g.Font; - edit_state.FontSize = g.FontSize; - edit_state.UpdateScrollOffset(); } if (g.ActiveId == id) { // Edit in progress - const float mx = g.IO.MousePos.x - frame_bb.Min.x - style.FramePadding.x; - const float my = is_multiline ? (g.IO.MousePos.y - frame_bb.Min.y - style.FramePadding.y) : g.FontSize*0.5f; + const float mouse_x = (g.IO.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX; + const float mouse_y = (is_multiline ? (g.IO.MousePos.y - child_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f)); if (select_all || (hovered && io.MouseDoubleClicked[0])) { @@ -6910,12 +6890,12 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV } else if (io.MouseClicked[0] && !edit_state.SelectedAllMouseLock) { - stb_textedit_click(&edit_state, &edit_state.StbState, mx + edit_state.Scroll.x, my + edit_state.Scroll.y); + stb_textedit_click(&edit_state, &edit_state.StbState, mouse_x, mouse_y); edit_state.CursorAnimReset(); } else if (io.MouseDown[0] && !edit_state.SelectedAllMouseLock) { - stb_textedit_drag(&edit_state, &edit_state.StbState, mx + edit_state.Scroll.x, my + edit_state.Scroll.y); + stb_textedit_drag(&edit_state, &edit_state.StbState, mouse_x, mouse_y); edit_state.CursorAnimReset(); } if (edit_state.SelectedAllMouseLock && !io.MouseDown[0]) @@ -6943,8 +6923,8 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const bool is_ctrl_only = is_ctrl_down && !is_alt_down && !is_shift_down; if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDLEFT | k_mask : STB_TEXTEDIT_K_LEFT | k_mask); } else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDRIGHT | k_mask : STB_TEXTEDIT_K_RIGHT | k_mask); } - else if (is_multiline && IsKeyPressedMap(ImGuiKey_UpArrow)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UP | k_mask); } - else if (is_multiline && IsKeyPressedMap(ImGuiKey_DownArrow)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DOWN| k_mask); } + else if (is_multiline && IsKeyPressedMap(ImGuiKey_UpArrow)) { bool move_cursor = true; if (is_ctrl_down) { child_window->ScrollY -= g.FontSize; } if (move_cursor) edit_state.OnKeyPressed(STB_TEXTEDIT_K_UP | k_mask); } + else if (is_multiline && IsKeyPressedMap(ImGuiKey_DownArrow)) { bool move_cursor = true; if (is_ctrl_down) { child_window->ScrollY += g.FontSize; } if (move_cursor) edit_state.OnKeyPressed(STB_TEXTEDIT_K_DOWN| k_mask); } else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Delete)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } @@ -6973,8 +6953,8 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV else if (IsKeyPressedMap(ImGuiKey_Escape)) { SetActiveId(0); cancel_edit = true; } else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_Z)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); } else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_Y)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); } - else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_A)) { edit_state.SelectAll(); } - else if (is_ctrl_only && (IsKeyPressedMap(ImGuiKey_X) || IsKeyPressedMap(ImGuiKey_C))) + else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_A)) { edit_state.SelectAll(); edit_state.CursorFollow = true; } + else if (is_ctrl_only && (IsKeyPressedMap(ImGuiKey_X) || IsKeyPressedMap(ImGuiKey_C)) && (!is_multiline || edit_state.HasSelection())) { // Cut, Copy const bool cut = IsKeyPressedMap(ImGuiKey_X); @@ -6991,7 +6971,10 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV } if (cut) + { + edit_state.CursorFollow = true; stb_textedit_cut(&edit_state, &edit_state.StbState); + } } else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_V)) { @@ -7010,23 +6993,21 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV s += ImTextCharFromUtf8(&c, s, NULL); if (c == 0) break; - if (c >= 0x10000) - continue; - if (!InputTextFilterCharacter(&c, flags, callback, user_data)) + if (c >= 0x10000 || !InputTextFilterCharacter(&c, flags, callback, user_data)) continue; clipboard_filtered[clipboard_filtered_len++] = (ImWchar)c; } clipboard_filtered[clipboard_filtered_len] = 0; if (clipboard_filtered_len > 0) // If everything was filtered, ignore the pasting operation + { stb_textedit_paste(&edit_state, &edit_state.StbState, clipboard_filtered, clipboard_filtered_len); + edit_state.CursorFollow = true; + } ImGui::MemFree(clipboard_filtered); } } } - edit_state.CursorAnim += g.IO.DeltaTime; - edit_state.UpdateScrollOffset(); - if (cancel_edit) { // Restore initial value @@ -7109,45 +7090,76 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV } } } - - RenderFrame(frame_bb.Min, frame_bb.Max, window->Color(ImGuiCol_FrameBg), true, style.FrameRounding); + + if (!is_multiline) + RenderFrame(frame_bb.Min, frame_bb.Max, window->Color(ImGuiCol_FrameBg), true, style.FrameRounding); const float font_offy_up = g.FontSize+1.0f; // FIXME: those offsets are part of the style or font API const float font_offy_dn = 2.0f; - const ImVec2 render_pos = frame_bb.Min + style.FramePadding; + const ImVec2 render_pos = is_multiline ? GetCurrentWindow()->DC.CursorPos : frame_bb.Min + style.FramePadding; //const ImVec2 render_scroll = (g.ActiveId == id) ? edit_state.Scroll : ImVec2(0.f, 0.f); - const ImVec2 render_scroll = (edit_state.Id == id) ? edit_state.Scroll : ImVec2(0.f, 0.f); - const ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x + style.FramePadding.x*2.0f, frame_bb.Min.y + size.y + style.FramePadding.y*2.0f); + const ImVec2 render_scroll = ImVec2((edit_state.Id == id) ? edit_state.ScrollX : 0.0f, 0.0f); + ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x + style.FramePadding.x*2.0f, frame_bb.Min.y + size.y + style.FramePadding.y*2.0f); if (g.ActiveId == id) + { + edit_state.CursorAnim += g.IO.DeltaTime; + if (edit_state.CursorFollow) + { + edit_state.CursorFollow = false; + + // Horizontal scroll in chunks of quarter width + ImVec2 cursor_offset; + CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, edit_state.Text.begin(), edit_state.Text.begin() + edit_state.StbState.cursor, NULL, &cursor_offset); + + const float scroll_increment_x = size.x * 0.25f; + if (cursor_offset.x < edit_state.ScrollX) + edit_state.ScrollX = ImMax(0.0f, cursor_offset.x - scroll_increment_x); + else if (cursor_offset.x - size.x >= edit_state.ScrollX) + edit_state.ScrollX = cursor_offset.x - size.x + scroll_increment_x; + + // Vertical scroll + if (is_multiline) + { + if (cursor_offset.y - g.FontSize < child_window->ScrollY) + child_window->ScrollY = ImMax(0.0f, cursor_offset.y - g.FontSize); + else if (cursor_offset.y - g.FontSize*0 - size.y >= child_window->ScrollY) + child_window->ScrollY = cursor_offset.y - size.y;// + g.FontSize; + } + } + } + + + if (edit_state.Id == id) // Display selection if we are last active so that it shows when it use scrollbar { // Draw selection const int select_begin_idx = edit_state.StbState.select_start; const int select_end_idx = edit_state.StbState.select_end; if (select_begin_idx != select_end_idx) { + // FIXME-OPT ImWchar* text_selected_begin = edit_state.Text.begin() + ImMin(select_begin_idx,select_end_idx); ImWchar* text_selected_end = edit_state.Text.begin() + ImMax(select_begin_idx,select_end_idx); ImVec2 rect_pos; - CalcTextSizeW(edit_state.Font, edit_state.FontSize, FLT_MAX, edit_state.Text.begin(), text_selected_begin, NULL, &rect_pos); + CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, edit_state.Text.begin(), text_selected_begin, NULL, &rect_pos); - ImU32 font_color = window->Color(ImGuiCol_TextSelectedBg); + ImU32 font_color = draw_window->Color(ImGuiCol_TextSelectedBg); for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) { - ImVec2 rect_size = CalcTextSizeW(edit_state.Font, edit_state.FontSize, FLT_MAX, p, text_selected_end, &p, NULL, true); - if (rect_size.x <= 0.0f) rect_size.x = 2.0f; // So we can see selected empty lines + ImVec2 rect_size = CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, p, text_selected_end, &p, NULL, true); + if (rect_size.x <= 0.0f) rect_size.x = 3.0f; // So we can see selected empty lines ImRect rect(render_pos - render_scroll + rect_pos + ImVec2(0.0f, (p == text_selected_begin) ? -font_offy_up : -g.FontSize), render_pos - render_scroll + rect_pos + ImVec2(rect_size.x, (p == text_selected_end) ? +font_offy_dn : 0.0f)); rect.Clip(clip_rect); if (rect.Overlaps(clip_rect)) - window->DrawList->AddRectFilled(rect.Min, rect.Max, font_color); + draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, font_color); rect_pos.x = 0.0f; rect_pos.y += g.FontSize; } } } - window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, window->Color(ImGuiCol_Text), buf, NULL, 0.0f, &clip_rect); + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, draw_window->Color(ImGuiCol_Text), buf, NULL, 0.0f, is_multiline ? NULL : &clip_rect); // Log as text if (GImGui->LogEnabled) @@ -7156,12 +7168,12 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV if (g.ActiveId == id) { ImVec2 cursor_pos; - CalcTextSizeW(edit_state.Font, edit_state.FontSize, FLT_MAX, edit_state.Text.begin(), edit_state.Text.begin() + edit_state.StbState.cursor, NULL, &cursor_pos); - cursor_pos += render_pos - edit_state.Scroll; + CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, edit_state.Text.begin(), edit_state.Text.begin() + edit_state.StbState.cursor, NULL, &cursor_pos); + cursor_pos += render_pos - ImVec2(edit_state.ScrollX, 0.0f); // Draw blinking cursor if (g.InputTextState.CursorIsVisible()) - window->DrawList->AddLine(cursor_pos + ImVec2(0,2-font_offy_up), cursor_pos + ImVec2(0,-3+font_offy_dn), window->Color(ImGuiCol_Text)); + draw_window->DrawList->AddLine(cursor_pos + ImVec2(0,2-font_offy_up), cursor_pos + ImVec2(0,-3+font_offy_dn), window->Color(ImGuiCol_Text)); // Notify OS of text input position for advanced IME if (io.ImeSetInputScreenPosFn && ImLengthSqr(edit_state.InputCursorScreenPos - cursor_pos) > 0.0001f) @@ -7170,6 +7182,15 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV edit_state.InputCursorScreenPos = cursor_pos; } + if (is_multiline) + { + // FIXME-OPT FIXME-WIP-MULTILINE + ImVec2 text_size = g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, buf); + ImGui::Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line + ImGui::EndChildFrame(); + ImGui::EndGroup(); + } + if (label_size.x > 0) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); From 490e9e42ff7ca888977cd2c07eae2137f4aafa24 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jun 2015 18:14:25 -0600 Subject: [PATCH 16/44] InputText(): shallow tweaks (#200) --- imgui.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 8946fa4d5..1c32a675e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6773,11 +6773,9 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const bool is_multiline = (flags & ImGuiInputTextFlags_Multiline) != 0; ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); - ImVec2 size = size_arg; - if (size.x == 0.0f) - size.x = ImGui::CalcItemWidth(); - if (size.y == 0.0f) - size.y = is_multiline ? label_size.y * 8.0f : label_size.y; // Arbitrary default + ImVec2 size; + size.x = (size_arg.x != 0.0f) ? size_arg.x : ImGui::CalcItemWidth(); + size.y = (size_arg.y != 0.0f) ? size_arg.y : is_multiline ? ImGui::GetTextLineHeight() * 8.0f : label_size.y; // Arbitrary default const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size + style.FramePadding*2.0f); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f)); @@ -6871,10 +6869,10 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV bool value_changed = false; bool cancel_edit = false; bool enter_pressed = false; - if (g.ActiveId == id) - //if (edit_state.Id == id) // Works, but double-click to select-all sets cursors to end which in turn tends to scroll toward the right when shrinking widget. + + // Update some data if we are active or last active + if (g.ActiveId == id) //if (edit_state.Id == id) // Works, but double-click to select-all sets cursors to end which in turn tends to scroll toward the right when shrinking widget. { - // Update some data if we are active or last active edit_state.BufSizeA = buf_size; } if (g.ActiveId == id) @@ -7130,7 +7128,6 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV } } - if (edit_state.Id == id) // Display selection if we are last active so that it shows when it use scrollbar { // Draw selection From b0e8643523941898fc882acb59598c63b6f95c2f Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jun 2015 18:31:15 -0600 Subject: [PATCH 17/44] Demo for InputTextMultiline() (#200) --- imgui.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/imgui.cpp b/imgui.cpp index 1c32a675e..fbb6fa184 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10953,6 +10953,15 @@ void ImGui::ShowTestWindow(bool* opened) //ImGui::ListBox("##listbox2", &listbox_item_current2, listbox_items, IM_ARRAYSIZE(listbox_items), 4); //ImGui::PopItemWidth(); + if (ImGui::TreeNode("Multi-line text input")) + { + static char text[1024*4] = "// F00F bug\nlabel:\n\tlock cmpxchg8b eax\n"; + ImGui::PushItemWidth(-1.0f); + ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(0.f, ImGui::GetTextLineHeight() * 8), ImGuiInputTextFlags_AllowTabInput); + ImGui::PopItemWidth(); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Multi-component Widgets")) { ImGui::Unindent(); From 9c399ee3be90509d93de58d755a54dc781a72aa3 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 17 Jun 2015 18:36:06 -0600 Subject: [PATCH 18/44] Demo for InputTextMultiline() (#200) --- imgui.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index fbb6fa184..d11135251 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10822,6 +10822,15 @@ void ImGui::ShowTestWindow(bool* opened) ImGui::TreePop(); } + if (ImGui::TreeNode("Multi-line Text Input")) + { + static char text[1024*4] = "// F00F bug\nlabel:\n\tlock cmpxchg8b eax\n"; + ImGui::PushItemWidth(-1.0f); + ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(0.f, ImGui::GetTextLineHeight() * 16), ImGuiInputTextFlags_AllowTabInput); + ImGui::PopItemWidth(); + ImGui::TreePop(); + } + if (ImGui::TreeNode("Dragging")) { ImGui::TextWrapped("You can use ImGui::GetItemActiveDragDelta() to query for the dragged amount on any widget."); @@ -10953,15 +10962,6 @@ void ImGui::ShowTestWindow(bool* opened) //ImGui::ListBox("##listbox2", &listbox_item_current2, listbox_items, IM_ARRAYSIZE(listbox_items), 4); //ImGui::PopItemWidth(); - if (ImGui::TreeNode("Multi-line text input")) - { - static char text[1024*4] = "// F00F bug\nlabel:\n\tlock cmpxchg8b eax\n"; - ImGui::PushItemWidth(-1.0f); - ImGui::InputTextMultiline("##source", text, IM_ARRAYSIZE(text), ImVec2(0.f, ImGui::GetTextLineHeight() * 8), ImGuiInputTextFlags_AllowTabInput); - ImGui::PopItemWidth(); - ImGui::TreePop(); - } - if (ImGui::TreeNode("Multi-component Widgets")) { ImGui::Unindent(); From d0ea5942a96a79b694e3bcba36398a2670ffde67 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 08:49:57 -0600 Subject: [PATCH 19/44] InputTextMultiline() can clip + BeginChildFrame() returns bool. (#200) --- imgui.cpp | 15 ++++++++++----- imgui.h | 2 +- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index d11135251..6ffccc699 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -3293,13 +3293,13 @@ void ImGui::EndChild() } // Helper to create a child window / scrolling region that looks like a normal widget frame. -void ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size) +bool ImGui::BeginChildFrame(ImGuiID id, const ImVec2& size) { ImGuiState& g = *GImGui; const ImGuiStyle& style = g.Style; ImGui::PushStyleColor(ImGuiCol_ChildWindowBg, style.Colors[ImGuiCol_FrameBg]); ImGui::PushStyleVar(ImGuiStyleVar_ChildWindowRounding, style.FrameRounding); - ImGui::BeginChild(id, size); + return ImGui::BeginChild(id, size); } void ImGui::EndChildFrame() @@ -6775,7 +6775,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV ImVec2 label_size = ImGui::CalcTextSize(label, NULL, true); ImVec2 size; size.x = (size_arg.x != 0.0f) ? size_arg.x : ImGui::CalcItemWidth(); - size.y = (size_arg.y != 0.0f) ? size_arg.y : is_multiline ? ImGui::GetTextLineHeight() * 8.0f : label_size.y; // Arbitrary default + size.y = (size_arg.y != 0.0f) ? size_arg.y : is_multiline ? ImGui::GetTextLineHeight() * 8.0f : label_size.y; // Arbitrary default of 8 lines high for multi-line const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size + style.FramePadding*2.0f); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f)); @@ -6784,7 +6784,12 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV if (is_multiline) { ImGui::BeginGroup(); - ImGui::BeginChildFrame(id, frame_bb.GetSize()); + if (!ImGui::BeginChildFrame(id, frame_bb.GetSize())) + { + ImGui::EndChildFrame(); + ImGui::EndGroup(); + return false; + } child_window = GetCurrentWindow(); child_window->DC.CursorPos += style.FramePadding; size.x -= child_window->ScrollbarWidth(); @@ -7092,7 +7097,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV if (!is_multiline) RenderFrame(frame_bb.Min, frame_bb.Max, window->Color(ImGuiCol_FrameBg), true, style.FrameRounding); - const float font_offy_up = g.FontSize+1.0f; // FIXME: those offsets are part of the style or font API + const float font_offy_up = g.FontSize+1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. const float font_offy_dn = 2.0f; const ImVec2 render_pos = is_multiline ? GetCurrentWindow()->DC.CursorPos : frame_bb.Min + style.FramePadding; diff --git a/imgui.h b/imgui.h index aa34324ab..b864361c5 100644 --- a/imgui.h +++ b/imgui.h @@ -373,7 +373,7 @@ namespace ImGui IMGUI_API ImVec2 CalcTextSize(const char* text, const char* text_end = NULL, bool hide_text_after_double_hash = false, float wrap_width = -1.0f); IMGUI_API void CalcListClipping(int items_count, float items_height, int* out_items_display_start, int* out_items_display_end); // calculate coarse clipping for large list of evenly sized items. Prefer using the ImGuiListClipper higher-level helper if you can. - IMGUI_API void BeginChildFrame(ImGuiID id, const ImVec2& size); // helper to create a child window / scrolling region that looks like a normal widget frame + IMGUI_API bool BeginChildFrame(ImGuiID id, const ImVec2& size); // helper to create a child window / scrolling region that looks like a normal widget frame IMGUI_API void EndChildFrame(); IMGUI_API ImU32 ColorConvertFloat4ToU32(const ImVec4& in); From c06373de930ab702606a1b6da48f920d85c79344 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 10:40:11 -0600 Subject: [PATCH 20/44] Cleanup --- imgui.cpp | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 6ffccc699..7538dcb3e 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6780,7 +6780,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const ImRect frame_bb(window->DC.CursorPos, window->DC.CursorPos + size + style.FramePadding*2.0f); const ImRect total_bb(frame_bb.Min, frame_bb.Max + ImVec2(label_size.x > 0.0f ? (style.ItemInnerSpacing.x + label_size.x) : 0.0f, 0.0f)); - ImGuiWindow* child_window = NULL; + ImGuiWindow* draw_window = window; if (is_multiline) { ImGui::BeginGroup(); @@ -6790,9 +6790,9 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV ImGui::EndGroup(); return false; } - child_window = GetCurrentWindow(); - child_window->DC.CursorPos += style.FramePadding; - size.x -= child_window->ScrollbarWidth(); + draw_window = GetCurrentWindow(); + draw_window->DC.CursorPos += style.FramePadding; + size.x -= draw_window->ScrollbarWidth(); } else { @@ -6800,7 +6800,6 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV if (!ItemAdd(total_bb, &id)) return false; } - ImGuiWindow* draw_window = child_window ? child_window : window; // NB: we are only allowed to access 'edit_state' if we are the active widget. ImGuiTextEditState& edit_state = g.InputTextState; @@ -6884,7 +6883,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV { // Edit in progress const float mouse_x = (g.IO.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX; - const float mouse_y = (is_multiline ? (g.IO.MousePos.y - child_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f)); + const float mouse_y = (is_multiline ? (g.IO.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f)); if (select_all || (hovered && io.MouseDoubleClicked[0])) { @@ -6926,8 +6925,8 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const bool is_ctrl_only = is_ctrl_down && !is_alt_down && !is_shift_down; if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDLEFT | k_mask : STB_TEXTEDIT_K_LEFT | k_mask); } else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDRIGHT | k_mask : STB_TEXTEDIT_K_RIGHT | k_mask); } - else if (is_multiline && IsKeyPressedMap(ImGuiKey_UpArrow)) { bool move_cursor = true; if (is_ctrl_down) { child_window->ScrollY -= g.FontSize; } if (move_cursor) edit_state.OnKeyPressed(STB_TEXTEDIT_K_UP | k_mask); } - else if (is_multiline && IsKeyPressedMap(ImGuiKey_DownArrow)) { bool move_cursor = true; if (is_ctrl_down) { child_window->ScrollY += g.FontSize; } if (move_cursor) edit_state.OnKeyPressed(STB_TEXTEDIT_K_DOWN| k_mask); } + else if (is_multiline && IsKeyPressedMap(ImGuiKey_UpArrow)) { bool move_cursor = true; if (is_ctrl_down) { draw_window->ScrollY -= g.FontSize; } if (move_cursor) edit_state.OnKeyPressed(STB_TEXTEDIT_K_UP | k_mask); } + else if (is_multiline && IsKeyPressedMap(ImGuiKey_DownArrow)) { bool move_cursor = true; if (is_ctrl_down) { draw_window->ScrollY += g.FontSize; } if (move_cursor) edit_state.OnKeyPressed(STB_TEXTEDIT_K_DOWN| k_mask); } else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Delete)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } @@ -7099,7 +7098,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const float font_offy_up = g.FontSize+1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. const float font_offy_dn = 2.0f; - const ImVec2 render_pos = is_multiline ? GetCurrentWindow()->DC.CursorPos : frame_bb.Min + style.FramePadding; + const ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; //const ImVec2 render_scroll = (g.ActiveId == id) ? edit_state.Scroll : ImVec2(0.f, 0.f); const ImVec2 render_scroll = ImVec2((edit_state.Id == id) ? edit_state.ScrollX : 0.0f, 0.0f); @@ -7125,10 +7124,10 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV // Vertical scroll if (is_multiline) { - if (cursor_offset.y - g.FontSize < child_window->ScrollY) - child_window->ScrollY = ImMax(0.0f, cursor_offset.y - g.FontSize); - else if (cursor_offset.y - g.FontSize*0 - size.y >= child_window->ScrollY) - child_window->ScrollY = cursor_offset.y - size.y;// + g.FontSize; + if (cursor_offset.y - g.FontSize < draw_window->ScrollY) + draw_window->ScrollY = ImMax(0.0f, cursor_offset.y - g.FontSize); + else if (cursor_offset.y - g.FontSize*0 - size.y >= draw_window->ScrollY) + draw_window->ScrollY = cursor_offset.y - size.y; } } } @@ -7150,7 +7149,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) { ImVec2 rect_size = CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, p, text_selected_end, &p, NULL, true); - if (rect_size.x <= 0.0f) rect_size.x = 3.0f; // So we can see selected empty lines + if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines ImRect rect(render_pos - render_scroll + rect_pos + ImVec2(0.0f, (p == text_selected_begin) ? -font_offy_up : -g.FontSize), render_pos - render_scroll + rect_pos + ImVec2(rect_size.x, (p == text_selected_end) ? +font_offy_dn : 0.0f)); rect.Clip(clip_rect); if (rect.Overlaps(clip_rect)) From 9180126db663fc7af87bae0d7f9df192185cc93b Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 11:30:10 -0600 Subject: [PATCH 21/44] Speeding up some wchar<>UTF8 functions and some specialization for Ascii. (testing 500 KB text for #200) --- imgui.cpp | 98 ++++++++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 45 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 7538dcb3e..8263c5c31 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -564,11 +564,12 @@ static inline int ImUpperPowerOfTwo(int v) { v--; v |= v >> 1; v |= v >> 2; v static inline bool ImCharIsSpace(int c) { return c == ' ' || c == '\t' || c == 0x3000; } // Helpers: UTF-8 <> wchar -static int ImTextCharToUtf8(char* buf, size_t buf_size, unsigned int in_char); // return output UTF-8 bytes count +static inline int ImTextCharToUtf8(char* buf, size_t buf_size, unsigned int in_char); // return output UTF-8 bytes count static ptrdiff_t ImTextStrToUtf8(char* buf, size_t buf_size, const ImWchar* in_text, const ImWchar* in_text_end); // return output UTF-8 bytes count static int ImTextCharFromUtf8(unsigned int* out_char, const char* in_text, const char* in_text_end); // return input UTF-8 bytes count static ptrdiff_t ImTextStrFromUtf8(ImWchar* buf, size_t buf_size, const char* in_text, const char* in_text_end, const char** in_remaining = NULL); // return input UTF-8 bytes count static int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end); // return number of UTF-8 code-points (NOT bytes count) +static inline int ImTextCountUtf8BytesFromChar(unsigned int in_char); // return output UTF-8 bytes count static int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in_text_end); // return number of bytes to express string as UTF-8 code-points //----------------------------------------------------------------------------- @@ -9745,48 +9746,50 @@ static int ImTextCountCharsFromUtf8(const char* in_text, const char* in_text_end } // Based on stb_to_utf8() from github.com/nothings/stb/ -static int ImTextCharToUtf8(char* buf, size_t buf_size, unsigned int c) +static inline int ImTextCharToUtf8(char* buf, size_t buf_size, unsigned int c) { - if (c) + if (c < 0x80) { - size_t i = 0; - size_t n = buf_size; - if (c < 0x80) - { - if (i+1 > n) return 0; - buf[i++] = (char)c; - return 1; - } - else if (c < 0x800) - { - if (i+2 > n) return 0; - buf[i++] = (char)(0xc0 + (c >> 6)); - buf[i++] = (char)(0x80 + (c & 0x3f)); - return 2; - } - else if (c >= 0xdc00 && c < 0xe000) - { - return 0; - } - else if (c >= 0xd800 && c < 0xdc00) - { - if (i+4 > n) return 0; - buf[i++] = (char)(0xf0 + (c >> 18)); - buf[i++] = (char)(0x80 + ((c >> 12) & 0x3f)); - buf[i++] = (char)(0x80 + ((c >> 6) & 0x3f)); - buf[i++] = (char)(0x80 + ((c ) & 0x3f)); - return 4; - } - //else if (c < 0x10000) - { - if (i+3 > n) return 0; - buf[i++] = (char)(0xe0 + (c >> 12)); - buf[i++] = (char)(0x80 + ((c>> 6) & 0x3f)); - buf[i++] = (char)(0x80 + ((c ) & 0x3f)); - return 3; - } + buf[0] = (char)c; + return 1; } - return 0; + if (c < 0x800) + { + if (buf_size < 2) return 0; + buf[0] = (char)(0xc0 + (c >> 6)); + buf[1] = (char)(0x80 + (c & 0x3f)); + return 2; + } + if (c >= 0xdc00 && c < 0xe000) + { + return 0; + } + if (c >= 0xd800 && c < 0xdc00) + { + if (buf_size < 4) return 0; + buf[0] = (char)(0xf0 + (c >> 18)); + buf[1] = (char)(0x80 + ((c >> 12) & 0x3f)); + buf[2] = (char)(0x80 + ((c >> 6) & 0x3f)); + buf[3] = (char)(0x80 + ((c ) & 0x3f)); + return 4; + } + //else if (c < 0x10000) + { + if (buf_size < 3) return 0; + buf[0] = (char)(0xe0 + (c >> 12)); + buf[1] = (char)(0x80 + ((c>> 6) & 0x3f)); + buf[2] = (char)(0x80 + ((c ) & 0x3f)); + return 3; + } +} + +static inline int ImTextCountUtf8BytesFromChar(unsigned int c) +{ + if (c < 0x80) return 1; + if (c < 0x800) return 2; + if (c >= 0xdc00 && c < 0xe000) return 0; + if (c >= 0xd800 && c < 0xdc00) return 4; + return 3; } static ptrdiff_t ImTextStrToUtf8(char* buf, size_t buf_size, const ImWchar* in_text, const ImWchar* in_text_end) @@ -9795,8 +9798,11 @@ static ptrdiff_t ImTextStrToUtf8(char* buf, size_t buf_size, const ImWchar* in_t const char* buf_end = buf + buf_size; while (buf_out < buf_end-1 && (!in_text_end || in_text < in_text_end) && *in_text) { - buf_out += ImTextCharToUtf8(buf_out, (uintptr_t)(buf_end-buf_out-1), (unsigned int)*in_text); - in_text++; + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + *buf_out++ = (char)c; + else + buf_out += ImTextCharToUtf8(buf_out, (uintptr_t)(buf_end-buf_out-1), c); } *buf_out = 0; return buf_out - buf; @@ -9807,9 +9813,11 @@ static int ImTextCountUtf8BytesFromStr(const ImWchar* in_text, const ImWchar* in int bytes_count = 0; while ((!in_text_end || in_text < in_text_end) && *in_text) { - char dummy[5]; // FIXME-OPT - bytes_count += ImTextCharToUtf8(dummy, 5, (unsigned int)*in_text); - in_text++; + unsigned int c = (unsigned int)(*in_text++); + if (c < 0x80) + bytes_count++; + else + bytes_count += ImTextCountUtf8BytesFromChar(c); } return bytes_count; } From 8fbb4a566ad00a92e97b44a91d46e4ba29c307a7 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 11:57:43 -0600 Subject: [PATCH 22/44] InputTextMultiline() preserve activeid/cursor/selection when using scrollbar + tidying up (#200) --- imgui.cpp | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 8263c5c31..c97151f16 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6819,9 +6819,10 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV g.MouseCursor = ImGuiMouseCursor_TextInput; } const bool user_clicked = hovered && io.MouseClicked[0]; + const bool user_scrolled = is_multiline && g.ActiveId == 0 && edit_state.Id == id && g.ActiveIdPreviousFrame == draw_window->GetID("#SCROLLY"); bool select_all = (g.ActiveId != id) && (flags & ImGuiInputTextFlags_AutoSelectAll) != 0; - if (focus_requested || user_clicked) + if (focus_requested || user_clicked || user_scrolled) { if (g.ActiveId != id) { @@ -7107,7 +7108,6 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV if (g.ActiveId == id) { - edit_state.CursorAnim += g.IO.DeltaTime; if (edit_state.CursorFollow) { edit_state.CursorFollow = false; @@ -7133,11 +7133,13 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV } } - if (edit_state.Id == id) // Display selection if we are last active so that it shows when it use scrollbar + if (g.ActiveId == id || (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetID("#SCROLLY"))) { + edit_state.CursorAnim += g.IO.DeltaTime; + // Draw selection - const int select_begin_idx = edit_state.StbState.select_start; - const int select_end_idx = edit_state.StbState.select_end; + int select_begin_idx = edit_state.StbState.select_start; + int select_end_idx = edit_state.StbState.select_end; if (select_begin_idx != select_end_idx) { // FIXME-OPT @@ -7146,7 +7148,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV ImVec2 rect_pos; CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, edit_state.Text.begin(), text_selected_begin, NULL, &rect_pos); - ImU32 font_color = draw_window->Color(ImGuiCol_TextSelectedBg); + ImU32 bg_color = draw_window->Color(ImGuiCol_TextSelectedBg); for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) { ImVec2 rect_size = CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, p, text_selected_end, &p, NULL, true); @@ -7154,21 +7156,14 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV ImRect rect(render_pos - render_scroll + rect_pos + ImVec2(0.0f, (p == text_selected_begin) ? -font_offy_up : -g.FontSize), render_pos - render_scroll + rect_pos + ImVec2(rect_size.x, (p == text_selected_end) ? +font_offy_dn : 0.0f)); rect.Clip(clip_rect); if (rect.Overlaps(clip_rect)) - draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, font_color); + draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); rect_pos.x = 0.0f; rect_pos.y += g.FontSize; } } - } - draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, draw_window->Color(ImGuiCol_Text), buf, NULL, 0.0f, is_multiline ? NULL : &clip_rect); + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, draw_window->Color(ImGuiCol_Text), buf, NULL, 0.0f, is_multiline ? NULL : &clip_rect); - // Log as text - if (GImGui->LogEnabled) - LogText(render_pos, buf, NULL); - - if (g.ActiveId == id) - { ImVec2 cursor_pos; CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, edit_state.Text.begin(), edit_state.Text.begin() + edit_state.StbState.cursor, NULL, &cursor_pos); cursor_pos += render_pos - ImVec2(edit_state.ScrollX, 0.0f); @@ -7183,6 +7178,11 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV edit_state.InputCursorScreenPos = cursor_pos; } + else + { + // Render text only + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, draw_window->Color(ImGuiCol_Text), buf, NULL, 0.0f, is_multiline ? NULL : &clip_rect); + } if (is_multiline) { @@ -7193,6 +7193,10 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV ImGui::EndGroup(); } + // Log as text + if (GImGui->LogEnabled) + LogText(render_pos, buf, NULL); + if (label_size.x > 0) RenderText(ImVec2(frame_bb.Max.x + style.ItemInnerSpacing.x, frame_bb.Min.y + style.FramePadding.y), label); @@ -9752,7 +9756,7 @@ static inline int ImTextCharToUtf8(char* buf, size_t buf_size, unsigned int c) { buf[0] = (char)c; return 1; - } + } if (c < 0x800) { if (buf_size < 2) return 0; From d30e8f38b7063046adb2289a1b41399d77ec24a2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 12:29:43 -0600 Subject: [PATCH 23/44] InputText() tidying up (#200) --- imgui.cpp | 33 +++++++++++++-------------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index c97151f16..221b79b33 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6867,22 +6867,18 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV SetActiveId(0); } - // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. - // Down the line we should have a cleaner concept of focused vs active in the library. - if (g.ActiveId == id) - g.ActiveIdIsFocusedOnly = !io.MouseDown[0]; - bool value_changed = false; bool cancel_edit = false; bool enter_pressed = false; - - // Update some data if we are active or last active - if (g.ActiveId == id) //if (edit_state.Id == id) // Works, but double-click to select-all sets cursors to end which in turn tends to scroll toward the right when shrinking widget. - { - edit_state.BufSizeA = buf_size; - } + if (g.ActiveId == id) { + edit_state.BufSizeA = buf_size; + + // Although we are active we don't prevent mouse from hovering other elements unless we are interacting right now with the widget. + // Down the line we should have a cleaner concept of focused vs active in the library. + g.ActiveIdIsFocusedOnly = !io.MouseDown[0]; + // Edit in progress const float mouse_x = (g.IO.MousePos.x - frame_bb.Min.x - style.FramePadding.x) + edit_state.ScrollX; const float mouse_y = (is_multiline ? (g.IO.MousePos.y - draw_window->DC.CursorPos.y - style.FramePadding.y) : (g.FontSize*0.5f)); @@ -7106,16 +7102,17 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const ImVec2 render_scroll = ImVec2((edit_state.Id == id) ? edit_state.ScrollX : 0.0f, 0.0f); ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x + style.FramePadding.x*2.0f, frame_bb.Min.y + size.y + style.FramePadding.y*2.0f); - if (g.ActiveId == id) + if (g.ActiveId == id || (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetID("#SCROLLY"))) { + edit_state.CursorAnim += g.IO.DeltaTime; + + // Scroll if (edit_state.CursorFollow) { - edit_state.CursorFollow = false; - // Horizontal scroll in chunks of quarter width ImVec2 cursor_offset; CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, edit_state.Text.begin(), edit_state.Text.begin() + edit_state.StbState.cursor, NULL, &cursor_offset); - + const float scroll_increment_x = size.x * 0.25f; if (cursor_offset.x < edit_state.ScrollX) edit_state.ScrollX = ImMax(0.0f, cursor_offset.x - scroll_increment_x); @@ -7131,11 +7128,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV draw_window->ScrollY = cursor_offset.y - size.y; } } - } - - if (g.ActiveId == id || (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetID("#SCROLLY"))) - { - edit_state.CursorAnim += g.IO.DeltaTime; + edit_state.CursorFollow = false; // Draw selection int select_begin_idx = edit_state.StbState.select_start; From 846cfc74a29528a983d976f16973983162b13fd8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 12:32:55 -0600 Subject: [PATCH 24/44] InputText() clear selection when using undo/redo (#200) --- imgui.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 221b79b33..be18ef935 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1201,6 +1201,7 @@ struct ImGuiTextEditState void CursorAnimReset() { CursorAnim = -0.30f; } // After a user-input the cursor stays on for a while without blinking bool CursorIsVisible() const { return CursorAnim <= 0.0f || fmodf(CursorAnim, 1.20f) <= 0.80f; } // Blinking bool HasSelection() const { return StbState.select_start != StbState.select_end; } + void ClearSelection() { StbState.select_start = StbState.select_end = StbState.cursor; } void SelectAll() { StbState.select_start = 0; StbState.select_end = (int)CurLenW; StbState.cursor = StbState.select_end; StbState.has_preferred_x = false; } void OnKeyPressed(int key); }; @@ -6951,8 +6952,8 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV edit_state.OnKeyPressed((int)c); } else if (IsKeyPressedMap(ImGuiKey_Escape)) { SetActiveId(0); cancel_edit = true; } - else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_Z)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); } - else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_Y)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); } + else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_Z)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_UNDO); edit_state.ClearSelection(); } + else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_Y)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_REDO); edit_state.ClearSelection(); } else if (is_ctrl_only && IsKeyPressedMap(ImGuiKey_A)) { edit_state.SelectAll(); edit_state.CursorFollow = true; } else if (is_ctrl_only && (IsKeyPressedMap(ImGuiKey_X) || IsKeyPressedMap(ImGuiKey_C)) && (!is_multiline || edit_state.HasSelection())) { From 60079988bd939a10b5689a75fb339b7993f67a62 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 12:41:48 -0600 Subject: [PATCH 25/44] ImFont::RenderText() faster handling of rendering above clipping rect (useful for #200) --- imgui.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index be18ef935..60effb1a2 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10095,7 +10095,13 @@ void ImFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_re if (y1 > clip_rect.w) break; float y2 = (float)(y1 + glyph->Height * scale); - if (y2 >= clip_rect.y) // FIMXE-OPT: could fast-forward until next line (without breaking word-wrapping) + if (y2 < clip_rect.y) + { + // Fast-forward until next line + char_width = 0.0f; + while (s < text_end && *s != '\n') s++; + } + else { float x1 = (float)(x + glyph->XOffset * scale); float x2 = (float)(x1 + glyph->Width * scale); From 5f362cabe9f9630b7ff6e300992c44e12cd1229f Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 17:11:20 -0600 Subject: [PATCH 26/44] InputText(): char width calculation calls font->GetCharAdvance() directly (#200) --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 60effb1a2..54bf62c08 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6573,7 +6573,7 @@ static ImVec2 CalcTextSizeW(ImFont* font, float font_size, float max_width, cons // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return (int)obj->CurLenW; } static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->Text[idx]; } -static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar* s = &obj->Text[line_start_idx+char_idx]; if (*s == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return CalcTextSizeW(GImGui->Font, GImGui->FontSize, FLT_MAX, s, s+1, NULL).x; } +static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->Text[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c); } static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; } static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) From 26d2b361d1902371fe8b3050d41a4b5a6053c59a Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 17:40:51 -0600 Subject: [PATCH 27/44] InputText(): removed extraneous CalcTextSizeW call for tracking cursor/scrolling, remove spike (#200) --- imgui.cpp | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 54bf62c08..5e7338717 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1314,7 +1314,7 @@ struct ImGuiState int LogAutoExpandMaxDepth; // Misc - float FramerateSecPerFrame[120]; // calculate estimate of framerate for user + float FramerateSecPerFrame[10]; // calculate estimate of framerate for user int FramerateSecPerFrameIdx; float FramerateSecPerFrameAccum; char TempBuffer[1024*3+1]; // temporary text buffer @@ -7107,13 +7107,15 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV { edit_state.CursorAnim += g.IO.DeltaTime; - // Scroll + // 1. Display the text (this can be more easily clipped) + // 2. Handle scrolling, highlight selection, display cursor: those all requires some form of 1d->2d cursor position calculation, which we will try to merge to minimize the cost. + ImVec2 cursor_offset; + CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, edit_state.Text.begin(), edit_state.Text.begin() + edit_state.StbState.cursor, NULL, &cursor_offset); + + // Scroll if (edit_state.CursorFollow) { // Horizontal scroll in chunks of quarter width - ImVec2 cursor_offset; - CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, edit_state.Text.begin(), edit_state.Text.begin() + edit_state.StbState.cursor, NULL, &cursor_offset); - const float scroll_increment_x = size.x * 0.25f; if (cursor_offset.x < edit_state.ScrollX) edit_state.ScrollX = ImMax(0.0f, cursor_offset.x - scroll_increment_x); @@ -7158,19 +7160,16 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, draw_window->Color(ImGuiCol_Text), buf, NULL, 0.0f, is_multiline ? NULL : &clip_rect); - ImVec2 cursor_pos; - CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, edit_state.Text.begin(), edit_state.Text.begin() + edit_state.StbState.cursor, NULL, &cursor_pos); - cursor_pos += render_pos - ImVec2(edit_state.ScrollX, 0.0f); - // Draw blinking cursor + ImVec2 cursor_screen_pos = render_pos + cursor_offset - ImVec2(edit_state.ScrollX, 0.0f); if (g.InputTextState.CursorIsVisible()) - draw_window->DrawList->AddLine(cursor_pos + ImVec2(0,2-font_offy_up), cursor_pos + ImVec2(0,-3+font_offy_dn), window->Color(ImGuiCol_Text)); + draw_window->DrawList->AddLine(cursor_screen_pos + ImVec2(0,2-font_offy_up), cursor_screen_pos + ImVec2(0,-3+font_offy_dn), window->Color(ImGuiCol_Text)); // Notify OS of text input position for advanced IME - if (io.ImeSetInputScreenPosFn && ImLengthSqr(edit_state.InputCursorScreenPos - cursor_pos) > 0.0001f) - io.ImeSetInputScreenPosFn((int)cursor_pos.x - 1, (int)(cursor_pos.y - g.FontSize)); // -1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety. + if (io.ImeSetInputScreenPosFn && ImLengthSqr(edit_state.InputCursorScreenPos - cursor_screen_pos) > 0.0001f) + io.ImeSetInputScreenPosFn((int)cursor_screen_pos.x - 1, (int)(cursor_screen_pos.y - g.FontSize)); // -1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety. - edit_state.InputCursorScreenPos = cursor_pos; + edit_state.InputCursorScreenPos = cursor_screen_pos; } else { From 10b4fa44c16c990ec7f55fe11f23aaca84fb56bc Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 17:45:11 -0600 Subject: [PATCH 28/44] Fixed framerate counter averaging buffer size (shouldn't have been included in previous commit) --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 5e7338717..9a9b72d9b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1314,7 +1314,7 @@ struct ImGuiState int LogAutoExpandMaxDepth; // Misc - float FramerateSecPerFrame[10]; // calculate estimate of framerate for user + float FramerateSecPerFrame[120]; // calculate estimate of framerate for user int FramerateSecPerFrameIdx; float FramerateSecPerFrameAccum; char TempBuffer[1024*3+1]; // temporary text buffer From c6d77f3bf5d777c327d55b2fc282f56f77ddd607 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 18:31:13 -0600 Subject: [PATCH 29/44] InputText: fixed handling of scaled font. re-organized bits of code toward merging all sizes calculations (#200) --- imgui.cpp | 51 +++++++++++++++++++++++---------------------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 9a9b72d9b..162d285ae 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1323,7 +1323,7 @@ struct ImGuiState { Initialized = false; Font = NULL; - FontBaseSize = FontSize = 0.0f; + FontSize = FontBaseSize = 0.0f; FontTexUvWhitePixel = ImVec2(0.0f, 0.0f); Time = 0.0f; @@ -6514,13 +6514,11 @@ bool ImGui::RadioButton(const char* label, int* v, int v_button) return pressed; } -static ImVec2 CalcTextSizeW(ImFont* font, float font_size, float max_width, const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false) +static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false) { - if (!text_end) - text_end = text_begin + ImStrlenW(text_begin); - - const float scale = font_size / font->FontSize; - const float line_height = font->FontSize * scale; + ImFont* font = GImGui->Font; + const float line_height = GImGui->FontSize; + const float scale = line_height / font->FontSize; ImVec2 text_size = ImVec2(0,0); float line_width = 0.0f; @@ -6545,13 +6543,7 @@ static ImVec2 CalcTextSizeW(ImFont* font, float font_size, float max_width, cons continue; } - const float char_width = font->GetCharAdvance((unsigned short)c); - if (line_width + char_width >= max_width) - { - s--; - break; - } - + const float char_width = font->GetCharAdvance((unsigned short)c) * scale; line_width += char_width; } @@ -6573,15 +6565,14 @@ static ImVec2 CalcTextSizeW(ImFont* font, float font_size, float max_width, cons // Wrapper for stb_textedit.h to edit text (our wrapper is for: statically sized buffer, single-line, wchar characters. InputText converts between UTF-8 and wchar) static int STB_TEXTEDIT_STRINGLEN(const STB_TEXTEDIT_STRING* obj) { return (int)obj->CurLenW; } static ImWchar STB_TEXTEDIT_GETCHAR(const STB_TEXTEDIT_STRING* obj, int idx) { return obj->Text[idx]; } -static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->Text[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c); } +static float STB_TEXTEDIT_GETWIDTH(STB_TEXTEDIT_STRING* obj, int line_start_idx, int char_idx) { ImWchar c = obj->Text[line_start_idx+char_idx]; if (c == '\n') return STB_TEXTEDIT_GETWIDTH_NEWLINE; return GImGui->Font->GetCharAdvance(c) * (GImGui->FontSize / GImGui->Font->FontSize); } static int STB_TEXTEDIT_KEYTOTEXT(int key) { return key >= 0x10000 ? 0 : key; } static ImWchar STB_TEXTEDIT_NEWLINE = '\n'; static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, STB_TEXTEDIT_STRING* obj, int line_start_idx) { - ImGuiState& g = *GImGui; const ImWchar* text = obj->Text.begin(); const ImWchar* text_remaining = NULL; - const ImVec2 size = CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); + const ImVec2 size = InputTextCalcTextSizeW(text + line_start_idx, text + obj->CurLenW, &text_remaining, NULL, true); r->x0 = 0.0f; r->x1 = size.x; r->baseline_y_delta = size.y; @@ -7099,18 +7090,21 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const float font_offy_dn = 2.0f; const ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; - //const ImVec2 render_scroll = (g.ActiveId == id) ? edit_state.Scroll : ImVec2(0.f, 0.f); - const ImVec2 render_scroll = ImVec2((edit_state.Id == id) ? edit_state.ScrollX : 0.0f, 0.0f); ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x + style.FramePadding.x*2.0f, frame_bb.Min.y + size.y + style.FramePadding.y*2.0f); - + ImVec2 text_size(0.f, 0.f); if (g.ActiveId == id || (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetID("#SCROLLY"))) { + //const ImVec2 render_scroll = (g.ActiveId == id) ? edit_state.Scroll : ImVec2(0.f, 0.f); + ImVec2 render_scroll = ImVec2((edit_state.Id == id) ? edit_state.ScrollX : 0.0f, 0.0f); edit_state.CursorAnim += g.IO.DeltaTime; // 1. Display the text (this can be more easily clipped) // 2. Handle scrolling, highlight selection, display cursor: those all requires some form of 1d->2d cursor position calculation, which we will try to merge to minimize the cost. ImVec2 cursor_offset; - CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, edit_state.Text.begin(), edit_state.Text.begin() + edit_state.StbState.cursor, NULL, &cursor_offset); + InputTextCalcTextSizeW(edit_state.Text.begin(), edit_state.Text.begin() + edit_state.StbState.cursor, NULL, &cursor_offset); + + if (is_multiline) + text_size = InputTextCalcTextSizeW(edit_state.Text.begin(), edit_state.Text.begin() + edit_state.CurLenW); // Scroll if (edit_state.CursorFollow) @@ -7118,9 +7112,9 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV // Horizontal scroll in chunks of quarter width const float scroll_increment_x = size.x * 0.25f; if (cursor_offset.x < edit_state.ScrollX) - edit_state.ScrollX = ImMax(0.0f, cursor_offset.x - scroll_increment_x); + render_scroll.x = edit_state.ScrollX = ImMax(0.0f, cursor_offset.x - scroll_increment_x); else if (cursor_offset.x - size.x >= edit_state.ScrollX) - edit_state.ScrollX = cursor_offset.x - size.x + scroll_increment_x; + render_scroll.x = edit_state.ScrollX = cursor_offset.x - size.x + scroll_increment_x; // Vertical scroll if (is_multiline) @@ -7142,12 +7136,12 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV ImWchar* text_selected_begin = edit_state.Text.begin() + ImMin(select_begin_idx,select_end_idx); ImWchar* text_selected_end = edit_state.Text.begin() + ImMax(select_begin_idx,select_end_idx); ImVec2 rect_pos; - CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, edit_state.Text.begin(), text_selected_begin, NULL, &rect_pos); + InputTextCalcTextSizeW(edit_state.Text.begin(), text_selected_begin, NULL, &rect_pos); ImU32 bg_color = draw_window->Color(ImGuiCol_TextSelectedBg); for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) { - ImVec2 rect_size = CalcTextSizeW(g.Font, g.FontSize, FLT_MAX, p, text_selected_end, &p, NULL, true); + ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines ImRect rect(render_pos - render_scroll + rect_pos + ImVec2(0.0f, (p == text_selected_begin) ? -font_offy_up : -g.FontSize), render_pos - render_scroll + rect_pos + ImVec2(rect_size.x, (p == text_selected_end) ? +font_offy_dn : 0.0f)); rect.Clip(clip_rect); @@ -7174,13 +7168,14 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV else { // Render text only - draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, draw_window->Color(ImGuiCol_Text), buf, NULL, 0.0f, is_multiline ? NULL : &clip_rect); + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, draw_window->Color(ImGuiCol_Text), buf, NULL, 0.0f, is_multiline ? NULL : &clip_rect); + + if (is_multiline) + text_size = g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, buf); } if (is_multiline) { - // FIXME-OPT FIXME-WIP-MULTILINE - ImVec2 text_size = g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, buf); ImGui::Dummy(text_size + ImVec2(0.0f, g.FontSize)); // Always add room to scroll an extra line ImGui::EndChildFrame(); ImGui::EndGroup(); From 86666489df97d40edef64196db43963cd0ba335f Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 18:31:49 -0600 Subject: [PATCH 30/44] ImFont: CalcTextSizeA() fixed font scaling with fallback character. --- imgui.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 162d285ae..4c843c423 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -9916,8 +9916,8 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons if (!text_end) text_end = text_begin + strlen(text_begin); // FIXME-OPT: Need to avoid this. + const float line_height = size; const float scale = size / FontSize; - const float line_height = FontSize * scale; ImVec2 text_size = ImVec2(0,0); float line_width = 0.0f; @@ -9983,7 +9983,7 @@ ImVec2 ImFont::CalcTextSizeA(float size, float max_width, float wrap_width, cons continue; } - const float char_width = ((size_t)c < IndexXAdvance.size()) ? IndexXAdvance[(size_t)c] * scale : FallbackXAdvance; + const float char_width = ((size_t)c < IndexXAdvance.size() ? IndexXAdvance[(size_t)c] : FallbackXAdvance) * scale; if (line_width + char_width >= max_width) { s = prev_s; From 2b68a5c0cfb1eb491a4dd99ea510254f0ab33342 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 19:09:04 -0600 Subject: [PATCH 31/44] InputTextMultine() optimised height calculation for inactive multi-line edit box (#200) --- imgui.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 4c843c423..3a94bf3a8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6514,6 +6514,19 @@ bool ImGui::RadioButton(const char* label, int* v, int v_button) return pressed; } +static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) +{ + int line_count = 0; + const char* s = text_begin; + while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding + if (c == '\n') + line_count++; + if (s[-1] != '\n' && s[-1] != '\r') + line_count++; + *out_text_end = s-1; + return line_count; +} + static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* text_end, const ImWchar** remaining = NULL, ImVec2* out_offset = NULL, bool stop_on_new_line = false) { ImFont* font = GImGui->Font; @@ -7168,10 +7181,10 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV else { // Render text only - draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, draw_window->Color(ImGuiCol_Text), buf, NULL, 0.0f, is_multiline ? NULL : &clip_rect); - + const char* buf_end = NULL; if (is_multiline) - text_size = g.Font->CalcTextSizeA(g.FontSize, FLT_MAX, 0.0f, buf); + text_size = ImVec2(size.x, InputTextCalcTextLenAndLineCount(buf, &buf_end) * g.FontSize); // We don't need width + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos, draw_window->Color(ImGuiCol_Text), buf, buf_end, 0.0f, is_multiline ? NULL : &clip_rect); } if (is_multiline) From b524c59c70f444f3b2bfff0970d5e48790dd3954 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 20:49:20 -0600 Subject: [PATCH 32/44] InputText(): removed a call to strlen() in the active edit path (#200) --- imgui.cpp | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 3a94bf3a8..ab014d174 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6539,22 +6539,18 @@ static ImVec2 InputTextCalcTextSizeW(const ImWchar* text_begin, const ImWchar* t const ImWchar* s = text_begin; while (s < text_end) { - const unsigned int c = (unsigned int)(*s++); - - if (c < 32) + unsigned int c = (unsigned int)(*s++); + if (c == '\n') { - if (c == '\n') - { - text_size.x = ImMax(text_size.x, line_width); - text_size.y += line_height; - line_width = 0.0f; - if (stop_on_new_line) - break; - continue; - } - if (c == '\r') - continue; + text_size.x = ImMax(text_size.x, line_width); + text_size.y += line_height; + line_width = 0.0f; + if (stop_on_new_line) + break; + continue; } + if (c == '\r') + continue; const float char_width = font->GetCharAdvance((unsigned short)c) * scale; line_width += char_width; @@ -7165,7 +7161,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV } } - draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, draw_window->Color(ImGuiCol_Text), buf, NULL, 0.0f, is_multiline ? NULL : &clip_rect); + draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, draw_window->Color(ImGuiCol_Text), buf, buf+edit_state.CurLenA, 0.0f, is_multiline ? NULL : &clip_rect); // Draw blinking cursor ImVec2 cursor_screen_pos = render_pos + cursor_offset - ImVec2(edit_state.ScrollX, 0.0f); From cd27f8a8e6fa2863eb042edb1b73b02a6b1706df Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 21:01:56 -0600 Subject: [PATCH 33/44] InputText(): adjusting selection block height differently for single and multi-line (#200) --- imgui.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index ab014d174..6ce4230b8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7095,8 +7095,6 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV if (!is_multiline) RenderFrame(frame_bb.Min, frame_bb.Max, window->Color(ImGuiCol_FrameBg), true, style.FrameRounding); - const float font_offy_up = g.FontSize+1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. - const float font_offy_dn = 2.0f; const ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x + style.FramePadding.x*2.0f, frame_bb.Min.y + size.y + style.FramePadding.y*2.0f); @@ -7147,12 +7145,14 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV ImVec2 rect_pos; InputTextCalcTextSizeW(edit_state.Text.begin(), text_selected_begin, NULL, &rect_pos); + float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. + float bg_offy_dn = is_multiline ? 0.0f : 2.0f; ImU32 bg_color = draw_window->Color(ImGuiCol_TextSelectedBg); for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) { ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines - ImRect rect(render_pos - render_scroll + rect_pos + ImVec2(0.0f, (p == text_selected_begin) ? -font_offy_up : -g.FontSize), render_pos - render_scroll + rect_pos + ImVec2(rect_size.x, (p == text_selected_end) ? +font_offy_dn : 0.0f)); + ImRect rect(render_pos - render_scroll + rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), render_pos - render_scroll + rect_pos + ImVec2(rect_size.x, bg_offy_dn)); rect.Clip(clip_rect); if (rect.Overlaps(clip_rect)) draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); @@ -7166,7 +7166,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV // Draw blinking cursor ImVec2 cursor_screen_pos = render_pos + cursor_offset - ImVec2(edit_state.ScrollX, 0.0f); if (g.InputTextState.CursorIsVisible()) - draw_window->DrawList->AddLine(cursor_screen_pos + ImVec2(0,2-font_offy_up), cursor_screen_pos + ImVec2(0,-3+font_offy_dn), window->Color(ImGuiCol_Text)); + draw_window->DrawList->AddLine(cursor_screen_pos + ImVec2(0,-g.FontSize+1), cursor_screen_pos + ImVec2(0,-1), window->Color(ImGuiCol_Text)); // Notify OS of text input position for advanced IME if (io.ImeSetInputScreenPosFn && ImLengthSqr(edit_state.InputCursorScreenPos - cursor_screen_pos) > 0.0001f) From c4720ec90f5285769b4fdb632fd40026658cb556 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 21:20:15 -0600 Subject: [PATCH 34/44] InputTextMultiline() fixed a frame of lag in handling vertical scrolling (#200) --- imgui.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 6ce4230b8..98292935a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7095,13 +7095,12 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV if (!is_multiline) RenderFrame(frame_bb.Min, frame_bb.Max, window->Color(ImGuiCol_FrameBg), true, style.FrameRounding); - const ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; + ImVec2 render_pos = is_multiline ? draw_window->DC.CursorPos : frame_bb.Min + style.FramePadding; ImVec4 clip_rect(frame_bb.Min.x, frame_bb.Min.y, frame_bb.Min.x + size.x + style.FramePadding.x*2.0f, frame_bb.Min.y + size.y + style.FramePadding.y*2.0f); ImVec2 text_size(0.f, 0.f); if (g.ActiveId == id || (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetID("#SCROLLY"))) { - //const ImVec2 render_scroll = (g.ActiveId == id) ? edit_state.Scroll : ImVec2(0.f, 0.f); ImVec2 render_scroll = ImVec2((edit_state.Id == id) ? edit_state.ScrollX : 0.0f, 0.0f); edit_state.CursorAnim += g.IO.DeltaTime; @@ -7126,10 +7125,14 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV // Vertical scroll if (is_multiline) { - if (cursor_offset.y - g.FontSize < draw_window->ScrollY) - draw_window->ScrollY = ImMax(0.0f, cursor_offset.y - g.FontSize); - else if (cursor_offset.y - g.FontSize*0 - size.y >= draw_window->ScrollY) - draw_window->ScrollY = cursor_offset.y - size.y; + float scroll_y = draw_window->ScrollY; + if (cursor_offset.y - g.FontSize < scroll_y) + scroll_y = ImMax(0.0f, cursor_offset.y - g.FontSize); + else if (cursor_offset.y - size.y >= scroll_y) + scroll_y = cursor_offset.y - size.y; + draw_window->DC.CursorPos.y += (draw_window->ScrollY - scroll_y); // To avoid a frame of lag + draw_window->ScrollY = scroll_y; + render_pos.y = draw_window->DC.CursorPos.y; } } edit_state.CursorFollow = false; From 324b1c2a28d3bdd4ae287a7d7bed53050c4cbd3e Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 22:35:08 -0600 Subject: [PATCH 35/44] InputTextMultiline(): made ctrl+up/down closer to windows behavior (#200) --- imgui.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 98292935a..913d25834 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6521,9 +6521,10 @@ static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** while (char c = *s++) // We are only matching for \n so we can ignore UTF-8 decoding if (c == '\n') line_count++; - if (s[-1] != '\n' && s[-1] != '\r') + s--; + if (s[0] != '\n' && s[0] != '\r') line_count++; - *out_text_end = s-1; + *out_text_end = s; return line_count; } @@ -6924,8 +6925,8 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const bool is_ctrl_only = is_ctrl_down && !is_alt_down && !is_shift_down; if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDLEFT | k_mask : STB_TEXTEDIT_K_LEFT | k_mask); } else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDRIGHT | k_mask : STB_TEXTEDIT_K_RIGHT | k_mask); } - else if (is_multiline && IsKeyPressedMap(ImGuiKey_UpArrow)) { bool move_cursor = true; if (is_ctrl_down) { draw_window->ScrollY -= g.FontSize; } if (move_cursor) edit_state.OnKeyPressed(STB_TEXTEDIT_K_UP | k_mask); } - else if (is_multiline && IsKeyPressedMap(ImGuiKey_DownArrow)) { bool move_cursor = true; if (is_ctrl_down) { draw_window->ScrollY += g.FontSize; } if (move_cursor) edit_state.OnKeyPressed(STB_TEXTEDIT_K_DOWN| k_mask); } + else if (is_multiline && IsKeyPressedMap(ImGuiKey_UpArrow)) { if (is_ctrl_down) draw_window->ScrollY -= g.FontSize; else edit_state.OnKeyPressed(STB_TEXTEDIT_K_UP | k_mask); } + else if (is_multiline && IsKeyPressedMap(ImGuiKey_DownArrow)) { if (is_ctrl_down) draw_window->ScrollY += g.FontSize; else edit_state.OnKeyPressed(STB_TEXTEDIT_K_DOWN| k_mask); } else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Delete)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } From 84987ac3e024f678c704a21c86f77a6126aa465d Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 23:08:42 -0600 Subject: [PATCH 36/44] ImFont::RenderText() better vertical clipping for large amount of text (for #200) --- imgui.cpp | 135 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 69 insertions(+), 66 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 913d25834..d01ab005c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -10023,21 +10023,25 @@ void ImFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_re if (!text_end) text_end = text_begin + strlen(text_begin); - const float scale = size / FontSize; - const float line_height = FontSize * scale; - // Align to be pixel perfect pos.x = (float)(int)pos.x + DisplayOffset.x; pos.y = (float)(int)pos.y + DisplayOffset.y; float x = pos.x; float y = pos.y; + if (y > clip_rect.w) + return; + const float scale = size / FontSize; + const float line_height = FontSize * scale; const bool word_wrap_enabled = (wrap_width > 0.0f); const char* word_wrap_eol = NULL; ImDrawVert* out_vertices = draw_list->vtx_write; const char* s = text_begin; + if (!word_wrap_enabled && y + line_height < clip_rect.y) + while (s < text_end && *s != '\n') // Fast-forward to next line + s++; while (s < text_end) { if (word_wrap_enabled) @@ -10085,6 +10089,13 @@ void ImFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_re { x = pos.x; y += line_height; + + if (y > clip_rect.w) + break; + if (!word_wrap_enabled && y + line_height < clip_rect.y) + while (s < text_end && *s != '\n') // Fast-forward to next line + s++; + continue; } if (c == '\r') @@ -10095,78 +10106,70 @@ void ImFont::RenderText(float size, ImVec2 pos, ImU32 col, const ImVec4& clip_re if (const Glyph* glyph = FindGlyph((unsigned short)c)) { char_width = glyph->XAdvance * scale; + + // Clipping on Y is more likely if (c != ' ' && c != '\t') { - // Clipping on Y is more likely + // We don't do a second finer clipping test on the Y axis (todo: do some measurement see if it is worth it, probably not) float y1 = (float)(y + glyph->YOffset * scale); - if (y1 > clip_rect.w) - break; float y2 = (float)(y1 + glyph->Height * scale); - if (y2 < clip_rect.y) + + float x1 = (float)(x + glyph->XOffset * scale); + float x2 = (float)(x1 + glyph->Width * scale); + if (x1 <= clip_rect.z && x2 >= clip_rect.x) { - // Fast-forward until next line - char_width = 0.0f; - while (s < text_end && *s != '\n') s++; - } - else - { - float x1 = (float)(x + glyph->XOffset * scale); - float x2 = (float)(x1 + glyph->Width * scale); - if (x1 <= clip_rect.z && x2 >= clip_rect.x) + // Render a character + float u1 = glyph->U0; + float v1 = glyph->V0; + float u2 = glyph->U1; + float v2 = glyph->V1; + + // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads + if (cpu_fine_clip) { - // Render a character - float u1 = glyph->U0; - float v1 = glyph->V0; - float u2 = glyph->U1; - float v2 = glyph->V1; - - // CPU side clipping used to fit text in their frame when the frame is too small. Only does clipping for axis aligned quads - if (cpu_fine_clip) + if (x1 < clip_rect.x) { - if (x1 < clip_rect.x) - { - u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1); - x1 = clip_rect.x; - } - if (y1 < clip_rect.y) - { - v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1); - y1 = clip_rect.y; - } - if (x2 > clip_rect.z) - { - u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1); - x2 = clip_rect.z; - } - if (y2 > clip_rect.w) - { - v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1); - y2 = clip_rect.w; - } + u1 = u1 + (1.0f - (x2 - clip_rect.x) / (x2 - x1)) * (u2 - u1); + x1 = clip_rect.x; + } + if (y1 < clip_rect.y) + { + v1 = v1 + (1.0f - (y2 - clip_rect.y) / (y2 - y1)) * (v2 - v1); + y1 = clip_rect.y; + } + if (x2 > clip_rect.z) + { + u2 = u1 + ((clip_rect.z - x1) / (x2 - x1)) * (u2 - u1); + x2 = clip_rect.z; + } + if (y2 > clip_rect.w) + { + v2 = v1 + ((clip_rect.w - y1) / (y2 - y1)) * (v2 - v1); + y2 = clip_rect.w; } - - // NB: we are not calling PrimRectUV() here because non-inlined causes too much overhead in a debug build. - out_vertices[0].pos = ImVec2(x1, y1); - out_vertices[0].uv = ImVec2(u1, v1); - out_vertices[0].col = col; - - out_vertices[1].pos = ImVec2(x2, y1); - out_vertices[1].uv = ImVec2(u2, v1); - out_vertices[1].col = col; - - out_vertices[2].pos = ImVec2(x2, y2); - out_vertices[2].uv = ImVec2(u2, v2); - out_vertices[2].col = col; - - out_vertices[3] = out_vertices[0]; - out_vertices[4] = out_vertices[2]; - - out_vertices[5].pos = ImVec2(x1, y2); - out_vertices[5].uv = ImVec2(u1, v2); - out_vertices[5].col = col; - - out_vertices += 6; } + + // NB: we are not calling PrimRectUV() here because non-inlined causes too much overhead in a debug build. + out_vertices[0].pos = ImVec2(x1, y1); + out_vertices[0].uv = ImVec2(u1, v1); + out_vertices[0].col = col; + + out_vertices[1].pos = ImVec2(x2, y1); + out_vertices[1].uv = ImVec2(u2, v1); + out_vertices[1].col = col; + + out_vertices[2].pos = ImVec2(x2, y2); + out_vertices[2].uv = ImVec2(u2, v2); + out_vertices[2].col = col; + + out_vertices[3] = out_vertices[0]; + out_vertices[4] = out_vertices[2]; + + out_vertices[5].pos = ImVec2(x1, y2); + out_vertices[5].uv = ImVec2(u1, v2); + out_vertices[5].col = col; + + out_vertices += 6; } } } From 73db855c7730cab8ad8c5973591449aff4ae6746 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 18 Jun 2015 23:23:20 -0600 Subject: [PATCH 37/44] InputTextMultiline() painfully merged most computation passes into one, better clipping, much faster for large text (#200) --- imgui.cpp | 106 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 80 insertions(+), 26 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index d01ab005c..0641ceaa8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -551,8 +551,9 @@ static void CloseInactivePopups(); // Helpers: String static int ImStricmp(const char* str1, const char* str2); static int ImStrnicmp(const char* str1, const char* str2, int count); -static char* ImStrdup(const char *str); +static char* ImStrdup(const char* str); static size_t ImStrlenW(const ImWchar* str); +static const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin); // Find beginning-of-line static const char* ImStristr(const char* haystack, const char* needle, const char* needle_end); static size_t ImFormatString(char* buf, size_t buf_size, const char* fmt, ...); static size_t ImFormatStringV(char* buf, size_t buf_size, const char* fmt, va_list args); @@ -786,6 +787,13 @@ static size_t ImStrlenW(const ImWchar* str) return n; } +static const ImWchar* ImStrbolW(const ImWchar* buf_mid_line, const ImWchar* buf_begin) // find beginning-of-line +{ + while (buf_mid_line > buf_begin && buf_mid_line[-1] != '\n') + buf_mid_line--; + return buf_mid_line; +} + static const char* ImStristr(const char* haystack, const char* needle, const char* needle_end) { if (!needle_end) @@ -7102,16 +7110,54 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV ImVec2 text_size(0.f, 0.f); if (g.ActiveId == id || (edit_state.Id == id && is_multiline && g.ActiveId == draw_window->GetID("#SCROLLY"))) { - ImVec2 render_scroll = ImVec2((edit_state.Id == id) ? edit_state.ScrollX : 0.0f, 0.0f); edit_state.CursorAnim += g.IO.DeltaTime; - // 1. Display the text (this can be more easily clipped) - // 2. Handle scrolling, highlight selection, display cursor: those all requires some form of 1d->2d cursor position calculation, which we will try to merge to minimize the cost. - ImVec2 cursor_offset; - InputTextCalcTextSizeW(edit_state.Text.begin(), edit_state.Text.begin() + edit_state.StbState.cursor, NULL, &cursor_offset); + // We need to: + // - Display the text (this can be more easily clipped) + // - Handle scrolling, highlight selection, display cursor (those all requires some form of 1d->2d cursor position calculation) + // - Measure text height (for scrollbar) + // We are attempting to do most of that in one main pass to minimize the computation cost (non-negligible for large amount of text) + 2nd pass for selection rendering (we could merge them by an extra refactoring effort) + const ImWchar* text_begin = edit_state.Text.begin(); + const ImWchar* text_end = text_begin + edit_state.CurLenW; + ImVec2 cursor_offset, select_start_offset; - if (is_multiline) - text_size = InputTextCalcTextSizeW(edit_state.Text.begin(), edit_state.Text.begin() + edit_state.CurLenW); + { + // Count lines + find lines numbers of cursor and select_start + int matches_remaining = 0; + int matches_line_no[2] = { -1, -999 }; + const ImWchar* matches_ptr[2]; + matches_ptr[0] = text_begin + edit_state.StbState.cursor; matches_remaining++; + if (edit_state.StbState.select_start != edit_state.StbState.select_end) + { + matches_ptr[1] = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); + matches_line_no[1] = -1; + matches_remaining++; + } + matches_remaining += is_multiline ? 1 : 0; // So that we never exit the loop until all lines are counted. + + int line_count = 0; + for (const ImWchar* s = text_begin; s < text_end+1; s++) + if ((*s) == '\n' || s == text_end) + { + line_count++; + if (matches_line_no[0] == -1 && s >= matches_ptr[0]) { matches_line_no[0] = line_count; if (--matches_remaining <= 0) break; } + if (matches_line_no[1] == -1 && s >= matches_ptr[1]) { matches_line_no[1] = line_count; if (--matches_remaining <= 0) break; } + } + + // Calculate 2d position + IM_ASSERT(matches_line_no[0] != -1); + cursor_offset.x = InputTextCalcTextSizeW(ImStrbolW(matches_ptr[0], text_begin), matches_ptr[0]).x; + cursor_offset.y = matches_line_no[0] * g.FontSize; + if (matches_line_no[1] >= 0) + { + select_start_offset.x = InputTextCalcTextSizeW(ImStrbolW(matches_ptr[1], text_begin), matches_ptr[1]).x; + select_start_offset.y = matches_line_no[1] * g.FontSize; + } + + // Calculate text height + if (is_multiline) + text_size = ImVec2(size.x, line_count * g.FontSize); + } // Scroll if (edit_state.CursorFollow) @@ -7119,9 +7165,9 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV // Horizontal scroll in chunks of quarter width const float scroll_increment_x = size.x * 0.25f; if (cursor_offset.x < edit_state.ScrollX) - render_scroll.x = edit_state.ScrollX = ImMax(0.0f, cursor_offset.x - scroll_increment_x); + edit_state.ScrollX = ImMax(0.0f, cursor_offset.x - scroll_increment_x); else if (cursor_offset.x - size.x >= edit_state.ScrollX) - render_scroll.x = edit_state.ScrollX = cursor_offset.x - size.x + scroll_increment_x; + edit_state.ScrollX = cursor_offset.x - size.x + scroll_increment_x; // Vertical scroll if (is_multiline) @@ -7137,30 +7183,38 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV } } edit_state.CursorFollow = false; + ImVec2 render_scroll = ImVec2(edit_state.ScrollX, 0.0f); // Draw selection - int select_begin_idx = edit_state.StbState.select_start; - int select_end_idx = edit_state.StbState.select_end; - if (select_begin_idx != select_end_idx) + if (edit_state.StbState.select_start != edit_state.StbState.select_end) { - // FIXME-OPT - ImWchar* text_selected_begin = edit_state.Text.begin() + ImMin(select_begin_idx,select_end_idx); - ImWchar* text_selected_end = edit_state.Text.begin() + ImMax(select_begin_idx,select_end_idx); - ImVec2 rect_pos; - InputTextCalcTextSizeW(edit_state.Text.begin(), text_selected_begin, NULL, &rect_pos); + const ImWchar* text_selected_begin = text_begin + ImMin(edit_state.StbState.select_start, edit_state.StbState.select_end); + const ImWchar* text_selected_end = text_begin + ImMax(edit_state.StbState.select_start, edit_state.StbState.select_end); float bg_offy_up = is_multiline ? 0.0f : -1.0f; // FIXME: those offsets should be part of the style? they don't play so well with multi-line selection. float bg_offy_dn = is_multiline ? 0.0f : 2.0f; ImU32 bg_color = draw_window->Color(ImGuiCol_TextSelectedBg); + ImVec2 rect_pos = render_pos + select_start_offset - render_scroll; for (const ImWchar* p = text_selected_begin; p < text_selected_end; ) { - ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); - if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines - ImRect rect(render_pos - render_scroll + rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), render_pos - render_scroll + rect_pos + ImVec2(rect_size.x, bg_offy_dn)); - rect.Clip(clip_rect); - if (rect.Overlaps(clip_rect)) - draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); - rect_pos.x = 0.0f; + if (rect_pos.y > clip_rect.w + g.FontSize) + break; + if (rect_pos.y < clip_rect.y) + { + while (p < text_selected_end) + if (*p++ == '\n') + break; + } + else + { + ImVec2 rect_size = InputTextCalcTextSizeW(p, text_selected_end, &p, NULL, true); + if (rect_size.x <= 0.0f) rect_size.x = (float)(int)(g.Font->GetCharAdvance((unsigned short)' ') * 0.50f); // So we can see selected empty lines + ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos +ImVec2(rect_size.x, bg_offy_dn)); + rect.Clip(clip_rect); + if (rect.Overlaps(clip_rect)) + draw_window->DrawList->AddRectFilled(rect.Min, rect.Max, bg_color); + } + rect_pos.x = render_pos.x - render_scroll.x; rect_pos.y += g.FontSize; } } @@ -7168,7 +7222,7 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV draw_window->DrawList->AddText(g.Font, g.FontSize, render_pos - render_scroll, draw_window->Color(ImGuiCol_Text), buf, buf+edit_state.CurLenA, 0.0f, is_multiline ? NULL : &clip_rect); // Draw blinking cursor - ImVec2 cursor_screen_pos = render_pos + cursor_offset - ImVec2(edit_state.ScrollX, 0.0f); + ImVec2 cursor_screen_pos = render_pos + cursor_offset - render_scroll; if (g.InputTextState.CursorIsVisible()) draw_window->DrawList->AddLine(cursor_screen_pos + ImVec2(0,-g.FontSize+1), cursor_screen_pos + ImVec2(0,-1), window->Color(ImGuiCol_Text)); From 1b28f11acb1267a5bc9672ebd66d06573a628864 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 19 Jun 2015 17:34:19 -0600 Subject: [PATCH 38/44] InputTextMultiline() filtering out \r for now (#200) --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 0641ceaa8..2388ab449 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -6720,7 +6720,7 @@ static bool InputTextFilterCharacter(unsigned int* p_char, ImGuiInputTextFlags f if (c < 128 && c != ' ' && !isprint((int)(c & 0xFF))) { bool pass = false; - pass |= ((c == '\n' || c == '\r') && (flags & ImGuiInputTextFlags_Multiline)); + pass |= (c == '\n' && (flags & ImGuiInputTextFlags_Multiline)); pass |= (c == '\t' && (flags & ImGuiInputTextFlags_AllowTabInput)); if (!pass) return false; From e7b43b014bfaa756417b91d0b89b4b4c09750758 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 19 Jun 2015 17:43:51 -0600 Subject: [PATCH 39/44] Can't scroll with mouse wheel when window is collapsed. --- imgui.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.cpp b/imgui.cpp index 2388ab449..f452f0e8b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2170,7 +2170,7 @@ void ImGui::NewFrame() g.HoveredWindow = g.HoveredRootWindow = NULL; // Scale & Scrolling - if (g.HoveredWindow && g.IO.MouseWheel != 0.0f) + if (g.HoveredWindow && g.IO.MouseWheel != 0.0f && !g.HoveredWindow->Collapsed) { ImGuiWindow* window = g.HoveredWindow; if (g.IO.KeyCtrl) From 747999bbafb4003149d24d892c59f01c1a3a224c Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 19 Jun 2015 18:01:13 -0600 Subject: [PATCH 40/44] Fixed mouse wheel scroll issues, introduced a few weeks ago (#200) --- imgui.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index f452f0e8b..3e5d1700b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -547,6 +547,7 @@ static bool CloseWindowButton(bool* p_opened = NULL); static void FocusWindow(ImGuiWindow* window); static ImGuiWindow* FindHoveredWindow(ImVec2 pos, bool excluding_childs); static void CloseInactivePopups(); +static void SetWindowScrollY(ImGuiWindow* window, float scroll_y); // Helpers: String static int ImStricmp(const char* str1, const char* str2); @@ -2195,7 +2196,7 @@ void ImGui::NewFrame() if (!(window->Flags & ImGuiWindowFlags_NoScrollWithMouse)) { const int scroll_lines = (window->Flags & ImGuiWindowFlags_ComboBox) ? 3 : 5; - window->ScrollY -= g.IO.MouseWheel * window->CalcFontSize() * scroll_lines; + SetWindowScrollY(window, window->ScrollY - g.IO.MouseWheel * window->CalcFontSize() * scroll_lines); } } } @@ -4028,6 +4029,7 @@ static void Scrollbar(ImGuiWindow* window) } // Apply scroll + // It is ok to modify ScrollY here because we are being called in Begin() after the calculation of SizeContents and before setting up our starting position const float scroll_y_norm = ImSaturate((clicked_y_norm - g.ScrollbarClickDeltaToGrabCenter - grab_h_norm*0.5f) / (1.0f - grab_h_norm)); window->ScrollY = (float)(int)(0.5f + scroll_y_norm * (window->SizeContents.y - window->Size.y)); @@ -4374,6 +4376,13 @@ ImVec2 ImGui::GetWindowPos() return window->Pos; } +static void SetWindowScrollY(ImGuiWindow* window, float new_scroll_y) +{ + window->DC.CursorMaxPos.y += window->ScrollY; + window->ScrollY = new_scroll_y; + window->DC.CursorMaxPos.y -= window->ScrollY; +} + static void SetWindowPos(ImGuiWindow* window, const ImVec2& pos, ImGuiSetCond cond) { // Test condition (NB: bit 0 is always true) and clear flags for next time @@ -6933,8 +6942,8 @@ static bool InputTextEx(const char* label, char* buf, size_t buf_size, const ImV const bool is_ctrl_only = is_ctrl_down && !is_alt_down && !is_shift_down; if (IsKeyPressedMap(ImGuiKey_LeftArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDLEFT | k_mask : STB_TEXTEDIT_K_LEFT | k_mask); } else if (IsKeyPressedMap(ImGuiKey_RightArrow)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_WORDRIGHT | k_mask : STB_TEXTEDIT_K_RIGHT | k_mask); } - else if (is_multiline && IsKeyPressedMap(ImGuiKey_UpArrow)) { if (is_ctrl_down) draw_window->ScrollY -= g.FontSize; else edit_state.OnKeyPressed(STB_TEXTEDIT_K_UP | k_mask); } - else if (is_multiline && IsKeyPressedMap(ImGuiKey_DownArrow)) { if (is_ctrl_down) draw_window->ScrollY += g.FontSize; else edit_state.OnKeyPressed(STB_TEXTEDIT_K_DOWN| k_mask); } + else if (is_multiline && IsKeyPressedMap(ImGuiKey_UpArrow)) { if (is_ctrl_down) SetWindowScrollY(draw_window, draw_window->ScrollY - g.FontSize); else edit_state.OnKeyPressed(STB_TEXTEDIT_K_UP | k_mask); } + else if (is_multiline && IsKeyPressedMap(ImGuiKey_DownArrow)) { if (is_ctrl_down) SetWindowScrollY(draw_window, draw_window->ScrollY + g.FontSize); else edit_state.OnKeyPressed(STB_TEXTEDIT_K_DOWN| k_mask); } else if (IsKeyPressedMap(ImGuiKey_Home)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTSTART | k_mask : STB_TEXTEDIT_K_LINESTART | k_mask); } else if (IsKeyPressedMap(ImGuiKey_End)) { edit_state.OnKeyPressed(is_ctrl_down ? STB_TEXTEDIT_K_TEXTEND | k_mask : STB_TEXTEDIT_K_LINEEND | k_mask); } else if (IsKeyPressedMap(ImGuiKey_Delete)) { edit_state.OnKeyPressed(STB_TEXTEDIT_K_DELETE | k_mask); } From 9e8da4dba0e483eeaac1c2fcf63adeb8e852a77d Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 19 Jun 2015 18:09:58 -0600 Subject: [PATCH 41/44] Comments --- imgui.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 3e5d1700b..a94587bfe 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -378,6 +378,7 @@ - plot: "smooth" automatic scale over time, user give an input 0.0(full user scale) 1.0(full derived from value) - plot: add a helper e.g. Plot(char* label, float value, float time_span=2.0f) that stores values and Plot them for you - probably another function name. and/or automatically allow to plot ANY displayed value (more reliance on stable ID) - file selection widget -> build the tool in our codebase to improve model-dialog idioms + - inputfloat: applying arithmetics ops (+,-,*,/) messes up with text edit undo stack. - slider: allow using the [-]/[+] buttons used by InputFloat()/InputInt() - slider: initial absolute click is imprecise. change to relative movement slider (same as scrollbar). - slider: add dragging-based widgets to edit values with mouse (on 2 axises), saving screen real-estate. @@ -5557,7 +5558,7 @@ ImGuiID ImGui::GetID(const void* ptr_id) // User can input math operators (e.g. +100) to edit a numerical values. // NB: only call right after InputText because we are using its InitialValue storage -static void ApplyNumericalTextInput(const char* buf, float *v) +static void InputTextApplyArithmeticOp(const char* buf, float *v) { while (ImCharIsSpace(*buf)) buf++; @@ -5588,17 +5589,17 @@ static void ApplyNumericalTextInput(const char* buf, float *v) return; if (op == '+') - *v = ref_v + op_v; + *v = ref_v + op_v; // add (uses "+-" to substract) else if (op == '*') - *v = ref_v * op_v; + *v = ref_v * op_v; // multiply else if (op == '/') { - if (op_v == 0.0f) + if (op_v == 0.0f) // divide return; *v = ref_v / op_v; } else - *v = op_v; + *v = op_v; // Constant } // Create text input in place of a slider (when CTRL+Clicking on slider) @@ -5631,7 +5632,7 @@ static bool SliderFloatAsInputText(const char* label, float* v, ImGuiID id, int } if (value_changed) { - ApplyNumericalTextInput(text_buf, v); + InputTextApplyArithmeticOp(text_buf, v); } return value_changed; } @@ -7310,7 +7311,7 @@ bool ImGui::InputFloat(const char* label, float *v, float step, float step_fast, const ImGuiInputTextFlags flags = extra_flags | (ImGuiInputTextFlags_CharsDecimal|ImGuiInputTextFlags_AutoSelectAll); if (ImGui::InputText("", buf, IM_ARRAYSIZE(buf), flags)) { - ApplyNumericalTextInput(buf, v); + InputTextApplyArithmeticOp(buf, v); value_changed = true; } From eec047c9f73b9a71564fb0440e5bfcb989bd8985 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 19 Jun 2015 18:14:50 -0600 Subject: [PATCH 42/44] Fixed leak on Shutdown (#200) --- imgui.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index a94587bfe..e233b3b35 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1719,8 +1719,6 @@ bool ImGuiTextFilter::PassFilter(const char* val) const return false; } -//----------------------------------------------------------------------------- - // On some platform vsnprintf() takes va_list by reference and modifies it. // va_copy is the 'correct' way to copy a va_list but Visual Studio prior to 2013 doesn't have it. #ifndef va_copy @@ -2266,6 +2264,9 @@ void ImGui::Shutdown() ImGui::MemFree(g.PrivateClipboard); g.PrivateClipboard = NULL; } + g.InputTextState.Text.clear(); + g.InputTextState.InitialText.clear(); + g.InputTextState.TempTextBuffer.clear(); if (g.LogFile && g.LogFile != stdout) { From 68534c231958617d36edd6380930e1ace93f6cd2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 21 Jun 2015 18:15:31 -0600 Subject: [PATCH 43/44] Added IsMouseReleased() helper (#248) --- imgui.cpp | 16 +++++++++++++--- imgui.h | 5 ++++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e233b3b35..d90b85941 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2089,8 +2089,10 @@ void ImGui::NewFrame() g.IO.MousePosPrev = g.IO.MousePos; for (size_t i = 0; i < IM_ARRAYSIZE(g.IO.MouseDown); i++) { - g.IO.MouseDownTime[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownTime[i] < 0.0f ? 0.0f : g.IO.MouseDownTime[i] + g.IO.DeltaTime) : -1.0f; - g.IO.MouseClicked[i] = (g.IO.MouseDownTime[i] == 0.0f); + g.IO.MouseDownDurationPrev[i] = g.IO.MouseDownDuration[i]; + g.IO.MouseDownDuration[i] = g.IO.MouseDown[i] ? (g.IO.MouseDownDuration[i] < 0.0f ? 0.0f : g.IO.MouseDownDuration[i] + g.IO.DeltaTime) : -1.0f; + g.IO.MouseClicked[i] = g.IO.MouseDownDuration[i] == 0.0f; + g.IO.MouseReleased[i] = g.IO.MouseDownDurationPrev[i] >= 0.0f && !g.IO.MouseDown[i]; g.IO.MouseDoubleClicked[i] = false; if (g.IO.MouseClicked[i]) { @@ -2894,7 +2896,7 @@ bool ImGui::IsMouseClicked(int button, bool repeat) { ImGuiState& g = *GImGui; IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); - const float t = g.IO.MouseDownTime[button]; + const float t = g.IO.MouseDownDuration[button]; if (t == 0.0f) return true; @@ -2908,6 +2910,13 @@ bool ImGui::IsMouseClicked(int button, bool repeat) return false; } +bool ImGui::IsMouseReleased(int button) +{ + ImGuiState& g = *GImGui; + IM_ASSERT(button >= 0 && button < IM_ARRAYSIZE(g.IO.MouseDown)); + return g.IO.MouseReleased[button]; +} + bool ImGui::IsMouseDoubleClicked(int button) { ImGuiState& g = *GImGui; @@ -10561,6 +10570,7 @@ void ImGui::ShowTestWindow(bool* opened) ImGui::Text("ImGui says hello."); //ImGui::Text("MousePos (%g, %g)", ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y); + //ImGui::Text("MouseClicked (%d, %d) MouseReleased (%d, %d)", ImGui::GetIO().MouseClicked[0], ImGui::GetIO().MouseClicked[1], ImGui::GetIO().MouseReleased[0], ImGui::GetIO().MouseReleased[1]); //ImGui::Text("MouseWheel %d", ImGui::GetIO().MouseWheel); //ImGui::Text("KeyMods %s%s%s", ImGui::GetIO().KeyCtrl ? "CTRL" : "", ImGui::GetIO().KeyShift ? "SHIFT" : "", ImGui::GetIO().KeyAlt? "ALT" : ""); //ImGui::Text("WantCaptureMouse: %d", ImGui::GetIO().WantCaptureMouse); diff --git a/imgui.h b/imgui.h index b864361c5..c7fe33ab3 100644 --- a/imgui.h +++ b/imgui.h @@ -356,6 +356,7 @@ namespace ImGui IMGUI_API bool IsMouseDown(int button); IMGUI_API bool IsMouseClicked(int button, bool repeat = false); IMGUI_API bool IsMouseDoubleClicked(int button); + IMGUI_API bool IsMouseReleased(int button); IMGUI_API bool IsMouseHoveringWindow(); // is mouse hovering current window ("window" in API names always refer to current window) IMGUI_API bool IsMouseHoveringAnyWindow(); // is mouse hovering any active imgui window IMGUI_API bool IsMouseHoveringRect(const ImVec2& rect_min, const ImVec2& rect_max);// is mouse hovering given bounding rect @@ -701,8 +702,10 @@ struct ImGuiIO ImVec2 MouseClickedPos[5]; // Position at time of clicking float MouseClickedTime[5]; // Time of last click (used to figure out double-click) bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? + bool MouseReleased[5]; // Mouse button went from !Down to Down bool MouseDownOwned[5]; // Track if button was clicked inside a window. We don't request mouse capture from the application if click started outside ImGui bounds. - float MouseDownTime[5]; // Time the mouse button has been down + float MouseDownDuration[5]; // Time the mouse button has been down + float MouseDownDurationPrev[5]; // Previous time the mouse button has been down float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the click point float KeysDownTime[512]; // Time the keyboard key has been down From 0884cb51cdecd2f7dbf1b72157c0edc1b32c9f1e Mon Sep 17 00:00:00 2001 From: ocornut Date: Sun, 21 Jun 2015 19:04:43 -0600 Subject: [PATCH 44/44] Added IsKeyReleased() (#248), added keyboard & mouse state panel in the demo window --- imgui.cpp | 41 +++++++++++++++++++++++++++++++++-------- imgui.h | 8 +++++--- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index d90b85941..d0568bcd8 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -2114,8 +2114,9 @@ void ImGui::NewFrame() g.IO.MouseDragMaxDistanceSqr[i] = ImMax(g.IO.MouseDragMaxDistanceSqr[i], ImLengthSqr(g.IO.MousePos - g.IO.MouseClickedPos[i])); } } + memcpy(g.IO.KeysDownDurationPrev, g.IO.KeysDownDuration, sizeof(g.IO.KeysDownDuration)); for (size_t i = 0; i < IM_ARRAYSIZE(g.IO.KeysDown); i++) - g.IO.KeysDownTime[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownTime[i] < 0.0f ? 0.0f : g.IO.KeysDownTime[i] + g.IO.DeltaTime) : -1.0f; + g.IO.KeysDownDuration[i] = g.IO.KeysDown[i] ? (g.IO.KeysDownDuration[i] < 0.0f ? 0.0f : g.IO.KeysDownDuration[i] + g.IO.DeltaTime) : -1.0f; // Calculate frame-rate for the user, as a purely luxurious feature g.FramerateSecPerFrameAccum += g.IO.DeltaTime - g.FramerateSecPerFrame[g.FramerateSecPerFrameIdx]; @@ -2871,7 +2872,7 @@ bool ImGui::IsKeyPressed(int key_index, bool repeat) { ImGuiState& g = *GImGui; IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown)); - const float t = g.IO.KeysDownTime[key_index]; + const float t = g.IO.KeysDownDuration[key_index]; if (t == 0.0f) return true; @@ -2881,6 +2882,15 @@ bool ImGui::IsKeyPressed(int key_index, bool repeat) if ((fmodf(t - delay, rate) > rate*0.5f) != (fmodf(t - delay - g.IO.DeltaTime, rate) > rate*0.5f)) return true; } + return false; +} + +bool ImGui::IsKeyReleased(int key_index) +{ + ImGuiState& g = *GImGui; + IM_ASSERT(key_index >= 0 && key_index < IM_ARRAYSIZE(g.IO.KeysDown)); + if (g.IO.KeysDownDurationPrev[key_index] >= 0.0f && !g.IO.KeysDown[key_index]) + return true; return false; } @@ -10569,12 +10579,6 @@ void ImGui::ShowTestWindow(bool* opened) ImGui::PushItemWidth(-140); // Right align, keep 140 pixels for labels ImGui::Text("ImGui says hello."); - //ImGui::Text("MousePos (%g, %g)", ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y); - //ImGui::Text("MouseClicked (%d, %d) MouseReleased (%d, %d)", ImGui::GetIO().MouseClicked[0], ImGui::GetIO().MouseClicked[1], ImGui::GetIO().MouseReleased[0], ImGui::GetIO().MouseReleased[1]); - //ImGui::Text("MouseWheel %d", ImGui::GetIO().MouseWheel); - //ImGui::Text("KeyMods %s%s%s", ImGui::GetIO().KeyCtrl ? "CTRL" : "", ImGui::GetIO().KeyShift ? "SHIFT" : "", ImGui::GetIO().KeyAlt? "ALT" : ""); - //ImGui::Text("WantCaptureMouse: %d", ImGui::GetIO().WantCaptureMouse); - //ImGui::Text("WantCaptureKeyboard: %d", ImGui::GetIO().WantCaptureKeyboard); // Menu if (ImGui::BeginMenuBar()) @@ -11607,6 +11611,27 @@ void ImGui::ShowTestWindow(bool* opened) ImGui::TreePop(); } + if (ImGui::TreeNode("Keyboard & Mouse State")) + { + ImGuiIO& io = ImGui::GetIO(); + + ImGui::Text("MousePos: (%g, %g)", io.MousePos.x, io.MousePos.y); + ImGui::Text("Mouse down:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (io.MouseDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("%d (%.02f secs)", i, io.MouseDownDuration[i]); } + ImGui::Text("Mouse clicked:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseClicked(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("Mouse released:"); for (int i = 0; i < IM_ARRAYSIZE(io.MouseDown); i++) if (ImGui::IsMouseReleased(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("MouseWheel: %.1f", io.MouseWheel); + + ImGui::Text("Keys down:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (io.KeysDownDuration[i] >= 0.0f) { ImGui::SameLine(); ImGui::Text("%d (%.02f secs)", i, io.KeysDownDuration[i]); } + ImGui::Text("Keys pressed:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyPressed(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("Keys release:"); for (int i = 0; i < IM_ARRAYSIZE(io.KeysDown); i++) if (ImGui::IsKeyReleased(i)) { ImGui::SameLine(); ImGui::Text("%d", i); } + ImGui::Text("KeyMods: %s%s%s", io.KeyCtrl ? "CTRL " : "", io.KeyShift ? "SHIFT " : "", io.KeyAlt ? "ALT " : ""); + + ImGui::Text("WantCaptureMouse: %s", io.WantCaptureMouse ? "true" : "false"); + ImGui::Text("WantCaptureKeyboard: %s", io.WantCaptureKeyboard ? "true" : "false"); + + ImGui::TreePop(); + } + if (ImGui::TreeNode("Mouse cursors")) { ImGui::TextWrapped("Your application can render a different mouse cursor based on what ImGui::GetMouseCursor() returns. You can also set io.MouseDrawCursor to ask ImGui to render the cursor for you in software."); diff --git a/imgui.h b/imgui.h index c7fe33ab3..269587762 100644 --- a/imgui.h +++ b/imgui.h @@ -351,8 +351,9 @@ namespace ImGui IMGUI_API bool IsRootWindowFocused(); // is current root window focused (top parent window in case of child windows) IMGUI_API bool IsRootWindowOrAnyChildFocused(); // is current root window or any of its child (including current window) focused IMGUI_API bool IsRectVisible(const ImVec2& size); // test if rectangle of given size starting from cursor pos is visible (not clipped). to perform coarse clipping on user's side (as an optimization) - IMGUI_API bool IsKeyDown(int key_index); // key_index into the keys_down[512] array, imgui doesn't know the semantic of each entry + IMGUI_API bool IsKeyDown(int key_index); // key_index into the keys_down[512] array, imgui doesn't know the semantic of each entry, uses your own indices! IMGUI_API bool IsKeyPressed(int key_index, bool repeat = true); // " + IMGUI_API bool IsKeyReleased(int key_index); // " IMGUI_API bool IsMouseDown(int button); IMGUI_API bool IsMouseClicked(int button, bool repeat = false); IMGUI_API bool IsMouseDoubleClicked(int button); @@ -704,10 +705,11 @@ struct ImGuiIO bool MouseDoubleClicked[5]; // Has mouse button been double-clicked? bool MouseReleased[5]; // Mouse button went from !Down to Down bool MouseDownOwned[5]; // Track if button was clicked inside a window. We don't request mouse capture from the application if click started outside ImGui bounds. - float MouseDownDuration[5]; // Time the mouse button has been down + float MouseDownDuration[5]; // Duration the mouse button has been down (0.0f == just clicked) float MouseDownDurationPrev[5]; // Previous time the mouse button has been down float MouseDragMaxDistanceSqr[5]; // Squared maximum distance of how much mouse has traveled from the click point - float KeysDownTime[512]; // Time the keyboard key has been down + float KeysDownDuration[512]; // Duration the keyboard key has been down (0.0f == just pressed) + float KeysDownDurationPrev[512]; // Previous duration the key has been down IMGUI_API ImGuiIO(); };