From 0962c9fb723ee1498a533f0e592d81f7e6c1ffd8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 8 Sep 2023 17:16:19 +0200 Subject: [PATCH] TypingSelect: Added first version of GetTypingSelectRequest() API. # Conflicts: # imgui_internal.h # imgui_widgets.cpp --- imgui_internal.h | 41 ++++++++++++++++++++ imgui_widgets.cpp | 98 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 139 insertions(+) diff --git a/imgui_internal.h b/imgui_internal.h index 70d1cecc3..bc7b2ff9b 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -23,6 +23,7 @@ Index of this file: // [SECTION] Inputs support // [SECTION] Clipper support // [SECTION] Navigation support +// [SECTION] Typing-select support // [SECTION] Columns support // [SECTION] Multi-select support // [SECTION] Docking support @@ -153,6 +154,8 @@ struct ImGuiTableInstanceData; // Storage for one instance of a same table struct ImGuiTableTempData; // Temporary storage for one table (one per table in the stack), shared between tables. struct ImGuiTableSettings; // Storage for a table .ini settings struct ImGuiTableColumnsSettings; // Storage for a column .ini settings +struct ImGuiTypingSelectData; // Storage for GetTypingSelectRequest() +struct ImGuiTypingSelectRequest; // Storage for GetTypingSelectRequest() (aimed to be public) struct ImGuiWindow; // Storage for one window struct ImGuiWindowTempData; // Temporary storage for one window (that's the data which in theory we could ditch at the end of the frame, in practice we currently keep it for each window) struct ImGuiWindowSettings; // Storage for a window .ini settings (we keep one of those even if the actual window wasn't instanced during this session) @@ -178,6 +181,7 @@ typedef int ImGuiScrollFlags; // -> enum ImGuiScrollFlags_ // F typedef int ImGuiSeparatorFlags; // -> enum ImGuiSeparatorFlags_ // Flags: for SeparatorEx() typedef int ImGuiTextFlags; // -> enum ImGuiTextFlags_ // Flags: for TextEx() typedef int ImGuiTooltipFlags; // -> enum ImGuiTooltipFlags_ // Flags: for BeginTooltipEx() +typedef int ImGuiTypingSelectFlags; // -> enum ImGuiTypingSelectFlags_ // Flags: for GetTypingSelectRequest() typedef void (*ImGuiErrorLogCallback)(void* user_data, const char* fmt, ...); @@ -1532,6 +1536,39 @@ struct ImGuiNavItemData void Clear() { Window = NULL; ID = FocusScopeId = 0; InFlags = 0; DistBox = DistCenter = DistAxial = FLT_MAX; } }; +//----------------------------------------------------------------------------- +// [SECTION] Typing-select support +//----------------------------------------------------------------------------- + +// Flags for GetTypingSelectRequest() +enum ImGuiTypingSelectFlags_ +{ + ImGuiTypingSelectFlags_None = 0, + ImGuiTypingSelectFlags_AllowBackspace = 1 << 0, // Backspace to delete character inputs. If using: ensure GetTypingSelectRequest() is not called more than once per frame (filter by e.g. focus state) +}; + +// Returned by GetTypingSelectRequest(), designed to eventually be public. +struct IMGUI_API ImGuiTypingSelectRequest +{ + const char* SearchBuffer; + int SearchBufferLen; + bool SelectRequest; // Set when buffer was modified this frame, requesting a selection. + bool RepeatCharMode; // Notify when buffer contains same character repeated, to implement special mode. + ImS8 RepeatCharSize; // Length in bytes of first letter codepoint (1 for ascii, 2-4 for UTF-8). If (SearchBufferLen==RepeatCharSize) only 1 letter has been input. +}; + +// Storage for GetTypingSelectRequest() +struct IMGUI_API ImGuiTypingSelectData +{ + ImGuiTypingSelectRequest Request; // User-facing data + char SearchBuffer[64]; // Search buffer: no need to make dynamic as this search is very transient. + ImGuiID FocusScope; + int LastRequestFrame = 0; + float LastRequestTime = 0.0f; + + ImGuiTypingSelectData() { memset(this, 0, sizeof(*this)); } +}; + //----------------------------------------------------------------------------- // [SECTION] Columns support //----------------------------------------------------------------------------- @@ -2019,6 +2056,7 @@ struct ImGuiContext short TooltipOverrideCount; ImVector ClipboardHandlerData; // If no custom clipboard handler is defined ImVector MenusIdSubmittedThisFrame; // A list of menu IDs that were rendered at least once + ImGuiTypingSelectData TypingSelectData; // Platform support ImGuiPlatformImeData PlatformImeData; // Data updated by current frame @@ -3057,6 +3095,9 @@ namespace ImGui IMGUI_API bool IsDragDropPayloadBeingAccepted(); IMGUI_API void RenderDragDropTargetRect(const ImRect& bb); + // Typing-Select API + IMGUI_API const ImGuiTypingSelectRequest* GetTypingSelectRequest(ImGuiTypingSelectFlags flags = ImGuiTypingSelectFlags_None); + // Internal Columns API (this is not exposed because we will encourage transitioning to the Tables API) IMGUI_API void SetWindowClipRectBeforeSetChannel(ImGuiWindow* window, const ImRect& clip_rect); IMGUI_API void BeginColumns(const char* str_id, int count, ImGuiOldColumnFlags flags = 0); // setup number of columns. use an identifier to distinguish multiple column sets. close with EndColumns(). diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index e4ade8c78..339f2800b 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -18,6 +18,7 @@ Index of this file: // [SECTION] Widgets: ColorEdit, ColorPicker, ColorButton, etc. // [SECTION] Widgets: TreeNode, CollapsingHeader, etc. // [SECTION] Widgets: Selectable +// [SECTION] Widgets: Typing-Select support // [SECTION] Widgets: ListBox // [SECTION] Widgets: PlotLines, PlotHistogram // [SECTION] Widgets: Value helpers @@ -6597,6 +6598,103 @@ bool ImGui::Selectable(const char* label, bool* p_selected, ImGuiSelectableFlags return false; } + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Typing-Select support +//------------------------------------------------------------------------- + +// [Experimental] Currently not exposed in public API. +// Consume character inputs and return search request, if any. +// This would typically only be called on the focused window or location you want to grab inputs for, e.g. +// if (ImGui::IsWindowFocused(...)) +// if (const ImGuiTypingSelectRequest* req = ImGui::GetTypingSelectRequest()) +// if (req->SearchRequest) +// // perform search +// However the code is written in a way where calling it from multiple locations is safe (e.g. to obtain buffer). +const ImGuiTypingSelectRequest* ImGui::GetTypingSelectRequest(ImGuiTypingSelectFlags flags) +{ + ImGuiContext& g = *GImGui; + ImGuiTypingSelectData* data = &g.TypingSelectData; + ImGuiTypingSelectRequest* out_request = &data->Request; + + // Clear buffer + if (data->SearchBuffer[0] != 0) + { + const float TYPING_SELECT_RESET_TIMER = 1.70f; // FIXME: Potentially move to IO config. + bool clear_buffer = false; + clear_buffer |= (g.NavFocusScopeId != data->FocusScope); + clear_buffer |= (data->LastRequestTime + TYPING_SELECT_RESET_TIMER < g.Time); + clear_buffer |= g.NavAnyRequest; + clear_buffer |= g.ActiveId != 0 && g.NavActivateId == 0; // Allow temporary SPACE activation to not interfere + clear_buffer |= IsKeyPressed(ImGuiKey_Escape) || IsKeyPressed(ImGuiKey_Enter); + clear_buffer |= IsKeyPressed(ImGuiKey_Backspace) && (flags & ImGuiTypingSelectFlags_AllowBackspace) == 0; + //if (clear_buffer) { IMGUI_DEBUG_LOG("GetTypingSelectRequest(): Clear SearchBuffer.\n"); } + if (clear_buffer) + data->SearchBuffer[0] = 0; + } + + // Append to buffer + const int buffer_max_len = IM_ARRAYSIZE(data->SearchBuffer) - 1; + int buffer_len = (int)strlen(data->SearchBuffer); + bool buffer_changed = false; + for (ImWchar w : g.IO.InputQueueCharacters) + { + if (w < 32 || (buffer_len == 0 && ImCharIsBlankW(w))) // Ignore leading blanks + continue; + int utf8_len = ImTextCountUtf8BytesFromStr(&w, &w + 1); + if (buffer_len + utf8_len > buffer_max_len) + break; + ImTextCharToUtf8(data->SearchBuffer + buffer_len, (unsigned int)w); + buffer_len += utf8_len; + buffer_changed = true; + } + g.IO.InputQueueCharacters.resize(0); + if ((flags & ImGuiTypingSelectFlags_AllowBackspace) && IsKeyPressed(ImGuiKey_Backspace, 0, ImGuiInputFlags_Repeat)) + { + char* p = (char*)(void*)ImTextFindPreviousUtf8Codepoint(data->SearchBuffer, data->SearchBuffer + buffer_len); + *p = 0; + buffer_len = (int)(p - data->SearchBuffer); + } + if (buffer_len == 0) + return NULL; + + // Return request if any + if (buffer_changed) + { + data->FocusScope = g.NavFocusScopeId; + data->LastRequestFrame = g.FrameCount; + data->LastRequestTime = (float)g.Time; + } + out_request->SearchBuffer = data->SearchBuffer; + out_request->SearchBufferLen = buffer_len; + out_request->SelectRequest = (data->LastRequestFrame == g.FrameCount); + out_request->RepeatCharMode = false; + out_request->RepeatCharSize = 0; + + // Calculate if buffer contains the same character repeated. + // - This can be used to implement a special search mode on first character. + // - Performed on UTF-8 codepoint for correctness. + // - RepeatCharMode is always set for first input character, because it usually leads to a "next". + const char* buf_begin = out_request->SearchBuffer; + const char* buf_end = out_request->SearchBuffer + out_request->SearchBufferLen; + const int c0_len = ImTextCountUtf8BytesFromChar(buf_begin, buf_end); + const char* p = buf_begin + c0_len; + for (; p < buf_end; p += c0_len) + if (memcmp(buf_begin, p, (size_t)c0_len) != 0) + break; + out_request->RepeatCharMode = (p == buf_end); + out_request->RepeatCharSize = out_request->RepeatCharMode ? (ImS8)c0_len : 0; + + return out_request; +} + + +//------------------------------------------------------------------------- +// [SECTION] Widgets: Multi-Select support +//------------------------------------------------------------------------- + +// + //------------------------------------------------------------------------- // [SECTION] Widgets: ListBox //-------------------------------------------------------------------------